文档导航

优化真实的 Snake 工作负载

所采用的 opt-in 路径结合了 MLX 编译、16 token 的长度桶和一个有界的已分词问题前缀缓存。它不改变权重、不量化模型、不缓存预测,也不在问题之间复用双向编码器的隐藏状态。

在一次成对的完整循环测试中,随附的优化路径在 2,400 次移动中达到 75.40 次移动/秒,而同一次测试中的 eager 推理是 70.82 次移动/秒:1.065×,约 6.5%。两者都是零死亡、2 次安全干预,并在 2,400/2,400 步上执行动作完全一致。预热并清空缓存后,合并的活动 MLX 内存增长为 0 字节。

启用它

laya-snake --optimize
laya-snake --optimize --max-speed

通用 API 暴露相同的 opt-in 控制项:

import laya_mlx as laya

agent = laya.load(
    "aac6fef/laya-multilingual-mlx",
    compile=True,
    pad_to_multiple=16,
    cache_prompts=True,
)

这三个选项默认全部关闭,保留现有的 eager 行为和基准配置。编译会针对输入形状特化;首次使用和新形状会产生编译成本。改变权重或模块结构后要构造新的 Agent。填充把序列长度向上取整到所请求的倍数,但不超过已配置的上下文上限。掩码会排除填充的 token。

cache_prompts=True 为每个 Agent 保留至多 128 个不可变的 PreparedQuestion 前缀,包括标记位置。缓存键包含 tokenizer 身份、特殊 token、问题类型、有序的渲染选项、指令和前缀预算。state 在每次 prepare 调用中只做一次清洗和分词,然后与每个问题前缀独立拼接。问题变化会创建或选取合适的前缀。每个问题仍然得到一次完整的模型前向。

形状与准备的消融

这个 demo 每次移动问三个问题:方向、安全路线估计和食物可达性估计。因此它的批大小是 3,而不是 1。在 32 个采样的真实录制棋盘上:

  • multilingual 的序列长度是 59、61、63 和 64。16 token 的倍数把它们全部放进一个 64 token 的桶。
  • 英文的序列长度是 66、68、69 和 70,映射到一个 80 token 的桶。
  • 把 multilingual 输入从 64 填充到 96 会给逐 token 的工作量增加 50%;它不是在单独短文本 API 固定样本中暗示的那种 93 到 96 的小幅调整。

候选在每一个相同 state 内按轮换顺序运行,且在此之前先访问过每一个被测形状一次。表中是同步的 Agent.predict 延迟,包含分词和输出转换,排除规划器/UI 工作和初始形状预热。

变体 Multilingual p50 / p95(ms) English p50 / p95(ms)
Eager 9.12 / 10.21 21.83 / 26.73
仅前缀复用 8.95 / 9.75 21.60 / 25.23
编译,实际长度 8.67 / 9.66 21.27 / 25.99
编译,填充到 96 10.92 / 11.72 25.66 / 29.88
编译 + 前缀复用 8.66 / 9.21 21.03 / 23.97
编译,工作负载桶 8.66 / 9.55 21.78 / 26.12
编译 + 桶 + 前缀复用 8.56 / 9.29 21.51 / 24.16

全部七个候选在每个 checkpoint 的 32/32 个棋盘上都与 eager 的提议方向和执行方向一致。在这些样本中,展示的四位小数概率和估计的最大差异是 0。这是有限样本下舍入输出的一致,不是声称内部浮点张量逐位相同。

消融使用有界的前缀准备包装器来筛选设计。下面的完整循环测试使用随附的真实 compile、pad_to_multiple 和 cache_prompts API 实现。它的正确性测试还比较了 state 截断、变化的标准、掩码清洗和缓存逐出之下的准备 ID 与标记。

随附的优化路径也通过了完整的真实 checkpoint 验证矩阵:三个 checkpoint 在 FP32 和 FP16 下各 63/63 个选出答案一致(共 378/378)。校准概率误差保持在现有容差内。每种配置还额外通过了 10 次有限、确定性的重复调用,测得活动内存增长 0 字节。优化后的验证数据。原始 eager 路径每种配置 100 次重复的结果仍在原始基准报告中。

对英文来说,在这个样本里用实际序列长度编译比强制使用更大的桶更好。demo 默认用 multilingual;通用 API 用户可以在启用编译和前缀复用的同时把 pad_to_multiple=None。

完整循环的成对测试

四个种子,各 600 次移动,候选顺序按种子交替。渲染包含 truecolor Rich 组装和 ANSI 序列化,不含终端模拟器的绘制。每次移动都做一次全新的预测。结果来自一次本地成对运行。

种子 Eager 移动/秒 优化后移动/秒 分数(两者) 动作一致数
101 68.60 78.04 20 600
102 70.07 78.62 24 600
103 76.75 85.62 23 600
104 68.50 63.15 16 600

优化路径在一个种子上更慢。因此,6.5% 是这次实测运行中的合并改进,而不是对每一个回合或每一台机器都保证的改进。更早的宽泛速度扫描和这次较晚的成对测试是不同的运行;不能把它们的绝对速率相减来声称加速。完整循环数据。

同时依据玩法和延迟来选择模型

两个 checkpoint 都跑了 20 个成对种子 × 300 次移动,checkpoint 顺序按种子交替。每个回合都使用相同的初始 state、食物 RNG 种子、精简特征描述和循环护盾。视野长度是固定的;这些是 300 次移动后的分数,不是以死亡或填满棋盘告终的完整对局。这个模型比较不包含终端渲染。

Checkpoint 存活 / 回合数 移动数 中位 / 平均分数 推理 p50 / p95(ms) 干预次数
laya 20 / 20 6000 7.0 / 6.9 23.15 / 28.21 0
multilingual 20 / 20 6000 10.0 / 9.9 9.38 / 14.38 2

multilingual 取得了更多吃食进展,并在这个工作负载上更快,所以它仍是默认的 demo checkpoint。这个结果评估的是这套特征辅助策略,不是通用推理质量或无辅助的 Snake 模型。每个回合与推理。

复现

uv run --extra demo python -m experiments.snake_runtime \
  --output artifacts/snake/runtime-multilingual.json
uv run --extra demo python -m experiments.snake_runtime \
  --model models/hub/laya-mlx --bucket 80 \
  --output artifacts/snake/runtime-english.json
uv run --extra demo python -m benchmarks.snake_optimized \
  --output artifacts/snake/optimized-paired.json
uv run --extra demo python -m benchmarks.snake_models \
  --episodes 20 --steps 300 --output artifacts/snake/models.json

按顺序运行 GPU 测量。先下载两个本地模型目录。已入库的源录制提供精确采样的棋盘状态。原始消融:multilingual、English、初始 96 token 试点。

更早的 compact 与 detailed 提示比较 在 64 个 state 上交替提示顺序,并在丢弃前 8 次预热迭代后发现中位数从 11.80 → 9.29 ms。它最初的临时记录没有存储棋盘快照,所以它是辅助证据,而不是主要的可复现消融。python -m benchmarks.snake_prompt 提供一个可复现的版本,会存储 state、种子、完整决策和方法。

实现遵循 MLX 的官方编译指南:使用长寿命的已编译可调用对象和常规的形状特化。它不会在依赖形状的 Python 模型代码上使用 shapeless=True。当前文档是在 Context7 CLI 请求因网络错误失败后,通过官方网站核对的。