云端 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

先核对配置,再开始实验

确认模型权重、运行峰值与租期,再选择周期。需要连接步骤时,可先查阅帮助中心。