클라우드 Mac에서 MLX 가중치 샤드 무결성 검사하기
모델 가중치를 클라우드 Mac으로 옮긴 뒤 로딩에 실패했다고 해서 반드시 통합 메모리가 부족한 것은 아닙니다. 다운로드 중단, 다른 버전의 샤드 혼입, 이전 파일을 가리키는 인덱스 때문에 실험이 첫 단계에서 멈출 수도 있습니다. 텐서를 로드하지 않는 검사로 파일 문제부터 배제한 다음 MLX를 실행하면, 불필요한 재다운로드나 근거 없는 추론 매개변수 조정을 피할 수 있습니다.
먼저 가중치 디렉터리를 고정하기
OsxNode에서 검증할 때는 한 번의 실험에 사용할 가중치, 설정, 인덱스를 별도의 weights 디렉터리에 모으고, 실행 중인 모델을 덮어쓰지 마세요. Python 3를 준비하고 검사 스크립트는 이 디렉터리 밖에 저장합니다. 이 글에서는 safetensors 가중치만 검사하며, 모델에 포함된 코드는 실행하지 않습니다.
모델 리비전 식별자, 양자화 설정, 다운로드 출처, 파일 목록을 기록하세요. 이 정보는 실제로 받은 버전을 기준으로 해야 합니다. 내용이 바뀔 수 있는 모델 이름만 적어 두어서는 안 됩니다. 아직 쓰는 중인 샤드를 손상된 파일로 오인하지 않도록 다운로더가 종료된 뒤 검사를 시작합니다.
파일 구조가 유효한지, 전송된 내용이 원본과 일치하는지, 모델이 추론을 수행할 수 있는지는 서로 다른 세 가지 검증 결과입니다. 어느 하나를 통과했다고 해서 나머지 둘까지 검증된 것은 아닙니다.
파일 헤더로 오프셋과 인덱스 검사하기
safetensors 파일의 첫 여덟 바이트는 JSON 헤더의 바이트 길이를 나타내는 리틀 엔디언 부호 없는 정수입니다. 텐서의 data_offsets는 파일 시작점이 아니라 헤더 뒤에 있는 데이터 영역을 기준으로 합니다. 두 기준을 혼동하면 정상적인 샤드를 범위 초과로 잘못 판정할 수 있습니다.
아래 스크립트는 데이터 영역이 빈틈없이 연속으로 채워져 있는지, 여러 샤드에 같은 텐서 이름이 중복되는지, 인덱스 매핑이 실제 파일과 일치하는지 검사합니다. verify_weights.py로 저장하세요. 여기서 헤더 크기를 16MiB로 제한한 것은 로컬 검증 정책일 뿐, 형식 자체의 제한은 아닙니다. 유효한 대형 헤더가 이 제한에 걸리면 출처를 확인한 뒤 한도를 조정하세요.
import json
import struct
import sys
from pathlib import Path
def require(ok, message):
if not ok:
raise ValueError(message)
def unique_object(pairs):
result = {}
for key, value in pairs:
require(key not in result, f"Duplicate JSON key: {key}")
result[key] = value
return result
def read_json(raw):
return json.loads(raw, object_pairs_hook=unique_object)
def verify(root):
require(root.is_dir(), "Missing model directory")
files = sorted(root.glob("*.safetensors"))
require(bool(files), "No safetensors files")
actual = {}
for path in files:
require(not path.is_symlink(), "Symlink shard rejected")
size = path.stat().st_size
with path.open("rb") as stream:
raw = stream.read(8)
require(len(raw) == 8, f"Short prefix: {path.name}")
length = struct.unpack("<Q", raw)[0]
require(
0 < length <= min(16 * 1024 * 1024, size - 8),
f"Invalid header length: {path.name}"
)
header = read_json(stream.read(length))
require(isinstance(header, dict), "Header must be an object")
data_size = size - 8 - length
ranges = []
for name, info in header.items():
if name == "__metadata__":
continue
require(isinstance(info, dict), f"Invalid tensor: {name}")
offsets = info.get("data_offsets")
require(
isinstance(offsets, list) and len(offsets) == 2
and all(type(v) is int for v in offsets),
f"Invalid offsets: {name}"
)
start, end = offsets
require(0 <= start <= end <= data_size,
f"Out of bounds: {name}")
require(name not in actual, f"Duplicate tensor: {name}")
actual[name] = path.name
ranges.append((start, end))
cursor = 0
for start, end in sorted(ranges):
require(start == cursor, f"Gap or overlap: {path.name}")
cursor = end
require(cursor == data_size, f"Trailing data: {path.name}")
index = root / "model.safetensors.index.json"
if index.exists():
document = read_json(index.read_text(encoding="utf-8"))
mapping = document.get("weight_map")
require(isinstance(mapping, dict), "Missing weight_map")
require(mapping == actual, "Shard index mismatch")
else:
require(len(files) == 1, "Multiple shards require an index")
print(f"PASS: {len(files)} files, {len(actual)} tensors")
try:
require(len(sys.argv) == 2, "Usage: verify_weights.py DIRECTORY")
verify(Path(sys.argv[1]))
except Exception as error:
print(f"FAIL: {error}", file=sys.stderr)
sys.exit(1)
python3 verify_weights.py weights를 실행하고 종료 코드가 0일 때만 다음 단계로 진행하세요. 이 스크립트는 표준 인덱스 파일명을 사용하고 샤드가 디렉터리 최상위에 있다고 가정합니다. 다른 구조라면 그에 맞게 명시적으로 수정해야 합니다. 검사를 우회하려고 인덱스를 삭제하지 마세요.
원본 측 해시로 전송 결과 검증하기
구조 검사로는 텐서 내부 값의 손상을 확인할 수 없습니다. 신뢰할 수 있는 원본 측에서 파일 쓰기가 끝난 뒤 체크섬 목록을 생성하고, 해당 파일들과 목록을 함께 대상 머신으로 옮기세요. 다음 명령은 모두 가중치 디렉터리 안에서 실행합니다.
shasum -a 256 ./*.safetensors ./config.json > SHA256SUMS
샤드 인덱스가 있다면 목록에 함께 추가합니다.
shasum -a 256 ./model.safetensors.index.json >> SHA256SUMS
대상 머신에서도 해당 디렉터리로 이동한 뒤 다음을 실행합니다.
shasum -a 256 -c SHA256SUMS
목록에 있는 모든 파일의 결과가 OK여야 합니다. 체크섬 목록 자체도 신뢰할 수 있는 경로로 받아야 합니다. 공격자가 파일과 목록을 함께 바꿀 수 있다면 해시만으로 출처를 인증할 수 없습니다. 원본에서 이미 손상된 가중치도 그대로 전송되면 이 검사를 통과하므로, 구조 검사 역시 유지해야 합니다.
전체를 다시 받기보다 실패 유형별로 대응하기
Short prefix가 나오거나 헤더 길이가 비정상이라면 먼저 다운로드 프로세스가 종료되었는지 확인하고 해당 샤드의 바이트 수를 대조하세요. 파일을 수동으로 채워 넣거나 다운로드 오류 페이지의 이름을 가중치 파일명으로 바꾸지 마세요.
Shard index mismatch가 나오면 인덱스에 있는 파일명과 디렉터리의 파일 목록을 하나씩 비교합니다. 복사 중 샤드가 빠졌거나 이전 버전 파일이 남아 있는 경우가 흔합니다. 불필요한 파일을 먼저 격리한 뒤 검사를 다시 실행하세요. 출처가 불분명한 가중치 묶음에 맞추려고 인덱스를 수정해서는 안 됩니다.
구조 검사는 통과했지만 해시 검증에 실패했다면 실패한 파일만 다시 받은 뒤 전체 검사를 재실행합니다. 전송 도구가 이어받기를 지원하더라도, 먼저 원격 원본 파일의 버전이 바뀌지 않았는지 확인해야 합니다. 그렇지 않으면 이전 파일의 앞부분과 새 파일의 뒷부분이 이어 붙을 수 있습니다.
해시 검증을 통과했는데도 로딩에 실패할 때 비로소 모델 아키텍처, 양자화 설정, 런타임 라이브러리 버전, 메모리 압박을 점검합니다. 이 순서로 진행하면 예외 메시지만 보고 원인을 추측할 필요 없이 파일 문제와 실행 환경 문제를 구분할 수 있습니다.
실험 시작 단계에 검사를 넣기
이 스크립트는 dtype, shape, 텐서 바이트 수 사이의 수학적 관계를 검증하지 않으며, 양자화 매개변수가 서로 맞는지도 판단하지 않습니다. 따라서 마지막에는 버전을 고정한 MLX 환경에서 모델을 한 번 로드하고, 고정된 입력으로 짧은 출력을 생성하는 테스트를 해야 합니다. 파일 헤더 검사만으로 이 단계를 대신할 수는 없습니다.
실험 기록에는 최소한 리비전 식별자, 체크섬 목록, 스크립트 종료 코드, 런타임 라이브러리 버전, 최초 로딩 결과를 남기세요. 성공 화면 한 장만 저장해 두면 다음에 디렉터리 내용이 바뀌었을 때 차이가 생긴 이유를 설명하기 어렵습니다.
실험 스크립트의 앞단에 구조 검사와 해시 검증을 연결하고, 어느 한 단계라도 실패하면 이후 작업을 중단하도록 하세요. 모델 리비전을 바꿀 때는 새 디렉터리를 사용하고 검증 기준도 새로 생성합니다. 대여 기간이 끝나기 전에 체크섬 목록과 검사 기록을 챙기세요. 그러면 다음 머신에 넘기는 것은 단순히 “다운로드 완료”라고 이름 붙인 폴더가 아니라 검증 가능한 실험 입력이 됩니다.
자주 묻는 질문
구조 검사를 통과하면 추론도 정상적으로 실행되나요?
보장되지 않습니다. 파일 배치와 샤드 인덱스만 검사하므로 모델 구조 및 양자화 설정과 맞는 환경에서 로딩과 고정 입력 테스트를 별도로 수행해야 합니다.
전송 후에 만든 SHA-256 목록으로 전송 성공을 증명할 수 있나요?
아니요. 이후 변경을 감지하는 기준으로는 쓸 수 있지만 이번 전송의 정확성은 증명하지 못합니다. 신뢰하는 원본에서 만든 목록을 확보해 대상 파일과 비교해야 합니다.
실험 기간에 맞춰 Mac 대여하기
Mac mini M4를 단독으로 이용하세요. 통합 메모리 16GB / SSD 256GB를 제공합니다. 일·주·월·분기 단위로 선결제하며, 기간이 끝나면 자동 갱신 없이 기기가 중지됩니다. 모델과 실행 환경은 직접 준비해야 하며, 실제 이용 가능 여부는 콘솔에 표시되는 최신 상태를 기준으로 합니다.
일 단위로 Mac 대여하기