转换说明
本页描述普通的 Core ML 导出。单独重写的 ANE 图和可选的权重调色板化记录在 ANE_ENGINEERING.md 中。
导出把 原始 Laya checkpoint 加载进 FP32 PyTorch 模块,严格检查所有 state-dict 键,追踪一个只做推理的实现,并保存为一个 Core ML ML Program。已发布的 checkpoint 文件本身以 FP16 张量为主;这里的 FP32 指的是导出/参考计算,而不是更高精度的源权重。不做训练、剪枝或权重量化。FP16 是一种转换精度选择;诊断时可选用 FP32。
运行时使用 checkpoint 的 tokenizer、prompt 布局、选项标记、问题类型 embedding、decision head、action head 和校准温度。Choice、score、noul、结构化 criteria、token 计账和零生成 token 都遵循上游 API。编码器是双向的:每个问题仍然各跑一遍自己的编码器序列。没有共享状态的隐藏状态缓存。
已验证的转换选择
coremltools==9.0、torch==2.7.0、numpy==2.1.3、Python 3.12。- TorchScript 追踪,带图检查、评估模式,原始权重加载进 FP32 模块。
- ML Program,部署目标为 macOS 15 / iOS 18。实际执行是在运行 macOS 27.2 的 M3 Max 上测试的;iPhone/iPad 和更早的 macOS 上的执行未测试。
- 默认序列长度从 16、32、64、96、128、192、256、384、512、768、1024 中选取,并以 checkpoint 的上下文上限为界。运行时会补齐到可用的最小长度,并把这些新增的 token 掩掉。
- 默认批大小为一,带 32 个标记槽。更多问题会分块运行。
--batch-size和--max-options会产生不同的导出签名。 - 对于已知的工作负载,可以使用固定形状。超出某个导出长度或选项容量的输入会报错;它们不会被静默截断去迁就更小的导出。原始 checkpoint 的上下文截断被保留。
Apple 记录了 TorchScript 转换 和 枚举输入形状。多个枚举输入需要相同数量的形状,并按索引配对;本导出据此把 input ID 和 attention mask 配对。
为可复现性保留的失败
这些是在本机和本操作系统上的观察,不是对每个 Core ML 版本的说法。
- PyTorch 的
__or__布尔运算符没有被转换。显式的torch.logical_or/torch.logical_and保留了相同的 mask 语义。 - NumPy 2.5 拒绝了 coremltools 9.0 内部一处已弃用的数组转标量转换。项目支持的依赖被钉在 NumPy 2.2 以下。PyTorch 被钉在转换器测试过的 2.7.0 版本,而不是 2.7.1。
- 强制
CPU_AND_GPU的RangeDim产生了很大的数值误差,且对重复的相同输入给出不同结果。原始 SDPA 导出只匹配了 47/63 个参考答案,显式的 matmul/softmax 注意力只匹配 20/63。FP32 未能解决观察到的短输入 GPU 失败。CPU / 自动选择为 SDPA 图给出了正确的输出。 - 枚举长度恢复了 GPU 保真度与可重复性。随后一个单独的小型回归测试在切分一个常量布尔局部注意力矩阵时暴露出 MPSGraph 编译器的
SIGTRAP。诊断指名为ElementsAttr::getValues<bool>/FoldStridedSliceOp。 - 最终实现切分 整数位置,之后再构造布尔局部掩码。这消除了编译器陷阱。它并没有治好普遍的 RangeDim GPU 失败:后续实验仍然只匹配 49/63,且不可重复。枚举长度仍是默认。
运行时会拒绝 RangeDim + cpu_gpu,除非为诊断实验显式允许。要复现那个失败的配置:
laya-coreml convert laya-multilingual models/range-experiment --shape-mode range
python -m benchmarks.validate models/range-experiment \
--name laya-multilingual --compute-units cpu_gpu --allow-unvalidated-gpu \
--repeats 10 --output artifacts/range-experiment.json
该测试在实测环境中预期会失败。失败与成功的原始报告都保留在 benchmarks/results/;包含 "passed": false 的报告不得被引用为已验证的配置。
设备证据
CPU_AND_NE 表示 CPU 和 Neural Engine 是被允许的,而不是每个算子都在 Neural Engine 上运行。基准记录 Core ML 计算计划的首选/支持设备和估计开销。这是一个预期的计划,不是 Instruments 的运行时硬件 trace、功耗测量,也不是独占 Neural Engine 执行的证明。
加载 Hub 快照
发布冒烟测试发现了一个单独的打包问题:从共享 Hugging Face 缓存加载一个符号链接的权重文件,会让 Core ML 的原生编译器报告缺失 model.mlmodelc/weights/weight.bin。所有六个等价的本地包都成功加载。运行时现在会在构造 MLModel 之前,把由符号链接支撑的包拷贝到一个内容寻址的普通文件缓存。拷贝前后以及复用时都会检查哈希;缓存发生变化或损坏会报错。普通文件的本地包不走这条拷贝路径。缓存位置和覆盖方式见 USAGE.md。
可复现性
每次导出都包含 coreml_config.json:原始权重 SHA256、源版本、形状、精度、注意力实现、工具版本、转换时间,以及每个包/tokenizer/配置文件的哈希。导出拒绝覆盖已存在的目录。失败的导出只会删除它新建的输出目录。
提交进仓库的 golden reference 是用 FP32 PyTorch MPS 从未修改的上游 Laya 版本 573e5b62696ba441230cd6be71d593331b5d23af 生成的。它包含完整的输入 token ID 和未取整的 logits。验证会逐字节比较这些 token,并检查选定答案、校准概率、动作概率、token 计账和重复的公开结果。
要在与上游兼容的环境中重新生成 golden reference:
git clone https://github.com/NandhaKishorM/laya .upstream
git -C .upstream checkout 6a5819129eb220570792e417e49723d697efd76f
python -m benchmarks.reference --upstream .upstream --model-root /path/to/original/checkpoints
确切的参考依赖记录在生成的 JSON 中。它们与钉定的导出环境相互独立;Transformers 不是 laya-coreml 的运行时或导出依赖。