【免费下载链接】SemIf-OpenJev
Semantic ifs from open models, on a 3090 at home. Independent; not affiliated with Jev or TypeSafe.
SemIf(语义决策开源基线)在 macOS arm64 上通过原生 MLX 后端运行 direct(直接读分)、serial(串行前缀复用)与 shared(并行共享状态)三种决策模式,使用 MLX-LM 的 Qwen3.5 实现与 Torch 后端完全相同的提示词和答案 token 检查,且不生成任何答案 token。本文以 docs/MLX.md 为主线,结合 MLX 后端源码、CLI 入口、MLX 基准脚本 与 MLX 回归测试 等仓库证据,完整覆盖 MLX 环境的安装、semif-score评分命令的三种模式与全部 MLX 专属参数、内存量化与分配缓存控制、混合缓存正确性保证、以及从基准复现到校验的完整证据闭环,读完即可在自己的 Apple Silicon 机器上跑通并验证整条 MLX 决策流水线。
MLX 后端在 SemIf 中的定位
SemIf 的默认后端仍然是 Torch/CUDA(cli.py 中--backend默认值为torch)。MLX 后端是加性的、专门面向 Apple Silicon 的补充路径,其边界非常明确:
- 支持:direct、serial-prefix、parallel-shared 三种决策模式,由 MLX-LM 的 Qwen3.5 原生实现驱动,与 Torch 后端共用同一套提示词构造(
encode_prompt)与答案 token 槽位检查,不生成任何答案 token。 - 不支持:MLX 的 reranker(重排器)模式被显式拒绝。在 cli.py 中有对应校验:
--backend mlx --mode reranker会直接报错,reranker 必须走 Torch 后端。 - 独立实现:浏览器 WebGPU demo 是另一套独立实现,与 MLX 后端无关。
- 隔离性:其他平台安装时不会导入 MLX。
mlx这个 extra 在 pyproject.toml 中被限定为sys_platform == 'darwin' and platform_machine == 'arm64',不会替代仓库现有的 Torch 依赖。
从源码结构看,mlx_backend.py 暴露三个与 Torch 后端同形的入口:score(direct)、SerialPrefixScorer(serial)、score_shared(shared),cli.py 在--backend mlx时用这三个函数整体替换默认实现,因此 CLI 层的命令形态对用户是一致的。
环境要求与安装
MLX 后端要求一台带有 Metal 的 Apple Silicon Mac,并使用隔离的 Python 环境:
python3 -m venv .venv source .venv/bin/activate pip install -e '.[test,mlx]'安装有两个值得注意的硬性前提:
- 需要 Git。
mlxextra 中的mlx-lm是以 git 依赖形式锁定的:mlx-lm @ git+https://github.com/ml-explore/mlx-lm.git@a63e24c389382619eb6d9af656e3b46024be217a(见 pyproject.toml),pip 必须借助 Git 才能解析并安装该固定提交。 - 平台与运行时被双重重锁。
mlx==0.32.2与 MLX-LM 的源码提交a63e24c389382619eb6d9af656e3b46024be217a(对应包版本 0.32.0)是生产运行时固定点,同时 manifests/mlx-validation.json 中的production_runtime也记录了这一组合,保证基准与日常评分使用的运行时完全一致。
在 mlx_backend.py 中,加载器首先校验platform.system() == "Darwin" and platform.machine() == "arm64",否则直接抛出RuntimeError;随后校验mx.metal.is_available()并把默认设备设为 GPU。MLX 的 Metal 不可用或缺少 extra 依赖都会在加载阶段被明确拒绝,而不是在推理时才神秘失败。
安装后首次评分:direct 模式
安装完成后的最小验证命令(与原文档保持一致,可直接复制运行):
semif-score --backend mlx --mode direct \ --model Qwen/Qwen3.5-4B \ --revision 851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a \ --input examples/decisions.jsonl \ --output results-mlx-direct.jsonl几个关键的运行事实:
- 首次运行会下载权重。固定的 checkpoint 会进入 Hugging Face 缓存,磁盘上权重约9 GB,GPU 执行还需要额外的显存(统一内存)。
- 视觉权重被排除。源 checkpoint 含视觉权重,MLX-LM 的原生 sanitizer 会将其从文本模型中剔除,因此实际加载的是纯文本模型。
- 精度基线是源精度:BF16,含少量 FP32 参数,不做任何额外变换。
- 输出是新的(create-only)。CLI 强制要求输出文件不存在(cli.py 中
args.output.exists()直接报错),并用open("x")独占创建,杜绝覆盖旧证据。 - 每条预测都记录运行时来源提交(
mlx_lm_source),这是可追溯性的一部分。
如果你习惯用 Python 直接调用,等价路径是:
from semif_phase1 import mlx_backend model, tokenizer, metadata = mlx_backend.load_model( "Qwen/Qwen3.5-4B", "851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a") row = {"id": "route-1", "state": "Customer cannot access an account after a password reset.", "question": "Which queue should handle this request?", "options": [{"id": "access", "description": "Account access support."}, {"id": "billing", "description": "Billing support."}]} result = mlx_backend.score(model, tokenizer, row, metadata)返回值包含probabilities(softmax 归一化的条件选项分数)、option_logits、answer_token_ids、input_ids_sha256、prompt_sha256与完整运行时元数据(model字段内含mlx_version、mlx_lm_version、mlx_lm_source、allocator_cache_limit_bytes、quantization、source_artifact_sha256等),每条结果都会标注probability_status: conditional option score; uncalibrated as decision confidence——这是全项目一致的边界声明:输出形状有保证,语义正确性与置信度校准不保证。
三种决策模式:direct / serial / shared
原文档强调,决策模式的选择取决于输入数据的形状:
# 逐行独立评分(fresh) semif-score --backend mlx --mode direct ... # 相邻行状态完全相同,串行复用前缀缓存 semif-score --backend mlx --mode serial ... # 所有行共享同一个精确状态,且决策 ID 唯一,并行共享 semif-score --backend mlx --mode shared ...三种模式在 mlx_backend.py 中的底层实现差异如下:
| 模式 | 核心入口 | 执行策略 | 适用场景 |
|---|---|---|---|
| direct | score()(L109-L122) | 每行独立前向,取最后一个位置的 logits 并仅抽出声明槽位 | 状态各不相同、行间无复用价值 |
| serial | SerialPrefixScorer.score()(L150-L182) | 仅缓存"当前精确状态"的前缀缓存,命中时对缓存做copy.deepcopy后再续后缀,绝不原地修改保留的前缀 | 连续行的 state 相同、后缀(问题/选项)不同 |
| shared | score_shared()(L185-L237) | 前缀只 prefill 一次,用entry.merge([entry] * len(rows))复制出独立分支,右侧补齐后一次性批量前向 | 所有行 state 完全一致、ID 唯一、希望一次批处理全部问题 |
shared 模式的实现细节尤其值得展开(对应原文档的 "Cache correctness" 一节):
- 右填充由原生循环缓存掩蔽。
entry.prepare(lengths=lengths, right_padding=[width - size for size in lengths])把每个后缀的真实长度交给循环(recurrent)缓存,因果注意力保证后面的 padding token 不会影响真实位置。 - 读取每个后缀的最后一个真实 token,而不是 padding 位置:
selected = [logits[i, lengths[i] - 1, mx.array(slots)] ...]。 - 分支状态绝不串味:一个问题的分支状态永远不会喂给另一个问题(deepcopy/merge 出的分支彼此独立)。
- 共享模式的内存随批大小与后缀长度增长,因此原文档特别提醒:内存占用与 batch size、后缀长度成正比。
- 输入长度限制不截断。
max_tokens超限直接抛ValueError(测试 test_token_limit_prevents_inference 验证了 "no truncation" 行为),而不是悄悄截断提示词。
缓存键是"精确 token ID"而非 Python 相等
这是一个易踩坑但被刻意设计的行为:SerialPrefixScorer的缓存命中判定基于prefix == self.prefix,而前缀由_state_prefix从原始状态文本/结构化 JSON重新编码而来。这意味着:
- 调用方在提交后修改了之前提供的 JSON 对象,不可能意外命中旧缓存——因为重新编码出的 token ID 序列已经不同;
- 即使 Python 层面
True == 1,JSON 序列化后的提示词不同,缓存也会正确失效(test_serial_keys_by_exact_tokens_not_python_value_equality 专门验证了这一场景); - 结构化状态变更(如把
"evidence": "A deployment succeeded."改成 failed)同样会强制重建前缀缓存(test_serial_invalidates_cache_for_mutated_structured_state)。
测试如何验证混合缓存
MLX 的缓存正确性并非空口承诺。tests/test_mlx.py 使用一个真实的小型 Qwen3.5 混合模型(ModelArgs(model_type="qwen3_5", ...),hidden 64、4 层、线性键值头宽 128 以满足 Metal delta 内核要求)做回归,无需下载任何权重即可验证:
- 变长后缀、问题重排序(
rows[::-1])、重复调用、状态变更、非法输入; test_hybrid_cache_branches_padding_and_order断言 fresh / serial / shared 三者概率在abs=1e-4内一致,同时承认"完整 prefill 与拆分 prefill 使用不同的 Metal reduction 形状,即使 FP32 也不逐位相同";test_shared_rejects_mixed_states_and_duplicate_ids验证 shared 模式对混合状态与重复 ID 的硬性拒绝。
这正是原文档"GPU 算术在不同内核、不同 prompt/batch 形状下存在差异"这一论断的测试级证据。
内存治理:MLX 分配缓存上限
这是原文档中容易被忽略但实操价值极高的一节。MLX 默认会缓存几乎全部系统内存(inactive allocation cache),在变长提示词的场景下,这会让统一内存(unified memory)被其他本地模型挤占。SemIf 的默认策略是:
- 加载器把 MLX 的 inactive allocation cache 上限设为256 MiB(源码常量
DEFAULT_CACHE_LIMIT_MIB = 256,见 mlx_backend.py); - 命令行可用
--mlx-cache-limit-mib 512调整,或用--mlx-cache-limit-mib 0完全禁用inactive allocation caching; - 基准与精度探针脚本接受同一参数(mlx_benchmark.py 与 mlx_precision_probe.py 都带
--mlx-cache-limit-mib); - Python 调用方可以在
mlx_backend.load_model(..., cache_limit_mib=512)中传入; - 预测元数据中的
allocator_cache_limit_bytes记录生效上限(字节数)。
实现上,mlx_backend.py 在加载模型前执行mx.set_cache_limit(cache_limit)(cache_limit = cache_limit_mib * 1024 * 1024),这是一个进程级(process-wide)的 MLX 分配器设置,不是 prefix cache。需要澄清的边界:
- 它约束的是分配缓存(inactive allocations),不是活跃模型或批处理的内存需求;
- 它不限制前缀缓存(prefix cache)本身;
- test_loader_applies_cache_limit_and_records_bytes 验证了默认 256、自定义值与字节记录逻辑;
- test_loader_rejects_invalid_cache_limit_before_loading 验证
-1、1.5、True这类非法输入在加载前就被拒绝(必须是非负整数 MiB)。
从 results/mlx/README.md 的历史记录看,这一设置是有真实事故背景的:早期一次运行在另一个模型占用大量内存时退出(exit 137),最初 inactive-cache 上限约为 122 GiB;改为 256 MiB 上限后完整工作负载得以跑完。该文档也谨慎说明这是"可能的内存压力解释,而非独立确认的 OS 诊断"。
运行时锁定与上游归一化修复
MLX 后端的可复现性依赖严格的运行时锁定:
- MLX 0.32.2(
mlx==0.32.2,且仅 macOS arm64 安装); - MLX-LM锁定在源码提交
a63e24c389382619eb6d9af656e3b46024be217a(包版本 0.32.0),pyproject.toml 中直接以 git URL 固定,无本地 fork、无 monkey patch。
锁这个提交有实质技术原因:该提交包含上游 Qwen 循环 q/k 归一化修复。MLX-LM 0.31.3 在应用 L2 epsilon 时按 mean 计算而非 sum,导致缩放错误;修复后 epsilon 作用于 sum(参考 L2 归一化)。仓库用一个小型测试直接暴露这一点(test_recurrent_qk_normalization_matches_reference_l2_epsilon):构造数值 1e-4 的小 q/k,断言normalize_qk的结果与x * rsqrt(sum(x², axis=-1, keepdims=True) + eps)参考实现一致,且 q 额外乘width**-0.5的缩放。
该修复对概率输出的影响在 results/mlx/README.md 的 "Precision investigation" 中有量化记录:在 12 个超过 0.05 审查阈值的质量决策上,原生 MLX 权重转 FP32 对比 CPU FP32 的最大概率差从0.109083 降到 0.009402;源码 RMSNorm 权重在 FP32 下折叠后对比也从0.101279 降到 0.007598。
另外,每条预测都会记录运行时的源码提交(mlx_lm_source,来自distribution("mlx-lm").read_text("direct_url.json")),因此即使未来有人换了运行时,历史证据也能被追溯。
内存量化:--mlx-bits 4 / 8
MLX 后端支持不落盘的内存量化:
semif-score --backend mlx --mode direct --mlx-bits 8 ... semif-score --backend mlx --mode direct --mlx-bits 4 ...关键事实(原文档 + 源码双重确认):
--mlx-bits 8或--mlx-bits 4应用确定性仿射量化(deterministic affine quantization),group size64;- 它从同一个锁定 checkpoint出发,在内存中调用
nn.quantize(model, group_size=64, bits=bits, mode="affine")(mlx_backend.py),不写出另一个模型文件; - 结果记录该变换与源文件哈希(
source_artifact_sha256); - 量化会改变模型概率,必须与未量化 MLX 运行分开独立评估,绝不允许靠改容差来保留 CUDA 的精度声明(manifests/mlx-validation.json 的
quantization_policy明文规定); - 量化要求未量化源 checkpoint:若源 config 已含
quantization或quantization_config,加载器直接拒绝(mlx_backend.py); bits只接受None/4/8,其他值报错(mlx_backend.py);--mlx-bits只有在--backend mlx时才合法(cli.py)。
量化带来的实测内存与行为权衡(来自 results/mlx/README.md 的量化结果表,BF16 为默认基线):
| 精度 | Authored144 平衡准确率 | Perturbations108 平衡准确率 | 变更选项(authored / perturbations) | 质量评分峰值 MLX 内存 |
|---|---|---|---|---|
| BF16 | 81.32% | 77.99% | 0 / 0 | 8.12 GiB |
| Q8 | 81.85% | 76.58% | 2 / 4 | 4.66 GiB |
| Q4 | 78.92% | 79.89% | 14 / 6 | 2.82 GiB |
同一文档明确警告:8-bit 变更了 6/252 个决策(最大概率移动 0.1761),4-bit 变更了 20/252 个(最大移动 0.6776),"这些小型 authored fixture 不足以支撑通用质量排名,量化是可测量的内存/行为权衡;4-bit 适合内存实验,但会显著改变部分分布"。此外,量化模型在压缩生成对比中三次都只生成 20/21 个答案(作为失败完成证据保留,而非等价性能),direct 路径则每次都返回全部决策。
本地模型目录与修订标签
原文档强调的加载约束在源码中是这样落地的(mlx_backend.py):
- 远程模型:
revision必须是不可变的 40 字符 commit ID(正则[0-9a-f]{40}),main、latest这类可变标签会被拒绝——test_remote_model_requires_immutable_revision验证了这一行为; - 本地模型目录:要求提供修订标签,且本地目录同样会被逐文件 SHA-256 哈希(
source_artifact_sha256),"用户自报的本地修订号不是唯一的溯源依据"。
模型加载的硬性约束:原生 Qwen3.5 文本评分
与 Torch 后端一致,MLX 加载器禁止自定义模型代码(mlx_backend.py):
config.json中出现model_file(自定义代码路径)或model_type不是qwen3_5时直接抛ValueError;- 加载使用
tokenizer_config={"trust_remote_code": False},杜绝远程代码执行面; - test_loader_rejects_custom_or_unsupported_models 覆盖了
model_file: "custom.py"与未知model_type两种拒绝场景。
Apple Silicon 演示与可回放录制
上图为完整录制的一次真实本地 CLI 运行(历史项目名 OpenJev 时期)。录制细节见 docs/media/README.md:终端录制文件为 docs/media/openjev-mlx.cast(asciicast v2 格式),可用asciinema play openjev-mlx.cast回放真实进程输出与时间轴,并非模拟动画。录制环境为 Apple M5 Max、128 GiB 统一内存、MLX 0.32.2、锁定 MLX-LM 0.32.0;当前命令统一使用semif-score。录制中的 6.36 秒墙钟时间包含加载与哈希,属于 CLI 演示而非吞吐基准;条件选项分数未校准。
当前版本在该环境下的复现命令:
semif-score --backend mlx --mode direct \ --model Qwen/Qwen3.5-4B \ --revision 851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a \ --input examples/decisions.jsonl \ --output results-mlx-demo.jsonlexamples/decisions.jsonl即演示所用的固定样例,演示用的真实输出保留在 docs/media/openjev-mlx-results.jsonl(含模型修订、源哈希、概率与时间元数据),可用gzip/shasum思路自行核对。
压缩保留的证据与透明读取
为了保持仓库体积,大量历史行级报告被无损 gzip 压缩(.json.gz/.jsonl.gz),而摘要、manifest、CLI 证据与本文档保持明文。关键设计(源码见 benchmarks/mlx_evidence.py):
- 验证与参考运行读取器透明接受
.gz文件:read_bytes()优先解析原始文件名,若仅有.gz版本则透明解压;若同目录同时存在明文与压缩版本会拒绝歧义并抛错; - 新的基准运行保持原始明文 JSON/JSONL 格式,不新写压缩文件;
- 原始载荷校验和与复现细节在 证据索引(该 README 同时是原文档
../results/mlx/README.md的仓库根路径形式)。
查看一个保留的压缩报告:
gzip -dc results/mlx/2026-09-17-bf16-fixed/quality.json.gz校验解压后载荷的原始哈希(不解压落盘):
python benchmarks/mlx_evidence.py results/mlx/UNCOMPRESSED_SHA256SUMS该脚本输出{"verified_original_payloads": N, "status": "ok"},逐个核对 30 个保留运行文件的原始载荷 SHA-256。
复现基准证据:mlx_benchmark.py
从仓库根目录、环境激活后运行。每个输出目录必须是全新的(args.output.mkdir(parents=True, exist_ok=False),存在即失败);中断的运行保留为部分证据而不会被覆盖;GPU 基准一次只跑一个进程(统一内存环境避免相互干扰)。
python benchmarks/mlx_benchmark.py --suite diagnostic --output results/mlx/my-pilot python benchmarks/mlx_benchmark.py --suite all --output results/mlx/my-bf16 python benchmarks/mlx_benchmark.py --suite quantization --bits 8 --reference-run results/mlx/my-bf16 --output results/mlx/my-q8 python benchmarks/mlx_benchmark.py --suite quantization --bits 4 --reference-run results/mlx/my-bf16 --output results/mlx/my-q4各 suite 的记录内容(与 mlx_benchmark.py 的diagnostic/quality/shape/generation函数一一对应):
- Diagnostic(诊断):精确 tokenizer 等价性(MLX tokenizer 与原始 transformers tokenizer 逐字节对比,不一致即
AssertionError);小规模 pilot 上的 fresh、serial、parallel、重排序评分对比。 - Quality(质量):144 条 authored 与 108 条 perturbation 用例,复用现有评估器指标、missing-evidence 行为、与已发布 Torch 预测的行级差异(
compare()输出 argmax 翻转、提示词不匹配、最大概率差与均值)。注意:已发布 Torch 预测使用了串行前缀复用,因此该对比包含后端差异与执行形状差异,不是单一变量对照。 - Systems(系统,777 决策):37 个 state × 21 个决策,在 fresh / serial / parallel 三种模式下全覆盖,记录每 state 延迟、决策/秒、峰值 MLX 分配,以及与 fresh 的每个选项变更。
shape()内先硬校验len(rows) == 777、len(groups) == 37、每组 21 条,不符合即报错。 - Generation(生成对比):3 次重复,比较 21 条 direct 分布与同一模型写出紧凑 yes/no 数组(
compact_messages,仅有序"yes"/"no"值,无键、无置信度对象、无解释)。记录原始输出、有效性(必须是完整有序数组,缺失即失败)、首 token 时间、完成时间与一致性。无效生成答案是失败,不是"等价更快/更慢"的答案——这个判定哲学与全项目一致。
量化套件(quantization)运行 diagnostic、quality、generation 对比;--suite all --bits 8(或 4)则额外把全部 777 决策以三种模式跑完该精度。量化套件要求--bits且必须指定--reference-run(未量化 MLX 证据目录),并且 reference 的 model/revision 必须与当前运行完全一致(mlx_benchmark.py 校验 manifest 后还会记录 reference 文件哈希)。
计时边界:哪些时间被算进去了?
原文档对计时范围有严格声明,源码中 manifest 的timing_scope字段也原样记录:
- 不计时:模型加载、artifact 哈希、初始 warmup、结果写入;
- 计时:prompt 渲染、tokenization、evaluation、同步、CPU 读出(即从预热后的 GPU 执行开始,含 CPU 读出);
- MLX 是惰性的:
mx.eval(...)求值数组、mx.synchronize()等待 GPU 完成是计时的必要组成部分(score、_prefill、score_shared中都成对出现); - 峰值 MLX 分配包含模型权重与临时数组,不是macOS 进程总内存,也不直接等同于CUDA 的分配器指标;CUDA 与 Mac 的计时描述的是不同硬件。
实测参考(来自 results/mlx/README.md,Apple M5 Max、128 GiB、BF16、修复后运行时):direct 475.59 s(1.63 decisions/s)、serial 66.84 s(11.62/s,相对 fresh 提速 7.12×)、shared 45.06 s(17.24/s,提速 10.55×);serial 与 shared 各变更 3/777 个决策,且每个变更决策在至少一种执行形状下是 0.5/0.5 平局,最大概率移动分别为 0.06713 / 0.06028。这是"测得的提速伴随微小数值差异",不是位级一致的复用。质量套件中 252 个决策全部与已发布 Torch 预测选择相同胜出选项(authored144 81.32%、perturbations108 77.99%)。
验证命令与证据完整性
原文档给出的验证命令在仓库中均有对应物:
pytest -q (cd results/raw && shasum -a 256 -c SHA256SUMS) (cd results/mlx && shasum -a 256 -c SHA256SUMS) python benchmarks/mlx_evidence.py results/mlx/UNCOMPRESSED_SHA256SUMS python benchmarks/verify_published.py python benchmarks/verify_mlx.py results/mlx/my-bf16pytest -q覆盖 tests/test_mlx.py 的全部混合缓存回归(非 Apple Silicon 机器上会自动跳过,见模块级pytest.skip与importorskip);- 两份
SHA256SUMS分别校验已发布 CUDA 原始证据与 MLX 保留压缩字节; verify_mlx.py对每个最终基准目录做结构化校验,保留的 bf16-fixed、q8-fixed、q4-fixed 三个目录全部通过;- 原始 CUDA 摘要与证据保持原样未被修改;Mac 测量数据放在
results/mlx/下各自带日期的目录中。
精度探针:深入调查概率差异
如果在基准中发现超过阈值的概率差异,可以在 GPU 计时基准结束后运行 FP32 诊断(CPU 参考工作会共享 Mac 内存,务必在计时基准之后运行):
python benchmarks/mlx_precision_probe.py \ --run results/mlx/my-bf16 --output results/mlx/my-bf16/precision-probe.jsonmlx_precision_probe.py 的工作方式(对应原文档描述):
- 读取 manifests/mlx-validation.json 中冻结的审查阈值
bf16_probability_difference_review_threshold(0.05),选择超过阈值的质量行以及任何变更选项的行; - 用相同提示词对比原生 BF16 与 FP32 的 MLX 执行,并加载 PyTorch CPU FP32 参考(
Qwen3_5ForCausalLM,dtype=torch.float32,device_map={"": "cpu"},加载不完整会直接RuntimeError); - 额外做一个单独的权重折叠诊断:MLX-LM 会把 Qwen 的
(1 + RMSNorm weight)折叠成常规 RMSNorm 权重,BF16 下折叠先舍入加法、后续 FP32 转型会保留该舍入;探针用 FP32 源数组走同一 sanitizer 隔离这一"权重转换舍入"与"执行舍入"; - 输出比较报告(
bf16_mlx_vs_published、fp32_mlx_vs_cpu、bf16_mlx_vs_fp32_mlx等),并明确声明限制:"从观测差异中选择的诊断,不是留出法质量基准;CPU 计时不与 GPU 计时比较"。
该探针不改变生产模型加载方式,也不把诊断结果冒充基准。对 results/mlx/README.md 中记录的 12 个超阈值决策,修复后运行时把与 CPU FP32 的最大概率差从 0.109 级降到 0.009 级。
验证契约与政策
manifests/mlx-validation.json 冻结了 MLX 验证的全部契约,值得逐条解读:
- exact_contracts:prompt 文本、输入 token ID、答案 token ID、决策 ID、选项 ID、有限归一化概率、完整 fixture 覆盖——这些必须逐位一致;
tiny_fp32_cache_probability_atol: 0.0001:小型 FP32 缓存回归的容差(测试用pytest.approx(..., abs=1e-4));bf16_policy:"报告每个变更选项;超过审查阈值要调查,它是诊断触发条件而非准确率/校准保证;不要求 CUDA 与 Metal 逐位一致";runtime_note:原始 pilot 用 0.31.3,生产固定上游 L2 epsilon 修复,容差不变——修复不通过放宽容差来"洗白"差异;bf16_policy与quantization_policy共同划定了全项目的证据边界:typed output 不保证语义正确性,softmax 分数不是校准置信度。
边界与限制速查
综合原文档与仓库证据,使用 MLX 后端前应记住以下边界:
- reranker 模式在 MLX 下不可用(CUDA-only);
- PyTorch MPS 与 MLX 是两条独立 Apple 路径,docs/APPLE_SILICON.md 记录了 MPS 在混合注意力上会回退参考内核(
causal_conv1d、flash-linear-attention仅 CUDA),MPS 不应直接对标已提交的 RTX 3090 数字; - MLX 输出是条件选项分布:保证输出形状,不保证正确决策或校准置信度;精度、内核形状、前缀拆分、批大小的变化都可能移动分数并改变接近的决策,所有观测到的胜出选项变更都保留在比较报告中;
- 已发布的 CUDA 数字不受影响,两个 Apple 后端都是加性的。
结语:一条可追溯的 Apple Silicon 决策基线
SemIf 的 MLX 后端不是简单地把模型搬到 Metal,而是一整套"固定运行时 + 原生混合缓存 + 确定性量化 + 冻结阈值 + 可复现证据"的工程闭环:加载器在源头拒绝自定义模型与可变修订,score_shared用原生循环缓存的分支合并与右填充掩蔽保证并行共享正确性,分配缓存上限防止统一内存被 MLX 默认策略吃满,manifest 记录每次运行的运行时源码提交与源文件哈希,基准与探针只写新目录,校验命令逐字节核对压缩证据。按照本文的安装、评分、基准与验证步骤,你可以在自己的 Apple Silicon 机器上完整复现这份证据链,并用 results/mlx/README.md 中的实测数据(M5 Max、BF16、777 决策全模式对比、量化权衡)作为自己环境下的参照基准。
【免费下载链接】SemIf-OpenJev
Semantic ifs from open models, on a 3090 at home. Independent; not affiliated with Jev or TypeSafe.
相关推荐
SemIf 在 Apple Silicon 上的运行指南:PyTorch/MPS 与原生 MLX 双后端实战
SemIf 在 Apple Silicon 上的运行指南:PyTorch/MPS 与原生 MLX 双后端实战 SemIf 提供两条 Apple Silicon
SemIf Apple Silicon MLX 实测证据全解读:M5 Max 上 BF16/Q8/Q4 的精度、吞吐与可复现校验
SemIf Apple Silicon MLX 实测证据全解读:M5 Max 上 BF16/Q8/Q4 的精度、吞吐与可复现校验 本指南以 SemIf 仓库 r
Soup 0.75.0 修复解读:`soup doctor` 正确报告 Apple Silicon MLX 后端
Soup 0.75.0 修复解读: soup doctor 正确报告 Apple Silicon MLX 后端 导读 在 Soup 0.75.0 中, 变更记录
人工智能大模型微调LoRACLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考