雲端 Mac 上的 MLX 權重分片完整性驗證

雲端 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,只有結束碼為零才能繼續。腳本預設使用標準索引檔名,且分片位於目錄最上層;其他配置需要明確調整腳本來支援,不要直接刪除索引來繞過檢查。

用來源端雜湊驗收傳輸結果

結構檢查無法偵測張量內部的數值損毀。應在可信任的來源端,等檔案寫入完成後產生清單,再將同一批檔案與清單傳到目標機器。以下指令都在權重目錄內執行:

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 統一記憶體 / 256GB SSD。可預付日租、週租、月租或季租;租期結束後若未續租,機器將停止運作。模型與執行環境請自行準備,實際可用狀態以控制台即時回報為準。

按日租用 Mac

確認規格後,再開始實驗

確認模型權重、最高資源用量與租期,再選擇適合的週期。需要連線說明時,請先參閱說明中心。