跳到主内容
@wquguru
精选90meng shao技巧与观点

Perplexity 开源 Lily:面向 Apple Silicon

Perplexity 为其 Mac 端 Hybrid Compute 构建并开源了一个本地推理引擎:Lily

原文
发到 X
推荐理由

Apple Silicon 本地部署的硬核工程复盘,给出了具体的内核优化细节与取舍权衡,对做端侧推理优化的开发者极具参考价值。

Perplexity 为其 Mac 端 Hybrid Compute 构建并开源了一个本地推理引擎:Lily https://www.perplexity.ai/hub/blog/optimizing-on-device-inference-for-apple-silicon

Lily 用 Rust 运行时 + 自定义 Metal 内核,只为一个模型(Qwen3.6-35B-A3B)和一种硬件(Apple Silicon)做深度定制,不依赖 PyTorch 或 MLX。在 M5 Max,prefill 吞吐平均为 MLX-LM 的 1.23 倍,decode 为 1.35 倍。

Perplexity 为什么要做专用引擎?

背景动机:Hybrid Compute 中云端负责研究与推理,本地模型处理私密文件和应用。若本地推理跟不上节奏,整个任务体验就会割裂,因此需要快速处理提示词(prefill)并维持高生成速率(decode)。

通用 vs. 专用: · MLX + MLX-LM 是 Mac 上的主流方案,通用、开箱即用,但其可复用算子必须兼容大量模型架构。 · Lily 放弃通用性,把模型结构、分阶段执行计划、内核选择都收进一个围绕 Qwen 和 Apple Silicon 的 Rust 运行时中,单进程完成加载、会话管理、OpenAI 兼容 API 和 Metal 内核执行。

优化的前提:模型与硬件各有三种"形状"

Qwen3.6-35B-A3B 的三种计算模式 · 不均匀的专家分组:350 亿参数,每 token 只激活约 30 亿;路由器从 256 个专家中选 8 个,再加一个共享专家。各专家收到的 token 数量差异很大。 · 随上下文增长的注意力缓存:10 层全注意力,使用 GQA(16 个查询头共享 2 个 KV 头),KV cache 随生成变长,每步 decode 读取更多数据。 · 固定大小的递归状态:30 层 Gated DeltaNet,将历史压缩进固定尺寸状态。prefill 阶段既可逐 token 顺序扫描,也可分块并行,哪种更快取决于维度、负载和硬件。

Apple Silicon 的差异化路径 · 统一内存:CPU/GPU 共享一个物理内存池,模型无需 GPU 副本,但读写仍消耗带宽,寄存器和片上存储快但小。 · 两种计算单元:prefill 是 GEMM(矩阵×矩阵),权重可在成百上千行间复用,适合走 M5 每个 GPU 核心内的 Neural Accelerator(通过 Metal 4 tensor 操作);batch-1 decode 是 GEMV(矩阵×向量),几乎无权重复用,受内存带宽限制,更适合 GPU 的 向量 ALU。

团队明确指出:这些路径 MLX 同样具备(专家分组、融合递归内核、GQA 感知注意力),是双方共同的起点。Lily 的优势来自围绕固定结构做协调,而非独占某种硬件能力。

优化策略三原则

1. GPU 路径匹配推理阶段:prefill 走矩阵路径,decode 走向量路径。 2. 把模型结构映射到 GPU 上并最小化数据搬运:权重用前才解压、专家路由不回 CPU、递归状态留在片上、复用 GQA 共享的 KV 数据。 3. 按实测负载形状选内核:根据行数、专家分布、算子维度、上下文长度动态选择 tile 大小、执行布局和注意力路径。

Prefill 优化:复用权重,路由留在 GPU

GEMM 内联反量化:4-bit 权重(每 64 个共享一个 bf16 scale/bias,模型从约 70GB 压到 19.4GB)逐小块在片上解压、立即参与乘法,从不在统一内存中生成完整 bf16 数组;效果:512 token prompt 下 prefill +77.4%

路由全程在 GPU:直方图→前缀和→scatter→block map 全部放进一个 command buffer,不让 CPU 中途检查中间结果;效果:+89%;说明"内核数量"不是好指标——更快的路径内核更多但零 CPU 等待

Tile 大小匹配专家负载:32 行 tile + 4 个 simdgroup,替代固定 16 行;效果:2K prompt 下 +13.2%

递归状态寄存器常驻:每个 simdgroup 负责状态矩阵一列,加载一次后在寄存器里完成整个扫描,用 simdgroup 操作交换数据而非 threadgroup 内存;状态和门用 fp32 以防误差累积;效果:+5.6%(专家 GEMM 占 prefill 约 90%,递归扫描本身占比小)

分块 prefill:长 prompt 切成有界块,只保留当前块的临时激活,权重、递归状态、KV cache 跨块延续;效果:“限制峰值内存,不改变输出;注意力部分仍随 prompt 长度呈二次增长

Decode 优化:最小化每 token 搬运的字节

行并行 GEMV:单行激活专用内核,一个 simdgroup 协作读权重的不同部分:效果:基础能力,对齐 MLX 已有做法

Token 交接留在 GPU:双 command buffer + 双 GPU 常驻 token 槽,GPU 选出 token 后直接写入下一步的输入槽,消除每 token 一次的 CPU 同步

并发调度独立内核:一步 decode 启动 795 个内核,依赖关系只有 555 个串行阶段;改用记录真实数据依赖的并发 Metal pass,仅在必要处插 barrier

四条链融合:专家输入投影+门控激活、专家输出投影+路由分数+共享专家、Q/K 准备、递归更新+归一化;中间值留在寄存器,同时消除相应 barrier

KV cache 读取合并:相邻线程请求相邻字节;效果:key 带宽 33.8→47.9 GB/s,value 42.0→61.8 GB/s,3,840 上下文下 decode +2.1%

GQA 打包:4 个查询头放进一个 threadgroup,每行 KV 只加载一次复用 4 次;8 次独立请求变 2 次共享加载,输出字节完全一致;效果:32K 上下文下 decode +23.8%

长上下文切换注意力布局:≥32K 切到固定块布局,把 KV cache 分成等份并行处理;效果:32K +7.7%,64K +27.4%,128K +40.2%

优化的边界:哪些没用,以及为什么

投机解码反而慢 18%:验证阶段一次处理 2–5 行,对这种硬件是低效形状;且多行常选中不同专家,反而增加了专家权重读取。缩小草稿模型词表提升了草稿速度 4.7–5.1%,但整体循环仍不划算。作者强调这是负载相关的结论——其 Blackwell 上的批量部署仍使用投机解码。

其他无效尝试:减少 GPU 启动次数、整阶段重叠、更大 prefill tile、更广泛融合、加速路由器、输出投影与 token 选择合并,均未改善端到端。

已接近硬件极限:MoE GEMM 和 GEMV 分别达到其访问模式下最快持续权重读取速率的 97.9% 和 90.3%;从稀疏 GEMV 中移除算术运算只改变 0.2% 吞吐,证实瓶颈是权重读取而非计算;prefill 矩阵乘法孤立测试达理论上限 93%,模型内 80–86%。

更进一步:量化金融体系

看懂新闻只是起点——沿量化金融路径,把它变成能交付的工程能力

进入量化体系 →

关联讨论

同一事件的更多信源

相似阅读

另一事件,读法相近