<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh">
    <title>zLLM · 努力成为最好的推理引擎</title>
    <subtitle>zLLM 努力成为最好的推理引擎：Rust 原生、统一架构，并覆盖从嵌入式设备到超大规模集群的完整部署场景。</subtitle>
    <link rel="self" type="application/atom+xml" href="https://zhuai.tech/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://zhuai.tech"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-09-17T00:00:00+00:00</updated>
    <id>https://zhuai.tech/atom.xml</id>
    <entry xml:lang="zh">
        <title>DeepSeek V4.1 Flash 工程实践（四）：长 Prefill 与最后一公里优化</title>
        <published>2026-09-17T00:00:00+00:00</published>
        <updated>2026-09-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhuai.tech/blog/deepseek-v41-flash-final-optimization/"/>
        <id>https://zhuai.tech/blog/deepseek-v41-flash-final-optimization/</id>
        
        <content type="html" xml:base="https://zhuai.tech/blog/deepseek-v41-flash-final-optimization/">&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;zhuai.tech&#x2F;blog&#x2F;deepseek-v41-flash-vision-tensor-alignment&#x2F;&quot;&gt;上一篇&lt;&#x2F;a&gt;用中间张量对齐了图像预处理、视觉塔、aligner 和双偏置路由。至此，DeepSeek V4.1 Flash 的文本、Engram、八卡语言主干和多模态链路都已能够运行。&lt;&#x2F;p&gt;
&lt;p&gt;收官篇讨论最后一个问题：&lt;strong&gt;当输入从几千 token 增长到 261K、再到 1M 时，怎样让完整请求继续稳定前进，并把冷 prefill 的时间真正降下来？&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;这轮优化并不是简单换一个更大的矩阵 kernel。长上下文会同时放大稀疏索引、临时张量、显存碎片、跨 stage 交接和 chunk 调度的成本。某项微基准快了几倍，也可能在整模型里没有收益；某个配置刚好跑到 1600 token&#x2F;s，也可能只剩几十 MiB 显存，无法成为生产配置。&lt;&#x2F;p&gt;
&lt;p&gt;因此本文只保留三类证据：算子与官方 reference 的对拍、固定输入的完整输出门禁，以及关闭 profiler 后的端到端计时。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;deepseek-v41-flash&#x2F;prefill-evolution-zh.svg&quot; alt=&quot;261K 冷 Prefill 从恢复基线到最终保留路径的性能演进&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;xian-bu-hui-guan-fang-de-kua-ceng-xi-shu-yu-yi&quot;&gt;先补回官方的跨层稀疏语义&lt;&#x2F;h2&gt;
&lt;p&gt;长 prefill 的第一步不是提速，而是修正稀疏选择。&lt;&#x2F;p&gt;
&lt;p&gt;DeepSeek V4.1 Flash 的 DSA 不只是“每层在全部历史里取 Top-K”。第 20 层先按块聚合索引分数，从历史中选择候选块；后续第 24、28、32、36 层再在这批候选中使用各自的分数精选位置。它把稀疏性从单层的历史维度扩展到跨层复用：前一层发布候选范围，后面的消费者只扫描这段受限历史。&lt;&#x2F;p&gt;
&lt;p&gt;早期本地配置关闭了这条候选链，后续层直接在全历史上做 Top-K。短输入时，候选容量足以覆盖全部历史，两条路径看起来相同；超过约 16K 后，候选开始真正排除位置，差异才出现。&lt;&#x2F;p&gt;
&lt;p&gt;zLLM 补回了三部分状态：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;源层生成的候选块需要跨 stage 保活并随 hidden 一起传递。&lt;&#x2F;li&gt;
&lt;li&gt;消费层只为候选槽计算自己的索引分数，再把局部结果映射回全局位置。&lt;&#x2F;li&gt;
&lt;li&gt;最新可见块、因果前缀和不足一块的尾部必须遵循官方边界。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;真实维度测试覆盖到 1,048,576 个历史位置，候选块和两级 Top-512 集合与官方 PyTorch 结果一致。这个修复也改变了性能问题的形状：后四个消费者的评分范围被封顶为 16,384 个位置，但前面的源索引层仍要遍历随上下文增长的历史。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;zui-xin-prefill-lu-jing-bu-zhong-fu-jie-ma-tong-yi-fen-suo-yin-shu-ju&quot;&gt;最新 prefill 路径：不重复解码同一份索引数据&lt;&#x2F;h2&gt;
&lt;p&gt;修正候选语义后，profiler 指向前级 FP4 索引评分。朴素实现会在每个评分 tile 中反复解码相同的 query 和 key。上下文越长，这部分重复工作越明显。&lt;&#x2F;p&gt;
&lt;p&gt;最终路径分成四步。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-bao-liu-shu-zhi-shun-xu-de-wu-sun-zheng-shu-biao-shi&quot;&gt;1. 保留数值顺序的无损整数表示&lt;&#x2F;h3&gt;
&lt;p&gt;官方 FP4 的尾数与 E8M0 scale 在满足可证明的指数范围时，可以先对齐到小整数，再使用 INT8 输入和 INT32 点积得到精确整数和。随后乘回二的幂，并继续按原来的 head 顺序执行 ReLU、权重与归约。&lt;&#x2F;p&gt;
&lt;p&gt;这不是近似量化。运行时先检查每个 tile 是否满足范围证明；任何条件不满足，就回退原来的 FP32 累加路径。测试覆盖不同 head 数、维度、尾块、因果前缀以及百万历史位置，GPU 评分和 Top-K 都保持逐位一致。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-query-yu-key-zhi-zhan-kai-yi-ci&quot;&gt;2. query 与 key 只展开一次&lt;&#x2F;h3&gt;
&lt;p&gt;只把点积换成整数还不够。如果每个 tile 仍重复解析 FP4 code 和 scale，解码成本仍会随评分次数重复出现。新路径把本批 query 和历史 key 各展开一次，后续评分直接读取已准备的数据。&lt;&#x2F;p&gt;
&lt;p&gt;在独立同形状微测中，128 个 query × 131,072 个历史位置从约 47.6 ms 降到 6.55 ms；32 个 query × 524,288 个历史位置从约 47.6 ms 降到 6.62 ms。它们只是索引评分微测，不能直接当作整模型加速倍数，但明确说明了重复解码值得消除。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-dynamic-lds-an-zhen-shi-head-shu-fen-pei&quot;&gt;3. Dynamic LDS 按真实 head 数分配&lt;&#x2F;h3&gt;
&lt;p&gt;早期 kernel 按最多 64 个 head 预留共享内存，而实际前级索引使用 32 个 head。Dynamic LDS 改为按当前 head 数计算共享内存，让一个 CU 可以同时保留更多工作块。&lt;&#x2F;p&gt;
&lt;p&gt;同一份 261,933-token 冷输入中，这一步把 TTFT 从 357.10 秒降到 306.66 秒，输入速度从 733.51 提升到 854.15 token&#x2F;s。完整 1,024-token 输出与控制组一致。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;4-zhi-zhong-suan-zhen-zheng-bu-man-zu-zheng-shu-tiao-jian-de-head&quot;&gt;4. 只重算真正不满足整数条件的 head&lt;&#x2F;h3&gt;
&lt;p&gt;如果一个 query 只有一个 head 不满足指数条件，整块退回标量 FP32 会丢掉大部分收益。最终实现为异常 head 建立 warp mask，只对这些点积执行原始顺序重算；异常比例过高时，才整块回退。&lt;&#x2F;p&gt;
&lt;p&gt;经过一次展开、候选评分复用和局部回退后，同一 261K 请求在 chunk=256 时达到 1188.42 token&#x2F;s，并保持完整输出哈希一致。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;dense-yu-moe-zhi-bao-liu-zheng-mo-xing-neng-dui-xian-de-gai-dong&quot;&gt;Dense 与 MoE：只保留整模型能兑现的改动&lt;&#x2F;h2&gt;
&lt;p&gt;索引加速后，瓶颈转移到 routed MoE、attention projection 和 dense 临时张量。&lt;&#x2F;p&gt;
&lt;p&gt;Dense 路径按矩阵形状增加 16、64、128 行的直接 fragment load 分支，同时保持 K 方向每 16 个元素的原累加顺序。它把 261K 冷 prefill 从 1023.58 提升到 1075.85 token&#x2F;s，约提高 5.1%。&lt;&#x2F;p&gt;
&lt;p&gt;MoE 上测试过扩大 K tile、输入预转换、合并 LDS、改变 route block 和提前读取权重。多数微基准没有在完整模型中兑现，或者只对很小的路由组有效。最终只保留不改变矩阵累加次序、能够通过 CPU&#x2F;GPU oracle 的路径；没有把局部快 3%–5% 写成端到端收益。&lt;&#x2F;p&gt;
&lt;p&gt;这也是最后一轮优化的基本原则：&lt;strong&gt;热点排序会随着前一个瓶颈被消除而变化。&lt;&#x2F;strong&gt; 旧 profile 里的第一名，不一定还是新版本最值得优化的部分。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;chunk-bu-shi-yue-da-yue-hao-ta-jue-ding-quan-zhong-fu-yong-he-xian-cun-gong-zuo-ji&quot;&gt;Chunk 不是越大越好，它决定权重复用和显存工作集&lt;&#x2F;h2&gt;
&lt;p&gt;当算子与分配器逐步稳定后，我们重新测试 prefill chunk。相同 261,933-token 输入的结果如下：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: right&quot;&gt;Chunk&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;TTFT&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;冷 prefill 速度&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;最低采样显存余量&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;256&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;220.40 秒&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1188.42 token&#x2F;s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;约 5.05 GiB&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;512&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;177.29 秒&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1477.44 token&#x2F;s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;约 5.00 GiB&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;1024&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;173.45 秒&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1510.12 token&#x2F;s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;约 2.63 GiB&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;2048&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;170.67 秒&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1534.76 token&#x2F;s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;约 0.26 GiB&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;更大的 chunk 能让同一批权重服务更多 token，减少流水线填充和排空次数，但也会扩大 activation、路由结果、RoPE 与索引工作区。窗口从 32 缩到 16 只带来约 0.31% 的变化，也没有恢复显存，因此没有作为优化保留。&lt;&#x2F;p&gt;
&lt;p&gt;不同 chunk 会进入不同的 BF16 行数分派，长生成文本存在分叉；同一 chunk、相同运行件和固定输入的输出可以复现，算子 oracle 也保持一致，但我们没有把跨 batch shape 的全文差异说成已经解决。性能数据表达的是当前执行路径的工程结果，不替代完整质量评测。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;arena-de-zui-hou-yi-gong-li-rang-gui-huan-de-da-kuai-zhong-xin-can-yu-qie-fen&quot;&gt;Arena 的最后一公里：让归还的大块重新参与切分&lt;&#x2F;h2&gt;
&lt;p&gt;长 prefill 中，临时张量的大小不断变化。旧的 L1 复用层可能拿一个很大的已完成 buffer 服务小请求，却把整个块借出去；剩余空间暂时无法回到 arena，于是后续大请求又落到 &lt;code&gt;hipMalloc&lt;&#x2F;code&gt;，还可能触发隐式设备同步。&lt;&#x2F;p&gt;
&lt;p&gt;修正后，如果已完成块来自 arena、容量至少为请求的两倍且余量足够，复用层会在同一把 arena 锁内归还整块，再按实际大小重新切出。余下区域立刻可以合并并服务后续请求。&lt;&#x2F;p&gt;
&lt;p&gt;在 3 GiB arena、chunk=2048 下，最终完整请求达到 &lt;strong&gt;1553.91 token&#x2F;s&lt;&#x2F;strong&gt;，TTFT 为 &lt;strong&gt;168.56 秒&lt;&#x2F;strong&gt;。一次 5 GiB arena 实验达到 &lt;strong&gt;1600.04 token&#x2F;s&lt;&#x2F;strong&gt;，但最紧显卡的采样余量只剩约 &lt;strong&gt;38 MiB&lt;&#x2F;strong&gt;，没有作为稳定方案保留。最终口径选择 3 GiB，而不是追逐刚好跨过 1600 的单次峰值。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;zui-zhong-xing-neng-shu-ju&quot;&gt;最终性能数据&lt;&#x2F;h2&gt;
&lt;p&gt;测试机器为双路 AMD EPYC 9334，共 64 个物理核，约 1 TiB 主机内存，8 张 Radeon Pro W7900D，每卡约 48 GiB。模型使用官方 safetensors checkpoint 和 ROCm 后端。以下均为完整 HTTP 请求；加载时间不计入。&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;场景&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;输入 &#x2F; 输出&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;最终结果&lt;&#x2F;th&gt;&lt;th&gt;说明&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;261K 冷 prefill，恢复基线&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;261,933 &#x2F; 1,024&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;332.73 token&#x2F;s，TTFT 787.22 秒&lt;&#x2F;td&gt;&lt;td&gt;修复生产配置后、优化前的同输入基线&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;261K 冷 prefill，最终保留&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;261,933 &#x2F; 1,024&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;1553.91 token&#x2F;s，TTFT 168.56 秒&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;3 GiB arena，完整 SSE 正常结束&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;单路 1M 完整上下文&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1,000,411 &#x2F; 1,024&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;732.59 token&#x2F;s，TTFT 1365.59 秒&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;完整生成结束，输出与前一次 1M 运行一致&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;单路 DSpark decode&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;约 2K 输出长测&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;25.82 &#x2F; 26.14 token&#x2F;s&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;两轮无 profiler 结果&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;八路 DSpark 聚合 decode&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;八路长测&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;107.61 &#x2F; 109.47 token&#x2F;s&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;当前可靠两轮基线&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;八路历史最好区间&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;八路长稳态&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;111.15–114.43 token&#x2F;s&lt;&#x2F;td&gt;&lt;td&gt;展示上限，不作为默认结果&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;从恢复基线到最终 261K 结果，TTFT 缩短约 &lt;strong&gt;78.6%&lt;&#x2F;strong&gt;，冷输入速度提高到约 &lt;strong&gt;4.67 倍&lt;&#x2F;strong&gt;。这不是某一个 kernel 的功劳，而是官方稀疏语义、索引表示、共享内存、dense 分块、chunk 和内存生命周期共同作用的结果。&lt;&#x2F;p&gt;
&lt;p&gt;TTFT 从 HTTP 请求发出计到首个非空文本事件，冷 prefill 速度按输入 token 数除以 TTFT。它包含请求处理、八卡 prefill 和首个输出成本，并非纯 GPU kernel 吞吐。显存数据来自每 5 秒一次的整卡采样，可能漏掉瞬时峰值。Prefill 与 decode 来自不同请求，不能相加成同一次运行的总吞吐。&lt;&#x2F;p&gt;
&lt;p&gt;原始汇总数据随文章提供：&lt;a href=&quot;&#x2F;data&#x2F;deepseek-v41-20260917&#x2F;final-performance.json&quot;&gt;最终性能 JSON&lt;&#x2F;a&gt;。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;zhe-tiao-you-hua-lu-jing-zui-hou-liu-xia-liao-shen-me&quot;&gt;这条优化路径最后留下了什么&lt;&#x2F;h2&gt;
&lt;p&gt;四篇文章从 Engram 开始，经权重装配、八卡状态传递和视觉张量对拍，最后回到长上下文性能。它们共同说明一件事：模型能力不再只存在于 Transformer 的稠密计算中。&lt;&#x2F;p&gt;
&lt;p&gt;知识可以由 Engram 检索，注意力可以由跨层候选限制历史范围，视觉语义可以作为特殊行进入同一语言主干，推测解码可以减少完整模型的执行轮数。推理引擎需要管理的，已经是计算、检索、状态和资源的组合。&lt;&#x2F;p&gt;
&lt;p&gt;最终有效的优化也遵循同一顺序：先恢复模型真正的语义，再找到重复工作；先通过算子 oracle，再看完整输出；最后才用端到端 TTFT、吞吐和显存决定是否保留。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;能够跑到一个峰值，只说明实验成功；能够在正确性、容量和稳定性边界内反复完成请求，才说明工程路径成立。&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh">
        <title>DeepSeek V4.1 Flash 工程实践（三）：接入图像——用中间张量找差异</title>
        <published>2026-09-16T00:00:00+00:00</published>
        <updated>2026-09-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhuai.tech/blog/deepseek-v41-flash-vision-tensor-alignment/"/>
        <id>https://zhuai.tech/blog/deepseek-v41-flash-vision-tensor-alignment/</id>
        
        <content type="html" xml:base="https://zhuai.tech/blog/deepseek-v41-flash-vision-tensor-alignment/">&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;zhuai.tech&#x2F;blog&#x2F;deepseek-v41-flash-eight-gpu&#x2F;&quot;&gt;上一篇&lt;&#x2F;a&gt;从权重装配讲到八卡文本链路，重点是让压缩 KV、selection 与 mHC 状态正确跨过设备边界。这一篇把图像接到同一条语言主干上。&lt;&#x2F;p&gt;
&lt;p&gt;多模态接入最容易出现一种假象：HTTP 请求成功，视觉塔没有报错，模型也生成了一段文字，看起来整条链已经工作；但只要 resize 少取一列、RoPE 排列次序不同，或者 aligner 把补零后的网格拼错，后面每一层拿到的都会是形状正确、语义错误的张量。&lt;&#x2F;p&gt;
&lt;p&gt;因此，我们没有把“能够描述一张图片”当作最终的正确性证据。zLLM 先完成端到端接线，再以官方 PyTorch 实现为 oracle，把视觉链拆成多个可观测断点，找出第一个开始分叉的位置。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;duo-mo-tai-de-ji-ben-yuan-li-ba-tu-pian-bian-cheng-yu-yan-mo-xing-ke-yi-chu-li-de-token&quot;&gt;多模态的基本原理：把图片变成语言模型可以处理的 token&lt;&#x2F;h2&gt;
&lt;p&gt;语言模型的基本输入是一行行 hidden vector，原始图片却是二维像素。多模态模型首先要把这两种数据转换到同一个表示空间，再让它们进入同一条推理链路。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;deepseek-v41-flash&#x2F;multimodal-principle-zh.svg&quot; alt=&quot;多模态推理基本原理：图像分块、视觉塔、语言空间对齐与联合推理&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;第一步是&lt;strong&gt;图像分块&lt;&#x2F;strong&gt;。图片按照固定大小切成 patch，例如 DeepSeek V4.1 Flash 使用 14×14 的 RGB 区域。每个 patch 展开成一行数值，保留它位于第几行、第几列的网格坐标。这样，一张连续图片就变成了一串带有二维位置的视觉 token：&lt;&#x2F;p&gt;
&lt;p&gt;第二步是&lt;strong&gt;视觉塔编码&lt;&#x2F;strong&gt;。patch projection 先把像素行映射到视觉 hidden，随后 ViT 通过视觉 attention、位置编码和 MLP，让每个 patch 不只表示局部颜色，也结合整张图的上下文。例如胡萝卜的一小块橙色像素，经过视觉塔后可以同时携带形状、物体类别、相邻物体和画面位置等信息。这里得到的不是一个代表整张图的单独向量，而是一组保留空间结构的语义向量。&lt;&#x2F;p&gt;
&lt;p&gt;第三步是&lt;strong&gt;对齐语言空间&lt;&#x2F;strong&gt;。视觉 hidden 的宽度和语言模型通常不同，视觉 token 数量也可能过多。aligner 或 merger 会合并相邻视觉位置，再把结果投影到语言模型的 hidden size。这个模块由训练得到，它不仅改变矩阵维度，还负责把视觉特征映射到语言模型已经能够理解的表示空间：&lt;&#x2F;p&gt;
&lt;p&gt;最后，模型把这些视觉 token 放进文本序列中预留的图片位置。文本 token 仍来自词表 embedding，图片位置则由视觉语义向量覆盖。对语言模型而言，输入已经是一条统一的 hidden 序列：&lt;&#x2F;p&gt;
&lt;p&gt;语言层的 attention 可以让问题 token 读取相关的视觉 token，也可以让视觉信息与前后文本逐层融合。模型最终仍通过 LM head 预测下一个文本 token，因此图像识别、描述和视觉问答都表现为普通的文本生成。&lt;&#x2F;p&gt;
&lt;p&gt;在推理工程中，视觉塔通常在 prefill 阶段为本次图片计算一次语义向量；这些视觉 token 随其余输入一起建立语言模型的 KV cache。进入逐 token decode 后，后续 token 通过缓存继续读取已经编码的图像上下文，不需要为每个输出 token 重新运行整座视觉塔。&lt;&#x2F;p&gt;
&lt;p&gt;DeepSeek V4.1 Flash 与 Qwen3-VL 都遵循这条主线，区别集中在怎样分块、怎样表示二维或时间位置、怎样合并视觉网格，以及视觉特征在语言层注入一次还是多次。理解这层共同原理后，后面的中间张量对拍就有了明确目标：每个环节都必须保持训练时约定的视觉语义和空间位置。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;xian-ba-wan-zheng-tu-wen-lian-lu-jie-qi-lai&quot;&gt;先把完整图文链路接起来&lt;&#x2F;h2&gt;
&lt;p&gt;一次请求从 OpenAI 兼容接口的 &lt;code&gt;image_url&lt;&#x2F;code&gt; part 开始，经过的并不只是一个 Vision Encoder：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;deepseek-v41-flash&#x2F;deepseek-v41-vision-pipeline-zh.svg&quot; alt=&quot;DeepSeek V4.1 Flash 从 image_url 到八卡语言主干的完整图文链路&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;zLLM 在 node 侧保留消息中图像与文字的原始顺序，先把图像占位符写进 prompt。每张图完成预处理后，占位符展开为一段二维 token 网格：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;[IMAGE_START]
&lt;&#x2F;span&gt;&lt;span&gt;[IMAGE] [IMAGE] ... [IMAGE] [IMAGE_NEWLINE]
&lt;&#x2F;span&gt;&lt;span&gt;[IMAGE] [IMAGE] ... [IMAGE] [IMAGE_NEWLINE]
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;[IMAGE_END]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这些位置在 &lt;code&gt;input_ids&lt;&#x2F;code&gt; 中都使用同一个 &lt;code&gt;image_token_id&lt;&#x2F;code&gt;。START、IMAGE、NEWLINE 和 END 的差异由额外的类型与 embedding 组装规则表达：普通 IMAGE 行由 aligner 输出覆盖，首尾与换行位置使用各自训练好的向量。&lt;&#x2F;p&gt;
&lt;p&gt;这个表示还必须适应 chunked prefill。一个图像 span 可能横跨两个输入块，所以视觉结果不能只在某个完整 prompt 上替换一次。zLLM 保存每张图对应的 token range 和覆盖行，在每个 prefill chunk 装配 embedding 时，只替换与当前范围相交的部分。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-yi-bu-fu-xian-tu-xiang-yu-chu-li-de-chi-san-gui-ze&quot;&gt;第一步：复现图像预处理的离散规则&lt;&#x2F;h2&gt;
&lt;p&gt;V4.1 Flash 的视觉配置使用 14×14 patch、1,024 维视觉 hidden、16 个 attention head 和 32 层 ViT。aligner 按 3×3 网格合并视觉输出，再投影到语言模型的 5,120 维 hidden。单张图最多占用 1,024 个语言 token，过小的图片则先根据最小像素预算等比放大。&lt;&#x2F;p&gt;
&lt;p&gt;图像预处理包含一串离散决策：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;根据宽高比与 token 上限计算能容纳的最大网格。&lt;&#x2F;li&gt;
&lt;li&gt;将目标宽高约束到 patch 与 downsample 对应的网格。&lt;&#x2F;li&gt;
&lt;li&gt;按比例执行 PIL &lt;code&gt;contain&lt;&#x2F;code&gt; 语义的 bicubic resize。&lt;&#x2F;li&gt;
&lt;li&gt;使用 RGB 127 的灰色在空白方向居中补边。&lt;&#x2F;li&gt;
&lt;li&gt;按 &lt;code&gt;(grid_y, grid_x, channel, y, x)&lt;&#x2F;code&gt; 展开 patch。&lt;&#x2F;li&gt;
&lt;li&gt;将像素从 &lt;code&gt;[0, 255]&lt;&#x2F;code&gt; 映射到 &lt;code&gt;[-1, 1]&lt;&#x2F;code&gt;。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;第一次差异就出现在第三步。通用图像工具里常见的 contain 实现会用 &lt;code&gt;ceil&lt;&#x2F;code&gt; 保证覆盖目标边界，官方路径则根据宽高比分支计算另一条边，并用 &lt;code&gt;round&lt;&#x2F;code&gt; 得到实际尺寸。差一个像素，可能改变补边位置和边界 patch；从此以后所有对应行都不同。&lt;&#x2F;p&gt;
&lt;p&gt;zLLM 因而单独实现这套规划和取整规则，没有把已有的通用 resize helper 直接套进来。图像预处理不是视觉塔外面的普通 I&#x2F;O，它已经是模型数值定义的一部分。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-er-bu-yong-wu-ge-zhong-jian-zhang-liang-xun-zhao-di-yi-ge-fen-cha-dian&quot;&gt;第二步：用五个中间张量寻找第一个分叉点&lt;&#x2F;h2&gt;
&lt;p&gt;仅比较最终生成文本，无法判断误差来自预处理、视觉 attention、aligner 还是语言模型。我们的做法是让官方实现逐段落盘，再让 zLLM 在相同位置导出张量：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;观测点&lt;&#x2F;th&gt;&lt;th&gt;它能排除或定位什么&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;patches&lt;&#x2F;td&gt;&lt;td&gt;resize、补边、归一化和 patch 行序&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2D RoPE cos&#x2F;sin&lt;&#x2F;td&gt;&lt;td&gt;高度与宽度坐标、频率排列与旋转布局&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;block 0 输出&lt;&#x2F;td&gt;&lt;td&gt;patch projection、QKV bias、attention 与残差顺序&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;final norm 输出&lt;&#x2F;td&gt;&lt;td&gt;32 层误差是否持续放大，以及 RMSNorm 语义&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;aligner 输出&lt;&#x2F;td&gt;&lt;td&gt;右&#x2F;下补零、3×3 unfold、通道次序和双线性投影&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;这种方法的关键不是多导出几个文件，而是始终寻找&lt;strong&gt;最早出现显著差异的边界&lt;&#x2F;strong&gt;。如果 patches 已经不同，就先停在预处理；如果 patches 一致而 block 0 分叉，就检查位置编码与第一个视觉块。这样可以避免拿最终文字反复猜测几十层之前的错误。&lt;&#x2F;p&gt;
&lt;p&gt;zLLM 为此增加了可选的视觉调试输出，并用真实权重建立聚焦测试。测试只运行视觉塔和 aligner，单卡大约一秒即可完成一次对拍，不必每次加载完整八卡语言链路后再观察回答。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-san-bu-2d-rope-de-h-w-shi-fen-kuai-pai-lie&quot;&gt;第三步：2D RoPE 的 h&#x2F;w 是分块排列&lt;&#x2F;h2&gt;
&lt;p&gt;视觉 attention 使用二维位置。对于网格位置 &lt;code&gt;(h, w)&lt;&#x2F;code&gt;，官方实现先构造高度与宽度两组频率，再展平为：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;[h·f0, h·f1, ..., h·fn, w·f0, w·f1, ..., w·fn]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;早期实现把它理解成了交错布局：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;[h·f0, w·f0, h·f1, w·f1, ...]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;两者 shape 完全相同，&lt;code&gt;cos² + sin² = 1&lt;&#x2F;code&gt; 之类的基本检查也都会通过，但每个通道获得的空间坐标不同。对拍 cos&#x2F;sin 后，我们把实现修正为 h&#x2F;w 分块，并保持官方将向量拆成两半执行旋转的约定。&lt;&#x2F;p&gt;
&lt;p&gt;这也是中间张量比 shape 检查更有价值的例子：排列错误不会造成越界，也不会让数值变成 NaN，只会让模型看到另一套空间。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-si-bu-aligner-bu-de-shi-er-wei-wang-ge-bu-shi-lian-xu-wei-xing&quot;&gt;第四步：aligner 补的是二维网格，不是连续尾行&lt;&#x2F;h2&gt;
&lt;p&gt;32 层 ViT 结束后，aligner 将视觉网格右侧和底部补到 3 的倍数，再对每个 3×3 窗口做 unfold。这里曾经把“补零”简化成在扁平张量末尾追加零行。&lt;&#x2F;p&gt;
&lt;p&gt;底部补零时，两种写法碰巧一致；右侧补零时则不同。假设原网格每行有 5 个 patch，补到 6 列后，每一行的第 6 个位置都应为零。只在扁平数组尾部补零，会把第二行的第一个有效 patch 放到第一行的补零位置，之后整张图持续错位。&lt;&#x2F;p&gt;
&lt;p&gt;修正后的实现按 &lt;code&gt;(source_y, source_x)&lt;&#x2F;code&gt; 计算目标 3×3 窗口与窗口内位置，并遵循官方 unfold 的通道主序：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;[channel 0 的 9 个位置,
&lt;&#x2F;span&gt;&lt;span&gt; channel 1 的 9 个位置,
&lt;&#x2F;span&gt;&lt;span&gt; ...]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;当前版本会把 final norm 输出下载到主机，完成这次网格拼装后再上传。单图中转不超过约 15 MB，先换取清晰、可对拍的语义；把拼装改为设备 kernel 是后续独立优化。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-wu-bu-588-lie-bu-dao-592-lie-rang-ying-jian-yue-shu-bao-chi-shu-xue-deng-jia&quot;&gt;第五步：588 列补到 592 列，让硬件约束保持数学等价&lt;&#x2F;h2&gt;
&lt;p&gt;一个 14×14 RGB patch 有：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;3 × 14 × 14 = 588
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;588 不是 16 的倍数，而当前 ROCm 矩阵执行路径要求输入列按 16 对齐。这里不能改变 patch 内容或重新解释权重。zLLM 同时在输入 patch 和 projection 权重的尾部补四个零，将 588 列扩展到 592 列：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;[x0 ... x587, 0, 0, 0, 0]
&lt;&#x2F;span&gt;&lt;span&gt;×
&lt;&#x2F;span&gt;&lt;span&gt;[w0 ... w587, 0, 0, 0, 0]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;新增列对点积贡献恒为零，所以输出与原始 588 维线性层数学等价，同时满足 kernel 的 tile 约束。这类适配应停留在 backend 的准备边界，不能泄漏成模型架构的新维度。&lt;&#x2F;p&gt;
&lt;p&gt;修正 contain 取整、RoPE 布局、aligner 网格拼装和 patch 对齐后，记录中的 aligner 最大相对差异约为 5%。我们把它视为 BF16 执行路径下仍需继续观察的误差范围，而没有宣称逐元素完全一致。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;heng-xiang-dui-bi-deepseek-v4-1-flash-yu-qwen3-vl-de-shi-jue-ta&quot;&gt;横向对比：DeepSeek V4.1 Flash 与 Qwen3-VL 的视觉塔&lt;&#x2F;h2&gt;
&lt;p&gt;zLLM 也接入了 Qwen3-VL。两者都把动态尺寸图片变成 patch，经 ViT 编码并投影到语言模型 hidden，但具体张量契约差异很大。下面以 zLLM 当前的 &lt;strong&gt;Qwen3-VL-32B&lt;&#x2F;strong&gt; 配置为对照对象；Qwen3-VL 家族其他尺寸和 MoE 版本的语言层规格可能不同。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;deepseek-v41-flash&#x2F;deepseek-qwen3vl-vision-comparison-zh.svg&quot; alt=&quot;DeepSeek V4.1 Flash 与 Qwen3-VL-32B 视觉塔架构对比&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;环节&lt;&#x2F;th&gt;&lt;th&gt;DeepSeek V4.1 Flash&lt;&#x2F;th&gt;&lt;th&gt;zLLM 当前 Qwen3-VL-32B 路径&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;patch&lt;&#x2F;td&gt;&lt;td&gt;14×14，单图每行 &lt;code&gt;3×14×14=588&lt;&#x2F;code&gt; 列&lt;&#x2F;td&gt;&lt;td&gt;16×16，temporal patch=2，每行 &lt;code&gt;3×2×16×16=1536&lt;&#x2F;code&gt; 列&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;视觉塔&lt;&#x2F;td&gt;&lt;td&gt;32 层、hidden 1,024、16 heads、RMSNorm + SwiGLU&lt;&#x2F;td&gt;&lt;td&gt;27 层、hidden 1,152、16 heads、LayerNorm + GELU MLP&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;图像尺寸规划&lt;&#x2F;td&gt;&lt;td&gt;以最多 1,024 个 LLM 图像 token 为目标，contain 后灰色补边&lt;&#x2F;td&gt;&lt;td&gt;以 min&#x2F;max pixels 做 smart resize，目标边长对齐 &lt;code&gt;patch×merge=32&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;视觉位置&lt;&#x2F;td&gt;&lt;td&gt;h&#x2F;w 分块的 2D RoPE&lt;&#x2F;td&gt;&lt;td&gt;插值后的学习位置 embedding，再叠加视觉 2D RoPE&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;空间降采样&lt;&#x2F;td&gt;&lt;td&gt;3×3 aligner，允许右侧&#x2F;底部补零&lt;&#x2F;td&gt;&lt;td&gt;2×2 merger，预处理先保证网格可整除&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;进入语言模型&lt;&#x2F;td&gt;&lt;td&gt;最终 aligner 输出覆盖 IMAGE 行，另有 START&#x2F;NEWLINE&#x2F;END 学习向量&lt;&#x2F;td&gt;&lt;td&gt;merger 输出覆盖 &lt;code&gt;&amp;lt;image_pad&amp;gt;&lt;&#x2F;code&gt;，由 &lt;code&gt;&amp;lt;vision_start&amp;gt;&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;&amp;lt;vision_end&amp;gt;&lt;&#x2F;code&gt; 包围&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;语言模型位置&lt;&#x2F;td&gt;&lt;td&gt;图像网格序列化后沿文本序列前进&lt;&#x2F;td&gt;&lt;td&gt;M-RoPE 为视觉 token 保留 temporal&#x2F;height&#x2F;width 三轴位置，并用 &lt;code&gt;rope_delta&lt;&#x2F;code&gt; 衔接后续文本&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;多层视觉注入&lt;&#x2F;td&gt;&lt;td&gt;主路径在 embedding 入口注入一次&lt;&#x2F;td&gt;&lt;td&gt;DeepStack 从 ViT 第 8、16、24 层取特征，经独立 merger 后继续注入语言模型前部&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;当前语言主干&lt;&#x2F;td&gt;&lt;td&gt;MoE，图像行使用 &lt;code&gt;bias_vl&lt;&#x2F;code&gt; 双偏置路由&lt;&#x2F;td&gt;&lt;td&gt;32B 接入路径使用 dense MLP，没有图像行 MoE 双偏置&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;两者最直观的共同点，是视觉输出行数必须与语言模型中的图像占位行严格相等。差一行就会让后续文本位置整体错位。它们解决这个问题的方式不同：DeepSeek 在 span 内显式加入每行 NEWLINE，并为 START、NEWLINE、END 准备学习向量；Qwen3-VL 使用独立的 vision 边界 token 和连续的 &lt;code&gt;&amp;lt;image_pad&amp;gt;&lt;&#x2F;code&gt; 区间，同时为整个序列构造三轴 position ids。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;patch-pai-lie-ti-xian-liao-ge-zi-de-xia-cai-yang-fang-shi&quot;&gt;Patch 排列体现了各自的下采样方式&lt;&#x2F;h3&gt;
&lt;p&gt;DeepSeek 的 patch 按普通二维行主序展开，aligner 到视觉塔末尾才处理 3×3 窗口，因此边缘网格可能需要补零。Qwen3-VL 的预处理会让同一个 2×2 merge group 中的 patch 连续排列，视觉塔结束后可以直接执行空间 merge。对单图，temporal patch 的两个槽位由同一帧填充；对视频，则由相邻两帧组成一个时间 patch。Qwen3-VL 因而从 patch 输入开始就同时保留图像和视频需要的时间维。&lt;&#x2F;p&gt;
&lt;p&gt;这也解释了为什么 DeepSeek 的 588 列需要补到 592，而 Qwen3-VL 的 1,536 列天然满足 16 对齐。对齐问题来自模型 patch 契约与 kernel tile 的组合，不能把一种视觉塔的处理规则复制到另一种模型。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;qwen3-vl-you-liang-ceng-wei-zhi-yu-yi&quot;&gt;Qwen3-VL 有两层位置语义&lt;&#x2F;h3&gt;
&lt;p&gt;DeepSeek 视觉塔直接使用二维 RoPE，输出进入语言模型后按展开后的 span 顺序参与文本位置计算。Qwen3-VL 在视觉塔内部先把一张 48×48 的学习位置表插值到实际图像网格，再应用二维 RoPE；进入语言模型时，又为视觉 token 构造 temporal、height、width 三轴 M-RoPE。文本 token 的三个轴使用相同位置，视觉区间则沿各自的网格坐标推进，&lt;code&gt;rope_delta&lt;&#x2F;code&gt; 负责让图像后的 decode 位置连续。&lt;&#x2F;p&gt;
&lt;p&gt;所以，对拍 Qwen3-VL 时仅检查视觉塔的 cos&#x2F;sin 还不够，还要检查学习位置表插值、三轴 position ids 和图像结束后的 &lt;code&gt;rope_delta&lt;&#x2F;code&gt;。DeepSeek 本文遇到的 h&#x2F;w 排列错误属于视觉塔内部；Qwen3-VL 的位置错误还可能发生在视觉输出进入语言模型之后。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;deepstack-rang-shi-jue-shu-chu-bu-zai-zhi-you-yi-fen&quot;&gt;DeepStack 让“视觉输出”不再只有一份&lt;&#x2F;h3&gt;
&lt;p&gt;DeepSeek 的主路径把最终 aligner embedding 放进图像 span，随后由语言模型逐层处理。Qwen3-VL 除了最终 merger 输出，还从 ViT 第 8、16、24 层各取一份中间特征，通过三个 DeepStack merger 投影到语言 hidden，并在语言模型前部对应层继续相加。&lt;&#x2F;p&gt;
&lt;p&gt;这条路径保留了不同深度的视觉信息：最终视觉特征负责输入 embedding，中间视觉特征继续修正早期语言 hidden。工程上必须把四份结果作为一个整体管理——一份主 embedding 和三份 DeepStack feature。只接最终 merger，形状与文本生成链路仍可能正常，但空间定位和细节感知会失去模型原本依赖的中间层信息。Qwen 官方也将 DeepStack 描述为融合多级 ViT 特征的核心架构更新。&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;QwenLM&#x2F;Qwen3-VL&quot;&gt;Qwen3-VL 官方仓库&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;zllm-gong-xiang-suan-zi-neng-li-bao-liu-mo-xing-zi-ji-de-bian-pai&quot;&gt;zLLM 共享算子能力，保留模型自己的编排&lt;&#x2F;h3&gt;
&lt;p&gt;两条路径可以共享图像解码、动态尺寸预算、patch tensor、视觉 attention、线性层、空间 merge 和 embedding scatter 等 backend capability。但 resize 规则、patch 行序、位置编码、merger 布局、span 协议与注入层次仍由各自的 model runtime 编排。&lt;&#x2F;p&gt;
&lt;p&gt;这种边界与&lt;a href=&quot;https:&#x2F;&#x2F;zhuai.tech&#x2F;blog&#x2F;deepseek-v41-flash-eight-gpu&#x2F;&quot;&gt;第二篇&lt;&#x2F;a&gt;介绍的格式无关实现一致：共享的是稳定的执行能力，模型语义在准备与编排层明确表达。视觉塔看起来都像“ViT + projector”，实现时仍需逐项对齐它们真实的数据契约。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-liu-bu-tu-xiang-span-bi-xu-gai-bian-engram-de-xu-lie-yu-yi&quot;&gt;第六步：图像 span 必须改变 Engram 的序列语义&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;zhuai.tech&#x2F;blog&#x2F;deepseek-v41-flash-engram&#x2F;&quot;&gt;第一篇&lt;&#x2F;a&gt;介绍过 Engram 如何从连续 token 的 n-gram 哈希中检索记忆。图像 span 的所有位置共享同一个 token id；如果直接把它们推入哈希窗口，会制造大量没有语言意义的重复 n-gram，还可能让图像前后的文本跨过整段图片错误连接。&lt;&#x2F;p&gt;
&lt;p&gt;zLLM 按官方语义把图像位置推入 &lt;code&gt;DEAD&lt;&#x2F;code&gt; 状态，阻断跨图像 span 的 n-gram。Engram 注入层还需要一份逐行 &lt;code&gt;image_mask&lt;&#x2F;code&gt;：文本行执行检索和门控，图像行关闭 Engram 门控，让视觉 embedding 直接通过。输出阶段如果采样到了内部 image token，也会立即截断，避免把协议内部标记输出并回喂到下一轮。&lt;&#x2F;p&gt;
&lt;p&gt;这说明图像接入不只是在 embedding 表里替换几行。任何依赖 token 序列的状态机，包括 n-gram、KV cache、位置与输出过滤，都必须知道这些行的类型。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-qi-bu-tu-xiang-xing-he-wen-ben-xing-shi-yong-bu-tong-de-moe-lu-you-pian-zhi&quot;&gt;第七步：图像行和文本行使用不同的 MoE 路由偏置&lt;&#x2F;h2&gt;
&lt;p&gt;视觉特征进入 40 层语言模型后，还要经过 MoE。每个 token 不会执行全部专家，而是由 router 从专家集合中选出 Top-K，再把这些专家的输出加权合并。&lt;&#x2F;p&gt;
&lt;p&gt;“双偏置路由”中的“双”，指 checkpoint 为同一个 router 提供了两张专家校准表：文本行使用 &lt;code&gt;bias&lt;&#x2F;code&gt;，图像 span 使用 &lt;code&gt;bias_vl&lt;&#x2F;code&gt;。它不表示把两份 bias 同时相加，也不表示执行两套 router。router 权重、专家网络和 Top-K 流程仍然只有一套，运行时根据当前行的类型选择其中一份 bias。&lt;&#x2F;p&gt;
&lt;p&gt;以专家 &lt;code&gt;e&lt;&#x2F;code&gt; 和一行 hidden state &lt;code&gt;h&lt;&#x2F;code&gt; 为例，V4.1 的路由可以简化成五步：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;原始 logit:       l_e = h · W_router[e]
&lt;&#x2F;span&gt;&lt;span&gt;无偏路由分数:     r_e = sqrt(softplus(l_e))
&lt;&#x2F;span&gt;&lt;span&gt;当前行使用的偏置: b_e = text ? bias[e] : bias_vl[e]
&lt;&#x2F;span&gt;&lt;span&gt;专家选择:         TopK(r_e + b_e)
&lt;&#x2F;span&gt;&lt;span&gt;专家混合权重:     r_e &#x2F; sum(r_selected) × route_scale
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;偏置只参与 Top-K 排名，最终混合权重仍由选中专家的无偏分数 &lt;code&gt;r_e&lt;&#x2F;code&gt; 计算。这样可以调整“哪些专家更容易被某类 token 选中”，同时不把 correction bias 注入专家输出的数值权重。&lt;&#x2F;p&gt;
&lt;p&gt;一个四专家、Top-2 的简化例子更直观：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;专家&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;原始分数 &lt;code&gt;r&lt;&#x2F;code&gt;&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;文本 bias&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;图像 &lt;code&gt;bias_vl&lt;&#x2F;code&gt;&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;E0&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.60&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.00&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.00&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;E1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.55&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.10&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.00&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;E2&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.50&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.00&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.20&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;E3&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.45&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.00&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.00&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;文本行按 &lt;code&gt;r + bias&lt;&#x2F;code&gt; 选中 E1 与 E0，图像行则按 &lt;code&gt;r + bias_vl&lt;&#x2F;code&gt; 选中 E2 与 E0。图像行对 E2 的实际混合权重仍使用原始的 0.50，而不是加过偏置的 0.70。双偏置因此表达的是&lt;strong&gt;按模态校准专家集合&lt;&#x2F;strong&gt;，而不是按模态放大专家输出。&lt;&#x2F;p&gt;
&lt;p&gt;如果图像行继续使用文本 bias，矩阵形状、专家数量和输出维度都仍然合法，但视觉 token 会被送往另一组专家。这与 RoPE 排列错误很相似：系统可以稳定运行，语义已经偏离模型训练时的路径。&lt;&#x2F;p&gt;
&lt;p&gt;zLLM 将 &lt;code&gt;router_bias_vl&lt;&#x2F;code&gt; 和逐行 &lt;code&gt;image_rows&lt;&#x2F;code&gt; mask 从模型权重一直传到通用 MoE 接口，再由 ROCm kernel 为每一行选择对应 bias。同一个 prefill batch 可以同时包含文本行与图像行，因此选择发生在行级，不能为整批只设置一种 bias。纯文本模型或没有第二组 bias 的路径继续使用原接口语义。CPU reference 与 HIP kernel 使用文本、图像交错的输入逐行对拍，同时检查两件事：专家 ID 必须匹配，route weight 也必须保持官方的无偏归一化规则。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;duan-dao-duan-jie-guo-yi-ji-ta-neng-zheng-ming-shen-me&quot;&gt;端到端结果，以及它能证明什么&lt;&#x2F;h2&gt;
&lt;p&gt;完成视觉张量修正和双偏置路由后，八卡端到端请求取得了几项直观结果：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;输入与提问&lt;&#x2F;th&gt;&lt;th&gt;实际结果&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;胡萝卜示例图，开放描述&lt;&#x2F;td&gt;&lt;td&gt;回答识别为“一堆胡萝卜”&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;玉米示例图，英文识别&lt;&#x2F;td&gt;&lt;td&gt;回答包含“fresh corn”&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;玉米细节描述，补齐双偏置前后&lt;&#x2F;td&gt;&lt;td&gt;输出由 10 token 增至 46 token&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;胡萝卜图片，中文数量提问&lt;&#x2F;td&gt;&lt;td&gt;正确回答“五根胡萝卜”&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;这些结果证明 HTTP、预处理、视觉塔、span、语言主干与路由已经连成一条可用链路，也显示 &lt;code&gt;bias_vl&lt;&#x2F;code&gt; 对图像 token 的专家选择有实际影响。它们是工程验收样例，不能替代覆盖 OCR、图表、空间关系、细粒度识别等任务的定量视觉评测。&lt;&#x2F;p&gt;
&lt;p&gt;当前仍有三个明确边界：部分图片在开放式英文提示下回答偏短；aligner 网格拼装仍有主机中转；zLLM 的 GELU 使用 tanh 近似，而官方视觉实现使用 erf，局部差异约在 &lt;code&gt;1e-3&lt;&#x2F;code&gt;。完整模型的统计对齐与稳定多模态吞吐还需要继续验收。&lt;&#x2F;p&gt;
&lt;p&gt;多模态接入真正困难的地方，是每个“看起来差不多”的细节都会被后续网络放大。可靠的方法是把链路切成可验证的数学边界：先对齐像素与 patch，再对齐位置和视觉层，随后对齐网格与语言 embedding，最后检查进入 MoE 后的逐行路由。这样得到的不只是一次能出字的演示，而是一条能够继续优化的工程基线。&lt;&#x2F;p&gt;
&lt;p&gt;下一篇将汇总当前性能数据，说明 50K prefill、连续 decode 和提前投影收益分别采用什么测量口径，以及哪些数字仍不能放在同一张对比表里。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;工程依据：zLLM 的 DeepSeek V4.1 Flash 接入记录、当前 Qwen3-VL runtime，以及 &lt;code&gt;69ff88ad&lt;&#x2F;code&gt;、&lt;code&gt;ae6248a5&lt;&#x2F;code&gt; 和 &lt;code&gt;51f83af8&lt;&#x2F;code&gt; 的实际改动。模型语义参考 DeepSeek 官方的 &lt;a href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;deepseek-ai&#x2F;DeepSeek-V4.1-Flash&#x2F;blob&#x2F;main&#x2F;inference&#x2F;vision.py&quot;&gt;vision.py&lt;&#x2F;a&gt;、&lt;a href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;deepseek-ai&#x2F;DeepSeek-V4.1-Flash&#x2F;blob&#x2F;main&#x2F;inference&#x2F;image_processor.py&quot;&gt;image_processor.py&lt;&#x2F;a&gt; 与 &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;QwenLM&#x2F;Qwen3-VL&quot;&gt;Qwen3-VL 官方仓库&lt;&#x2F;a&gt;。&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh">
        <title>DeepSeek V4.1 Flash 工程实践（二）：从权重装配到八卡文本链路</title>
        <published>2026-09-15T00:00:00+00:00</published>
        <updated>2026-09-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhuai.tech/blog/deepseek-v41-flash-eight-gpu/"/>
        <id>https://zhuai.tech/blog/deepseek-v41-flash-eight-gpu/</id>
        
        <content type="html" xml:base="https://zhuai.tech/blog/deepseek-v41-flash-eight-gpu/">&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;zhuai.tech&#x2F;blog&#x2F;deepseek-v41-flash-engram&#x2F;&quot;&gt;上一篇&lt;&#x2F;a&gt;讲了 Engram：把训练好的记忆放进主机内存，再用查表、AVX-512 BF16 和 CPU&#x2F;GPU 重叠，让它参与推理。这一篇回到 GPU 主干。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;把模型分成八段，只完成了设备分工；要得到正确文本，还得把模型的状态接完整。&lt;&#x2F;strong&gt; DeepSeek V4.1 Flash 的压缩 KV 会被多层共享，稀疏选择会被后续层复用，mHC 的混合系数还要跨子层传递。设备边界切在这些依赖中间，不能让它们随局部变量一起消失。&lt;&#x2F;p&gt;
&lt;p&gt;我们在八张 AMD Radeon Pro W7900D 上接入官方 safetensors 权重，从权重解释、ROCm 算子、会话状态一路修到输出 head。下面按这条真实路径展开。本文记录的是接入与后续修复，不把今天仍在推进的其他功能混进当时的验收结论。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;xian-que-ding-yi-lun-tui-li-dao-di-yao-jing-guo-shen-me&quot;&gt;先确定一轮推理到底要经过什么&lt;&#x2F;h2&gt;
&lt;p&gt;这次语言主干有 40 层、5,120 维 hidden，mHC 使用四路残差表示。八卡入口按连续完整层分段，&lt;code&gt;layer_ends&lt;&#x2F;code&gt; 是每段的排他上界：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-yaml &quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;layer_ends&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;15&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;20&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;25&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;30&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;35&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;40&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;GPU&lt;&#x2F;th&gt;&lt;th&gt;负责的主干层&lt;&#x2F;th&gt;&lt;th&gt;这一段遇到的特殊状态&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;td&gt;L0–L4&lt;&#x2F;td&gt;&lt;td&gt;L1 注入 Engram；L2 发布压缩 KV 与 selection&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;td&gt;L5–L9&lt;&#x2F;td&gt;&lt;td&gt;先继续使用 L2 的状态，L8 发布新的一组&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;&#x2F;td&gt;&lt;td&gt;L10–L14&lt;&#x2F;td&gt;&lt;td&gt;继续使用 L8 的状态；L14 注入 Engram 并发布新状态&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;&#x2F;td&gt;&lt;td&gt;L15–L19&lt;&#x2F;td&gt;&lt;td&gt;使用 L14 的共享压缩 KV 与 selection&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;&#x2F;td&gt;&lt;td&gt;L20–L24&lt;&#x2F;td&gt;&lt;td&gt;L20 发布后半段共享 KV；L24 更新 selection&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;&#x2F;td&gt;&lt;td&gt;L25–L29&lt;&#x2F;td&gt;&lt;td&gt;使用 L20 的 KV；L28 更新 selection&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;6&lt;&#x2F;td&gt;&lt;td&gt;L30–L34&lt;&#x2F;td&gt;&lt;td&gt;使用 L20 的 KV；L32 更新 selection&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;7&lt;&#x2F;td&gt;&lt;td&gt;L35–L39&lt;&#x2F;td&gt;&lt;td&gt;使用 L20 的 KV；L36 更新 selection，之后完成主干末层&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;层号从零开始。这里的八卡是连续层流水线：每一层的 attention、router 和专家计算由负责它的设备完成。表中每卡五层，描述的是层的放置，不表示八张卡在同一时刻处理同一个 token 的八个独立片段。&lt;&#x2F;p&gt;
&lt;p&gt;关闭投机解码时，一轮完整路径可以概括为：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;文本 → tokenizer &#x2F; 对话编码 → embedding
&lt;&#x2F;span&gt;&lt;span&gt;     → GPU0：L0–L4 → GPU1：L5–L9 → … → GPU7：L35–L39
&lt;&#x2F;span&gt;&lt;span&gt;     → mHC 最终折叠 → final norm → LM head → 采样
&lt;&#x2F;span&gt;&lt;span&gt;     → 下一个 token 回到入口
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;prefill 可以把多个输入块排进流水线；普通单路 decode 的下一 token 则依赖这一轮末尾的采样结果。因此，八卡提供了容量与分工，但吞吐还取决于依赖链、块大小和各段耗时。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-yi-bu-ba-wen-jian-li-de-zhang-liang-zhuang-cheng-mo-xing-xu-yao-de-quan-zhong&quot;&gt;第一步：把文件里的张量装成模型需要的权重&lt;&#x2F;h2&gt;
&lt;p&gt;权重装配首先回答三个问题：张量叫什么、逻辑形状是什么、字节该怎样解释。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;safetensors-yu-gguf-jie-jue-de-bu-shi-tong-yi-ceng-wen-ti&quot;&gt;Safetensors 与 GGUF 解决的不是同一层问题&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;strong&gt;Safetensors&lt;&#x2F;strong&gt; 更接近一个安全、简单的张量容器。它记录张量名、dtype、shape 和对应的字节区间，支持单文件或由 index JSON 管理的多分片权重。它不执行反序列化代码，适合保存官方训练权重，也方便按张量或按行读取。但“一个张量叫 &lt;code&gt;layers.2.attn.wq_a.weight&lt;&#x2F;code&gt; 时应当怎样参与 DeepSeek V4.1 推理”，仍由模型配置和实现解释。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;GGUF&lt;&#x2F;strong&gt; 除了张量目录，还通常携带模型架构、上下文参数、tokenizer 等推理元数据，并定义 GGML 生态中的量化类型。它更像面向分发和本地推理的模型包：一个文件可以同时告诉引擎“这是什么模型”和“这些 packed bytes 使用哪种量化格式”。代价是转换过程已经决定了张量命名、量化方案和元数据约定；第三方 GGUF 是否完整表达新架构，仍需逐项校验。&lt;&#x2F;p&gt;
&lt;p&gt;二者的区别不能简化成“Safetensors 是高精度，GGUF 是低精度”。Safetensors 可以保存 FP8、MXFP4 和 BF16 等不同 dtype；GGUF 也可以包含 F32、F16、BF16 或多种量化张量。真正的区别在于容器契约、元数据约定和量化表示方式。&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;&#x2F;th&gt;&lt;th&gt;Safetensors&lt;&#x2F;th&gt;&lt;th&gt;GGUF&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;主要角色&lt;&#x2F;td&gt;&lt;td&gt;通用、安全的张量存储&lt;&#x2F;td&gt;&lt;td&gt;面向推理的模型与量化容器&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;模型结构&lt;&#x2F;td&gt;&lt;td&gt;通常结合外部 config 与模型代码解释&lt;&#x2F;td&gt;&lt;td&gt;通常由文件 metadata 描述&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;tokenizer&lt;&#x2F;td&gt;&lt;td&gt;通常是外部文件&lt;&#x2F;td&gt;&lt;td&gt;通常可随 GGUF metadata 携带&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;常见组织&lt;&#x2F;td&gt;&lt;td&gt;官方 checkpoint，多文件分片&lt;&#x2F;td&gt;&lt;td&gt;单文件或按体积分片的发布包&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;zLLM 读取方式&lt;&#x2F;td&gt;&lt;td&gt;按名字、shape、dtype 读取张量或指定行&lt;&#x2F;td&gt;&lt;td&gt;解析 metadata、张量目录和 GGML 量化类型，按需取得矩阵&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h3 id=&quot;zllm-de-ge-shi-wu-guan-fa-sheng-zai-mo-xing-bian-pai-ceng&quot;&gt;zLLM 的“格式无关”发生在模型编排层&lt;&#x2F;h3&gt;
&lt;p&gt;zLLM 没有要求 Safetensors 和 GGUF 在字节层长成同一种样子。实现分成四层：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;文件容器
&lt;&#x2F;span&gt;&lt;span&gt;  SafetensorsStore &#x2F; GgufReader
&lt;&#x2F;span&gt;&lt;span&gt;        ↓
&lt;&#x2F;span&gt;&lt;span&gt;模型权重装配
&lt;&#x2F;span&gt;&lt;span&gt;  张量命名、shape 校验、逻辑权重角色
&lt;&#x2F;span&gt;&lt;span&gt;        ↓
&lt;&#x2F;span&gt;&lt;span&gt;统一的准备后权重与 Backend capability
&lt;&#x2F;span&gt;&lt;span&gt;  LinearWeight &#x2F; PreparedLayer &#x2F; ExpertSource
&lt;&#x2F;span&gt;&lt;span&gt;        ↓
&lt;&#x2F;span&gt;&lt;span&gt;DeepSeek V4.1 runtime
&lt;&#x2F;span&gt;&lt;span&gt;  attention → shared KV &#x2F; selection → MoE → mHC
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;容器层只负责 metadata、张量索引和原始字节访问。Safetensors 路径将 BlockFP8、MXFP8、MXFP4 和 dense 张量装配成带有明确逻辑形状的模型权重；GGUF 路径解析自身量化类型，准备成同一后端可以消费的线性权重和专家来源。&lt;&#x2F;p&gt;
&lt;p&gt;两条路径在“准备权重”之前可以不同。例如当前 DeepSeek 实现分别有 Safetensors 与 GGUF 的 layer preparation，因为它们的张量名、融合方式和量化切片并不相同；准备之后则进入同一份 &lt;code&gt;DeepSeekV4PreparedLayer&lt;&#x2F;code&gt; 和同一套 layer runtime。attention、MoE、KV 生命周期与 mHC 顺序不会因为文件扩展名再写一遍。&lt;&#x2F;p&gt;
&lt;p&gt;因此，这里的格式无关不是“任何 GGUF 都能自动替换官方 checkpoint”，而是：&lt;strong&gt;模型算法不绑定文件容器，容器差异在读取与权重装配边界收敛。&lt;&#x2F;strong&gt; 新格式仍要提供完整的架构 metadata、张量映射和后端支持；缺少 Engram、mHC、共享压缩 KV 或对应量化 kernel 时，引擎应明确拒绝，而不是带着错误假设继续生成。&lt;&#x2F;p&gt;
&lt;p&gt;zLLM 的权重层会探测顶层命名空间。例如当前 loader 在发现 &lt;code&gt;language_model.embed.weight&lt;&#x2F;code&gt; 时采用 &lt;code&gt;language_model.&lt;&#x2F;code&gt; 前缀，否则使用无前缀名称。这样能兼容实际遇到的不同 checkpoint 组织方式；前缀来自张量目录，不能凭下载目录名猜测。&lt;&#x2F;p&gt;
&lt;p&gt;同样，文件里“都是量化权重”也不足以决定执行方式：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;权重形态&lt;&#x2F;th&gt;&lt;th&gt;装配时必须知道什么&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;BlockFP8&lt;&#x2F;td&gt;&lt;td&gt;数据矩阵与二维 scale 网格，以及行、列两个方向的分块大小&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;MXFP8&lt;&#x2F;td&gt;&lt;td&gt;每行沿输入维度分组的 codes 与 scale；不能当成二维分块&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;MXFP4 专家&lt;&#x2F;td&gt;&lt;td&gt;packed 数据、分组 scale、专家编号，以及 gate&#x2F;up&#x2F;down 的逻辑形状&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;未量化张量&lt;&#x2F;td&gt;&lt;td&gt;dtype、shape，以及它在 norm、混合或投影中的用途&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;专家权重还存在独立存储与合并存储两种组织。当前实现既能读取逐 expert 命名的矩阵，也能从合并的三维专家张量里按 expert 对应的行区间取出数据。对 FFN 而言，gate&#x2F;up 是 &lt;code&gt;[intermediate, hidden]&lt;&#x2F;code&gt;，down 是 &lt;code&gt;[hidden, intermediate]&lt;&#x2F;code&gt;；装配时要同时保持 packed 列与逻辑列之间的关系。&lt;&#x2F;p&gt;
&lt;p&gt;这些差异在权重层处理。模型编排拿到的是可解释的矩阵与专家来源，随后由后端选择相应执行路径。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-er-bu-fp8-de-cuo-wu-cang-zai-scale-de-er-wei-bu-ju-li&quot;&gt;第二步：FP8 的错误，藏在 scale 的二维布局里&lt;&#x2F;h2&gt;
&lt;p&gt;接入中一次很具体的修复，是官方 V4.1 主干线性权重的 &lt;strong&gt;32×32 BlockFP8&lt;&#x2F;strong&gt;。官方推理实现明确使用这套分块。&lt;a href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;deepseek-ai&#x2F;DeepSeek-V4.1-Flash&#x2F;blob&#x2F;main&#x2F;inference&#x2F;model.py&quot;&gt;官方模型实现&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;对一个 &lt;code&gt;[M, N]&lt;&#x2F;code&gt; 矩阵，二维 scale 网格的形状为：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;[ceil(M &#x2F; 32), ceil(N &#x2F; 32)]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;元素 &lt;code&gt;(r, c)&lt;&#x2F;code&gt; 使用的 scale 来自 &lt;code&gt;(r &#x2F; 32, c &#x2F; 32)&lt;&#x2F;code&gt;，这里取整数商。行、列两个方向都决定选哪一个 scale。行独立的 MXFP8 则沿每行的输入维度分组，即使同样写着“32”，也不是同一种寻址。&lt;&#x2F;p&gt;
&lt;p&gt;我们为这次修复加入了一个很小的测试：64×64 的 codes 全部编码同一个值，四个 32×32 块分别使用对应 1、2、4、8 倍的 scale。解码后，四个象限就应分别得到 1、2、4、8。这个测试直接检查行方向和列方向是否都跨到了正确的 scale。&lt;&#x2F;p&gt;
&lt;p&gt;还有一个细节：小矩阵的 scale shape 可能无法区分 32×32 与旧的 128×128 布局。因此 loader 要结合模型版本解释格式，不能只看 scale 张量有几个元素。&lt;&#x2F;p&gt;
&lt;p&gt;修复也分成两层。&lt;code&gt;80b8d3e5&lt;&#x2F;code&gt; 校正权重装配，&lt;code&gt;07cf53f1&lt;&#x2F;code&gt; 补齐 ROCm 的 32×32 BlockFP8 路径。CPU 能正确解码，并不自动意味着 GPU kernel 使用了相同的 scale 索引。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-san-bu-fen-qing-ben-ceng-kv-gong-xiang-ya-suo-kv-he-selection&quot;&gt;第三步：分清本层 KV、共享压缩 KV 和 selection&lt;&#x2F;h2&gt;
&lt;p&gt;普通的“每层一个 KV cache”直觉，在这里需要展开成几份不同的数据。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;滑窗 KV&lt;&#x2F;strong&gt; 属于每一层，用于该层的局部历史。当前配置的窗口为 128。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;压缩 KV&lt;&#x2F;strong&gt; 由指定源层产生，后续一组层共享使用。zLLM 的 V4.1 映射是：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;发布源层&lt;&#x2F;th&gt;&lt;th&gt;使用范围&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;ratio&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;L2&lt;&#x2F;td&gt;&lt;td&gt;L2–L7&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;L8&lt;&#x2F;td&gt;&lt;td&gt;L8–L13&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;L14&lt;&#x2F;td&gt;&lt;td&gt;L14–L19&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;L20&lt;&#x2F;td&gt;&lt;td&gt;L20–L39&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;L0、L1 不走这条压缩分支。L20 之后的 &lt;code&gt;ratio=1&lt;&#x2F;code&gt; 仍表示使用由 L20 产生的 1:1 表示，不能解释成“关闭压缩分支，让各层自己生成一份”。压缩比描述粒度，源层映射描述数据来自哪里，两者都要保留。&lt;&#x2F;p&gt;
&lt;p&gt;这可以看成 DSA（DeepSeek Sparse Attention）的进一步工程化。DSA 首先解决的是&lt;strong&gt;历史维度的稀疏&lt;&#x2F;strong&gt;：面对不断增长的上下文，indexer 不再让当前 query 对全部历史 KV 做完整 attention，而是先为历史建立较轻的索引表示，计算相关性并选出 Top-K 位置，随后只读取这些位置的 KV 完成 attention。上下文越长，被跳过的无关历史越多，稀疏带来的收益越明显。&lt;&#x2F;p&gt;
&lt;p&gt;V4.1 又把稀疏从时间轴推进到了网络深度：相邻层不必各自生成一份压缩历史，也不必每层都重新做一次完整选择。一个 KV source 层负责发布这一组层共用的压缩 KV 与 index key；index source 层按设定的节奏重新计算 selection；其他层直接复用最近的结果。它形成两种正交的稀疏：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;&#x2F;th&gt;&lt;th&gt;解决的问题&lt;&#x2F;th&gt;&lt;th&gt;运行时行为&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;基于历史的稀疏&lt;&#x2F;td&gt;&lt;td&gt;当前 query 没必要访问所有历史 token&lt;&#x2F;td&gt;&lt;td&gt;indexer 从历史中选 Top-K，只对入选位置做 attention&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;跨层的稀疏&lt;&#x2F;td&gt;&lt;td&gt;相邻层没必要重复构造近似的历史表示与选择结果&lt;&#x2F;td&gt;&lt;td&gt;源层发布压缩 KV、index key 或 selection，组内后续层共享&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;这种共享仍然保留每一层自己的 attention 计算：各层用自己的 hidden state 产生 query，也维护自己的滑窗 KV；跨层复用的是对远端历史的压缩表示，以及在有效层范围内沿用的历史位置。计算保留层间差异，同时把重复度高的历史整理和检索工作从每层一次，改为按组产生、按需刷新。&lt;&#x2F;p&gt;
&lt;p&gt;从 L20–L39 最容易看出这种解耦：压缩 KV 始终来自 L20，但 selection 会在 L20、L24、L28、L32、L36 更新。换句话说，&lt;strong&gt;历史数据由谁提供&lt;&#x2F;strong&gt;与&lt;strong&gt;何时重新判断哪些历史值得读取&lt;&#x2F;strong&gt;是两条独立时间线。较稳定、体量较大的压缩历史可以跨更多层复用，较轻的选择结果则可以更频繁刷新。&lt;&#x2F;p&gt;
&lt;p&gt;这与我们在 GLM 5.3 路径中处理 DSA 的思路异曲同工。GLM 5.3 的完整 indexer 层先建立历史索引并产生 selection，后续 IndexShare 层复用最近一次有效选择；连续的 MTP 步骤也会在状态完备时复用 selection。两个模型的具体权重和层映射不同，但工程目标一致：&lt;strong&gt;先在长历史中找出值得计算的位置，再让后续计算复用这次检索，而不是在每一层重复扫描整段历史。&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;selection&lt;&#x2F;strong&gt; 是 indexer 为各 query 选出的历史位置。当前配置在 L2、L8、L14、L20、L24、L28、L32、L36 更新选择，其余层复用最近发布的结果。它与压缩 KV 也不是同一份状态：L24 可以更新选择，同时继续读取 L20 的压缩历史。&lt;&#x2F;p&gt;
&lt;p&gt;在 runtime 中，把这三份状态分开，才能准确表达“本层写什么、读谁的历史、是否重新选择”。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-si-bu-kua-qia-shi-zhuang-tai-bu-neng-zai-stage-ru-kou-qing-ling&quot;&gt;第四步：跨卡时，状态不能在 stage 入口清零&lt;&#x2F;h2&gt;
&lt;p&gt;第一个典型例子是 GPU0 到 GPU1。&lt;&#x2F;p&gt;
&lt;p&gt;GPU0 执行到 L2 时已经产生共享历史与 selection。GPU1 从 L5 开始，L5–L7 还需要它们，直到 L8 才发布新的一组。如果每个 stage 入口都创建一份全新的局部共享状态，L5 接收到 hidden，却找不到应当沿用的选择结果。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;cc0be412&lt;&#x2F;code&gt; 的修复让 selection 成为流水 work item 的一部分：进入 stage 时接回上一段发布的状态，完成本段后继续传给下一段。压缩 KV 的源层则通过全模型的层映射查找，不再依赖“本 stage 内最近见过哪个 source”。&lt;&#x2F;p&gt;
&lt;p&gt;这两种状态采用不同的管理方式：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;数据&lt;&#x2F;th&gt;&lt;th&gt;在链路中的管理方式&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;hidden&lt;&#x2F;td&gt;&lt;td&gt;随当前输入块流过各个 stage&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;selection&lt;&#x2F;td&gt;&lt;td&gt;随输入块延续，遇到新的 indexer 层时更新&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;压缩 KV 历史&lt;&#x2F;td&gt;&lt;td&gt;保存在会话 cache 表中，消费者按源层映射访问&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;层本地滑窗 KV&lt;&#x2F;td&gt;&lt;td&gt;由负责该层的会话状态维护&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;这里的“随输入块延续”是逻辑依赖，并不意味着每次都要把数据下载到 CPU。后续优化又进一步改变了它的物理位置。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-wu-bu-tong-yi-hui-hua-kua-qia-gong-xiang-bu-tong-hui-hua-bi-ci-ge-chi&quot;&gt;第五步：同一会话跨卡共享，不同会话彼此隔离&lt;&#x2F;h2&gt;
&lt;p&gt;跨 stage 的来源修正后，还出现了另一个生命周期问题：每个 stage 各自从模板 fork 一张全层 cache 表。&lt;&#x2F;p&gt;
&lt;p&gt;这样得到的每一张表都可能合法，但它们不是同一张表。GPU0 把 L2 的历史写进自己的实例，GPU1 去另一份实例里读取 L2，就会读到空状态。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;307c5e85&lt;&#x2F;code&gt; 将建立会话改为统一入口：&lt;strong&gt;先为这个会话 fork 一份全层 cache 表，再把同一份共享引用交给八个 stage。&lt;&#x2F;strong&gt; Engram 的序列状态也在会话层统一创建，由同一会话内的 stage 共用。&lt;&#x2F;p&gt;
&lt;p&gt;这同时界定了共享范围。模型和只读权重可以跨会话复用；会被追加和修改的序列历史必须属于具体会话。重置会话时，不仅要处理各层局部 cache，也要清理共享历史与 Engram 的 token 状态。&lt;&#x2F;p&gt;
&lt;p&gt;“共享”能成立的前提，是明确共享的是哪份数据、由哪个会话拥有，以及写入何时可见。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-liu-bu-mhc-de-pre-mix-ye-yao-kua-ceng-kua-qia-yan-xu&quot;&gt;第六步：mHC 的 pre-mix 也要跨层、跨卡延续&lt;&#x2F;h2&gt;
&lt;p&gt;权重和 KV 都接上之后，还不能只检查 hidden 的形状。&lt;&#x2F;p&gt;
&lt;p&gt;V4.1 的 mHC 维护四路残差表示，子层输入需要按系数折叠。我们早期沿用了较独立的子层处理方式，后续根据实际语义改成连续传递 pre-mix：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;上一层 FFN 发布的 pre
&lt;&#x2F;span&gt;&lt;span&gt;  → 本层 attention 的输入折叠
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;本层 attention 发布的 pre
&lt;&#x2F;span&gt;&lt;span&gt;  → 本层 FFN 的输入折叠
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;本层 FFN 发布的 pre
&lt;&#x2F;span&gt;&lt;span&gt;  → 下一层 attention，或最终输出 head 的折叠
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;首层没有上游输入时，使用实现规定的初始处理。到最后一层，则使用最后 FFN 发布的 pre 折叠展开态，再进入 final norm 与 LM head。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;8919f797&lt;&#x2F;code&gt; 因此让层执行返回 hidden 和待传递的 pre，并将 pre 一起带过 stage 边界。比如 GPU0 结束 L4 后，GPU1 的 L5 不只需要 L4 的 hidden，也需要对应的 pre。&lt;&#x2F;p&gt;
&lt;p&gt;这种错误很难只靠 shape 检查发现：来自不同子层的系数可能形状相同，却代表不同执行时刻的状态。数值链路需要核对“谁产生、谁消费”，不能只核对“能否相乘”。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;zheng-que-zhi-hou-ba-gong-xiang-zhuang-tai-liu-zai-xu-yao-ta-de-she-bei-shang&quot;&gt;正确之后：把共享状态留在需要它的设备上&lt;&#x2F;h2&gt;
&lt;p&gt;最初跑通时，共享状态存在并能到达消费者，是第一目标。随后我们才优化它的物理访问。&lt;&#x2F;p&gt;
&lt;p&gt;一项是&lt;strong&gt;压缩 KV 的本地镜像&lt;&#x2F;strong&gt;。最初后续卡可以读取源卡上的历史，但长 prefill 会反复扫描同一份远端数据。后续代码在消费者侧维护本地副本，在布局和已有前缀不变时只复制新增行；扩容、布局变化或前缀不一致时重新建立需要的内容。这样增加了一部分本地显存占用，换取计算时的本卡访问。&lt;&#x2F;p&gt;
&lt;p&gt;另一项是&lt;strong&gt;selection 留在 GPU&lt;&#x2F;strong&gt;。早期路径先把索引下载成 host 向量，再上传给后续层。优化后的 ROCm selection 保存设备 buffer 引用，同卡复用，跨卡使用有序 P2P 传递，避免每个消费层反复经过 CPU。&lt;&#x2F;p&gt;
&lt;p&gt;两项都不能只把指针换过去。异步任务在消费完成前必须持有源数据，生产、复制和消费之间必须有正确的顺序；临时分配还要转换为能够安全跨 stage 使用的生命周期。逻辑共享与物理放置分开，才有空间从“能运行”走到“运行得快”。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ru-he-que-ren-zhe-tiao-lian-zhen-de-cheng-li&quot;&gt;如何确认这条链真的成立&lt;&#x2F;h2&gt;
&lt;p&gt;这次验证按不同层次进行，没有用某一个“能出字”的结果替代全部检查：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;格式级&lt;&#x2F;strong&gt;：用独立 scale 象限测试检查 BlockFP8 的二维解释。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;模型编排级&lt;&#x2F;strong&gt;：小尺寸全前向测试覆盖压缩共享、候选选择与 Engram，执行 prefill 和 decode，并检查 Engram 开关对结果的影响。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;真实权重级&lt;&#x2F;strong&gt;：装配官方 checkpoint，执行完整 40 层、最终折叠、head 与采样。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;优化回归级&lt;&#x2F;strong&gt;：固定输入与生成预算，比较完整输出文本哈希，并分别记录首字延迟与生成速度。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;还有一条需要公开说明的边界：官方模型包含候选块粗筛后的两级选择。在本文核对的 ROCm 接入配置里，&lt;code&gt;candidate_source_layer=None&lt;&#x2F;code&gt;、&lt;code&gt;candidate_topk_blocks=0&lt;&#x2F;code&gt;，使用单级 Top-K；两级候选逻辑已有 CPU reference，但不能据此把 ROCm 路径写成完整复现了官方所有选择步骤。两种选择策略也不能未经验证就宣称对所有输入等价。&lt;&#x2F;p&gt;
&lt;p&gt;第一篇给出的 50K 首字延迟约 26.06 秒、单路约 20.45 token&#x2F;s，来自后续优化后的固定文本测试，关闭 DSpark。它们不是某个 FP8 修复或状态修复单独带来的性能收益，也不是这次写作重新跑出的数据。&lt;&#x2F;p&gt;
&lt;p&gt;从文件到八卡文本生成，最重要的工作是保持同一个模型在不同位置上的连续性：字节解释相同、历史来源明确、会话边界清楚、混合状态顺序正确。性能优化随后改变数据在哪里、什么时候搬运，而不能丢掉这些关系。&lt;&#x2F;p&gt;
&lt;p&gt;下一篇继续讲图像：如何从预处理、视觉塔和 aligner 的中间张量找到差异，再把图文输入接进这条语言主干。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;工程依据：zLLM 的 V4.1 接入记录，以及 &lt;code&gt;80b8d3e5&lt;&#x2F;code&gt;、&lt;code&gt;07cf53f1&lt;&#x2F;code&gt;、&lt;code&gt;cc0be412&lt;&#x2F;code&gt;、&lt;code&gt;307c5e85&lt;&#x2F;code&gt;、&lt;code&gt;8919f797&lt;&#x2F;code&gt; 和 &lt;code&gt;e9b539e4&lt;&#x2F;code&gt; 的实际改动。模型配置参考&lt;a href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;deepseek-ai&#x2F;DeepSeek-V4.1-Flash&#x2F;blob&#x2F;main&#x2F;inference&#x2F;config.json&quot;&gt;官方推理配置&lt;&#x2F;a&gt;。&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh">
        <title>DeepSeek V4.1 Flash 的 Engram：让知识不必每次重新算</title>
        <published>2026-09-14T00:00:00+00:00</published>
        <updated>2026-09-14T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhuai.tech/blog/deepseek-v41-flash-engram/"/>
        <id>https://zhuai.tech/blog/deepseek-v41-flash-engram/</id>
        
        <content type="html" xml:base="https://zhuai.tech/blog/deepseek-v41-flash-engram/">&lt;p&gt;&lt;strong&gt;在我们看来，DeepSeek V4.1 Flash 是一部大模型工程化的杰作。&lt;&#x2F;strong&gt; 它将图文理解、稀疏专家、压缩稀疏注意力与 Engram 条件记忆组合起来，让模型的知识容量、计算量和运行时资源有了更细致的分工。&lt;&#x2F;p&gt;
&lt;p&gt;其中最值得展开的是：哪些信息需要结合上下文计算，哪些局部模式可以从训练好的记忆中检索，哪些中间结果能够在多层之间共享。这些设计直接影响推理时要读多少权重、保留多少历史，以及哪些工作可以提前完成。真正接入一个推理引擎，才能体会这些细节如何共同支撑完整模型。&lt;&#x2F;p&gt;
&lt;p&gt;这个系列将从 zLLM 的实际接入过程，逐篇拆解这套设计。我们首先做了一个资源放置决定：&lt;strong&gt;Engram 的大表与投影留在主机侧，主干网络继续交给 GPU。&lt;&#x2F;strong&gt; 最初门控也在 CPU 上完成，后续优化再把 prefill 的门控移回 GPU。&lt;&#x2F;p&gt;
&lt;p&gt;这个决定贯穿了后面的工作。权重怎样读、每个 token 在什么时候进入记忆状态、跨卡执行时状态怎样传递，以及图像位置能不能参加文本查表，都需要落实到推理链路里。&lt;&#x2F;p&gt;
&lt;p&gt;截至 2026 年 9 月 14 日，zLLM 已在八张 W7900D 上，用官方 safetensors 权重跑通图文请求到文本生成的完整流程。当前已验证的文本性能为：&lt;strong&gt;50K 输入约 26.06 秒出首字，单路生成约 20.45 token&#x2F;s&lt;&#x2F;strong&gt;。这些是单路、关闭 DSpark 的文本测试结果：前者对应 50,032-token 输入的平均首字延迟，后者对应 25-token 提示下连续生成 700 token 的平均速度。系列第一篇完整讲解 Engram 的原理与工程优化；其他模型组件和完整性能表留到后续各篇。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ji-yu-nei-cun-de-zhi-shi-engram&quot;&gt;基于内存的知识——Engram&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;deepseek-v41-flash&#x2F;engram-lookup.png&quot; alt=&quot;Engram n-gram 哈希记忆层：由 token 确定地址，读取主机内存中的记忆，经 WKV 投影和上下文门控更新隐藏状态&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;上半部分不依赖当前层 hidden，可提前查表和投影；下半部分等待 hidden 后完成门控。图中的两组 key&#x2F;value 表示同一份投影结果。&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;wei-shen-me-you-xie-dong-xi-zhi-de-ji-zhu-er-bu-bi-mei-ci-zhong-xin-suan&quot;&gt;为什么有些东西值得记住，而不必每次重新算&lt;&#x2F;h3&gt;
&lt;p&gt;语言模型的计算中，一部分用于理解新上下文、组合信息和推理；另一部分用于重新构造已经见过很多次的局部模式，例如常见词组、固定搭配和实体名称的表示。传统 Transformer 将这些能力共同编码在网络权重里，每次遇到输入，都要通过网络计算把相关表示重新建立起来。&lt;&#x2F;p&gt;
&lt;p&gt;Engram 尝试让其中适合记忆的部分直接通过检索获得。训练过程中，一部分可复用的局部模式被学进 n-gram 记忆表；推理时，根据 token 组合找到表行，取出已经学好的向量，再由当前上下文决定如何吸收。官方的机制分析认为，这能减轻早期层重建静态模式的负担，为复杂推理保留更多有效深度。&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;deepseek-ai&#x2F;Engram&quot;&gt;Engram 官方研究说明&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;可以用一个直观例子理解：模型遇到一个熟悉的多 token 专有名词，记忆表可以提供与这个局部组合有关的已学习特征，后续网络再判断它在当前句子里扮演什么角色。这是机制上的示意，并不表示某一行一定对应一条可读事实，也不表示我们已经验证了某个词组具体落在哪个桶。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;原来需要网络反复构造的一部分表示，现在有了一条直接查表取得的路径。&lt;&#x2F;strong&gt; 这里的“提取潜在知识”，更准确地说，是训练让部分知识和模式由专门的记忆参数承载；zLLM 加载已有权重并执行检索，没有在接入时从旧模型里额外抽取一套知识库。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;jian-shao-ji-suan-fu-dan-yu-jian-shao-xian-cun-zhan-yong-shi-liang-ge-huan-jie&quot;&gt;减少计算负担，与减少显存占用，是两个环节&lt;&#x2F;h3&gt;
&lt;p&gt;在模型设计上，Engram 将部分静态模式的重建工作交给条件记忆。记忆表很大，但每次只访问固定数量的行，扩展容量不需要每个 token 扫描整张表。它改善的是记忆容量与计算量之间的关系；我们不能据此声称给现有 V4.1 关闭 Engram 就会更慢，或启用它后自动少执行几层 Transformer。&lt;&#x2F;p&gt;
&lt;p&gt;在引擎实现上，zLLM 利用这种稀疏访问，把大表放在主机内存。显存因而不必承担整张 Engram 表的存储，可以留给主干权重、专家、KV cache 和临时张量。节省的是原本将同一张表完整放进显存的容量，整机仍然需要承担这份记忆的存储成本。&lt;&#x2F;p&gt;
&lt;p&gt;两层表按约 &lt;code&gt;3.84 亿行 × 256 维 × 2 层&lt;&#x2F;code&gt; 计算，仅每值一个字节的代码数据就约为 &lt;strong&gt;183 GiB&lt;&#x2F;strong&gt;，还没计入 scale。这是根据张量规模计算的容量，说明放置策略的重要性，并非本次 RSS 或显存节省量的实测。相比之下，一次 token 在两层各取 24 行，代码数据合计只有 12 KiB；操作系统实际访问以页为单位，后续还有投影读权重，不能把 12 KiB 当成整步内存流量。&lt;&#x2F;p&gt;
&lt;p&gt;查表也不是完整答案。向量仍要经过 WKV 投影和上下文门控，模型还要继续完成主干推理。因此，我们后面的优化分成几个独立环节：减少小行读取开销、提高投影效率、让线程靠近数据，以及提前执行不依赖 hidden 的工作。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;ba-zhi-shi-bian-cheng-ke-xun-zhi-de-xiang-liang&quot;&gt;把知识变成可寻址的向量&lt;&#x2F;h3&gt;
&lt;p&gt;Engram 给模型增加了一条由 token 序列触发的记忆读取路径：根据当前位置附近的短序列计算地址，从训练好的大表里取出向量，再结合当前隐藏状态决定注入多少。&lt;&#x2F;p&gt;
&lt;p&gt;这里的“知识”以模型参数中的向量形式存在。它没有供人直接编辑的词条，也不会在一次聊天后自动把新事实写回权重。我们可以把它理解为模型内部的一种条件记忆：输入决定查哪里，当前上下文决定如何使用查到的内容。&lt;&#x2F;p&gt;
&lt;p&gt;我们接入的配置在 &lt;strong&gt;L1 和 L14&lt;&#x2F;strong&gt; 放置 Engram，层号从零开始。每个位置分别构造 2-gram、3-gram、4-gram，每种长度有 8 个 hash heads，共查 24 行，每行 256 维。&lt;&#x2F;p&gt;
&lt;p&gt;因此，每个 Engram 层针对一个 token 取出的向量共有：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;3 种 n-gram 长度 × 8 个 head × 256 维 = 6,144 个值
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;每层表约有 3.84 亿行，但一次访问只涉及其中 24 行。&lt;strong&gt;总容量很大，单步读取很稀疏&lt;&#x2F;strong&gt;，这正是我们把它放在主机侧的出发点。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;di-yi-bu-xian-ba-cha-biao-di-zhi-suan-dui&quot;&gt;第一步：先把查表地址算对&lt;&#x2F;h3&gt;
&lt;p&gt;最先需要对齐的是 token 到地址的映射。官方实现会先规范化 token 文本，再把规范化结果相同的 token 合并到同一个压缩 ID。大小写、重音和空白处理都会影响最终地址；压缩词表大小还会影响 hash 乘子的生成。&lt;a href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;deepseek-ai&#x2F;DeepSeek-V4.1-Flash&#x2F;blob&#x2F;main&#x2F;inference&#x2F;engram.py&quot;&gt;官方 Engram 实现&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;zLLM 的做法是沿用官方生成规则，把压缩 token map 离线导出为 &lt;code&gt;engram_token_map.bin&lt;&#x2F;code&gt;，运行时读取；每层使用的乘子、素数桶和桶偏移则展开为静态表。当前映射包含 129,280 个 &lt;code&gt;u32&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;p&gt;运行时的路径很直接：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;token ID
&lt;&#x2F;span&gt;&lt;span&gt;  → 压缩 token ID
&lt;&#x2F;span&gt;&lt;span&gt;  → 当前 token 与最近三个位置
&lt;&#x2F;span&gt;&lt;span&gt;  → 2 &#x2F; 3 &#x2F; 4-gram 的 24 路 hash
&lt;&#x2F;span&gt;&lt;span&gt;  → 对应 Engram 层的 24 个表行号
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这一步错了，后面依然可能得到形状正确的向量，但取到的已经是另一组记忆。接入时因此先对齐 hash，再验证投影和门控。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;di-er-bu-da-biao-an-xing-du-tou-ying-liu-zai-nei-cun-li-suan&quot;&gt;第二步：大表按行读，投影留在内存里算&lt;&#x2F;h3&gt;
&lt;p&gt;当前权重层的 &lt;code&gt;engram_embedding_rows&lt;&#x2F;code&gt; 只读取选中的行。官方表使用 MXFP8，即 E4M3 数据加按 32 个值分组的 E8M0 scale；读取数据时也读取对应的 scale，然后在 CPU 上解码这 24 行。&lt;&#x2F;p&gt;
&lt;p&gt;最初接入版本通过文件偏移逐行读取，热数据由操作系统页缓存承接，冷访问可能触发存储读取。后续优化分支改成只读 mmap，并在加载阶段逐页预热；这一演进在下文的读取优化中展开。预热不等于锁页，内存压力下页面仍可能被回收。&lt;&#x2F;p&gt;
&lt;p&gt;投影等计算权重显式保存在内存里。查出的 6,144 维向量经过 &lt;code&gt;wkv&lt;&#x2F;code&gt; 投影，产生后续门控需要的 key 和 value。每层 &lt;code&gt;wkv&lt;&#x2F;code&gt; 的形状为 &lt;code&gt;25,600 × 6,144&lt;&#x2F;code&gt;，在准备阶段转为 BF16，约占 300 MiB。最初使用 AVX2&#x2F;FMA 和 Rayon 行并行，后续换成 AVX-512 BF16 与固定线程组。&lt;&#x2F;p&gt;
&lt;p&gt;这也解释了为什么“只查 24 行”不等于 Engram 没有成本：查表之后还要读取一块较大的投影矩阵。把它留在 CPU 上，节省了显存，同时把这部分压力交给主机内存带宽。实际延迟还受冷读、线程调度和 CPU&#x2F;GPU 同步影响，需要独立测量。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;di-san-bu-zai-zheng-que-de-ceng-ru-kou-zhu-ru&quot;&gt;第三步：在正确的层入口注入&lt;&#x2F;h3&gt;
&lt;p&gt;Engram 在 L1、L14 的入口修改隐藏状态，此时隐藏状态仍是 mHC 展开后的多路表示。&lt;&#x2F;p&gt;
&lt;p&gt;CPU 实现先算出 key 和 value，再对每一路隐藏状态分别求门控。隐藏状态和 key 各自计算归一化因子，得到相关性分数 &lt;code&gt;dot&lt;&#x2F;code&gt;，然后执行：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;gate = sigmoid(copysign(sqrt(max(abs(dot), 1e-6)), dot))
&lt;&#x2F;span&gt;&lt;span&gt;hidden += gate × value
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;门控决定当前上下文要吸收多少记忆。实现中把 q、k 的逐元素权重预乘保存，各路仍分别计算自己的归一化与 gate。&lt;&#x2F;p&gt;
&lt;p&gt;最初接线通过层入口 hook 调用 CPU Engram，再让更新后的隐藏状态继续进入 GPU 主干。优化版本把查表、投影、门控拆开：大表和投影保持在 CPU，prefill 的预计算结果交给 GPU 门控，decode 则仍可在 CPU 上完成门控。执行位置会随路径而异。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;di-si-bu-rang-ji-yi-zhuang-tai-gen-sui-hui-hua&quot;&gt;第四步：让记忆状态跟随会话&lt;&#x2F;h3&gt;
&lt;p&gt;每个 token 只应进入 hash 历史一次。运行到两个 Engram 层时，各层读取同一个位置的历史，使用自己的 hash 参数。若在每层重复追加 token，后续 n-gram 就会错位。&lt;&#x2F;p&gt;
&lt;p&gt;zLLM 因此把追加 token 和应用 Engram 分开：prefill 追加一段，decode 每次追加一个；L1、L14 的 hook 只使用已经建立的位置状态。&lt;&#x2F;p&gt;
&lt;p&gt;权重可以共享，序列历史需要隔离。当前实现通过 &lt;code&gt;Arc&lt;&#x2F;code&gt; 共享投影权重，会话 fork 时创建新的 hash 状态，reset 时清空历史。后续的会话隔离修复，是让这个模块能够进入服务运行的必要步骤。&lt;&#x2F;p&gt;
&lt;p&gt;图像接入后，这条规则又多了一个边界：图像 span 写入 &lt;code&gt;DEAD&lt;&#x2F;code&gt; 标记，阻断跨图像位置的文本 n-gram；图像行本身还要跳过 Engram 门控。我们在多模态对拍时补齐了这两项，避免把图像占位符当作普通文本记忆的输入。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;du-qu-you-hua-cong-zhu-xing-pread-dao-mmap-yu-re&quot;&gt;读取优化——从逐行 pread 到 mmap 预热&lt;&#x2F;h2&gt;
&lt;p&gt;初版按行读取足以验证正确性，但一行只有 256 个量化值，数据和 scale 分别读取时，会产生大量小粒度文件调用。prefill 又把这个过程重复到整段 token 上，小请求的固定成本逐渐显现。&lt;&#x2F;p&gt;
&lt;p&gt;优化提交 &lt;code&gt;e9b539e4&lt;&#x2F;code&gt; 为 safetensors 增加只读 mmap。推理时从映射区域取选中的行，减少逐行 &lt;code&gt;pread&lt;&#x2F;code&gt; 的系统调用；数据仍保留量化形式，大表没有整表反量化为 F32。&lt;&#x2F;p&gt;
&lt;p&gt;加载阶段还会对 Engram 的数据和 scale 做预热：先提示顺序访问与预读，再逐页触碰，最后恢复 &lt;code&gt;MADV_RANDOM&lt;&#x2F;code&gt;，使运行时按随机查表模式访问。这样把一部分首次缺页和存储读取移到装载阶段。代价是启动时间与主机内存占用，收益需要在服务热态测量。&lt;&#x2F;p&gt;
&lt;p&gt;这条路径没有使用 &lt;code&gt;mlock&lt;&#x2F;code&gt;。&lt;code&gt;mmap&lt;&#x2F;code&gt; 本身也不保证页面已经进入 RAM；真正预热来自显式触页。描述运行状态时，需要同时核对内存压力和页面驻留，不能仅凭映射成功就认定整张表始终常驻。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;avx-512-bf16-rang-yi-fen-quan-zhong-fu-wu-duo-ge-token&quot;&gt;AVX-512 BF16——让一份权重服务多个 token&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;chu-ban-de-ping-jing-zhong-fu-sao-miao-wkv&quot;&gt;初版的瓶颈：重复扫描 WKV&lt;&#x2F;h3&gt;
&lt;p&gt;初版投影是一行 token 对应一次 GEMV。WKV 的 BF16 存储已经减少了权重字节数，但 prefill 若逐 token 调用，仍会重复扫描同一份约 300 MiB 的矩阵。&lt;&#x2F;p&gt;
&lt;p&gt;优化的重点因此是把多个 token 放进同一次矩阵乘，让加载进来的权重在一组输入之间复用。AVX-512 BF16 是实现这件事的指令基础，批量与布局决定能否发挥它的作用。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;an-16-ge-shu-chu-yu-bf16-dui-da-bao&quot;&gt;按 16 个输出与 BF16 对打包&lt;&#x2F;h3&gt;
&lt;p&gt;优化后的 WKV 按下面的顺序保存：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;[输出块][输入维度对][16 个输出通道]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;每个 &lt;code&gt;u32&lt;&#x2F;code&gt; 装两个 BF16 权重。内层一次加载 16 个输出通道对应的权重对，再广播某个 token 的两个输入值，调用 &lt;code&gt;_mm512_dpbf16_ps&lt;&#x2F;code&gt;，以 F32 累加到 16 个输出。多个 token 各自保留累加器，共用这一次加载的权重。&lt;&#x2F;p&gt;
&lt;p&gt;已检查的版本按 30、16、8、4、2、1 行处理输入块和尾部。30 行块意味着同一份加载可以服务最多 30 个 token；decode 只有一行时不会凭空获得这份批量复用收益。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;shu-ru-ye-yao-pei-he-nei-ceng-fang-wen-shun-xu&quot;&gt;输入也要配合内层访问顺序&lt;&#x2F;h3&gt;
&lt;p&gt;输入改成 &lt;code&gt;[输入维度对][token 行]&lt;&#x2F;code&gt; 的 pair-major 布局。同一对维度在多个 token 上的值相邻，减少按 token 大跨度跳读。官方 MXFP8 表行还可以直接解码成这份 BF16 packed 输入，省去先完整展开 F32、再转 BF16 的中间过程。&lt;&#x2F;p&gt;
&lt;p&gt;这一步实际遇到过布局错误：每个 token 的输入来自 24 个 head，合并时必须保留 head 与 pair 的对应关系。只改打包方式、不同时核对消费者索引，可能产生维度合法而内容错位的输入。因此补充了 packed MXFP8 与 F32 解码路径对照，以及批量 BF16 与 scalar reference 对照。&lt;&#x2F;p&gt;
&lt;p&gt;代码在运行时检查 &lt;code&gt;avx512f&lt;&#x2F;code&gt; 和 &lt;code&gt;avx512bf16&lt;&#x2F;code&gt;，支持时使用对应 kernel。它改变了输入精度处理与累加方式，不能只凭算子跑得更快就断言输出完全相同。局部 reference 测试、真实输入生成和端到端性能要分别验证。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;numa-bang-ding-xian-cheng-he-quan-zhong-yi-qi-fang&quot;&gt;NUMA 绑定——线程和权重一起放&lt;&#x2F;h2&gt;
&lt;p&gt;矩阵乘要连续读取大块权重，线程所在 CPU 与页面所在内存节点的关系就很重要。若工作线程在一个 socket，数据大量位于另一个 socket，跨 socket 访问可能抵消向量化收益。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;xian-shi-bie-yun-xu-shi-yong-de-wu-li-he&quot;&gt;先识别允许使用的物理核&lt;&#x2F;h3&gt;
&lt;p&gt;当前实现先调用 &lt;code&gt;sched_getaffinity&lt;&#x2F;code&gt;，只考虑进程允许使用的 CPU；再从 sysfs 读取 &lt;code&gt;physical_package_id&lt;&#x2F;code&gt; 和 &lt;code&gt;core_id&lt;&#x2F;code&gt;，按物理 package 分组，并去除同一物理核的 SMT 重复项。双 package 情况下，L1、L14 分别使用不同的 CPU 组。&lt;&#x2F;p&gt;
&lt;p&gt;这里代码实际按 &lt;strong&gt;package&lt;&#x2F;strong&gt; 分组。它符合本次双路机器的放置思路，但不是任意硬件上都成立的完整 NUMA 拓扑求解：一个 socket 也可能被配置成多个 NUMA node。迁移机器时必须核对拓扑，不能把 socket 和 NUMA node 永远画等号。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;yong-gu-ding-worker-ti-dai-lin-shi-fen-pei-ren-wu&quot;&gt;用固定 worker 替代临时分配任务&lt;&#x2F;h3&gt;
&lt;p&gt;每层创建持久的 &lt;code&gt;EngramTeam&lt;&#x2F;code&gt;，worker 启动时绑定到选定 CPU。每个 worker 处理固定区间的输出块，空闲时 park，收到任务后唤醒，完成后通知提交方。这样把线程位置与工作分片固定下来，减少通用线程池动态调度造成的迁移和局部性变化。&lt;&#x2F;p&gt;
&lt;p&gt;选核也没有占满所有物理核：一个 package 至少有 16 个可用物理核时，只取其中四分之三。例如完整可用的 32 核 package 会选 24 核，给 GPU 提交、传输和服务线程留出余量。这是给其他工作保留调度空间，不等同于通过 cpuset 强制保证它们只运行在剩余核上。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;zhuang-zai-yu-first-touch-yao-gen-zhao-ji-suan-wei-zhi-zou&quot;&gt;装载与 first-touch 要跟着计算位置走&lt;&#x2F;h3&gt;
&lt;p&gt;WKV 重排由对应的固定 worker 直接写入最终 packed 缓冲区，让首次写页发生在后续使用这些分片的 CPU 上。大表预热也会临时绑定到该层 CPU 组中的一个核，再触碰映射页面。&lt;&#x2F;p&gt;
&lt;p&gt;这是“线程绑定＋数据首次访问”的组合。当前代码没有用 &lt;code&gt;mbind&lt;&#x2F;code&gt; 强制迁移已有页面；若文件页早已被其他进程载入，或系统另有内存策略，绑核触页不能保证所有数据重新放到目标节点。因此，它表达的是放置策略，实际效果还需检查页面分布和远端访问。&lt;&#x2F;p&gt;
&lt;p&gt;AVX-512 解决每次读取后如何高效计算，NUMA 放置解决这些字节从哪里读取，预留 CPU 则避免投影抢占 GPU 提交所需的主机资源。三者服务于同一条完整流水线。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;tou-ying-jie-guo-shang-chuan-ye-yao-zhao-gu-xiao-fei-ta-de-gpu&quot;&gt;投影结果上传，也要照顾消费它的 GPU&lt;&#x2F;h3&gt;
&lt;p&gt;prefill 门控移到 GPU 后，投影结果还需要从 CPU 上传。测试发现，相同大小的结果，L1 上传约 15 ms，L14 却约 90 ms：L14 的 CPU 工作组与消费结果的 GPU 分属不同 NUMA 节点。&lt;&#x2F;p&gt;
&lt;p&gt;我们为上传增加靠近目标 GPU 的 pinned staging，让传输使用本地的页锁定中转缓冲。那一轮 50K 测试从约 26.67–26.84 秒降到 25.87–25.88 秒，输出哈希一致。这是该轮相邻实验的结果，最终性能表采用后续复测值。&lt;&#x2F;p&gt;
&lt;p&gt;绑定也需要实测。把全部 GPU 提交线程整体移到另一 NUMA 节点的实验，反而测到 28.47 秒，因而撤回。有效的 CPU 数据放置与上传中转优化，不能推广为“所有线程都按 PCIe 拓扑绑定就一定更快”。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ti-qian-tou-ying-ba-cpu-gong-zuo-yu-gpu-zhong-die&quot;&gt;提前投影——把 CPU 工作与 GPU 重叠&lt;&#x2F;h2&gt;
&lt;p&gt;Engram 的查表地址只依赖 token 历史，WKV 投影只依赖查出的向量；只有门控需要当前层的 hidden。这个数据依赖允许把大块计算提前。&lt;&#x2F;p&gt;
&lt;p&gt;优化后在 stage 0 准备两层的 Engram batch，分别启动投影任务；GPU 同时推进主干，到 L1 或 L14 时再取得投影结果。若投影已完成，层入口只需执行后半段；尚未完成则仍要等待。L14 前面的主干工作更多，提供的潜在重叠窗口也更长，实际隐藏了多少时间需要 profile 证明。&lt;&#x2F;p&gt;
&lt;p&gt;prefill 使用预计算结果时，会把 key&#x2F;value 投影结果交给 GPU 完成门控，使大批量 hidden 留在设备上。decode 的后续提交 &lt;code&gt;153598f6&lt;&#x2F;code&gt; 也提前启动投影，但保持 CPU 门控路径。因此这里减少的是层入口暴露的等待与部分数据往返，大表和 WKV 仍由 CPU 承担。&lt;&#x2F;p&gt;
&lt;p&gt;decode 提前投影的相邻 A&#x2F;B 中，固定 25-token 提示、生成 700 token，平均速度从 19.417 提升到 20.454 token&#x2F;s，提升 5.34%，完整文本哈希保持一致。完整性能表留在后续实测篇。AVX-512、NUMA、预热和流水线调整共同参与了 prefill 优化，整轮收益不能全部归因于某一项；prefill 的批量收益也不能直接外推到单 token decode。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;hou-xu-yu-gao&quot;&gt;后续预告&lt;&#x2F;h2&gt;
&lt;p&gt;接下来按每天一篇的节奏，继续展开其他工程环节：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;第二篇：从权重装配到八卡文本链路&lt;&#x2F;li&gt;
&lt;li&gt;第三篇：接入图像——用中间张量找差异&lt;&#x2F;li&gt;
&lt;li&gt;第四篇：性能实测——prefill、decode 与优化前后对比&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="zh">
        <title>穿越者必备：一台手机装下现代文明，在古代直接开挂</title>
        <published>2026-09-09T00:00:00+00:00</published>
        <updated>2026-09-09T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhuai.tech/blog/zllm-app-minicpm5-2b-qualcomm-npu/"/>
        <id>https://zhuai.tech/blog/zllm-app-minicpm5-2b-qualcomm-npu/</id>
        
        <content type="html" xml:base="https://zhuai.tech/blog/zllm-app-minicpm5-2b-qualcomm-npu/">&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;zllm-app&#x2F;traveler-hero.webp&quot; alt=&quot;穿越者手持本地大模型手机，用太阳能点亮古代农田、水车、粮仓和工坊&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;假如明天一睁眼，你穿越到了古代。&lt;&#x2F;p&gt;
&lt;p&gt;没有电网，没有互联网，没有搜索引擎，更没有云端大模型。你背不出《天工开物》，记不住怎么轮作，也分不清发酵失败和正常起泡。脑子里那些“我好像在短视频里看过”的现代知识，真到要用的时候，十有八九只剩一句：&lt;strong&gt;这东西到底怎么做来着？&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;别人穿越带打火机，你带一台装有 &lt;strong&gt;zllm-app&lt;&#x2F;strong&gt; 的手机。&lt;&#x2F;p&gt;
&lt;p&gt;手机里完整装着 MiniCPM5-2B。模型权重就是知识本身，不需要网络，不需要服务器，不需要提前准备问题清单。种粮、制肥、酿酒、翻译、建水车、办工坊、训练队伍，想到什么就问什么。&lt;&#x2F;p&gt;
&lt;p&gt;再带一块折叠太阳能板和一个充电宝：白天晒太阳，晚上召唤现代文明。&lt;&#x2F;p&gt;
&lt;p&gt;这不是穿越，是开管理员权限。&lt;&#x2F;p&gt;
&lt;video controls playsinline preload=&quot;metadata&quot; poster=&quot;&#x2F;videos&#x2F;zllm-traveler-guide-poster.png&quot; style=&quot;width: 100%; border-radius: 16px;&quot;&gt;
  &lt;source src=&quot;&#x2F;videos&#x2F;zllm-traveler-guide.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
&lt;&#x2F;video&gt;
&lt;p&gt;屏幕上先敲下一句穿越开局题，MiniCPM5-2B 当场把种粮、制肥、酿酒和队伍训练铺成路线图；紧接着再敲一句中文，英语、日语、韩语同时到手。飞行模式也拦不住，因为整个答案都从手机里长出来。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;xian-ba-shen-qi-zhuang-jin-kou-dai&quot;&gt;先把神器装进口袋&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https:&#x2F;&#x2F;zhuai.tech&#x2F;downloads&#x2F;zllm-app-0.1-sm8635-preview.apk&quot;&gt;zllm-app 0.1 SM8635 预览版 APK（8.7 MB）&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;SHA-256：&lt;code&gt;d3cbc48b928da4932cea37c4538dc0ed687336b1c4d5d93d18692b3f92d2cb5a&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;这是可直接安装的技术预览版。APK 不把 2B 模型硬塞进安装包：首次启动会自动从 Hugging Face 或魔搭择快下载约 2.6 GiB 核心模型，支持断点续传和 SHA-256 校验。模型就位后，断网也能继续对话。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;dang-qian-ji-xing-xian-zhi&quot;&gt;当前机型限制&lt;&#x2F;h3&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;项目&lt;&#x2F;th&gt;&lt;th&gt;当前支持边界&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;已完整验证机型&lt;&#x2F;td&gt;&lt;td&gt;Motorola XT2451-4&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;SoC &#x2F; NPU&lt;&#x2F;td&gt;&lt;td&gt;Qualcomm SM8635（骁龙 8s Gen 3）&#x2F; HTP V73&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;内存&lt;&#x2F;td&gt;&lt;td&gt;12 GB 已验证；更低内存暂不支持&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;系统与 ABI&lt;&#x2F;td&gt;&lt;td&gt;Android 16、arm64-v8a 已验证&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;存储&lt;&#x2F;td&gt;&lt;td&gt;核心模型约 2.6 GiB，建议至少预留 4 GiB&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;当前模型包和 QNN context 图是针对 &lt;strong&gt;SM8635 &#x2F; HTP V73&lt;&#x2F;strong&gt; 构建的。其他骁龙机型、其他 HTP 版本、联发科、三星或麒麟平台目前均不在支持范围内。神器已经出世，但这一版先只认这块 NPU。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;xian-dai-wen-ming-bu-zai-yun-duan-jiu-zai-mo-xing-quan-zhong-li&quot;&gt;现代文明，不在云端，就在模型权重里&lt;&#x2F;h2&gt;
&lt;p&gt;大多数 AI App 离开网络就只剩一个输入框。zllm-app 不一样：MiniCPM5-2B 的权重、tokenizer、对话模板和推理引擎全部在手机本地。&lt;&#x2F;p&gt;
&lt;p&gt;你问一句，手机自己算；模型答一句，答案直接从本地生成。问题不用发给任何服务器，回答也不需要从千年后的互联网传回来。&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;文字 ───────────────────────┐
&lt;&#x2F;span&gt;&lt;span&gt;                            ↓
&lt;&#x2F;span&gt;&lt;span&gt;语音 → SenseVoice 本地识别 → MiniCPM5-2B
&lt;&#x2F;span&gt;&lt;span&gt;                            ↓
&lt;&#x2F;span&gt;&lt;span&gt;                    高通 HTP&#x2F;NPU 推理
&lt;&#x2F;span&gt;&lt;span&gt;                            ↓
&lt;&#x2F;span&gt;&lt;span&gt;                    流式回答、自动保存
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;只要手机还能亮，知识就还在。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wo-men-zhen-de-ba-ta-pao-zai-liao-gao-tong-npu-shang&quot;&gt;我们真的把它跑在了高通 NPU 上&lt;&#x2F;h2&gt;
&lt;p&gt;“手机里跑大模型”最容易写成一句宣传语，真正做起来却是一场硬仗。&lt;&#x2F;p&gt;
&lt;p&gt;这次真机是一台 Motorola XT2451-4，搭载 SM8635、12 GB 内存和 HTP V73，运行 QAIRT 2.38。我们没有把模型扔给 CPU 慢慢磨，而是正面接入 Qualcomm QNN：动态加载 &lt;code&gt;libQnnHtp.so&lt;&#x2F;code&gt;，创建 backend、device、context 和执行图，把 MiniCPM5-2B 的核心计算送进手机 NPU。&lt;&#x2F;p&gt;
&lt;p&gt;最终，embedding、42 层 Transformer、final norm 和词表投影全部在 HTP 上跑通。SenseVoice-Small 的 encoder 和 CTC head 也进入 HTP：按住麦克风说话，手机先在本地把语音转成文字，再把问题交给 MiniCPM5-2B，答案一边生成一边显示。&lt;&#x2F;p&gt;
&lt;p&gt;中间踩过的坑足够写一本小册子：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;固定前缀的 activation outlier 把量化范围拉歪，42 层 K&#x2F;V 误差一度完全不可接受；&lt;&#x2F;li&gt;
&lt;li&gt;QNN 在这套硬件和 SDK 上不能直接使用我们原本期待的 S32&#x2F;F16&#x2F;F32 输出路径，只能重新设计 S8 输入、S8 输出和 scale 还原；&lt;&#x2F;li&gt;
&lt;li&gt;全 INT4 很快，却在长生成中跑偏；全 INT8 质量稳，却只有 8.97 tok&#x2F;s；&lt;&#x2F;li&gt;
&lt;li&gt;DSpark 推测解码成功跑通，但 draft 接受率不够，5.723 tok&#x2F;s 反而比普通 decode 更慢；&lt;&#x2F;li&gt;
&lt;li&gt;每次重新加载 43 张图，光启动成本就要几秒，最后给 decode 和 ASR runner 加上常驻模式，让图只加载一次。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;最终的 2B INT8 方案，长回答实测约 &lt;strong&gt;8.7–9.8 tok&#x2F;s&lt;&#x2F;strong&gt;。常驻图复用后，App 内第二问从 14.1 秒降到 9.65 秒。长输入超过阈值时，再由 AR8 批量 prefill 接管；831-token 新会话从原来的 95–143 秒缩短到 36.9 秒。&lt;&#x2F;p&gt;
&lt;p&gt;这不是 PPT 里的“支持 NPU”，而是一部真实手机、一套真实 QNN 图、一条从输入框到流式回答的完整链路。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-yi-wen-chuan-yue-di-yi-zhou-zen-me-huo-xia-lai&quot;&gt;第一问：穿越第一周，怎么活下来？&lt;&#x2F;h2&gt;
&lt;p&gt;别人刚穿越，先吟诗、认亲、找靠山。你打开 zllm-app，先解决真正重要的事：饮水、食物、住处、安全、当地气候和权力结构。&lt;&#x2F;p&gt;
&lt;p&gt;几秒之后，手机给出第一天、前三天和第一周行动表。&lt;&#x2F;p&gt;
&lt;p&gt;别人还在研究自己是哪朝哪代，你已经开始建立根据地。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-er-wen-zen-me-rang-liang-shi-fan-bei&quot;&gt;第二问：怎么让粮食翻倍？&lt;&#x2F;h2&gt;
&lt;p&gt;古代真正的硬通货不是金银，是粮食。&lt;&#x2F;p&gt;
&lt;p&gt;你不需要背下一整套农学教材，只要告诉 zllm-app：这里是什么气候，地里种什么，什么时候播种，叶子是什么颜色，去年收了多少。它会替你把问题拆开：选种、育苗、轮作、堆肥、排水、密植、除草、虫害、收割、晾晒、仓储。&lt;&#x2F;p&gt;
&lt;p&gt;第一年，你让农户留下对照田，记录每块地的播种量、肥料和收成。第二年，经验不再靠“老辈人说”，而是变成一张张能比较的账。哪种种子产量高，哪种堆肥有效，哪块地容易积水，全部有据可查。&lt;&#x2F;p&gt;
&lt;p&gt;别人靠天吃饭，你开始做农业实验。&lt;&#x2F;p&gt;
&lt;p&gt;当粮食稳定增长，你就不只是养活自己。你能养活工匠、商队、护卫和一座正在扩张的城。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-san-wen-mei-you-hua-fei-han-zen-me-zuo-fei-liao&quot;&gt;第三问：没有化肥厂，怎么做肥料？&lt;&#x2F;h2&gt;
&lt;p&gt;穿越者不可能第一天手搓一座化工厂，但你有模型，就知道技术树该从哪里点。&lt;&#x2F;p&gt;
&lt;p&gt;先从腐熟堆肥、绿肥、轮作、豆科固氮、草木灰和排水开始；再根据作物表现区分缺氮、缺磷、缺钾；每次只改一个变量，做小块对照，记录投入与产量。&lt;&#x2F;p&gt;
&lt;p&gt;最可怕的不是你会背某个配方，而是你知道怎样试验、怎样排错、怎样把偶然经验变成可复制的方法。&lt;&#x2F;p&gt;
&lt;p&gt;一个村学会，十个村照做；十个村做成，整片土地的产量都开始上涨。&lt;&#x2F;p&gt;
&lt;p&gt;这才是知识真正的威力。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-si-wen-liang-shi-duo-liao-zen-me-niang-jiu-zhi-cu-bao-cun-shi-wu&quot;&gt;第四问：粮食多了，怎么酿酒、制醋、保存食物？&lt;&#x2F;h2&gt;
&lt;p&gt;农业有了余粮，发酵就是下一棵科技树。&lt;&#x2F;p&gt;
&lt;p&gt;温度、卫生、容器、时间、批次记录——zllm-app 会把看似玄学的“祖传手感”拆成流程。哪一批酒用了什么原料，什么时候入缸，什么时候起泡，什么时候出现异味，全部记下来。&lt;&#x2F;p&gt;
&lt;p&gt;稳定的酒能贸易，醋能调味和加工食物，发酵技术还能继续延伸到酱、腌菜和长期储藏。&lt;&#x2F;p&gt;
&lt;p&gt;别人家酿酒靠运气，你的工坊开始做质量管理。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-wu-wen-yu-yan-bu-tong-shou-ji-jiu-shi-ni-de-hong-lu-si&quot;&gt;第五问：语言不通？手机就是你的鸿胪寺&lt;&#x2F;h2&gt;
&lt;p&gt;穿越不一定落在中原，也可能掉进商路、边城、港口，甚至一个完全听不懂当地话的地方。&lt;&#x2F;p&gt;
&lt;p&gt;这时候，翻译不是锦上添花，是保命技能。&lt;&#x2F;p&gt;
&lt;p&gt;MiniCPM5-2B 的真机质量回归已经覆盖翻译小样本；语音前端 SenseVoice-Small 支持中文、英文、日语、韩语和粤语，中文、英文、粤语都通过了手机端 HTP 转写门禁。你可以让对方对着手机说，再让 zllm-app 整理含义、翻译成自己的语言，还能把回信改写成更礼貌、更正式或更适合交易的版本。&lt;&#x2F;p&gt;
&lt;p&gt;别人遇到异族商队只能比手画脚，你能谈价格、问路线、读契约、写回信。&lt;&#x2F;p&gt;
&lt;p&gt;一台手机，就是翻译、书记官和外交顾问。&lt;&#x2F;p&gt;
&lt;p&gt;商路从此不再是地图上的线，而是你的情报网、贸易网和人才网。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-liu-wen-gu-dai-zen-me-lian-chu-yi-zhi-qiang-jun&quot;&gt;第六问：古代怎么练出一支强军？&lt;&#x2F;h2&gt;
&lt;p&gt;真正决定一支军队战斗力的，从来不只是武器。&lt;&#x2F;p&gt;
&lt;p&gt;zllm-app 给你的，是一整套组织系统：十人一组、逐级负责；统一口令、队列和集合时间；固定训练体能、负重、行军、警戒、救护和撤离；建立粮秣清点、装备登记、伤病记录和奖惩制度；每次行动结束必须复盘。&lt;&#x2F;p&gt;
&lt;p&gt;别人练兵靠将领当天心情，你开始使用标准训练表。&lt;&#x2F;p&gt;
&lt;p&gt;别人行军走散、粮草不明、命令传错，你的队伍知道谁负责谁、物资在哪里、出了问题向谁报告。平时能修堤、救火、护粮、搬运；战时令行禁止，不会一声锣响就乱成一团。&lt;&#x2F;p&gt;
&lt;p&gt;你带去的不是某一种神兵利器，而是现代组织学。&lt;&#x2F;p&gt;
&lt;p&gt;这东西比神兵更可怕。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;yi-bu-shou-ji-jiu-shi-yi-ke-wan-zheng-de-ke-ji-shu&quot;&gt;一部手机，就是一棵完整的科技树&lt;&#x2F;h2&gt;
&lt;p&gt;穿越者最容易犯的错误，是一上来就想造蒸汽机。&lt;&#x2F;p&gt;
&lt;p&gt;真正的发展顺序是：先解决水和粮食，再建立记录与度量；有稳定余粮后组织工匠；有工匠后改进农具、窑炉、水车和作坊；有剩余产能后再发展冶炼、机械、交通和教育。&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;你手里的资源&lt;&#x2F;th&gt;&lt;th&gt;下一步可以做什么&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;一块地、一些种子&lt;&#x2F;td&gt;&lt;td&gt;轮作、育苗、堆肥、对照田、储粮&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;粮食开始富余&lt;&#x2F;td&gt;&lt;td&gt;酿酒、制醋、食品加工、建立仓储&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;能接触不同语言的人&lt;&#x2F;td&gt;&lt;td&gt;翻译、贸易、外交、搜集技术&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;有木匠和铁匠&lt;&#x2F;td&gt;&lt;td&gt;改农具、做水车、统一零件尺寸&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;有稳定税粮&lt;&#x2F;td&gt;&lt;td&gt;养工匠、办学堂、建道路、训队伍&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;有成熟工坊&lt;&#x2F;td&gt;&lt;td&gt;发展冶炼、机械、印刷与规模生产&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;模型不是替你凭空造出机器，而是让你永远知道：目标是什么，原理是什么，缺什么条件，先做哪一步。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;chuan-yue-zhe-zhen-zheng-de-wai-gua&quot;&gt;穿越者真正的外挂&lt;&#x2F;h2&gt;
&lt;p&gt;一台手机，MiniCPM5-2B，zllm-app，高通 NPU，再加一块折叠太阳能板。&lt;&#x2F;p&gt;
&lt;p&gt;没有网络，照样问答；没有云端，照样生成；说一句话，SenseVoice 在本地识别；答到一半退出，会话和 KV 还能保存；换一个话题，可以另开一条完全独立的对话。&lt;&#x2F;p&gt;
&lt;p&gt;别人穿越，知识全靠脑子里那点模糊印象。&lt;&#x2F;p&gt;
&lt;p&gt;你穿越，口袋里装着一位不会睡觉的农学顾问、工艺顾问、翻译、历史老师、组织教官和技术总监。&lt;&#x2F;p&gt;
&lt;p&gt;从第一口干净的水，到第一亩高产田；从第一缸稳定的酒，到第一支令行禁止的队伍；从第一次跨语言交易，到一整套不断向前滚动的技术体系——所有问题，都可以从手机屏幕上的一句话开始。&lt;&#x2F;p&gt;
&lt;p&gt;这就是 zllm-app。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;完全离线，把现代文明装进口袋。&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;真要挑剔，这件穿越神器还差最后一块拼图：TTS。等它能把答案直接念出来，手机里这位现代军师就不只能为你写策，还能当场开口。&lt;&#x2F;p&gt;
&lt;p&gt;不过也好。&lt;&#x2F;p&gt;
&lt;p&gt;神器，不可轻易示人。&lt;&#x2F;p&gt;
&lt;p&gt;这一声，就留给下一个版本。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh">
        <title>从总共 1M 到 15+ 路 × 1M：GLM-5.3 KV Cache 换出的容量突破</title>
        <published>2026-09-08T00:00:00+00:00</published>
        <updated>2026-09-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhuai.tech/blog/glm53-cpu-kv-offload/"/>
        <id>https://zhuai.tech/blog/glm53-cpu-kv-offload/</id>
        
        <content type="html" xml:base="https://zhuai.tech/blog/glm53-cpu-kv-offload/">&lt;p&gt;**这次优化最大的收益，是把原来总共约 1M token 的上下文容量，推向 15+ 路、每路 1M token 历史同时 decode 的规模。**同一套 GPU，能够同时承接的长历史容量朝着 15+ 倍扩展。&lt;&#x2F;p&gt;
&lt;p&gt;原来，一份百万 token 的长对话就可能吃掉整套系统的上下文预算。现在，每台机器的 1 TiB 主机内存开始承接 MLA 历史，GPU 留下 32K 热缓存供当前计算使用。我们要支持的是多位用户各自带着百万 token 历史持续生成，而不是只把结束的会话存下来。&lt;&#x2F;p&gt;
&lt;p&gt;**15+ 路 × 1M 是本轮的容量估算规模，尚不是已经完成的并发性能实测。**下文会展开它的内存账；近乎无损的速度结论来自已经完成的 50K 对照。这两项成果分别回答“能承接多少历史”和“换出会损失多少速度”。&lt;&#x2F;p&gt;
&lt;p&gt;我们做到了一个有明确验收范围的结果：&lt;strong&gt;固定 50,023 token 输入、1,024 token 输出，保留 32K GPU 热缓存，MLA 历史换到主机内存后，MTP=3 三组对照的吞吐损失为 0.15%–1.30%；不开 MTP 的三组对照略快约 2%。所有完整输出都与各自基线一致。&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;这个结果来得并不直接。最初我们想把索引计算也交给 CPU，后来退回；热缓存一度损失约三成速度；几次局部优化看起来很有道理，完整模型却没有变快；最后那一点不稳定，又把我们从 attention kernel 带到了 Linux 内存管理和 GPU 驱动。&lt;&#x2F;p&gt;
&lt;p&gt;这篇文章按决策的形成过程展开：为什么走一条路，什么证据让我们保留它，又是什么证据迫使我们转向。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;qi-dian-ba-rong-liang-he-dang-qian-ji-suan-de-gong-zuo-ji-fen-kai&quot;&gt;起点：把容量和当前计算的工作集分开&lt;&#x2F;h2&gt;
&lt;p&gt;硬件沿用&lt;a href=&quot;https:&#x2F;&#x2F;zhuai.tech&#x2F;blog&#x2F;rocm-glm53-single-pipeline&#x2F;&quot;&gt;上一篇 GLM-5.3 优化文章&lt;&#x2F;a&gt;的双机系统：每台 8 张 AMD Radeon PRO W7900D，单卡 48 GiB，EPYC 9334 平台，每台 1 TiB RAM。节点内使用 HIP P2P，节点之间通过 TCP 传递层段边界的 activation；没有 XGMI。&lt;&#x2F;p&gt;
&lt;p&gt;这次改变的是 KV 的放置。权重和每层计算仍留在负责它的设备，历史数据也留在对应节点的主机内存中。&lt;&#x2F;p&gt;
&lt;p&gt;机会来自 GLM-5.3 的稀疏注意力路径：DSA 索引器从历史中选出 Top-2048 位置，MLA attention 消费选中的 KV。部分后续层复用 selection，但各层仍有自己的 KV 内容。&lt;&#x2F;p&gt;
&lt;p&gt;因此，全历史需要保存的规模，与一轮 attention 真正消费的规模，可以分开管理。&lt;&#x2F;p&gt;
&lt;p&gt;本次 Q8G64 MLA 每行包含 512 B latent、16 B scale 和 128 B RoPE 数据，共 &lt;strong&gt;656 B&lt;&#x2F;strong&gt;。原始工程记录的容量账如下；它只计算 MLA 数据，不含 DSA、MTP 层、元数据、对齐或 scratch。&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;对象&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;MLA 数据大小&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;50,000 token，单层单副本&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;31.281 MiB&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;50,000 token，78 个主模型层、两份副本合计&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4.765 GiB&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;一层选中 2,048 行，单副本&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.28125 MiB&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;32,768 行热缓存，单层单副本&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;20.5 MiB&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;单份 50K 历史还不惊人，但它会随会话数量和长度累积。我们的目标是让 GPU 上主要的 MLA 历史存储被热缓存容量约束，增长部分由 RAM 承担。&lt;&#x2F;p&gt;
&lt;p&gt;这里的“热缓存”也不是只保留最近 32K token 的滑动窗口。**旧位置仍保存在 RAM，DSA 仍可以选中它们；缓存未命中时再读取。**它改变物理位置，不主动截断模型可访问的历史。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-yi-ci-jue-ce-cpu-ji-cun-li-shi-ye-zuo-indexer&quot;&gt;第一次决策：CPU 既存历史，也做 Indexer？&lt;&#x2F;h2&gt;
&lt;p&gt;最初的方案相当自然：主机有大量内存和 CPU 核心，既然历史准备放在 RAM，就让 CPU 完成全历史打分和 Top-K，再把选中的 KV 送给 GPU。&lt;&#x2F;p&gt;
&lt;p&gt;这样还有一个调度上的诱惑：得到 selection 后，后续复用选集的层可以提前搬运历史 KV，与 GPU 的独立工作重叠。&lt;&#x2F;p&gt;
&lt;p&gt;我们已有 CPU Q8 打分、blocked 布局和向量化实现，于是补上固定 worker team、局部直方图、Top-K 工作区复用、增量 mirror、回滚和预取。这条路值得验证，不能只靠“CPU 比 GPU 慢”就否定。&lt;&#x2F;p&gt;
&lt;p&gt;真机结果却给了两个独立的否定理由。&lt;&#x2F;p&gt;
&lt;p&gt;第一是速度。50K 测试中，GPU 基线约 &lt;strong&gt;14.03 token&#x2F;s&lt;&#x2F;strong&gt;，CPU DSA 候选只有 &lt;strong&gt;10.92–11.32 token&#x2F;s&lt;&#x2F;strong&gt;。CPU 算术之外，query 回传、线程协调、选集上传和 GPU 等待也进入关键路径。历史上 131K 的 CPU 测量不能简单乘以 &lt;code&gt;50&#x2F;131&lt;&#x2F;code&gt;，因为活跃 worker 数也随上下文改变。&lt;&#x2F;p&gt;
&lt;p&gt;第二是正确性。CPU Q8 query 的最终选集与 GPU BF16 query 的精确路径存在 cutoff 差异，固定贪心输出发生分叉。CPU 内部计算自洽，并不等于它与原 GPU 路径等价。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;决定：保留 GPU exact DSA，关闭 CPU selection；继续推进 MLA 历史的 RAM 驻留。&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;这次退回缩小了问题。存储容量扩展可以独立成立，不必等 CPU 索引路线成功。提前预取仍是有价值的重叠机会，但不能预先记成零开销。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-er-ci-jue-ce-xian-rang-cpu-guan-li-gpu-re-huan-cun&quot;&gt;第二次决策：先让 CPU 管理 GPU 热缓存&lt;&#x2F;h2&gt;
&lt;p&gt;保留 GPU selection 后，早期热缓存路径仍需要把选集交回主机：CPU 查映射、判断命中、整理缺失行，再提交上传和 GPU attention。&lt;&#x2F;p&gt;
&lt;p&gt;这条路首先验证了容量方向，但性能很差。2026 年 9 月 7 日的早期战役中，真实 32K 热缓存配置经历 packed 复制和批量 mirror 修正后，三轮中位仍约 &lt;strong&gt;10.41 token&#x2F;s&lt;&#x2F;strong&gt;。该阶段报告相对其参考值约慢 29%；这些早期数字包含不同诊断条件，不能与后文最终验收直接拼成严格 A&#x2F;B。&lt;&#x2F;p&gt;
&lt;p&gt;这里先暴露了两个具体问题。&lt;&#x2F;p&gt;
&lt;p&gt;**一个是换出分支丢掉了原来的压缩复制路径。**此前 owner 量化一次，再把每行 656 B 的 packed KV 复制给 peer。进入热缓存后，fallback 变成两端各自 append、重新量化，也损失了原有通道重叠。于是先把 packed 复制接回热缓存。&lt;&#x2F;p&gt;
&lt;p&gt;**另一个是小传输的次数。**逐层、逐 token 更新主机镜像，78 层的 owner 与 peer 合计可以产生 156 次流操作。尝试把每行分成三段并增加在途传输后，操作数变为 468，性能反而退到约 8.8 token&#x2F;s。&lt;&#x2F;p&gt;
&lt;p&gt;这个实验否定了“增加异步并发就能解决传输成本”的直觉。数据本来就很小，更多提交可能比更多字节更贵。&lt;&#x2F;p&gt;
&lt;p&gt;改成每 64 行批量回传后，操作数量大幅下降，全量缓存阶段确实改善。但进入真正热缓存后，仍有明显差距。&lt;&#x2F;p&gt;
&lt;p&gt;随后我们尝试把 peer 镜像维护移入 worker 锁段，又让 append kernel 双写连续日志。三轮中位分别约 &lt;strong&gt;10.39&lt;&#x2F;strong&gt; 和 &lt;strong&gt;10.06 token&#x2F;s&lt;&#x2F;strong&gt;，没有得到预期的量级收益。&lt;&#x2F;p&gt;
&lt;p&gt;**决定：保留批量日志和必要的生命周期修复；撤回“锁迁移、日志双写足以解决主要差距”的判断。**实现完成与性能假设成立，需要分别验收。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;yi-ci-bi-xu-cheng-ren-de-wu-pan-zhan-bi-zui-gao-bu-deng-yu-hui-tui-lai-zi-na-li&quot;&gt;一次必须承认的误判：占比最高，不等于回退来自那里&lt;&#x2F;h2&gt;
&lt;p&gt;我们曾给热缓存 attention 加同步 trace。测得的总时间里，kernel 占 77.4%，主机锁与 remap 约占 22%。当时据此判断：剩余问题主要在窗口 attention kernel。&lt;&#x2F;p&gt;
&lt;p&gt;后续复核推翻了这个归因方法。&lt;&#x2F;p&gt;
&lt;p&gt;这份 profile 测的是&lt;strong&gt;候选自身的绝对时间构成&lt;&#x2F;strong&gt;，没有证明 GPU 常驻与换出之间的差额发生在同一位置。常驻路径本来也要执行 attention。另外，同步与 trace 把完整运行压到约 4.21 token&#x2F;s，已经明显改变了原有提交和重叠关系。&lt;&#x2F;p&gt;
&lt;p&gt;据此提出的 selected gather 方向，检查源码时又发现已有相关实现。需要验证的是实际热路径和依赖，不能把已有机制重新当作待实现方案。&lt;&#x2F;p&gt;
&lt;p&gt;我们调整测量方法：诊断只用来定位，正式性能关闭 profile、trace 和额外同步；每个候选配同版本 GPU 基线，完整输出一致后再比较吞吐。需要归因的始终是两条路径的差额。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-san-ci-jue-ce-rang-gpu-zhi-jie-guan-li-re-huan-cun&quot;&gt;第三次决策：让 GPU 直接管理热缓存&lt;&#x2F;h2&gt;
&lt;p&gt;新的证据把注意力带回提交链：GPU 已经算出 selection，却要让 CPU 等待回读，再做映射和打包，才能继续提交 attention。一次选集回传的主机等待曾达到约 0.7–1.2 ms；其中包含此前 GPU 排队，不能把它全算成复制时间，但它确实暴露了控制往返。&lt;&#x2F;p&gt;
&lt;p&gt;于是改变缓存管理的位置。选集留在 GPU，GPU 直接查热槽；命中读显存，未命中读已注册的 RAM mirror，形成当前 attention 使用的紧凑选集。&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;GPU exact DSA ──→ 历史位置 selection
&lt;&#x2F;span&gt;&lt;span&gt;                           │
&lt;&#x2F;span&gt;&lt;span&gt;                           ▼
&lt;&#x2F;span&gt;&lt;span&gt;                    GPU hot gather
&lt;&#x2F;span&gt;&lt;span&gt;                    ├─ 命中：读显存热槽
&lt;&#x2F;span&gt;&lt;span&gt;本节点 RAM 历史 ────└─ 未命中：读注册的主机内存
&lt;&#x2F;span&gt;&lt;span&gt;                           │
&lt;&#x2F;span&gt;&lt;span&gt;                           ▼
&lt;&#x2F;span&gt;&lt;span&gt;                    紧凑 KV → GPU attention
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;新增 KV → GPU recent 环 → 分批回传 → RAM 历史
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;主机内存因此承担历史容量，GPU 保持索引、缓存查找和 attention 的连续执行。CPU 仍负责资源与生命周期管理，但不再为每次 selection 主持一轮查表和搬运。&lt;&#x2F;p&gt;
&lt;p&gt;它也没有让 PCIe 读取免费。早期某些采样窗口 miss 不到 1%，说明这个负载具有缓存复用机会；不能由此保证所有输入都有相同命中率。&lt;&#x2F;p&gt;
&lt;p&gt;最初 GPU 管理版本约 &lt;strong&gt;11.72 token&#x2F;s&lt;&#x2F;strong&gt;，仍有首次注册停顿。提前注册并补齐多行路径后，甚至有一轮退到 &lt;strong&gt;5.13 token&#x2F;s&lt;&#x2F;strong&gt;。我们继续保留方向，但不把“稳态事件间隔接近常驻”当作完整吞吐通过。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;gpu-guan-huan-cun-ye-hui-fan-cuo-kua-block-ke-jian-xing-yu-zhi-huan-chang-sao-miao&quot;&gt;GPU 管缓存也会犯错：跨 block 可见性与置换长扫描&lt;&#x2F;h2&gt;
&lt;p&gt;MTP 把单行路径里不明显的问题放大了。一次 verify 会处理多行，多个 query 可能选到同一个缺失位置。&lt;&#x2F;p&gt;
&lt;p&gt;早期实现允许同一次 gather 内，一个 block 读另一个 block 刚填的热槽。真实 MTP 测试出现异常 scale，随后没有有限的可选 token。&lt;&#x2F;p&gt;
&lt;p&gt;我们把可见性边界收紧：**本轮新填的槽，到下一轮才作为命中来源；同轮重复 miss 直接读取 RAM。**本轮正在使用的槽先 pin，新增但尚未回传到 RAM 的 token 则由 recent 环保护。&lt;&#x2F;p&gt;
&lt;p&gt;之后完整 1,024-token MTP 输出与同版本常驻一致，但吞吐只有 &lt;strong&gt;16.175 对 27.530 token&#x2F;s&lt;&#x2F;strong&gt;，仍慢约 41.2%。两边接受的 draft 都是 665 个，差距不能归因于 MTP 接受率。&lt;&#x2F;p&gt;
&lt;p&gt;接下来把小批 verify 也接回 owner→peer packed KV 复制，候选升到 &lt;strong&gt;20.035 token&#x2F;s&lt;&#x2F;strong&gt;。这是值得保留的完整执行路径修复。&lt;&#x2F;p&gt;
&lt;p&gt;缓存置换又带来一个反直觉结果。使用带引用位的时钟置换时，在“热窗已满、引用位全部置位、只有 1% miss”的合成压力形态下，少数 miss block 要负责长距离清扫，gather 达到约 &lt;strong&gt;1.03–1.07 ms&lt;&#x2F;strong&gt;。miss 更多时，反而可能因参与清扫的 block 增多而更快。&lt;&#x2F;p&gt;
&lt;p&gt;改成跳过当前 pin 槽的环形替换后，同一探针降到约 &lt;strong&gt;0.032 ms（2,048 行）&lt;&#x2F;strong&gt;。但是完整 MTP 运行只有 &lt;strong&gt;19.767 token&#x2F;s&lt;&#x2F;strong&gt;，没有体现探针的巨大收益。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;决定：保留更简单的置换实现，消除已证实的压力形态；否定“这个局部热点就是完整差距”的推断。&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-si-ci-jue-ce-chi-kai-attention-jian-cha-ti-jiao-xian-cheng-zai-zuo-shen-me&quot;&gt;第四次决策：离开 attention，检查提交线程在做什么&lt;&#x2F;h2&gt;
&lt;p&gt;缩小到活跃 decode 线程的 CPU 采样，发现约 21.1% CPU cycles 落在主机 memcpy。调用链最终落到只读 MoE 权重对象的 &lt;code&gt;clone&lt;&#x2F;code&gt;：每次向 worker 提交工作，都可能深拷贝 Dense router。&lt;&#x2F;p&gt;
&lt;p&gt;这与 KV 的语义无关，却真实地影响了同一条执行链。修复让只读权重组通过 &lt;code&gt;Arc&lt;&#x2F;code&gt; 共享，保留算子和权重数值。&lt;&#x2F;p&gt;
&lt;p&gt;我们也给 GPU 常驻基线加入同一修复。v15 同版本 MTP 对照达到 &lt;strong&gt;25.425 对 26.832 token&#x2F;s&lt;&#x2F;strong&gt;，损失收窄到 &lt;strong&gt;5.24%&lt;&#x2F;strong&gt;。这是显著改善，但仍略超 5% 门槛；21.1% 的 CPU 采样占比也不能换算成等比例端到端收益。&lt;&#x2F;p&gt;
&lt;p&gt;后续继续复用 recent 环承载 peer 日志，省掉重复复制；移交 prefill 已有的缓冲，减少热缓存切换时的分配与释放；小日志用 compute 拷贝和打包，减少 DMA 队列操作。&lt;&#x2F;p&gt;
&lt;p&gt;这些改变没有一次性解决问题。某轮 no-MTP 进入 4.80%，下一轮 MTP 又损失 9.87%。日志里不断出现 &lt;strong&gt;0.6–1.2 秒的长停顿&lt;&#x2F;strong&gt;，即使大多数事件间隔已经接近常驻。&lt;&#x2F;p&gt;
&lt;p&gt;我们没有删掉前 128 个 token，也没有只取 p50 来宣布完成。用户实际等待的这些停顿必须计入原有完整 decode 口径。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;di-wu-ci-jue-ce-cong-man-kernel-zhui-dao-cao-zuo-xi-tong-ye-qian-yi&quot;&gt;第五次决策：从慢 kernel 追到操作系统页迁移&lt;&#x2F;h2&gt;
&lt;p&gt;设备时间线显示，长停顿未必落在 hot gather。一次采集中 gather 最大只有约 0.054–0.056 ms，却有约 792 ms 的同步等待，整条流水线几乎没有设备工作。&lt;&#x2F;p&gt;
&lt;p&gt;这时，把 profiler 中一个被拖长的 kernel 名称当作根因，会再次走错路。我们开始对齐两台机器的系统设置与驱动行为。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;guan-bi-numa-zi-dong-ping-heng-fang-xiang-zheng-que-zheng-ju-huan-bu-gou&quot;&gt;关闭 NUMA 自动平衡：方向正确，证据还不够&lt;&#x2F;h3&gt;
&lt;p&gt;发现 Amd-1 的 &lt;code&gt;kernel.numa_balancing=0&lt;&#x2F;code&gt;，Amd-2 为 &lt;code&gt;1&lt;&#x2F;code&gt;，长停顿集中在后者。统一关闭后，第一组 MTP 损失只有 &lt;strong&gt;0.51%&lt;&#x2F;strong&gt;，看起来已经成功。&lt;&#x2F;p&gt;
&lt;p&gt;但第三组交错复测损失 &lt;strong&gt;5.49%&lt;&#x2F;strong&gt;，门禁停止。决定保留系统设置一致性，继续寻找剩余长尾，不用平均值掩盖失败组。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;yu-xian-xie-ru-zhu-ce-fan-wei-de-wei-ye-yi-ge-bei-shi-yan-fou-ding-de-jie-shi&quot;&gt;预先写入注册范围的尾页：一个被实验否定的解释&lt;&#x2F;h3&gt;
&lt;p&gt;另一个假设是：RAM mirror 预留容量中的尾页尚未实际写入，后续追加首次触页影响了 GPU 映射。于是注册前把 spare capacity 写零，保持有效 KV 字节和逻辑长度不变。&lt;&#x2F;p&gt;
&lt;p&gt;该候选仍出现 &lt;strong&gt;7.35%&lt;&#x2F;strong&gt; 损失和秒级间隔。预触页没有解决稳定性，不能把它写成最终根因。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;zhu-dong-nei-cun-zheng-li-zhong-yu-qu-de-diao-yong-lian-zheng-ju&quot;&gt;主动内存整理：终于取得调用链证据&lt;&#x2F;h3&gt;
&lt;p&gt;进一步使用 BPF 对齐实际推理进程、失效地址和恢复 worker，捕获到这样的路径：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;kcompactd
&lt;&#x2F;span&gt;&lt;span&gt;  → proactive_compact_node
&lt;&#x2F;span&gt;&lt;span&gt;  → migrate_pages
&lt;&#x2F;span&gt;&lt;span&gt;  → try_to_migrate_one
&lt;&#x2F;span&gt;&lt;span&gt;  → amdgpu_hmm_invalidate_hsa
&lt;&#x2F;span&gt;&lt;span&gt;  → 相关队列恢复工作
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这次证据确认了主动页整理、HMM 失效与队列恢复路径。失效地址采样中，大量事件并不与显式注册的 KV mirror 相交，所以也不能再把它们解释为“KV 尾页首次写入”。这次采样没有复现之前完整的一秒停顿，因而我们没有声称每次长停顿都已逐一归因。&lt;&#x2F;p&gt;
&lt;p&gt;两台机器随后将 &lt;code&gt;vm.compaction_proactiveness&lt;&#x2F;code&gt; 从 20 调为 0，NUMA 自动平衡保持关闭。Linux 官方文档说明，0 会关闭主动整理，页迁移可能造成应用延迟尖峰；本次因果判断还要靠同设置下的完整对照。&lt;a href=&quot;https:&#x2F;&#x2F;docs.kernel.org&#x2F;admin-guide&#x2F;sysctl&#x2F;vm.html#compaction-proactiveness&quot;&gt;Linux 内核文档&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;这些是实验机的运行时调整，原值和恢复命令均留档，没有修改开机配置。它们属于这次验收条件，不能省略，也不能当作所有 GPU 机器通用的调优配方。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;liu-zu-jiao-cuo-dui-zhao-dao-zhe-li-cai-ken-ding-jin-hu-wu-sun&quot;&gt;六组交错对照：到这里，才肯定“近乎无损”&lt;&#x2F;h2&gt;
&lt;p&gt;最终使用同一 v23 二进制，固定 50,023 输入、1,024 输出、greedy seed01。换出组启用 32,768 行热缓存，对照组保持 GPU 常驻；双方关闭 NUMA 自动平衡与主动 compaction，关闭 profile、trace、attach 和 CPU DSA。&lt;&#x2F;p&gt;
&lt;p&gt;每次重新启动实验运行时，交错安排常驻与换出顺序。吞吐沿用首个文本事件至 SSE DONE 的原有口径，没有删除停顿。&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;模式&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;组&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;RAM 历史 + GPU 热缓存（token&#x2F;s）&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;GPU 常驻（token&#x2F;s）&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;换出吞吐损失&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;MTP=3&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;27.839&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;27.883&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.157%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;MTP=3&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;27.923&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;27.966&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.150%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;MTP=3&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;3&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;27.738&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;28.102&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.297%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;no-MTP&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;14.042&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;13.788&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;−1.846%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;no-MTP&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;14.029&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;13.753&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;−2.007%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;no-MTP&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;3&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;14.029&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;13.747&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;−2.056%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;全部 12 个请求完成，完整输出文件 hash 与各自模式的参考一致。负损失表示换出略快；我们把它理解为这套负载下基本打平，不把它外推成换出必然加速。&lt;&#x2F;p&gt;
&lt;p&gt;这个结果支持的是单路长历史 decode。初始 prefill、追加输入、多会话容量和并发吞吐还需要各自验证。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;rong-liang-you-hua-bi-xu-tong-guo-hui-hua-sheng-ming-zhou-qi&quot;&gt;容量优化必须通过会话生命周期&lt;&#x2F;h2&gt;
&lt;p&gt;一次连续生成跑通，还不足以支撑真实对话。用户会继续追加输入，MTP 会拒绝草稿并回滚，会话还会保存和恢复。&lt;&#x2F;p&gt;
&lt;p&gt;成功路径的完整性边界是：新增 KV 可以先在 GPU recent 环和在途回传中；**导出或恢复全量历史之前，必须补交日志、等待回传，并确认 CPU 行数追上逻辑行数。**不能把已提交的异步复制当作已经落入 RAM。&lt;&#x2F;p&gt;
&lt;p&gt;大段 append prefill 又与 decode 不同。一次要处理大量新行，当前实现会先从 RAM 恢复所需全历史 GPU MLA KV，完成追加，随后 decode 再转回热缓存。小尾块不能被误判成 verify，owner 和 peer 也必须保持一致。&lt;&#x2F;p&gt;
&lt;p&gt;第一次真实大段 append 没通过，暴露的却是 MTP terminal hidden：收尾保存了原始 BF16 残差，后续路径期待 final norm 后的 F32 hidden。修复统一了归一化边界，并处理旧缓存恢复。&lt;&#x2F;p&gt;
&lt;p&gt;v25 随后完成同版本对照：命中 50,085 行历史，追加 9,854 token，最终 prompt 为 59,939 token，生成 1,024 token。完整输出一致。&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;大段 append 对照&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;换出&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;GPU 常驻&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;首 token 时间&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;17.905 s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;18.350 s&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Decode&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;26.850 token&#x2F;s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;27.004 token&#x2F;s&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;单组 decode 损失 &lt;strong&gt;0.57%&lt;&#x2F;strong&gt;。这说明换出后的长对话可以继续追加，但它仍是单组追加验收，不替代前面的六组单路测试。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;shi-lu-shi-yan-luo-ji-shang-sheng-liao-xian-cun-shi-ji-allocation-reng-neng-ba-qia-cheng-man&quot;&gt;十路实验：逻辑上省了显存，实际 allocation 仍能把卡撑满&lt;&#x2F;h2&gt;
&lt;p&gt;准备十份独立 50K 历史时，第六份 prefill 在尾节点 OOM。1 Hz 采样最高约 &lt;strong&gt;47.98 GiB&lt;&#x2F;strong&gt;，还没有进入十路共同 decode。&lt;&#x2F;p&gt;
&lt;p&gt;问题在通用 GPU 内存池。小的长期 KV、热缓存和会话工作区，可以 best-fit 借到 prefill 释放的大块 allocation，并长期占住。逻辑上只用几十 KiB 或 MiB，物理上却可能扣留远大于此的空间；统计又只报逻辑字节，进一步低估占用。&lt;&#x2F;p&gt;
&lt;p&gt;因此我们给长期缓存分配加上精确容量限制，主要 KV&#x2F;DSA 统计改用实际 allocation 大小，短期 activation 保留原池策略。测试先用大块污染内存池，再验证小热缓存不会占住那些大块。&lt;&#x2F;p&gt;
&lt;p&gt;修复后，v28 完成十份长历史及后续十路 append，整组实际吞吐 &lt;strong&gt;98.56 token&#x2F;s&lt;&#x2F;strong&gt;，采样观察到的显存最高约 &lt;strong&gt;45.12 GiB&lt;&#x2F;strong&gt;。&lt;&#x2F;p&gt;
&lt;p&gt;接着调整并发调度：MTP 深度改为 1 后，完整请求吞吐到 &lt;strong&gt;125.26 token&#x2F;s&lt;&#x2F;strong&gt;；每段 decode 合批上限从 4 改为 1，达到 &lt;strong&gt;145.67 token&#x2F;s&lt;&#x2F;strong&gt;。后一组与前一组输入、完整输出及 usage 一致。多路工作可以持续推进流水线，单路有效的深推测和较大合批，不一定适合十路。&lt;&#x2F;p&gt;
&lt;p&gt;再单独放宽 A0 的已就绪工作合批上限，得到 &lt;strong&gt;145.28 token&#x2F;s&lt;&#x2F;strong&gt;，基本持平，没有证据支持保留，因而撤去该改动。&lt;&#x2F;p&gt;
&lt;p&gt;这些并发数字按实际 completion token 总数除以整个 append 请求墙钟计算，包含请求首尾；没有把 SSE 文本事件数直接当成 token 数。十路结果也没有 GPU 全常驻的对应性能对照，不能用它宣称并发换出无损。最新并发候选尚需回归单路门禁。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;rong-liang-shou-yi-cong-zong-gong-yue-1m-zou-xiang-15-lu-ge-zi-1m&quot;&gt;容量收益：从总共约 1M，走向 15+ 路各自 1M&lt;&#x2F;h2&gt;
&lt;p&gt;把收益落到使用场景上：原来的总预算约 1M token，如果一位用户带着百万 token 历史，这份预算就基本被占满。换出后，我们希望让 15+ 位这样的用户同时处于 decode，每一路都保有自己的百万 token 历史。&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;&#x2F;th&gt;&lt;th&gt;原来的容量口径&lt;&#x2F;th&gt;&lt;th&gt;换出后的容量估算目标&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;所有活跃会话的历史总量&lt;&#x2F;td&gt;&lt;td&gt;约 1M token&lt;&#x2F;td&gt;&lt;td&gt;15M+ token&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;每路携带 1M 历史时的规模&lt;&#x2F;td&gt;&lt;td&gt;约 1 路&lt;&#x2F;td&gt;&lt;td&gt;15+ 路同时 decode&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;MLA 全历史的主要存储位置&lt;&#x2F;td&gt;&lt;td&gt;GPU 显存&lt;&#x2F;td&gt;&lt;td&gt;每节点 1 TiB 主机内存&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;GPU 上的 MLA 历史数据&lt;&#x2F;td&gt;&lt;td&gt;随历史长度增长&lt;&#x2F;td&gt;&lt;td&gt;每路保留 32K 热缓存，按需读取旧位置&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;这就是“容量几乎放开了”的工程含义：在相同 GPU 硬件上，把可承接的活跃长历史规模推向 &lt;strong&gt;15+ 倍&lt;&#x2F;strong&gt;。它扩展的是多会话的历史容量；15+ 路同时生成时的单路速度，仍由计算、索引、传输和调度共同决定。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;15-lu-bai-mo-li-shi-ram-zhuang-de-xia-ma&quot;&gt;15 路百万历史，RAM 装得下吗？&lt;&#x2F;h3&gt;
&lt;p&gt;按本文相同的主模型 MLA 格式，取 &lt;code&gt;1M = 1,048,576 token&lt;&#x2F;code&gt;，78 层、owner&#x2F;peer 两份副本：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;单路 1M 历史 = 1,048,576 × 656 B × 78 × 2
&lt;&#x2F;span&gt;&lt;span&gt;             ≈ 99.94 GiB
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;15 路合计   ≈ 1,499.06 GiB（约 1.464 TiB） 主模型 MLA 历史
&lt;&#x2F;span&gt;&lt;span&gt;两台主机 RAM 合计 = 2 TiB
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这里必须按&lt;strong&gt;两台机器合计 2 TiB&lt;&#x2F;strong&gt; 计算，不能把全部历史算到一台 1 TiB 主机上。MLA 历史跟随所属层分散驻留；owner&#x2F;peer 双副本已经包含在上述数字中，也不意味着每台都保存完整模型的全部历史。&lt;&#x2F;p&gt;
&lt;p&gt;以 15 路为例，主模型 MLA 历史合计约 1.464 TiB；按两节点近似均分，每台约 0.732 TiB。实际按各节点负责的层数分配，仍需逐节点核算余量。**从主模型 MLA 历史载荷看，15 路并没有用尽双机内存，容量可以继续向 15+ 路扩展。**这是编码尺寸推导的容量依据，尚不是整机内存峰值或最大并发路数实测。&lt;&#x2F;p&gt;
&lt;p&gt;与此同时，15 路的 32K 热缓存，主模型两份副本跨全部 GPU 的载荷合计约 &lt;strong&gt;46.85 GiB&lt;&#x2F;strong&gt;。这一项不随每路历史从 50K 增长到 1M 而同比增长，正是容量扩展的关键。&lt;&#x2F;p&gt;
&lt;p&gt;完整服务预算还要加上 GPU DSA 历史、token 映射、MTP 层、各会话工作区、分配与注册开销，以及 prefill&#x2F;大段 append 恢复全历史时的瞬态显存。因此，RAM 能容纳这些 MLA 字节，是 15+ 路目标的容量基础，还不是整套系统在这个规模下的验收结论。&lt;&#x2F;p&gt;
&lt;p&gt;目前完成的速度与生命周期实测是：50K 单路近乎无损、约 60K 大段追加，以及十份 50K 长历史的完整并发请求。**下一步最有价值的容量验收，就是把每路历史推到 1M、把活跃路数推到 15+ 路，同时记录真实内存占用、首 token 延迟和持续 decode 吞吐。**当前的 1M admission 配置也要与新的总容量目标匹配。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;zui-hou-liu-xia-de-jue-ce&quot;&gt;最后留下的决策&lt;&#x2F;h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;路线或判断&lt;&#x2F;th&gt;&lt;th&gt;处理结果&lt;&#x2F;th&gt;&lt;th&gt;决定性证据&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;CPU 完成最终 DSA selection&lt;&#x2F;td&gt;&lt;td&gt;本轮退回&lt;&#x2F;td&gt;&lt;td&gt;速度下降，固定贪心输出分叉&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;MLA 历史放 RAM，GPU 保留热工作集&lt;&#x2F;td&gt;&lt;td&gt;保留&lt;&#x2F;td&gt;&lt;td&gt;六组单路对照通过，完整输出一致&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;CPU 主持每次 selection 的热槽管理&lt;&#x2F;td&gt;&lt;td&gt;主路径转向 GPU 管理&lt;&#x2F;td&gt;&lt;td&gt;回读等待打断提交链&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;增加更多逐行异步拷贝&lt;&#x2F;td&gt;&lt;td&gt;否定&lt;&#x2F;td&gt;&lt;td&gt;操作数增多，吞吐反而下降&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;批量日志、packed owner&#x2F;peer 复制&lt;&#x2F;td&gt;&lt;td&gt;保留&lt;&#x2F;td&gt;&lt;td&gt;减少重复处理，补齐单行与 MTP 路径&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;锁迁移或日志双写足以填平差距&lt;&#x2F;td&gt;&lt;td&gt;否定&lt;&#x2F;td&gt;&lt;td&gt;实现后完整吞吐没有量级改善&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;kernel 占比高，所以回退一定在 kernel&lt;&#x2F;td&gt;&lt;td&gt;撤回&lt;&#x2F;td&gt;&lt;td&gt;绝对占比不能解释常驻&#x2F;换出差额&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;简化 GPU 置换&lt;&#x2F;td&gt;&lt;td&gt;保留，但不夸大收益&lt;&#x2F;td&gt;&lt;td&gt;压力探针明显改善，完整模型未同步提速&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;只读权重深拷贝改为共享&lt;&#x2F;td&gt;&lt;td&gt;保留&lt;&#x2F;td&gt;&lt;td&gt;同版本完整对照差距收窄&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;只关 NUMA 自动平衡就足够&lt;&#x2F;td&gt;&lt;td&gt;否定“足够”&lt;&#x2F;td&gt;&lt;td&gt;重复门禁仍失败&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;预触注册尾页就是长尾解法&lt;&#x2F;td&gt;&lt;td&gt;否定&lt;&#x2F;td&gt;&lt;td&gt;秒级停顿与 7.35% 失败组仍在&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;统一 NUMA、关闭主动整理后验收&lt;&#x2F;td&gt;&lt;td&gt;本机保留&lt;&#x2F;td&gt;&lt;td&gt;内核路径证据与六组交错对照支持&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;小逻辑 buffer 就等于小显存占用&lt;&#x2F;td&gt;&lt;td&gt;否定&lt;&#x2F;td&gt;&lt;td&gt;十路准备 OOM，实际 allocation 超额&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;并发时继续增加 A0 合批&lt;&#x2F;td&gt;&lt;td&gt;本次撤去&lt;&#x2F;td&gt;&lt;td&gt;完整吞吐基本持平&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;这轮工程最值得复用的，是把历史容量、当前工作集和控制依赖分别处理。RAM 提供容量，GPU 热缓存维持局部访问，GPU 自己消费 selection，批量日志维护新增历史；主机和驱动的内存行为，也必须进入端到端测量。&lt;&#x2F;p&gt;
&lt;p&gt;每一次转向都有一个共同标准：完整请求有没有变好，输出是否一致，失败能否复现。沿着这个标准，我们才把“旁边还有 1T 内存”的硬件余量，变成了可用的长历史容量，并把目标从总共约 1M 推向 15+ 路各自 1M 的同时解码。&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;em&gt;资料与口径：依据 zLLM 工程记录 &lt;code&gt;docs&#x2F;glm53-cpu-kv-decode.md&lt;&#x2F;code&gt;（2026-09-05 至 09-08）、双机基线记录 &lt;code&gt;docs&#x2F;glm53-rocm-amd12-baseline-20260906.md&lt;&#x2F;code&gt;，并核对本地 GPU 热缓存实现。正式单路数据来自记录中的 &lt;code&gt;cp0-v23&lt;&#x2F;code&gt; 六组门禁；追加为 &lt;code&gt;append-v25&lt;&#x2F;code&gt;，并发为 &lt;code&gt;concurrency-v28&lt;&#x2F;code&gt; 与 &lt;code&gt;concurrency-tuning&lt;&#x2F;code&gt;。历史探索、诊断探针、正式配对和后续并发候选在文中分别标注，不能跨版本合并成同一条严格性能曲线。本文没有重新运行远端 benchmark。&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh">
        <title>古董机器也能跑顶尖大模型</title>
        <published>2026-09-07T00:00:00+00:00</published>
        <updated>2026-09-07T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhuai.tech/blog/old-xeon-rtx3060-qwen38-flash-next/"/>
        <id>https://zhuai.tech/blog/old-xeon-rtx3060-qwen38-flash-next/</id>
        
        <content type="html" xml:base="https://zhuai.tech/blog/old-xeon-rtx3060-qwen38-flash-next/">&lt;p&gt;双路老至强，一张 RTX 3060，再加上系统可用约 125 GiB 的内存。这就是这次运行 Qwen3.8-Flash-Next 的全部计算硬件。&lt;&#x2F;p&gt;
&lt;p&gt;在这台机器上，zLLM 已经跑通模型的完整 &lt;strong&gt;48 层文本推理&lt;&#x2F;strong&gt;，并接入常驻服务。驱动分配的锁页主存方案在最新回归中约为 &lt;strong&gt;6.0–6.4 token&#x2F;s&lt;&#x2F;strong&gt;，完成了 &lt;strong&gt;50 次请求与 4 次长生成的零失败验证&lt;&#x2F;strong&gt;。最新同题同参的单请求 MTP 基线达到 &lt;strong&gt;13.158 token&#x2F;s&lt;&#x2F;strong&gt;。&lt;&#x2F;p&gt;
&lt;p&gt;几 token 每秒，意味着回答可以逐步出现在屏幕上，也意味着长答案需要耐心。对一台显存只有 12 GiB 的老平台来说，真正值得展开的是：它怎样把远大于显存的权重用起来，又怎样从“成功生成一次”走到连续提供服务。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;lao-ping-tai-de-you-shi-shi-neng-zhuang-xia-zu-gou-duo-de-nei-cun&quot;&gt;老平台的优势，是能装下足够多的内存&lt;&#x2F;h2&gt;
&lt;p&gt;本次机器与模型配置如下，性能与验证结论均来自这台机器的接入记录。&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;项目&lt;&#x2F;th&gt;&lt;th&gt;配置&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;CPU&lt;&#x2F;td&gt;&lt;td&gt;双路 Intel Xeon E5-2696 v4&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;GPU&lt;&#x2F;td&gt;&lt;td&gt;NVIDIA GeForce RTX 3060，12 GiB 显存&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;系统内存&lt;&#x2F;td&gt;&lt;td&gt;125 GiB RAM&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;系统&lt;&#x2F;td&gt;&lt;td&gt;Fedora 44&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;GPU 软件环境&lt;&#x2F;td&gt;&lt;td&gt;NVIDIA driver 610.57.04，CUDA 13.3&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;CPU 与 GPU 连接&lt;&#x2F;td&gt;&lt;td&gt;PCIe 3.0 x16&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;模型&lt;&#x2F;td&gt;&lt;td&gt;Qwen3.8-Flash-Next，GGUF 架构名 &lt;code&gt;qwen4exp&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;权重&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;unsloth&#x2F;Qwen3.8-Flash-Next-GGUF&lt;&#x2F;code&gt;，UD-Q4_K_XL 四分片&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;“古董”主要说的是这套双路 E5 平台。整机仍然依赖一张支持 CUDA 的 GPU，以及足够大的主存。标题中的“顶尖大模型”也不代表本文做过能力排名：这是一篇部署与推理工程实录，没有独立评测模型质量或量化后的能力损失。&lt;&#x2F;p&gt;
&lt;p&gt;仅模型的量化专家权重就有 &lt;strong&gt;77,017,907,200 字节，约 71.73 GiB&lt;&#x2F;strong&gt;，已经远超显存容量。不过，它采用 MoE，也就是混合专家结构：每层会从 512 个专家中选择 10 个参与当前计算，另有共享专家。&lt;&#x2F;p&gt;
&lt;p&gt;这给了老机器一个机会。全部专家需要有地方存放，但每一步只需把实际选中的专家交给 GPU。大内存承担容量，显存承担当前计算，两者通过 PCIe 配合。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;12-gib-xian-cun-zen-yang-zhuang-pei-wan-zheng-mo-xing&quot;&gt;12 GiB 显存怎样装配完整模型&lt;&#x2F;h2&gt;
&lt;p&gt;我们把权重分成三种去向。&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;权重类型&lt;&#x2F;th&gt;&lt;th&gt;存放与执行方式&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;非专家权重&lt;&#x2F;td&gt;&lt;td&gt;以量化形式驻留显存&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;约 71.73 GiB 的专家权重&lt;&#x2F;td&gt;&lt;td&gt;常驻主存，路由命中后按需上传显存&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;大型 PLE 表与 embedding&lt;&#x2F;td&gt;&lt;td&gt;读取当前需要的压缩行，再生成计算所需数据&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;每到一层，路由先选专家。显存缓存中已经有的直接复用，缺失的从主存上传，随后执行量化专家计算。专家始终保留 GGUF 的压缩编码，没有先全部展开成半精度浮点数。&lt;&#x2F;p&gt;
&lt;p&gt;这一步很关键。如果模型文件在磁盘上很小，运行时却把权重重新撑大，容量和传输上的优势就会丢掉。zLLM 为实际使用的 Q4_K、Q5_K、Q5_1、Q8_0 等格式实现了直接消费压缩权重的 CUDA 算子。&lt;&#x2F;p&gt;
&lt;p&gt;显存中的专家缓存也有固定预算。我们一次申请一块显存，在内部管理区间与引用，确保上一轮计算不再使用某段区域之后，才允许复用。早期依赖动态分配的实现能完成短输出，却会在 128、256 token 的生成中耗尽显存；固定预算让长输出的资源占用有了明确上限。&lt;&#x2F;p&gt;
&lt;p&gt;这里的“按需上传”是&lt;strong&gt;主存到显存&lt;&#x2F;strong&gt;，专家已经完整驻留主存。PLE 与 embedding 仍可能发生少量随机读盘，因此整个过程仍有磁盘访问。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;pao-chu-yi-ci-gao-fen-zhi-hou-zhen-zheng-de-nan-ti-cai-chu-xian&quot;&gt;跑出一次高分之后，真正的难题才出现&lt;&#x2F;h2&gt;
&lt;p&gt;最初的单请求实验，速度从 4.770 token&#x2F;s 逐步提高。消除缓存命中时的显存深复制、减少主存中转、优化量化读取，再让专家上传与计算重叠，都带来了收益。&lt;&#x2F;p&gt;
&lt;p&gt;我们还接入了 shared MTP：用一个额外的草稿层提出候选 token，再由完整的 48 层模型验证。候选被接受时，一轮验证可以推进多个位置；被拒绝时，则恢复相关状态与缓存。bench 入口的 MTP 循环已经跑通，但常驻 Node 服务的 MTP 仍在收敛，当前线上配置因此关闭 MTP。&lt;&#x2F;p&gt;
&lt;p&gt;早期测试记录如下。这些都是独立进程的一次测量，属于历史实验结果。&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;场景&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;输入 &#x2F; 输出 token&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;普通解码 token&#x2F;s&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;MTP token&#x2F;s&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;代码题&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;42 &#x2F; 128&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;10.779&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;12.708&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;代码题&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;42 &#x2F; 256&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;10.682&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;11.239&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;中文聊天&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;43 &#x2F; 256&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;11.371&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;11.238&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;12.708 token&#x2F;s 的准确含义是：原始 42-token 模板 prompt、生成 128 token、专家缓存 &lt;strong&gt;5.25 GiB&lt;&#x2F;strong&gt;、MTP 置信度阈值 &lt;strong&gt;0.6&lt;&#x2F;strong&gt;、专家预取 &lt;strong&gt;0&lt;&#x2F;strong&gt;、prefill chunk &lt;strong&gt;64&lt;&#x2F;strong&gt;、MTP 专家缓存 &lt;strong&gt;0.25 GiB&lt;&#x2F;strong&gt;，4 个草稿、Q8G64 KV、独立 bench 进程的一次 MTP 测量，接受率 97.1%。在同一配置、同一题目上，驱动 pinned 主存和草稿 pinned 驻留把结果刷新到 &lt;strong&gt;13.158 token&#x2F;s&lt;&#x2F;strong&gt;，接受率 97.09%；verify 用时从 8.960 秒降到 8.662 秒，draft 用时从 0.758 秒降到 0.713 秒，上传字节数不变。这是当前最高纪录，仍然不是常驻服务速度，也不包含模型预读。&lt;&#x2F;p&gt;
&lt;p&gt;随后做的 MTP 复测使用了 59 token 的中文代码题提示，得到 8.32 token&#x2F;s；提示词与 42 token 基线不同，接受率降到 74.6%，验证阶段上传约 126.7 GiB，因此不能与 12.708 做 A&#x2F;B 比较。45 token 对齐提示的复测仍在进行中。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wei-shen-me-jie-ru-xin-mo-xing-bu-xu-yao-zhong-xie-zheng-ge-yin-qing&quot;&gt;为什么接入新模型不需要重写整个引擎&lt;&#x2F;h2&gt;
&lt;p&gt;Qwen3.8-Flash-Next 的结构很特殊：Hyper-Connection、GDN、PLE、QSA 稀疏注意力和 512 专家路由同时出现。zLLM 仍然能在较短时间内接入，原因是架构把“模型语义”与“设备执行”拆开了。新增模型主要完成三类代码：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;描述模型规格&lt;&#x2F;strong&gt;：在 &lt;code&gt;src&#x2F;model_spec&#x2F;qwen4exp.rs&lt;&#x2F;code&gt; 声明 48 层布局、张量形状、路由常量、QSA&#x2F;PLE&#x2F;HC 参数和 GGUF 元数据。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;装配并实现模型算法&lt;&#x2F;strong&gt;：在 &lt;code&gt;src&#x2F;runtime&#x2F;qwen4exp&#x2F;mod.rs&lt;&#x2F;code&gt; 读取权重、实现前向结构；&lt;code&gt;cpu.rs&lt;&#x2F;code&gt; 提供 CPU reference；&lt;code&gt;cuda.rs&lt;&#x2F;code&gt; 和 &lt;code&gt;cuda_mtp.rs&lt;&#x2F;code&gt; 只组合该模型需要的 CUDA 算子与 MTP 流程。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;接入运行入口&lt;&#x2F;strong&gt;：在配置和 Node 注册处增加 &lt;code&gt;Qwen4ExpNodeModelConfig&lt;&#x2F;code&gt;、&lt;code&gt;Qwen4ExpCudaEngine&lt;&#x2F;code&gt;，复用已有的 chat template、流式输出、取消、stop&#x2F;EOS 和 scheduler 协议。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;这次模型专属的 5 个文件合计约 &lt;strong&gt;2,228 行 Rust&lt;&#x2F;strong&gt;（规格 186 行、装配 588 行、CPU reference 565 行、CUDA 组合 693 行、MTP 196 行）。它们调用的是已有的 GGUF 读取、KV cache、CUDA context&#x2F;stream、固定显存 arena、专家 LRU、量化 kernel、服务协议和生命周期管理；这些基础设施不需要为每个模型复制一份。具体来说，专家搬运与缓存位于 &lt;code&gt;backend&#x2F;cuda&#x2F;expert.rs&lt;&#x2F;code&gt;，Q4&#x2F;Q5&#x2F;Q8 packed kernel、attention、routing 和 tensor kernel 由共享模块提供。新模型增加的是“如何解释权重、如何排列计算”，而不是再造一套推理运行时。&lt;&#x2F;p&gt;
&lt;p&gt;这种拆分也让性能和正确性可以分开检验。CPU reference、张量格式对照、单算子 CUDA 测试和逐 token 输出一致性先回答“算得对不对”；固定 arena、分组 DMA、缓存策略和 MTP 草稿长度再用完整请求测量“跑得快不快”。一次 kernel microbenchmark 变快，不能直接合入主线；必须同时通过 reference 对拍、长输出稳定性和同输入端到端吞吐。13.158 token&#x2F;s 与旧 12.708 的对比中，验收率、轮数和上传字节逐位一致，提升来自 pinned 源上的稳定 overlap 路径；注册普通堆内存的旧快路已经移除。常驻 Node MTP 仍要单独完成正确性与性能验收。&lt;&#x2F;p&gt;
&lt;p&gt;更大的转折发生在服务接入之后。独立进程生成一次就退出，常驻服务却要反复接收请求。运行到第三次、第五次，或者更晚，堆损坏才暴露出来。前一次已经把内存写坏，下一次分配内存时才崩溃，报错的位置未必就是出错的位置。&lt;&#x2F;p&gt;
&lt;p&gt;排查先修复了 PLE 读取时的列宽错误，随后又通过反复对照，把另一类损坏收窄到注册普通堆内存后进行异步 DMA 的直读路径。改用独立的锁页暂存槽后，连续请求通过了，但每次上传多了一次 CPU 内存复制，服务解码从约 6.4 降到了约 4.2 token&#x2F;s。&lt;&#x2F;p&gt;
&lt;p&gt;最后保留的方案，是让专家权重直接读入 &lt;strong&gt;CUDA 驱动分配的锁页主存&lt;&#x2F;strong&gt;，并由运行时持续持有这块内存，再从中直接上传 GPU。它消除了额外的主存复制，也避开了原来的普通堆注册路径。&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;常驻服务的专家上传方案&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;解码速度&lt;&#x2F;th&gt;&lt;th&gt;验证结果&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;旧的注册堆直读&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;约 6.4 token&#x2F;s&lt;&#x2F;td&gt;&lt;td&gt;连续请求发生堆损坏&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;锁页暂存槽中转&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;约 4.2 token&#x2F;s&lt;&#x2F;td&gt;&lt;td&gt;修复后 30 次请求通过&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;驱动分配的锁页主存直读&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;约 6.0–6.4 token&#x2F;s&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;50 次请求及 4 次长生成零失败&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;这组记录说明了服务路径上的性能恢复。它与前面的短提示 benchmark 配置、执行入口不同，不能直接用来计算性能回退比例。修复后的版本仍需建立同条件的 MTP 性能与正确性基线。&lt;&#x2F;p&gt;
&lt;p&gt;锁页内存还有一个运维细节：进程正常退出后，&lt;code&gt;cuMemFreeHost&lt;&#x2F;code&gt; 会把约 95 GiB 交还给驱动的 host 池，操作系统的 &lt;code&gt;MemAvailable&lt;&#x2F;code&gt; 不会立刻恢复；下次分配可以复用，但当前的内存预检可能误拒运行。强制 &lt;code&gt;SIGKILL&lt;&#x2F;code&gt; 则可能留下 NVIDIA 驱动记账泄漏，最终需要重启。部署时应使用可执行清理逻辑的优雅退出，并避免用 &lt;code&gt;pkill -9&lt;&#x2F;code&gt; 回收节点。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;xian-zhi-su-du-de-shi-mei-ge-token-yao-ban-duo-shao-quan-zhong&quot;&gt;限制速度的，是每个 token 要搬多少权重&lt;&#x2F;h2&gt;
&lt;p&gt;大内存解决了“放在哪里”，接下来就要支付搬运成本。&lt;&#x2F;p&gt;
&lt;p&gt;早期代码题的 256-token MTP 测试中，每个输出 token 平均对应约 &lt;strong&gt;632.5 MB 的专家上传&lt;&#x2F;strong&gt;。这台机器的锁页传输探针测得 &lt;strong&gt;11.90 GB&#x2F;s&lt;&#x2F;strong&gt;，按该带宽计算，仅搬运这些数据就需要约 &lt;strong&gt;53.2 毫秒&#x2F;token&lt;&#x2F;strong&gt;。&lt;&#x2F;p&gt;
&lt;p&gt;20 token&#x2F;s 的总时间预算只有 50 毫秒&#x2F;token。仅传输就已经超过预算，还没有算上 GPU 计算、草稿生成和同步。这解释了为什么当时没有达到 20 token&#x2F;s，也解释了为什么继续增加草稿长度没有持续提速。&lt;&#x2F;p&gt;
&lt;p&gt;这笔账针对当时的配置，不能当作最新服务的耗时分解。但它指出了后续方向：提高有用专家的复用，减少重复上传，让搬运与计算更充分地重叠。GPU 利用率再高，也无法抵消关键路径上必须等待的数据传输。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;shi-ji-shi-yong-huan-yao-kan-shou-zi-deng-dai-yu-fu-wu-hui-shou&quot;&gt;实际使用，还要看首字等待与服务回收&lt;&#x2F;h2&gt;
&lt;p&gt;解码速度描述的是开始生成之后的速度。长材料送进模型，首先要经过 prefill，也就是输入处理。接入稀疏 QSA 注意力后，记录中约 &lt;strong&gt;10.6K token 的输入处理耗时为 351.2 秒&lt;&#x2F;strong&gt;。因此，这台机器处理长文档时，首字等待可能以分钟计。&lt;&#x2F;p&gt;
&lt;p&gt;记录中的 65,536 是经过测试的上下文配置上限；它不能被解读为已经完成 65,536-token 满长输入测试。更长的上下文仍有待验证，视觉能力也尚未接入。&lt;&#x2F;p&gt;
&lt;p&gt;最新锁页主存方案还有一个实际运维问题：在该环境中，强制杀死持有大量锁页内存的进程后，出现过驱动未回收内存的现象，最终需要重启机器恢复。常规回收应采用能够执行资源释放的退出流程；原记录尚未验证优雅退出是否能完全消除这一问题。&lt;&#x2F;p&gt;
&lt;p&gt;正确性证据同样有边界。早期 CUDA 输出与自有 CPU reference 做过对照，代码题和中文聊天的 256-token MTP 输出也曾与普通解码逐 token 相同。但独立 llama.cpp 对照在第五个 token 出现分歧，完整 logits 对齐尚未完成。这些验证支持已测场景的工程判断，不能替代模型质量评测。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;lao-ji-qi-de-jia-zhi-bei-ruan-jian-zhong-xin-wa-chu-lai-liao&quot;&gt;老机器的价值，被软件重新挖出来了&lt;&#x2F;h2&gt;
&lt;p&gt;这次接入最有意义的结果，是把一台双路 E5、125 GiB 内存、12 GiB 显存的机器，变成了能够运行完整 Qwen3.8-Flash-Next 文本模型的推理节点。&lt;&#x2F;p&gt;
&lt;p&gt;代价也很具体：专家占用约 77 GB 主存，PCIe 限制生成速度，长输入有明显等待，服务退出还需要继续验证。当前约 6.0–6.4 token&#x2F;s 的服务记录，与单请求 MTP 的 13.158 token&#x2F;s 属于不同入口和口径。&lt;&#x2F;p&gt;
&lt;p&gt;老平台的大内存、消费级显卡的计算能力，以及量化模型的稀疏结构，能够在合适的执行方式下配合起来。把每份权重放到合适的位置，保持压缩编码，管理好异步传输的生命周期，这些软件工作让已有硬件承担了原本看起来超出容量的任务。&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;em&gt;本文依据 zLLM《接入 Qwen3.8-Flash-Next 实录》及其 2026-09-07 后续记录整理。未重新运行 benchmark；历史单请求成绩与最新服务验证分别列示。&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh">
        <title>来自阿布扎比的 K2-Horizon 到底怎么样？M5 实测：会做题，写中文却翻车</title>
        <published>2026-09-06T00:00:00+00:00</published>
        <updated>2026-09-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhuai.tech/blog/k2-horizon-mova/"/>
        <id>https://zhuai.tech/blog/k2-horizon-mova/</id>
        
        <content type="html" xml:base="https://zhuai.tech/blog/k2-horizon-mova/">&lt;p&gt;来自阿布扎比的 K2-Horizon 到底怎么样？让它算一道题，再让它写一篇文章，
得到的是两张很不一样的成绩单。&lt;&#x2F;p&gt;
&lt;p&gt;zLLM 接入 K2-Horizon-MoVA-36B-A4B 后，我在一台 Apple M5、24 GiB 统一内存的 Mac 上，
用 IQ3_XS 量化版本试了两道题，又让它写了两篇中文短文。蜗牛爬井和猫抓老鼠都答对了。
但到了菜市场随笔，正文开始混入英语、日语和阿拉伯语片段，最后因为重复生成而停止。&lt;&#x2F;p&gt;
&lt;p&gt;把温度降到零之后，文章有了改善，却没有完全解决问题。
不过，在评价模型之前还得回答一个更基础的问题：zLLM 为了让它在 Metal 上完整跑起来，
究竟接入了什么？K2-Horizon 并不是换一组矩阵尺寸就能复用普通 Transformer 的模型。
它把稀疏专家放进了 attention 的 value 路径，一层里会出现两套路由。&lt;&#x2F;p&gt;
&lt;p&gt;因此，这篇文章有两条主线：前半部分讲 zLLM 如何把 K2 的 GGUF、MoVA、MoE 和 KV cache
接成完整推理；后半部分再看&lt;strong&gt;会做题与能稳定写好中文之间的距离&lt;&#x2F;strong&gt;。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;xian-ren-qing-lai-yuan-ifm-zong-bu-zai-a-bu-zha-bi&quot;&gt;先认清来源：IFM 总部在阿布扎比&lt;&#x2F;h2&gt;
&lt;p&gt;K2-Horizon 来自 Institute of Foundation Models，简称 IFM。
根据官方介绍，IFM 隶属穆罕默德·本·扎耶德人工智能大学（MBZUAI），总部位于阿联酋阿布扎比，
在巴黎和硅谷设有研究中心。K2 Horizon 系列于 2026 年 9 月 3 日发布。
&lt;a href=&quot;https:&#x2F;&#x2F;ifm.ai&#x2F;about&#x2F;&quot;&gt;IFM 机构介绍&lt;&#x2F;a&gt;；&lt;a href=&quot;https:&#x2F;&#x2F;ifm.ai&#x2F;k2&#x2F;press-release&#x2F;&quot;&gt;官方发布公告&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;因此，把它直接称为“欧洲模型”并不准确。更准确地说，这是一个由总部位于阿布扎比、
在巴黎和硅谷设有研究中心的全球团队推出的模型。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;36b-de-rong-liang-yue-4b-de-ji-huo-liang&quot;&gt;36B 的容量，约 4B 的激活量&lt;&#x2F;h2&gt;
&lt;p&gt;这次运行的模型全名是 &lt;strong&gt;K2-Horizon-MoVA-36B-A4B&lt;&#x2F;strong&gt;。
官方以 36B 总参数、每个 token 激活约 4B 参数的口径命名，标称上下文长度为 524,288 tokens。
这些是官方规格，本次没有测试 512K 长上下文。&lt;a href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;IFM&#x2F;K2-Horizon-MoVA-36B-A4B&quot;&gt;官方模型卡&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;普通 MoE 主要在前馈网络里挑选少量专家。K2-Horizon 的 MoVA 则把专家路由进一步用到
attention 的 value 路径，让注意力中的值表示也能使用稀疏专家。这是这个模型在架构上的一个特点。
&lt;a href=&quot;https:&#x2F;&#x2F;ifm.ai&#x2F;blog&#x2F;k2&#x2F;&quot;&gt;IFM 对 MoVA 的说明&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;本地 GGUF 元数据对应 48 层、2,560 维 hidden、32 个 query heads 和 8 个 KV heads；
value experts 是 64 选 4，前馈部分在前 3 个 dense 层之后使用 100 选 8 的 routed experts，
另有 shared expert。zLLM 已接入相应的分组 RMSNorm、attention output gate、MoVA、MoE
以及完整的 Metal prefill&#x2F;decode 路径，并支持 Q8g64 KV cache。&lt;&#x2F;p&gt;
&lt;p&gt;实际使用的权重文件是：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;K2-Horizon-MoVA-36B-A4B-IQ3_XS.gguf
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;文件大小为 15,695,083,968 bytes，约 &lt;strong&gt;14.62 GiB&lt;&#x2F;strong&gt;。
它是第三方低比特量化文件，内部混用了 IQ3_S、IQ3_XXS、Q3_K、Q6_K 和 F32 张量，
并不是所有参数都存成同一种“三比特”格式。&lt;&#x2F;p&gt;
&lt;p&gt;每个 token 只激活少量专家，减少的是计算量，不代表只需加载 4B 参数的权重。
本次测到的质量也只属于这个 GGUF 文件与运行配置，不能直接当成官方 BF16 模型的表现。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;jie-ru-di-yi-bu-bu-neng-ba-k2-dang-cheng-pu-tong-gqa&quot;&gt;接入第一步：不能把 K2 当成普通 GQA&lt;&#x2F;h2&gt;
&lt;p&gt;zLLM 没有把 K2 的常量塞进 Metal kernel。模型尺寸、层数、norm、attention、专家数量和
Top-K 都集中在独立的 &lt;code&gt;K2HorizonConfig&lt;&#x2F;code&gt;；打开 GGUF 时，再从元数据逐项读取并校验。&lt;&#x2F;p&gt;
&lt;p&gt;这些校验不只看 hidden size。当前实现会确认模型使用 sigmoid expert routing、选中专家权重需要
归一化、每个稀疏层带一个 shared expert、key&#x2F;value head dimension 一致，以及 dense 前缀后
每层都进入 MoE。一个结构不符的 GGUF 会在装载阶段明确失败，而不是带着错误 shape 进入 GPU。&lt;&#x2F;p&gt;
&lt;p&gt;模型编排则放在独立的 &lt;code&gt;runtime&#x2F;k2_horizon&#x2F;&lt;&#x2F;code&gt;。它描述的是 K2 一层真实发生的事情：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;hidden
&lt;&#x2F;span&gt;&lt;span&gt;  → grouped RMSNorm
&lt;&#x2F;span&gt;&lt;span&gt;  → query + attention gate
&lt;&#x2F;span&gt;&lt;span&gt;  → key
&lt;&#x2F;span&gt;&lt;span&gt;  → dense value（前 3 层）或 64 选 4 value experts（后 45 层）
&lt;&#x2F;span&gt;&lt;span&gt;  → RoPE + GQA + KV append
&lt;&#x2F;span&gt;&lt;span&gt;  → attention gate + output projection + residual
&lt;&#x2F;span&gt;&lt;span&gt;  → grouped RMSNorm
&lt;&#x2F;span&gt;&lt;span&gt;  → dense FFN（前 3 层）或 100 选 8 routed experts + shared expert
&lt;&#x2F;span&gt;&lt;span&gt;  → residual
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这里最容易接错的是 value。普通 GQA 的 value 是一次线性投影；K2 从第 4 层开始，先用
sigmoid router 和 bias 从 64 个 value experts 中选 4 个，再计算并加权合并它们的输出。
它输出 8 个 KV heads、每个 128 维，共 1,024 维 value，随后才进入 attention。&lt;&#x2F;p&gt;
&lt;p&gt;FFN 还有另一套独立路由：100 个 routed experts 选 8 个，同时执行 shared expert，
选中权重归一化后再乘 2.5 的缩放因子。两套路由读的是不同权重，服务的是不同数据流，
不能因为它们都叫“专家”就合并成一个通用分支。&lt;&#x2F;p&gt;
&lt;p&gt;第三方 GGUF 把很小但敏感的 MoVA router 量化成了 IQ3_S。zLLM 装载时只把这部分局部解码为
F32 并常驻，让路由打分保持稳定；大块 value expert 权重仍保留量化格式交给 Metal 直接计算。
这个处理没有放宽整个权重层的契约，也避免为了一个小 router 展开整组专家权重。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;jie-ru-di-er-bu-iq3-xs-bu-shi-yi-chong-kernel&quot;&gt;接入第二步：IQ3_XS 不是一种 kernel&lt;&#x2F;h2&gt;
&lt;p&gt;文件名写着 IQ3_XS，真正打开张量目录后却能看到五种存储类型：322 个 IQ3_S、
240 个 IQ3_XXS、3 个 Q3_K、1 个 Q6_K 和 232 个 F32 张量。
一个只实现“IQ3”的入口远远不够。&lt;&#x2F;p&gt;
&lt;p&gt;为完整执行这个文件，Metal 路径需要处理普通量化 GEMV、gate&#x2F;up 融合、按专家编号索引的
gate&#x2F;up&#x2F;down、MoVA 的 64 选 4 value 计算，以及 250,624 词表的 Q6_K lm_head。
这些 kernel 直接读取 packed GGUF 权重；如果每生成一个 token 都先全量反量化，
14.62 GiB 文件带来的容量和带宽收益就会被破坏。&lt;&#x2F;p&gt;
&lt;p&gt;prefill 与 decode 也不能硬套一个执行形态。prefill 同时处理多行 token，先完成批量路由，
再按 expert 分组执行；decode 每次只有一行，走设备常驻的 Top-K 结果和 indexed expert kernel。
在 45 个稀疏层里，每层有 100 个 FFN experts，一共 4,500 组 expert 权重，约 10.00 GiB。
本次零配置运行会在加载阶段把它们预载到统一内存可见的 Metal 资源中，避免生成过程中逐层读盘。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;jie-ru-di-san-bu-24-gib-li-huan-yao-gei-kv-liu-wei-zhi&quot;&gt;接入第三步：24 GiB 里还要给 KV 留位置&lt;&#x2F;h2&gt;
&lt;p&gt;“权重文件只有 14.62 GiB”不等于剩余内存都能拿来做上下文。模型运行还需要 expert resident
资源、activation、scratch、Metal 对象和系统本身。zLLM 启动时读取模型结构与机器预算，
再计算每 token 的 KV 成本。本机日志给出的结果是：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;权重 14.62 GiB
&lt;&#x2F;span&gt;&lt;span&gt;KV 预算 0.96 GiB
&lt;&#x2F;span&gt;&lt;span&gt;KV 每 token 约 101,376 bytes
&lt;&#x2F;span&gt;&lt;span&gt;最终上下文 9,216 tokens
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;K2 在 24 GiB 机器上采用单独的四分之三工作集上限，并至少给系统留 2 GiB 余量。
KV cache 默认使用 Q8g64：每 64 个值共享一组量化参数。短上下文使用 direct attention；
超过 256 tokens 后切到 split-KV，把历史分段并行计算，再归并局部结果。
这也是为什么模型标称 524,288 tokens，而这台机器的零配置入口只选择 9,216 tokens。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wan-zheng-decode-zen-me-ya-jin-yi-ci-replay&quot;&gt;完整 decode 怎么压进一次 replay&lt;&#x2F;h2&gt;
&lt;p&gt;普通 decode 一轮要走完 48 层。早期 profile 里，单个 token 会产生约 1,100 个 Metal commands；
如果每轮都由 CPU 重建和提交这张图，调度成本会不断重复。&lt;&#x2F;p&gt;
&lt;p&gt;zLLM 为 K2 单独实现了完整单 token decode replay。第一次记录从已上传的 token embedding、48 层 attention、
两套 expert 路由、MoE、final norm、lm_head 到 argmax 的执行序列；之后只更新 token、position
和 KV 写入位置，再重放同一计划。Q8 attention 的 replay 会根据当前位置选择 direct 或 split-KV
pipeline，因此跨过 256-token 边界后仍使用正确的长上下文路径。&lt;&#x2F;p&gt;
&lt;p&gt;这里有一个容易被性能数字掩盖的限制：greedy 生成可以重放整轮；使用 temperature&#x2F;top-p
随机采样时，需要取得 logits 并维护采样状态，当前 K2 路径不会进入同一 replay 快路。
所以后面的官方采样写作测试，不能拿 greedy 的约 30 tok&#x2F;s 直接类比。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;neng-chu-zi-zhi-qian-xian-zheng-ming-shu-zhi-lian-lu-cheng-li&quot;&gt;“能出字”之前，先证明数值链路成立&lt;&#x2F;h2&gt;
&lt;p&gt;接入验收没有停在“模型加载成功”。现有记录包含几组分层验证：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;K2 的 Q8 direct&#x2F;split KV append 与 reference 对拍通过；&lt;&#x2F;li&gt;
&lt;li&gt;BF16 split-KV 曾发现 vectorized kernel 参数绑定回归，修复后通过 CPU oracle；&lt;&#x2F;li&gt;
&lt;li&gt;Q6_K GEMV 与解码权重后的 dot product 对拍通过；&lt;&#x2F;li&gt;
&lt;li&gt;250,624 词表下，Metal top-p sampling 用多个随机点与 CPU reference 逐点一致；&lt;&#x2F;li&gt;
&lt;li&gt;完整真实权重完成 load、prefill、decode、Q8 KV 和 replay 端到端运行。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;同一组四道短题在 low&#x2F;greedy 设置下，zLLM 与 llama.cpp 对同一个 GGUF 给出了逐题相同的错误答案；
切到 high reasoning 并给足预算后，既有 llama.cpp 记录在 3 个随机种子中得到 12&#x2F;12 正确。
这个对照不能证明所有数值绝对一致，但它排除了“那四个答案只在 zLLM 上算错”的简单归因。&lt;&#x2F;p&gt;
&lt;p&gt;console 还踩过一个比 kernel 更隐蔽的坑：通用入口曾显式发送 &lt;code&gt;enable_thinking=false&lt;&#x2F;code&gt;。
K2 的 chat template 会把它解释成一个空思考围栏，相当于强制模型直接回答，输出容易出现多语种混杂。
修复后的入口不替 K2 决定这个字段，让模板保持默认 high thinking；MiniCPM5 和 Qwen
仍按各自已经验证的设置处理。模型支持不止是矩阵乘法，模板语义同样属于推理正确性。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;xian-zai-pao-duo-kuai-ping-jing-zai-na-li&quot;&gt;现在跑多快，瓶颈在哪里&lt;&#x2F;h2&gt;
&lt;p&gt;相同 55-token prompt、greedy、256-token completion 的既有记录中，llama.cpp 使用 F16 KV，
prefill 为 0.322 秒，decode 为 7.800 秒，即 32.691 tok&#x2F;s；zLLM 使用 Q8 KV，最好记录的
prefill 为 5.999 秒，decode 为 8.623 秒，即 &lt;strong&gt;29.690 tok&#x2F;s&lt;&#x2F;strong&gt;，慢 9.18%。&lt;&#x2F;p&gt;
&lt;p&gt;这不是一场完全同配置的 KV 对照，但足以说明当前状态：decode 已接近，短 prompt prefill
仍明显落后。单 token profile 中，两套 sigmoid router——100 选 8 的 FFN 路由和 64 选 4 的
value 路由——排在 GPU 时间前列；256 tokens 之后的 split-KV 和 replay 提交成本也还有空间。&lt;&#x2F;p&gt;
&lt;p&gt;最后一轮压力测试曾因机器热降频，把直通 decode 从约 30 ms&#x2F;token 拉到 80–100 ms&#x2F;token。
因此文章保留降频前的可复现数据，不拿过热后的瞬时结果代表正常性能。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;xian-gei-zu-si-kao-yu-suan-zai-kan-da-an&quot;&gt;先给足思考预算，再看答案&lt;&#x2F;h2&gt;
&lt;p&gt;测试采用 high 思考档，temperature 为 1.0，top_p 为 0.95，与官方推荐一致。
每个请求的上下文上限为 9,216 tokens，最多生成 4,096 tokens；问题之间不共享对话历史。
&lt;a href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;IFM&#x2F;K2-Horizon-MoVA-36B-A4B#best-practices&quot;&gt;官方推荐设置&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;思考档位不是界面上的显示选项。本地模板会根据档位改变提示中的思考标记；
关闭思考时，还会注入一个空的思考围栏。拿一个仍在思考、却被几十个 token 上限截断的输出评分，
很容易把“还没答完”误写成“答错了”。&lt;&#x2F;p&gt;
&lt;p&gt;这里两道题都生成到了正常结束：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;题目&lt;&#x2F;th&gt;&lt;th&gt;正确答案&lt;&#x2F;th&gt;&lt;th&gt;实测答案&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;生成 token 数，含思考&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;蜗牛白天爬 3 米、夜里滑 2 米，爬出 10 米深的井需要几天？&lt;&#x2F;td&gt;&lt;td&gt;第 8 天&lt;&#x2F;td&gt;&lt;td&gt;第 8 天&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1,344&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;3 只猫 3 分钟抓 3 只老鼠，9 只猫抓 9 只老鼠需要多久？&lt;&#x2F;td&gt;&lt;td&gt;3 分钟&lt;&#x2F;td&gt;&lt;td&gt;3 分钟&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;700&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;蜗牛题的关键是最后一天爬出后不再下滑。猫抓老鼠的关键是工作并行，猫与老鼠数量同时增加三倍，
时间不变。模型最终都处理对了。&lt;&#x2F;p&gt;
&lt;p&gt;但第一道题的解释里已经出现了这样的句子：&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;每天 daytime 爬升 3 米， nighttime 回滑 2 米&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;后面还有“白天下爬到”这样的病句。答案正确，并没有让说明文字自动变得自然。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;yi-xie-cai-shi-chang-wen-ti-jiu-ming-xian-liao&quot;&gt;一写菜市场，问题就明显了&lt;&#x2F;h2&gt;
&lt;p&gt;第一篇写作测试要求：&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;请写一篇400至600字的中文生活随笔，题目《雨停之后的菜市场》。通过具体人物、动作、声音和气味组织文章，语言自然克制，不写提纲，不解释写作过程，直接给出标题和正文。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;提示要求全程使用中文，没有任何外语写作要求。模型给出的正文开头却是：&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;雨刚停，菜市场还不全是人。薄荷黑布遮在丑毛さらに码头上，粪水从竹筐的缝隙里滴着Becoming一条条银白色的短河。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;&lt;code&gt;さらに&lt;&#x2F;code&gt; 是日语，&lt;code&gt;Becoming&lt;&#x2F;code&gt; 是英语。后面继续出现 &lt;code&gt;Granny&lt;&#x2F;code&gt;、&lt;code&gt;へえ&lt;&#x2F;code&gt; 和 &lt;code&gt;السجائر&lt;&#x2F;code&gt;，
末尾停在 &lt;code&gt;ポリ袋ákááááááááá&lt;&#x2F;code&gt;，运行时给出的结束原因是 &lt;code&gt;repetition&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;p&gt;这已经超过了“有一点翻译腔”的程度。即使把外语删掉，句子也没有清楚的人物、动作与空间关系。
按只统计正文汉字的口径，最终只有 88 字，远没有完成要求的随笔。&lt;&#x2F;p&gt;
&lt;p&gt;第二篇换成说明性短文，题目是《小模型能做题，就能写好文章吗？》，
要求区分答案正确、推理可靠和语言自然，并给出一个具体例子。&lt;&#x2F;p&gt;
&lt;p&gt;这一次有了完整段落，但正文仍然写出：&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Such表述语言自然。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;最后把推理过程写成一段连贯的文字 describing the solving journey, 语言自然。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;结尾甚至出现：&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;再 fingernail 推理的透明度和语言的自然度。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;&lt;code&gt;fingernail&lt;&#x2F;code&gt; 在这里没有合理语义。问题还不只在词汇：文章把“语言自然”说成文字创作专属，
又试图用一道方程的解法证明写作能力，论证范围也没有把握好。&lt;&#x2F;p&gt;
&lt;p&gt;这篇的结束原因是 &lt;code&gt;stop&lt;&#x2F;code&gt;。它提醒我们：&lt;strong&gt;正常停止，只代表生成过程结束；文章是否合格，还要读正文。&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wen-du-jiang-dao-ling-neng-gai-shan-duo-shao&quot;&gt;温度降到零，能改善多少？&lt;&#x2F;h2&gt;
&lt;p&gt;我保留 high 思考档，用完全相同的两条写作提示，把 temperature 改成 0，各再运行一次。&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;文体&lt;&#x2F;th&gt;&lt;th&gt;temperature=1.0&lt;&#x2F;th&gt;&lt;th&gt;temperature=0&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;菜市场随笔&lt;&#x2F;td&gt;&lt;td&gt;多语种混杂，重复终止；正文 88 个汉字&lt;&#x2F;td&gt;&lt;td&gt;正常结束，489 个汉字；仍夹杂英语&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;评价维度短文&lt;&#x2F;td&gt;&lt;td&gt;正常结束，481 个汉字；有无意义英语插入&lt;&#x2F;td&gt;&lt;td&gt;正常结束，652 个汉字；中文明显改善，但超出篇幅要求&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;这里的字数只统计标题后正文中的汉字，不含标点、拉丁字母与 Markdown 标记。&lt;&#x2F;p&gt;
&lt;p&gt;零温度的菜市场随笔有了完整场景，但仍写出 &lt;code&gt;freshly 捕来的鲫鱼&lt;&#x2F;code&gt;、&lt;code&gt;Somehow 却不显得刺鼻&lt;&#x2F;code&gt;、
&lt;code&gt;女孩 grabs 住鱼&lt;&#x2F;code&gt;。声音描写也很奇怪：模型把议价声写成“咔嚓”。&lt;&#x2F;p&gt;
&lt;p&gt;评价维度短文改善得更多，没有再出现前一轮那种无意义的外语插入。
文中的 &lt;code&gt;AI&lt;&#x2F;code&gt; 是常见缩写，不应算作语言失控。不过，它仍没有守住 400–600 字的要求。&lt;&#x2F;p&gt;
&lt;p&gt;因此，这轮结果不能简单概括成“它不会中文”。更准确的是：
&lt;strong&gt;在这组提示和当前运行环境下，中文写作的完成度、语言一致性与指令遵循还不稳定，文体和生成设置都会影响体验。&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;这里还留着一个工程问题：异常来自哪里？本次没有用相同提示对照 BF16 权重，
也没有在另一个引擎上重跑这两篇文章，因此无法分开量化损失、引擎数值、模板和模型自身的影响。
降低温度还会改变当前 zLLM K2 路径的采样状态与 replay decode 的启用资格，
这组对照也不能用来做严格的单一因素归因。&lt;&#x2F;p&gt;
&lt;p&gt;它足以说明当前这套本地配置的使用体验，却不足以给整个模型家族下结论。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;zai-zllm-zhong-yun-xing&quot;&gt;在 zLLM 中运行&lt;&#x2F;h2&gt;
&lt;p&gt;从 zLLM 仓库目录使用已经编译好的 release 程序，权重路径替换成自己的位置：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;.&#x2F;target&#x2F;release&#x2F;zllm-metal &lt;&#x2F;span&gt;&lt;span&gt;\
&lt;&#x2F;span&gt;&lt;span&gt;  &#x2F;Volumes&#x2F;ORICO&#x2F;models&#x2F;K2-Horizon-MoVA-36B-A4B-IQ3_XS.gguf
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;--release&lt;&#x2F;code&gt; 属于 Cargo 的构建参数，不需要传给 &lt;code&gt;zllm-metal&lt;&#x2F;code&gt;。
加载完成后直接输入问题，&lt;code&gt;&#x2F;reset&lt;&#x2F;code&gt; 清空对话，&lt;code&gt;&#x2F;exit&lt;&#x2F;code&gt; 退出。
本机启动时自动选择了 9,216-token 上下文；官方标称的 512K 不等于这台机器的可用容量。&lt;&#x2F;p&gt;
&lt;p&gt;这次接入让 zLLM 多了一条可运行的 MoVA 模型路径，真实写作又给它补上了能力边界。
以后再看一个模型的做题成绩，我会顺手让它写一篇具体的生活随笔：
没有唯一答案的任务，往往更容易看清语言是否连贯、细节是否成立，以及一篇文章能不能真正交到读者手里。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ce-shi-ji-lu&quot;&gt;测试记录&lt;&#x2F;h2&gt;
&lt;p&gt;测试日期为 2026 年 9 月 6 日。环境为 Apple M5、24 GiB，GGUF IQ3_XS，Metal，Q8g64 KV cache。
共六次独立请求，temperature=1.0 的四次请求未固定随机种子；temperature=0 的两次为写作对照。
生成预算均为 4,096 tokens，包含思考与回答。本次采样生成不作为 replay 性能测试。&lt;&#x2F;p&gt;
&lt;p&gt;正式运行入口是 &lt;code&gt;zllm-metal&lt;&#x2F;code&gt;，传入 K2 的 GGUF 路径后自动识别并加载模型。
质量测试复用了 &lt;code&gt;zllm-metal&lt;&#x2F;code&gt; 的正式推理路径和已有 release 构建，没有重新编译引擎。
记录中的源码 HEAD 仅表示测试时的工作区版本，不等于已核实的二进制构建提交。
运行时返回合并文本流，未单独拆分思考与回答；本文只从明确标题后的正文摘录写作内容，
按末尾的最终答案核验做题结果，没有把思考中的英语算成中文正文错误。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;&#x2F;data&#x2F;k2-horizon-20260906&#x2F;results.json&quot;&gt;完整提示、六次原始输出、结束原因和运行版本指纹（JSON）&lt;&#x2F;a&gt;
可供复核。另附&lt;a href=&quot;&#x2F;data&#x2F;k2-horizon-20260906&#x2F;high.log&quot;&gt;采样日志&lt;&#x2F;a&gt;、&lt;a href=&quot;&#x2F;data&#x2F;k2-horizon-20260906&#x2F;greedy.log&quot;&gt;零温度日志&lt;&#x2F;a&gt;、
&lt;a href=&quot;&#x2F;data&#x2F;k2-horizon-20260906&#x2F;high.yaml&quot;&gt;采样配置&lt;&#x2F;a&gt;和&lt;a href=&quot;&#x2F;data&#x2F;k2-horizon-20260906&#x2F;greedy.yaml&quot;&gt;零温度配置&lt;&#x2F;a&gt;。
JSON 已将日志中的换行还原，模型文字保持原样。配置含测试机的路径，跨机器复现时需要修改。&lt;&#x2F;p&gt;
&lt;p&gt;算子验证与先前的性能记录见&lt;a href=&quot;&#x2F;data&#x2F;k2-horizon-20260906&#x2F;prior-integration-report.md&quot;&gt;既有接入报告&lt;&#x2F;a&gt;，
其中的数据与这次写作测试分别记录。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh">
        <title>双8卡(AMD W7900D)机器 如何跑 GLM5.3</title>
        <published>2026-09-05T00:00:00+00:00</published>
        <updated>2026-09-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhuai.tech/blog/rocm-glm53-single-pipeline/"/>
        <id>https://zhuai.tech/blog/rocm-glm53-single-pipeline/</id>
        
        <content type="html" xml:base="https://zhuai.tech/blog/rocm-glm53-single-pipeline/">&lt;p&gt;16 张显卡，总共 768 GiB 显存，运行 GLM-5.3，最初却只有 &lt;strong&gt;4.08 token&#x2F;s&lt;&#x2F;strong&gt;：每生成一个 token，要等约 245 毫秒。&lt;&#x2F;p&gt;
&lt;p&gt;显存足够把模型装下，并不意味着算力就能顺利叠加。我们的两台机器各有 8 张 48 GiB 的 W7900D，节点内走 PCIe 4.0，节点间走 TCP，没有可供这条执行路径使用的 XGMI 高速互联。模型必须分布在多张卡上，但每增加一次跨卡协作，都可能给单个 token 增加一次等待。&lt;&#x2F;p&gt;
&lt;p&gt;这轮 zLLM 的 ROCm 优化，就是从这个约束出发：先减少层内通信，再优化每张卡上的实际工作，最后通过 MTP 推测解码减少完整模型的执行轮数。&lt;&#x2F;p&gt;
&lt;p&gt;直接核对 Amd-1 的结果文件后，单卡 stage、不开 MTP 的完整测试为 &lt;strong&gt;11.321 token&#x2F;s&lt;&#x2F;strong&gt;；最新双卡算子并行版本三轮达到 &lt;strong&gt;13.842–14.043 token&#x2F;s，中位数 13.968，约 14 token&#x2F;s&lt;&#x2F;strong&gt;。相比最初约 4.08 token&#x2F;s，单卡 stage 路线的吞吐提高到约 2.77 倍。&lt;&#x2F;p&gt;
&lt;p&gt;旧双卡版本叠加 MTP 的完整测试还记录了 &lt;strong&gt;25.275 token&#x2F;s&lt;&#x2F;strong&gt;。它证明组合路线已经运行过，但不能据此认定最新约 14 token&#x2F;s 的版本加上 MTP 也已经完成验收。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;数据说明：本文主结果来自 Amd-1 上的 JSON 与运行日志。测试语料文件名为 &lt;code&gt;50k-allgpu.txt&lt;&#x2F;code&gt;，实际请求输入为 46,152–46,158 token，输出均为 1,024 token。Decode 速度按首个文本事件之后的 1,023 个生成 token 除以剩余生成时间计算；下文“50K”沿用语料名称，算子 profile 中的 50K 则是其工作量口径。不同提交、nonce 与 MTP 配置的结果不能视为同版本严格 A&#x2F;B。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;xian-jue-ding-shu-ju-zai-na-li-zai-jue-ding-zen-me-bing-xing&quot;&gt;先决定数据在哪里，再决定怎么并行&lt;&#x2F;h2&gt;
&lt;p&gt;单路自回归生成有一个硬约束：下一个 token 必须等待当前 token 完成采样。&lt;&#x2F;p&gt;
&lt;p&gt;对这次测试的 78 层模型，一个 token 要依次经过所有层、最终输出投影（LM Head）和采样，才能开始下一轮。把这些层放到 16 张卡上，只是把执行链分段，不能自动让同一个 token 同时穿过所有层。&lt;&#x2F;p&gt;
&lt;p&gt;我们的起点是让每张卡负责一段&lt;strong&gt;连续、完整的层&lt;&#x2F;strong&gt;。权重、KV Cache、稀疏注意力索引状态、专家权重和临时工作区都留在本地，只有层段边界的 hidden activation 需要交接：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;&#x2F;images&#x2F;rocm-glm53&#x2F;execution-map.svg&quot;&gt;&lt;img src=&quot;&#x2F;images&#x2F;rocm-glm53&#x2F;execution-map.svg&quot; alt=&quot;GLM-5.3 详细执行图：硬件容量、16-stage 流程、DSA&#x2F;MLA&#x2F;MoE 数据规模，以及流量与延迟比例&quot; &#x2F;&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;点击图片可放大查看。图中数据量与延迟按各自尺度绘制；层内 profile 含嵌套 scope，不应相加为完整 token 的耗时比例。&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;这里的“单卡 stage”指每段由一张卡执行，整个模型仍然使用 16 张卡。&lt;&#x2F;p&gt;
&lt;p&gt;此前尝试过更细的双卡协作：一层中反复交换 query、selection 和专家计算的 partial，每层有 4–6 次交接。数据量未必大，但每次交换后的 event 等待都会把两张卡的进度差带入关键路径。重复 78 层后，局部计算并行省下的时间，被协作成本抵消。&lt;&#x2F;p&gt;
&lt;p&gt;因此，单请求 decode 的近似账本应该是：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;每 token 延迟
&lt;&#x2F;span&gt;&lt;span&gt;  ≈ 所有 stage 的串行执行时间之和
&lt;&#x2F;span&gt;&lt;span&gt;  + stage 交接
&lt;&#x2F;span&gt;&lt;span&gt;  + LM Head、采样和回环
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这也决定了如何理解“负载均衡”。多个请求或多个 prefill chunk 可以重叠时，最慢 stage 会限制流水线吞吐；单请求逐 token decode 则首先取决于整条链的总时间。调整层边界只有在减少等待、改善执行效率或缓解资源限制时，才会带来收益。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;cong-4-08-dao-11-32-shi-jian-sheng-zai-liao-na-li&quot;&gt;从 4.08 到 11.32，时间省在了哪里&lt;&#x2F;h2&gt;
&lt;p&gt;归档测试使用 GLM-5.3 UD-IQ4_XS GGUF、78 层、Q8G64 KV，使用“50K”语料（实际约 46.15K token），生成 1,024 token，关闭 MTP、DSpark 和旧双卡协作路径。&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;阶段&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Decode（token&#x2F;s）&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;每 token 延迟&lt;&#x2F;th&gt;&lt;th&gt;主要变化&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;初始单流水线&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4.084&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;244.8 ms&lt;&#x2F;td&gt;&lt;td&gt;完成拓扑调整，单行算子仍待优化&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;单卡 stage（非并行）&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;11.321&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;约 88.3 ms&lt;&#x2F;td&gt;&lt;td&gt;专用 IQ kernel、MLA 优化、Q8 shared expert、W8 直读与调度优化&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;按 11.321 token&#x2F;s 换算，吞吐约为最初的 2.77 倍，单 token 延迟减少约 63.9%。下面展开其中最有效的几项变化。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;decode-zhi-you-yi-xing-bu-neng-zhi-jie-zhao-ban-da-ju-zhen-cheng-fa&quot;&gt;Decode 只有一行，不能直接照搬大矩阵乘法&lt;&#x2F;h3&gt;
&lt;p&gt;Prefill 一次处理多行输入，适合用矩阵乘法摊薄权重读取成本。普通 decode 一次只有一行，更接近矩阵乘向量（GEMV）：每读入一大批权重，只计算一个 token 的输出。&lt;&#x2F;p&gt;
&lt;p&gt;这时，权重读取、反量化指令、线程映射和 kernel 启动开销都很显眼。我们针对 gfx1100 直接编写 HIP&#x2F;ROCm 算子，为 IQ3_S、IQ4_XS 和 Q8&#x2F;W8A16 建立专用路径。&lt;&#x2F;p&gt;
&lt;p&gt;例如，IQ3_S 的 gate&#x2F;up 使用 8-lane subgroup，再通过 &lt;code&gt;u16&lt;&#x2F;code&gt; 宽读取降低加载开销。在布局和输出 bit pattern 不变的前提下，探针时间从约 207–211 微秒降到 188–190 微秒。&lt;&#x2F;p&gt;
&lt;p&gt;但相似代码不保证相似收益。IQ4_XS down 当时的有效带宽已约为 465 GB&#x2F;s；移植另一种码本读取写法，时间反而从 102 微秒退化到 144 微秒。这个实验说明，在当前 shape 上，继续围绕码本缓存做文章并没有抓住主要瓶颈。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;zui-zhi-de-bao-liu-de-yi-ci-you-hua-bie-ba-liang-hua-quan-zhong-zhong-xin-cheng-da&quot;&gt;最值得保留的一次优化：别把量化权重重新撑大&lt;&#x2F;h3&gt;
&lt;p&gt;MLA，也就是多头潜在注意力，是每层都会执行的聚合热点。其中 &lt;code&gt;kv_b&lt;&#x2F;code&gt; 的驻留格式曾经浪费了大量带宽。&lt;&#x2F;p&gt;
&lt;p&gt;旧路径把 W8 权重先解成 F16，再以 F32 dense 形式常驻，方便后续 absorb&#x2F;PV 计算使用。磁盘上的权重虽然量化了，执行时却重新膨胀：相关阶段每层需要读取约 58.6 MB。&lt;&#x2F;p&gt;
&lt;p&gt;新路径让算子直接消费 W8 权重和 scale，流量降到约 14.7 MB，减少到原来的四分之一。完整测试从约 9.76 提升到 10.50&#x2F;10.28 token&#x2F;s，两轮平均收益约 6.5%。&lt;&#x2F;p&gt;
&lt;p&gt;这是比“再换一个 tile”更直接的优化：&lt;strong&gt;如果热路径最终还是读取 F32，量化就只节省了模型文件大小，没有充分兑现执行时的带宽优势。&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;其他 MLA 优化也有效。PV 连续加载向量化后，设备 scope 从约 0.484 降到 0.439 ms&#x2F;layer，完整模型收益约 2.5%–3.5%；selected scan 把 QK 中主要由一个 wave 完成的工作分给 8 个 wave，partial 与 merge 合计从约 90.6 降到 57–59 微秒。&lt;&#x2F;p&gt;
&lt;p&gt;不过，这些局部节省不会自动等额转化成 token 延迟下降。下一部分解释为什么。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;kernel-kuai-liao-zheng-tiao-lian-que-ke-neng-geng-man&quot;&gt;Kernel 快了，整条链却可能更慢&lt;&#x2F;h2&gt;
&lt;p&gt;DSA 稀疏注意力索引器要从历史位置中选出后续注意力计算使用的集合。在 50K 场景中，score 计算约 91 微秒，已经达到 profile 估算计算上限的约 68%；radix select 的串行尾部更值得处理。&lt;&#x2F;p&gt;
&lt;p&gt;原实现最后两级由一个线程块串行重扫。拆成多级 kernel 后，相关 kernel 时间从约 105 降到 96 微秒，确实更快了。但新增两次各约 12 微秒的 launch 后，完整 select 仍约 139 微秒，没有得到预期的端到端收益。&lt;&#x2F;p&gt;
&lt;p&gt;这些实验使我们的优化单位逐渐变大：先看一个 kernel，再看完整算子，最后看整个 stage 在 token 关键路径上的耗时。&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;主要限制&lt;&#x2F;th&gt;&lt;th&gt;本轮代表&lt;&#x2F;th&gt;&lt;th&gt;有效方向&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;权重读取带宽&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;q_b&lt;&#x2F;code&gt;、&lt;code&gt;kv_b&lt;&#x2F;code&gt;、MoE 权重流&lt;&#x2F;td&gt;&lt;td&gt;压缩驻留、连续宽读取、共享权重读取&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;计算与反量化指令&lt;&#x2F;td&gt;&lt;td&gt;DSA score、IQ 解码&lt;&#x2F;td&gt;&lt;td&gt;调整 wave 映射、缩短指令依赖&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;同步与调度&lt;&#x2F;td&gt;&lt;td&gt;radix 多级选择、launch、双卡 join&lt;&#x2F;td&gt;&lt;td&gt;减少交接、融合依赖链、消除 bridge copy&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;下图把更新后的原始记录中的带宽数据展开：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;&#x2F;images&#x2F;rocm-glm53&#x2F;bandwidth-roof.svg&quot;&gt;&lt;img src=&quot;&#x2F;images&#x2F;rocm-glm53&#x2F;bandwidth-roof.svg&quot; alt=&quot;主要算子的有效带宽与计算、同步瓶颈分类&quot; &#x2F;&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;图中的有效带宽来自权重字节数除以 kernel 时间，是分析指标；本轮没有取得可用的底层显存流量与 occupancy counter，不能把它当成硬件计数器实测。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;graph-shi-yan-qi-dong-geng-kuai-wan-zheng-qing-qiu-fan-er-man-liao-4-5&quot;&gt;Graph 实验：启动更快，完整请求反而慢了 4%–5%&lt;&#x2F;h2&gt;
&lt;p&gt;HIP Graph 的思路是把重复执行的 kernel 依赖链预先记录下来，后续直接 replay，减少逐个提交 kernel 的开销。对于单行 decode 中大量短 kernel，这个方向很自然。我们的实验也确实测到了启动收益，但接入生产路径后，收益没有兑现。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;tan-zhen-you-xiao-duan-dao-duan-wu-xiao&quot;&gt;探针有效，端到端无效&lt;&#x2F;h3&gt;
&lt;p&gt;在 64 个小 kernel 组成的探针中，eager 设备时间线约为 0.76–0.80 ms，预热后的静态 Graph replay 约为 0.23–0.27 ms。折算到每个节点，大约从 12 微秒降到 4 微秒。这说明 Graph 能压缩提交间隙；它并没有让 kernel 内部的计算和权重读取变快。&lt;&#x2F;p&gt;
&lt;p&gt;但在修复正确性之后，5 节点 MoE Graph 的完整请求对照结果如下：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;同二进制、50K 输入 × 1,024-token decode&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;第一轮&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;第二轮&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Graph 关闭&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;9.509 token&#x2F;s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;9.607 token&#x2F;s&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Graph 开启&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;9.119 token&#x2F;s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;9.124 token&#x2F;s&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;吞吐变化&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;−4.1%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;−5.0%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;这是历史 Graph 专项实验，修复提交为 &lt;code&gt;b4433700&lt;&#x2F;code&gt;，结果记档于 &lt;code&gt;51a4f30d&lt;&#x2F;code&gt;。这些数值只用于说明开关 Graph 的同版本对照，&lt;strong&gt;本文最新单卡 stage 主口径为已核对的 11.321 token&#x2F;s&lt;&#x2F;strong&gt;，不能把这次历史回退比例直接套到最新版上。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;wei-shen-me-sheng-xia-launch-huan-shi-yu-liao&quot;&gt;为什么省下 launch，还是亏了&lt;&#x2F;h3&gt;
&lt;p&gt;关键在于固定地址带来的桥接成本。这个实现的 Graph 节点记录了设备指针，而上游输出需要搬到 Graph 使用的固定槽位。每层因此多出 &lt;strong&gt;3 次设备到设备的 D2D 拷贝&lt;&#x2F;strong&gt;，以及这些拷贝对应的 host 提交开销。&lt;&#x2F;p&gt;
&lt;p&gt;实验只包住了 5 个节点，能够摊薄的 launch 间隙有限；新增的桥接工作却每层都要执行。按单节点探针的差额粗算，5 个节点仅对应约 40 微秒的潜在节省，这只是量级估计，并不是生产 Graph 的净收益。真正的账要包含桥接、replay 和后续依赖等待，完整测试已经给出了负收益答案。&lt;&#x2F;p&gt;
&lt;p&gt;同时，小段 MoE Graph 没有覆盖 attention 中较长的串行链，也不会减少专家权重读取、反量化计算或跨卡等待。它只优化了关键路径上的一小部分，却为接入这一小部分增加了固定成本。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;zheng-que-xing-wen-ti-xiu-hao-liao-xing-neng-wen-ti-reng-ran-cun-zai&quot;&gt;正确性问题修好了，性能问题仍然存在&lt;&#x2F;h3&gt;
&lt;p&gt;更早的版本还出现过死循环、NaN 和非法访存。根因是 Graph 构建期间使用的中间 buffer 在构建结束后被释放回内存池，但节点仍保留旧设备指针。内存池复用这些地址后，replay 就可能读写其他对象的内存。&lt;&#x2F;p&gt;
&lt;p&gt;独立探针没有复现，是因为探针一直持有相关 buffer；生产运行的资源生命周期不同。修复让 Graph 对象持有所需 buffer，保证 replay 期间地址仍然有效。强加同步曾短暂掩盖症状，但长上下文测试仍失败，因为同步无法修复悬垂指针。&lt;&#x2F;p&gt;
&lt;p&gt;上表的负收益是在修复后测得的。因此，“能稳定 replay”和“能降低端到端延迟”是两个分别需要验收的结果。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;shen-me-tiao-jian-xia-zhi-de-zai-shi&quot;&gt;什么条件下值得再试&lt;&#x2F;h3&gt;
&lt;p&gt;这次实验后，生产路径保持 &lt;code&gt;decode_graph=false&lt;&#x2F;code&gt;。后续值得验证的是覆盖更长依赖链的整层 Graph：上游直接写入固定 workspace，消除额外 bridge copy，同时由 Graph 明确持有其引用的资源，避免工作区扩容或内存池复用使旧指针失效。&lt;&#x2F;p&gt;
&lt;p&gt;覆盖范围扩大后，仍需在同一二进制、相同输入和输出正确性要求下，关闭 profiler 测完整请求。&lt;strong&gt;Graph 的验收指标是最终 token 延迟，不是录进去了多少个节点，也不是 replay 探针快了多少。&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;shuang-qia-bing-xing-wei-shen-me-mei-you-jie-jin-fan-bei&quot;&gt;双卡并行为什么没有接近翻倍&lt;&#x2F;h2&gt;
&lt;p&gt;优化单卡路径后，我们重新尝试双卡算子并行。新的 &lt;code&gt;parallel_operator_pairs&lt;&#x2F;code&gt; 在 MLA 中拆 query heads，在 MoE 中拆中间维，并在较大的 attention 和 FFN 边界归并结果。&lt;&#x2F;p&gt;
&lt;p&gt;交换格式也一起调整。KV 由 owner 量化一次，只传新增的 Q8 latent、scale 和 rope 信息，单 token、单层的 KV 交换从 2,304 B 降到 656 B，减少 71.5%。Router 则在两侧重复计算，省去传递路由表的握手。&lt;&#x2F;p&gt;
&lt;p&gt;单卡 stage 与双卡路线的非 MTP 性能量级如下：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;执行方式&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Decode（token&#x2F;s）&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;每 token 延迟&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;每个 stage 使用单卡（非并行）&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;11.321&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;约 88.3 ms&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;双卡 operator pair，三轮中位&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;13.968（约 14）&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;约 71.6 ms&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;以 11.321 token&#x2F;s 为参照，双卡三轮中位数约高 23.4%，每 token 的延迟减少约 16.7 毫秒。这是不同版本之间的量级比较，不是同版本只切换并行开关的严格 A&#x2F;B。双卡拆分的收益远小于理想二分，profile 给出了几个具体原因。&lt;&#x2F;p&gt;
&lt;p&gt;首先，&lt;strong&gt;拆 query heads 没有拆历史扫描&lt;&#x2F;strong&gt;。两卡仍各自 gather Top-2048、扫描完整逻辑 KV。MLA 的 pair scope 约 0.35–0.45 ms&#x2F;layer，与单卡的 0.35–0.36 ms&#x2F;layer 相比，基本没有加速。&lt;&#x2F;p&gt;
&lt;p&gt;其次，大权重计算拆开了，整层依赖链没有一起减半。Norm、router、DSA selection、部分共享专家工作和结果归并仍要付出成本，FFN&#x2F;MoE 整段只快约 30%–40%。&lt;&#x2F;p&gt;
&lt;p&gt;最后，attention 和 MoE 每层都要双向交换 partial。仅 peer-copy kernel 在 78 层累计就约 3.8 ms&#x2F;token，此外还有 event、join 和两侧进度差。&lt;&#x2F;p&gt;
&lt;p&gt;以上瓶颈来自较早双卡版本的 profile，用来解释优化方向，不能直接当作最新 &lt;code&gt;da1e40be&lt;&#x2F;code&gt; 版本的耗时分解。在这组 profile 中，跨节点 TCP 的裸传输只有几十微秒&#x2F;token，主要损失反而发生在节点内的逐层协作。真正需要减少的是重复扫描和同步等待，单纯缩小消息字节数还不够。&lt;&#x2F;p&gt;
&lt;p&gt;按照 Amdahl 定律，未拆分的串行工作会限制整体加速；新增的通信和同步还会进一步抵消收益。两张卡都在忙，并不意味着每个 token 的关键路径缩短了一半。&lt;&#x2F;p&gt;
&lt;p&gt;继续优化的方向是让 owner 保存唯一的权威 hidden 状态，把 partial 改为向 owner 单向归约，减少反向交换和重复 residual；再探索按历史维度拆分 DSA&#x2F;MLA，让两张卡真正各扫一部分历史。跨越下一档性能需要改变数据分工。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;mtp-de-shou-yi-lai-zi-shao-pao-ji-ci-wan-zheng-mo-xing&quot;&gt;MTP 的收益，来自少跑几次完整模型&lt;&#x2F;h2&gt;
&lt;p&gt;当普通 decode 的单 token 延迟已经优化到约 88.3 毫秒，继续抠掉几微秒的边际收益会越来越小。MTP 提供了另一个方向：先提出多个候选 token，再由目标模型验证，让一轮昂贵的验证提交多个有效 token。&lt;&#x2F;p&gt;
&lt;p&gt;它没有消除自回归语义，而是利用候选前缀，把部分逐 token 工作变成多行验证。是否更快，取决于每轮最终接受多少 token，以及 draft、verify、回退和状态追赶的总成本。&lt;&#x2F;p&gt;
&lt;p&gt;Amd-1 的 &lt;code&gt;paired-mtp5-g2-50k-d1024-r1.json&lt;&#x2F;code&gt; 记录了真实的双卡加 MTP 测试：输入 46,152 token、输出 1,024 token，decode 用时 40.475 秒，吞吐 &lt;strong&gt;25.275 token&#x2F;s&lt;&#x2F;strong&gt;。配置为 &lt;code&gt;parallel_operator_pairs=true&lt;&#x2F;code&gt;、draft depth 5、verify group rows 2；日志确认 MTP L78 也使用 operator pair。&lt;&#x2F;p&gt;
&lt;p&gt;对应完整请求日志记录：target rounds 为 &lt;strong&gt;315&lt;&#x2F;strong&gt;，验证候选 &lt;strong&gt;1,567&lt;&#x2F;strong&gt; 个，接受 &lt;strong&gt;709&lt;&#x2F;strong&gt; 个，候选接受率 &lt;strong&gt;45.25%&lt;&#x2F;strong&gt;。按日志口径，每个 target round 平均提交约 &lt;strong&gt;3.25 个 token&lt;&#x2F;strong&gt;（1,024 &#x2F; 315）。每层 draft depth 的接受数依次为 241、179、130、92、67，说明加深候选深度后，后续位置的有效产出逐步减少。&lt;&#x2F;p&gt;
&lt;p&gt;同一路线其他配置的完整测试为：depth 3 &#x2F; group 2 达到 23.793 token&#x2F;s，depth 5 &#x2F; group 1 为 20.647 token&#x2F;s。它们说明 verify 组织方式会影响端到端收益；由于请求 nonce 与配置不同，这里不把差值归因于单一因素。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;25.275 是旧双卡配置的一次完整实测，不是最新 &lt;code&gt;da1e40be&lt;&#x2F;code&gt; 双卡版本加 MTP 的成绩，也不是多轮稳定基线。&lt;&#x2F;strong&gt; 日志中的 1,024-token 请求先完成，随后才出现 rocprof 附加的另一轮诊断；本文不把后续 profiler 结果混进这一吞吐数字。最新双卡版本仍需单独验证 MTP 组合的速度与正确性。&lt;&#x2F;p&gt;
&lt;p&gt;对这条路线，下一步最值得关注的也不是单独增加 draft depth，而是让多行 verify 真正共享投影、MLA 和 MoE 的权重读取。多验证一行的成本越低，接受更多候选才越有价值。&lt;&#x2F;p&gt;
&lt;p&gt;正确性同样要覆盖状态：候选不匹配后，target KV、DSA selection 和 MTP cache 必须回到正确位置；接受前缀后，相关缓存还要完成追赶。最终吞吐必须建立在这些状态一致的基础上。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wei-shen-me-xuan-4-bit-ke-yi-sheng-dao-5-6-bit-fp8-chao-chu-liao-dang-qian-ying-jian-yu-suan&quot;&gt;为什么选 4 bit：可以升到 5&#x2F;6 bit，FP8 超出了当前硬件预算&lt;&#x2F;h2&gt;
&lt;p&gt;我们当前选择 4-bit 量化，是为了先给长上下文、MTP 和多卡执行留出足够的显存余量。&lt;strong&gt;4 bit 是当前部署的起点，后续可以上升到 5 bit、6 bit。按照我们对这条执行路径的判断，升位基本不会带来性能损失，但模型质量会有明显提升。&lt;&#x2F;strong&gt; 如果更看重回答质量，这比继续压低权重 bit 数更值得做。&lt;&#x2F;p&gt;
&lt;p&gt;位宽增加不等于端到端耗时同比增加。当前单路 decode 的时间还包含 DSA、attention、kernel 提交、跨卡同步和采样回环；低 bit 格式本身也要支付码本、scale、符号处理和反量化指令的成本。因此，从 4 bit 升到 5&#x2F;6 bit 后，权重读取量增加，并不会直接变成同等比例的 token 延迟增加。这里的判断针对本项目的模型、硬件和执行路径；本文没有列出 5&#x2F;6 bit 的独立速度或质量测评数值。&lt;&#x2F;p&gt;
&lt;p&gt;FP8 则越过了另一条边界：&lt;strong&gt;在当前这套硬件上，无法满足本文完整部署的显存预算。&lt;&#x2F;strong&gt; 这套机器的高质量 4-bit checkpoint 已约 362 GiB，按权重翻倍粗估，8-bit 接近 724 GiB，而 16 张卡的物理显存合计只有 768 GiB。剩余约 44 GiB 还要容纳长上下文 KV、DSA 索引状态、MTP、peer 副本、workspace、HIP runtime 和显存碎片，无法支撑当前部署。这个判断首先来自容量与运行余量，不是说 API 无法表达 FP8。&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;权重方案&lt;&#x2F;th&gt;&lt;th&gt;本项目的选择与判断&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;4 bit&lt;&#x2F;td&gt;&lt;td&gt;当前主路径，优先保留上下文与执行余量&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;5&#x2F;6 bit&lt;&#x2F;td&gt;&lt;td&gt;可升级方向；在当前路径上基本不损失性能，质量可明显提升，部署时需重新核算显存余量&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;FP8&lt;&#x2F;td&gt;&lt;td&gt;当前硬件无法满足完整部署的显存预算&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;实际执行也不要求所有张量使用同一位宽。当前 routed expert 使用 IQ3_S&#x2F;IQ4_XS，适合直接消费的投影保留 W8A16，KV 使用 Q8G64，LM Head 使用 Q8G128。升级权重位宽时，仍应按算子选择格式，并用目标任务检查质量收益。&lt;&#x2F;p&gt;
&lt;p&gt;无论选择 4、5 还是 6 bit，都应让热路径直接消费压缩权重，避免重新膨胀成 F32 常驻。这样，增加的显存预算才能用于改善模型质量，而不是消耗在不必要的中间表示上。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;xia-yi-bu-ji-xu-yan-wan-zheng-qing-qiu-ji-shi&quot;&gt;下一步，继续沿完整请求计时&lt;&#x2F;h2&gt;
&lt;p&gt;Prefill 已有历史 cooperative 路径在 50K 口径下达到 &lt;strong&gt;1331.62 token&#x2F;s&lt;&#x2F;strong&gt; 的归档结果。它属于另一执行配置，不能与最新 decode 数字拼成同一次运行的成绩。下一阶段希望补齐多行算子、改善 chunk 与 stage 调度，向 1500 token&#x2F;s 推进，并用完整 50K 的首 token 延迟验收。&lt;&#x2F;p&gt;
&lt;p&gt;Decode 的下一步是在最新双卡版本上重新验证 MTP：先检查输出与缓存状态，再测多轮接受率、多行验证成本和完整请求吞吐。旧配置的 25.275 token&#x2F;s 提供了实验依据，但 30+ 仍只是目标，不能把不同版本的收益直接相乘。&lt;&#x2F;p&gt;
&lt;p&gt;最新记录还提到，目标 GPU 在执行本地层段的活跃窗口里，有效占用可达到 70% 以上。它不代表 16 张卡在整段单路 decode 中同时达到 70% 利用率，也不能与硬件 occupancy 混用。局部工作已经更紧凑，是否改善用户等待时间，仍要回到端到端计时。&lt;&#x2F;p&gt;
&lt;p&gt;这轮优化中，我们保留了能减少权重读取的 W8 直读，也否决了微基准更快、整条链却没有收益的尝试。后续仍沿用同一标准：用 reference 检查数值和状态，用 profiler 找到等待发生在哪里，最后关闭 profiler，在相同输入与配置下测完整请求。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;每个 token 都要走完的那条路，才是最终需要优化的对象。&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;em&gt;资料口径：算法与历史实验依据 2026-09-05 更新的项目优化记录；当前速度由 Amd-1 的原始 JSON、配置与日志交叉核对。以下为本次引用的完整请求，未重新运行 benchmark。&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;路线 &#x2F; 结果文件&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;输入 token&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;输出 token&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Decode（token&#x2F;s）&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;单卡 stage：&lt;code&gt;c343909e-current-routepair-no-mtp-strict1024-r1.json&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;46,158&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1,024&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;11.321&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;双卡：&lt;code&gt;paired-directjoin-da1e40be-formal-no-mtp.json&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;46,153&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1,024&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;14.043&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;双卡复测：&lt;code&gt;paired-directjoin-da1e40be-formal-recheck-no-mtp.json&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;46,153&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1,024&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;13.842&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;双卡复测：&lt;code&gt;paired-directjoin-da1e40be-formal-recheck-no-mtp-r2.json&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;46,153&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1,024&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;13.968&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;旧双卡 + MTP：&lt;code&gt;paired-mtp5-g2-50k-d1024-r1.json&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;46,152&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1,024&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;25.275&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;结果文件分别位于 Amd-1 的 &lt;code&gt;&#x2F;workspace&#x2F;zllm-kv-eval-run&#x2F;runs&#x2F;&lt;&#x2F;code&gt; 和 &lt;code&gt;results&#x2F;&lt;&#x2F;code&gt;，基础语料 SHA-256 一致，nonce 不完全相同。最新三轮双卡结果的输出 SHA-256 一致；这验证了这三轮输出一致性，不等同于独立模型质量评测。部分 runner 的 &lt;code&gt;topology&lt;&#x2F;code&gt; 字段沿用了“16 serial stages”模板，运行日志实际确认双卡路线每节点为 4 个 owner-peer pair，两节点共 8 个逻辑 stage。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh">
        <title>算法层：从注意力、KV Cache 到 FFN 与 MoE</title>
        <published>2026-09-04T00:00:00+00:00</published>
        <updated>2026-09-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhuai.tech/blog/algorithm-layer/"/>
        <id>https://zhuai.tech/blog/algorithm-layer/</id>
        
        <content type="html" xml:base="https://zhuai.tech/blog/algorithm-layer/">&lt;p&gt;算法层回答的不是“在哪块 GPU 上跑”，而是“这一步数学上应该得到什么”。在
zLLM 中，它首先是一组设备无关的规格、数据流和 CPU &lt;code&gt;f32&lt;&#x2F;code&gt; reference：CPU
oracle 给出可读、可测的正确性基准，Metal、CUDA、ROCm、Vulkan 与 NPU kernel
则是在同一语义上的加速实现。&lt;&#x2F;p&gt;
&lt;p&gt;这一区分很重要。高性能 kernel 可以换布局、融合算子、降低精度或并行提交，但
不能悄悄改变 causal mask、RoPE、Top-K 路由、KV 写入位置或数值定义。新算法先
在 CPU&#x2F;reference 上钉住 shape、边界与结果，再与设备实现做 oracle 对拍；通过
oracle 只证明局部语义一致，最终仍要执行完整 prefill&#x2F;decode 并验证输出与性能。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;1-cong-token-dao-xia-yi-token&quot;&gt;1. 从 token 到下一 token&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;algorithm-layer&#x2F;thinking-flow.svg&quot; alt=&quot;从 token、向量化、注意力与 FFN 到下一 token 的算法流程&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;可以把一层 Transformer 类比为一次有结构的思考，但不要把类比当成生物学结论：
模型的“角度、形状、视觉、语义、历史、关系”并不是六个预先命名的槽位，而是训练
后分布在高维向量中的特征方向。一个维度通常也没有独立、稳定的人类解释。&lt;&#x2F;p&gt;
&lt;p&gt;完整路径是：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;分词&lt;&#x2F;strong&gt;：tokenizer 把文本或多模态占位符变成 token id。token 是词、子词、
字节片段或特殊符号，不必等同于一个汉字或单词。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;向量化&lt;&#x2F;strong&gt;：embedding 按 id 查表，把离散 token 变成 &lt;code&gt;hidden_size&lt;&#x2F;code&gt; 维向量；
位置编码再让注意力知道顺序与相对位置。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;逐层加工&lt;&#x2F;strong&gt;：每层先用 attention 从当前与历史 token 聚合相关信息，再用
FFN 或 MoE 对每个 token 的表示做非线性特征变换；归一化与残差连接贯穿其中。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;输出&lt;&#x2F;strong&gt;：final norm 与 LM head 把 hidden 投影到词表大小的 logits，采样或
贪心选择一个 token。decode 把它作为下一轮输入，直到 EOS 或达到长度上限。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;sheng-wei-ju-zhen-cheng-fa-he-bing-jiang-wei-jiu-jing-shi-shen-me&quot;&gt;“升维、矩阵乘法、合并、降维”究竟是什么&lt;&#x2F;h3&gt;
&lt;p&gt;若一行 hidden 写成 &lt;code&gt;x ∈ R^d&lt;&#x2F;code&gt;，线性层本质是 &lt;code&gt;y = xW&lt;&#x2F;code&gt;。矩阵乘法把旧坐标的
加权组合映射到一组新坐标：它既可把 &lt;code&gt;d&lt;&#x2F;code&gt; 维投影到更宽的 &lt;code&gt;d_ff&lt;&#x2F;code&gt; 维，也可投影成
Q&#x2F;K&#x2F;V，或把多头结果映射回 &lt;code&gt;d&lt;&#x2F;code&gt; 维。&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;升维&lt;&#x2F;strong&gt;不是凭空增加事实，而是提供更宽的中间工作空间，使门控与非线性能够
表达更多特征组合。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;多头&lt;&#x2F;strong&gt;把同一 hidden 投影到多个子空间。不同 head 可能学到局部、语义、位置、
指代或视觉关系，但并没有人工规定“第 3 头就是形状”。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;合并&lt;&#x2F;strong&gt;通常是对 value 的加权求和，再拼接多个 head；它是在聚合信息，不是把
原句复制一遍。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;降维&lt;&#x2F;strong&gt;用输出投影或 FFN down projection 回到 &lt;code&gt;hidden_size&lt;&#x2F;code&gt;，从而能与残差相加
并送入下一层。它是学习得到的投影，不等于简单删除末尾若干维。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;因此“一层的实际思考过程”更准确地说，是 attention 的跨 token 读取与 FFN 的
逐 token 特征变换交替进行；层数提供连续的表示修正，而向量维度提供每一步的表达
空间。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;2-attention-jue-ding-ci-ke-ying-du-qu-na-xie-xin-xi&quot;&gt;2. Attention：决定此刻应读取哪些信息&lt;&#x2F;h2&gt;
&lt;p&gt;经典 scaled dot-product attention 为：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;Attention(Q,K,V) = softmax(QKᵀ &#x2F; √dₖ + mask)V&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;可以用三个问题理解它：Q 是“当前在找什么”，K 是“每段历史用什么索引被找到”，
V 是“找到后取回什么内容”。causal mask 保证生成第 N 个 token 时看不到未来。
&lt;a href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;1706.03762&quot;&gt;《Attention Is All You Need》&lt;&#x2F;a&gt;用全局 self-attention
替代了当时序列模型中的循环主干，但今天的主流模型已经发展出多种计算与存储折中。&lt;&#x2F;p&gt;
&lt;p&gt;这些方案并不都是“门限分组”，应按压缩发生的位置区分：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;方案&lt;&#x2F;th&gt;&lt;th&gt;核心办法&lt;&#x2F;th&gt;&lt;th&gt;主要减少什么&lt;&#x2F;th&gt;&lt;th&gt;zLLM 对应&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;MHA，多头注意力&lt;&#x2F;td&gt;&lt;td&gt;每个 Q head 有自己的 K&#x2F;V head&lt;&#x2F;td&gt;&lt;td&gt;基线方案，表达力强但 KV 较大&lt;&#x2F;td&gt;&lt;td&gt;通用多头几何与 block&#x2F;reference 语义&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;MQA&lt;&#x2F;td&gt;&lt;td&gt;所有 Q head 共享一组 K&#x2F;V&lt;&#x2F;td&gt;&lt;td&gt;KV Cache 与 K&#x2F;V 带宽&lt;&#x2F;td&gt;&lt;td&gt;可视为 &lt;code&gt;num_kv_heads = 1&lt;&#x2F;code&gt; 的 GQA 退化形态&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;GQA，分组查询注意力&lt;&#x2F;td&gt;&lt;td&gt;一组 Q heads 共享一个 K&#x2F;V head&lt;&#x2F;td&gt;&lt;td&gt;KV Cache、K&#x2F;V 投影和带宽&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;attention&#x2F;gqa.rs&lt;&#x2F;code&gt;，支持 full&#x2F;sliding window 与 hybrid layer&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;MLA &#x2F; Gated MLA&lt;&#x2F;td&gt;&lt;td&gt;把 KV 压到低秩 latent，需要时重建；可带输出门&lt;&#x2F;td&gt;&lt;td&gt;KV 表示与投影开销&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;attention&#x2F;mla.rs&lt;&#x2F;code&gt;，用于 GLM-5.2、DeepSeek-V3、Kimi-K3 等编排&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Sliding &#x2F; Block Attention&lt;&#x2F;td&gt;&lt;td&gt;只看最近窗口或显式可见块&lt;&#x2F;td&gt;&lt;td&gt;长上下文 attention 计算&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;gqa::CausalWindow&lt;&#x2F;code&gt;、&lt;code&gt;attention&#x2F;block.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;DSA &#x2F; MSA&lt;&#x2F;td&gt;&lt;td&gt;学习索引器或块索引，每个 query 只选 Top-K token&#x2F;块&lt;&#x2F;td&gt;&lt;td&gt;长上下文的 QK 与 AV 计算&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;attention&#x2F;dsa.rs&lt;&#x2F;code&gt;、&lt;code&gt;attention&#x2F;msa.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;压缩稀疏注意力&lt;&#x2F;td&gt;&lt;td&gt;保留近期窗口，把更早历史池化压缩后选择或全读&lt;&#x2F;td&gt;&lt;td&gt;远端历史的存储与计算&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;attention&#x2F;compressed_sparse.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Gated DeltaNet &#x2F; KDA&lt;&#x2F;td&gt;&lt;td&gt;用短卷积和固定大小 recurrent state 递推历史&lt;&#x2F;td&gt;&lt;td&gt;避免随上下文线性增长的完整 KV&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;attention&#x2F;gated_delta_net.rs&lt;&#x2F;code&gt;、&lt;code&gt;attention&#x2F;kda.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;门控、分组、低秩和稀疏选择是四件不同的事。门控控制信息通过多少；GQA 让多个
query head 共享 KV；MLA 压缩表示；DSA&#x2F;MSA 只让当前 query 聚焦一部分历史。
它们都像人类思考中的“聚焦”：当前问题不必同时、同强度地翻阅全部记忆，但工程上
必须明确这种聚焦是否仍扫描全历史来做选择，否则“稀疏结果”未必带来稀疏计算。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;3-kv-cache-li-shi-xin-xi-de-ke-fu-yong-biao-shi&quot;&gt;3. KV Cache：历史信息的可复用表示&lt;&#x2F;h2&gt;
&lt;p&gt;自回归生成每轮只多一个 token。如果每轮都重新计算此前全部 token 的 K 和 V，
大量矩阵乘法会被重复。KV Cache 保存各层已经得到的历史 K&#x2F;V 或等价状态，decode
只计算新 token，再让它查询历史并追加新记录。&lt;&#x2F;p&gt;
&lt;p&gt;它可类比为“工作记忆的索引和内容”，但不是原始对话的无损副本：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;GQA 保存较少的 KV heads；MHA&#x2F;GQA 的容量通常仍随 token 数线性增长。&lt;&#x2F;li&gt;
&lt;li&gt;MLA 保存归一化后的低秩 latent 与 RoPE 分量，读取时再重建所需表示。&lt;&#x2F;li&gt;
&lt;li&gt;zLLM 的 MLA cache 支持 &lt;code&gt;F16&lt;&#x2F;code&gt;，也定义了 latent INT8 per-group、RoPE 保持 F16
的布局；量化减少容量，但会引入可测的数值误差。&lt;&#x2F;li&gt;
&lt;li&gt;sliding window 只需保留有效窗口；DSA&#x2F;MSA 还要维护索引信息。&lt;&#x2F;li&gt;
&lt;li&gt;Gated DeltaNet 与 KDA 保存固定大小 recurrent&#x2F;短卷积 state，它们与 full
attention KV 的更新语义不同，不能强塞进同一种存储抽象。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;code&gt;kv_cache&#x2F;mod.rs&lt;&#x2F;code&gt; 负责逻辑层到 cache 槽的映射、GQA&#x2F;MLA 形态、格式、步长、容量
和有效长度等设备无关语义；backend 才负责 buffer 分配、驻留位置、量化 kernel
与同步。KV 跟随负责该层计算的设备驻留，多机按连续完整层切分时也不跨网络搬运
每层 KV。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;4-ffn-mei-ceng-nei-bu-de-te-zheng-jia-gong&quot;&gt;4. FFN：每层内部的特征加工&lt;&#x2F;h2&gt;
&lt;p&gt;Attention 解决“从别的 token 取什么”，FFN 解决“当前 token 取到信息后怎样变换”。
主流 gated MLP 可概括为：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;y = W_down(act(xW_gate) ⊙ (xW_up))&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;gate&#x2F;up 把 hidden 投影到更宽的 intermediate 空间，激活函数与逐元素乘法形成非线性
选择，down 再投影回 hidden。zLLM 的 &lt;code&gt;moe&#x2F;dense_mlp.rs&lt;&#x2F;code&gt; 保存这一平台无关数据流，
当前激活规格覆盖 SiLU、clamped SiLU、SiTU、OpenAI 风格 SwiGLU 与 GELU-Tanh；
矩阵乘法仍由 backend capability 实现。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;dense-ffn-yu-moe&quot;&gt;Dense FFN 与 MoE&lt;&#x2F;h3&gt;
&lt;p&gt;Dense FFN 的整套 gate&#x2F;up&#x2F;down 权重对每个 token 都参与计算。它可被口语化为“一层
的全部专家都工作”，但源码里 dense MLP 是一个完整网络块，并不是先存在许多专家
再全部选中。&lt;&#x2F;p&gt;
&lt;p&gt;MoE 则放置多组 expert FFN，由 router 给每个 token 打分并只激活 Top-K routed
experts；shared experts 可始终执行。这个设计更接近大脑中“不同区域按任务活跃”的
类比：总参数容量很大，但单个 token 的活跃计算量较小。这个类比只解释稀疏激活，
不意味着 Transformer expert 对应固定脑区或具备可命名的人格。&lt;&#x2F;p&gt;
&lt;p&gt;zLLM 的 FFN&#x2F;MoE 模块边界如下：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;模块&lt;&#x2F;th&gt;&lt;th&gt;功能&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;moe&#x2F;dense_mlp.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;dense gated MLP 的规格、激活与 &lt;code&gt;gate&#x2F;up → activation → down&lt;&#x2F;code&gt; 数据流&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;moe&#x2F;routing.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;路由分数、Top-K、分组 assignment 与活跃 expert 统计&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;moe&#x2F;topk_moe.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;routed&#x2F;shared expert 的组合、路由后缩放和输出累加&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;moe&#x2F;latent_moe.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;原 hidden 做路由，低维 latent 进入 routed expert，shared MLP 仍读原 hidden&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;moe&#x2F;prefill.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;多 token prefill 的 expert 分组执行与合并&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;moe&#x2F;expert_predictor.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;根据真实历史路由预测后续 expert，服务异步预取；不持权重、不执行 I&#x2F;O&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;MoE 首先降低的是每 token 的计算量，不会自动降低模型总权重。高吞吐、低延迟场合
通常需要让大量甚至全部 expert 权重驻留内存&#x2F;显存，所以显存占用仍接近完整 MoE
模型；router 省下 FLOPs，却没有让未选 expert 从文件中消失。&lt;&#x2F;p&gt;
&lt;p&gt;在容量受限且性能要求较低的场合，可以把冷 expert 放在 CPU 内存或 SSD，路由后
按需流式加载；zLLM 也把 expert source、prefetch 与预测反馈放在 backend&#x2F;runtime
边界上。但这会引入 I&#x2F;O 延迟、预取命中率、并发工作集和抖动问题。只有加载与当前
层计算真正重叠，且命中率足够高，流式 expert 才可能在可接受延迟下换取较低驻留量。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;5-zllm-suan-fa-ceng-de-mo-kuai-di-tu&quot;&gt;5. zLLM 算法层的模块地图&lt;&#x2F;h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;目录&#x2F;模块&lt;&#x2F;th&gt;&lt;th&gt;它定义什么&lt;&#x2F;th&gt;&lt;th&gt;它不负责什么&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;attention&#x2F;mod.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;注意力家族的设备无关规格入口&lt;&#x2F;td&gt;&lt;td&gt;设备 buffer 与 command submission&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;attention&#x2F;rope.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;位置旋转、布局与 reference&lt;&#x2F;td&gt;&lt;td&gt;模型 tokenizer&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;attention&#x2F;gqa.rs&lt;&#x2F;code&gt;、&lt;code&gt;mla.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;GQA&#x2F;MLA 几何、窗口、投影关系与 f32 reference&lt;&#x2F;td&gt;&lt;td&gt;平台专属融合 kernel&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;attention&#x2F;dsa.rs&lt;&#x2F;code&gt;、&lt;code&gt;msa.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;token&#x2F;block 评分、因果 Top-K 与稀疏选择语义&lt;&#x2F;td&gt;&lt;td&gt;宣称所有选择路径天然是 O(K)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;attention&#x2F;compressed_sparse.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;滑窗、压缩历史、可见位置与选择计划&lt;&#x2F;td&gt;&lt;td&gt;具体压缩 buffer 的物理驻留&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;attention&#x2F;gated_delta_net.rs&lt;&#x2F;code&gt;、&lt;code&gt;kda.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;recurrent&#x2F;conv state 的形态、更新与 reference&lt;&#x2F;td&gt;&lt;td&gt;将其伪装成普通 KV Cache&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;attention&#x2F;hybrid.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;full attention 与线性&#x2F;recurrent attention 的混合层状态&lt;&#x2F;td&gt;&lt;td&gt;决定设备放置策略&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;attention&#x2F;hyper_connection.rs&lt;&#x2F;code&gt;、&lt;code&gt;attn_res.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;多残差流、attention residual 等层内连接语义&lt;&#x2F;td&gt;&lt;td&gt;HTTP 会话或跨节点传输&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;moe&#x2F;*&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;dense FFN、Top-K&#x2F;latent MoE、路由、共享 expert 与预取反馈语义&lt;&#x2F;td&gt;&lt;td&gt;SSD&#x2F;显存分配和 DMA&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;kv_cache&#x2F;*&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;cache 逻辑布局、格式、容量、有效长度与持久化数据边界&lt;&#x2F;td&gt;&lt;td&gt;某平台的分配、同步和 kernel&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;runtime&#x2F;prefill.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;通用 chunk、batch、stage 与完整层循环&lt;&#x2F;td&gt;&lt;td&gt;某个模型的 layer 顺序&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;runtime&#x2F;generation.rs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;token 生成、EOS、位置推进生命周期&lt;&#x2F;td&gt;&lt;td&gt;具体 attention&#x2F;FFN 数学&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;runtime&#x2F;&amp;lt;model&amp;gt;&#x2F;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;按模型规格组合 embedding、每层 attention + FFN、norm、LM head&lt;&#x2F;td&gt;&lt;td&gt;复制 backend 算法实现&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;生产执行只有三种完整任务：&lt;code&gt;NewPrefill&lt;&#x2F;code&gt; 创建新 session 与 cache，&lt;code&gt;AppendPrefill&lt;&#x2F;code&gt;
在已有历史后追加一段 token，&lt;code&gt;DecodeRound&lt;&#x2F;code&gt; 输入一个 token、跑完所有层、追加状态并
产生下一个 token。算法层规定正确顺序，runtime 组合完整模型，backend 管资源与
提交，kernel 加速局部计算。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;6-cpu-oracle-ru-he-shou-zhu-zheng-que-xing&quot;&gt;6. CPU oracle 如何守住正确性&lt;&#x2F;h2&gt;
&lt;p&gt;CPU oracle 的价值不是“CPU 也能勉强跑模型”，而是把复杂设备优化拆成可验证问题：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;用小而真实的 shape 验证分词后位置、mask、RoPE、矩阵维度和 cache append。&lt;&#x2F;li&gt;
&lt;li&gt;用确定输入对比 CPU &lt;code&gt;f32&lt;&#x2F;code&gt; reference 与设备 kernel，检查 shape、有限值、绝对&#x2F;
相对误差，以及稀疏索引和 MoE expert id 这类离散结果。&lt;&#x2F;li&gt;
&lt;li&gt;逐层比较 hidden、路由与 logits，定位误差从哪一层开始放大。&lt;&#x2F;li&gt;
&lt;li&gt;固定 token 序列做 prefill&#x2F;decode 回归，并测试 cache 边界、追加前缀和长上下文。&lt;&#x2F;li&gt;
&lt;li&gt;最后跑完整模型任务；编译通过、单算子 oracle 通过、固定 token 一致与真机性能
达标是四种不同状态，不能互相替代。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;容差必须按 dtype、量化方式和误差传播设定，不能把统一的宽松阈值当作正确。对于
Top-K 路由、causal 选择、cache 长度和提交位置，很多不变量必须精确相等；对于
F16&#x2F;BF16&#x2F;量化矩阵输出，才使用有依据的 &lt;code&gt;atol&#x2F;rtol&lt;&#x2F;code&gt;。即使局部 kernel 对拍通过，
也可能因为额外转换、同步或不合适的 shape 让端到端性能倒退，因此正确性 oracle
之后仍需要完整 prefill、decode 吞吐、峰值内存与稳定性验证。&lt;&#x2F;p&gt;
&lt;p&gt;这就是算法层在 zLLM 中的定位：它不是又一套设备实现，而是所有设备实现共同遵守
的数学合同。CPU reference 让合同可执行、可回归；attention&#x2F;KV 定义怎样读取历史，
FFN&#x2F;MoE 定义怎样加工当前表示，runtime 再把它们组合成一次完整而可验证的生成。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh">
        <title>量化不是压缩包：从模型分层到 4-bit 推理的工程真相</title>
        <published>2026-09-03T00:00:00+00:00</published>
        <updated>2026-09-03T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhuai.tech/blog/model-layer-quantization/"/>
        <id>https://zhuai.tech/blog/model-layer-quantization/</id>
        
        <content type="html" xml:base="https://zhuai.tech/blog/model-layer-quantization/">&lt;p&gt;大模型量化最容易被讲成一句话：把 16-bit 权重压到 4-bit，模型缩小四倍。
这句话没有错，却漏掉了几乎所有真正困难的部分。&lt;&#x2F;p&gt;
&lt;p&gt;量化首先解决的是&lt;strong&gt;容量问题&lt;&#x2F;strong&gt;：权重太大，显存或统一内存放不下。它还有一个
潜在收益——&lt;strong&gt;速度可能更快&lt;&#x2F;strong&gt;：decode 常常受内存带宽限制，权重每生成一个 token
都要重新读一遍，4-bit 权重比 BF16 少搬很多字节。但“文件更小”不自动等于
“推理更快”。如果运行时先把 4-bit 全量反量化成 F16&#x2F;F32，或者设备没有对应的
packed kernel，容量与带宽收益会被部分甚至全部抹掉。&lt;&#x2F;p&gt;
&lt;p&gt;所以量化不是一个文件转换选项，而是一条贯穿模型规格、权重装配、设备驻留、
算子和质量验证的完整执行路径。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;yi-xian-fen-ceng-mo-xing-shi-shen-me-quan-zhong-zen-yang-cun-shi-liang-jian-shi&quot;&gt;一、先分层：模型是什么，权重怎样存，是两件事&lt;&#x2F;h2&gt;
&lt;p&gt;在 zLLM 中，模型层没有被做成一个包办一切的 &lt;code&gt;Model&lt;&#x2F;code&gt; 黑盒。它被拆成几类稳定职责：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;model_spec&#x2F;&amp;lt;model&amp;gt;  架构常量：层数、hidden、attention、MoE、tokenizer id
&lt;&#x2F;span&gt;&lt;span&gt;runtime&#x2F;&amp;lt;model&amp;gt;     LayerSpec 展开与完整 prefill&#x2F;decode 编排
&lt;&#x2F;span&gt;&lt;span&gt;weight&#x2F;model        checkpoint 张量名、shape 校验、模型装配
&lt;&#x2F;span&gt;&lt;span&gt;weight&#x2F;format       FP8、W4A16、GGUF K-quant 等字节布局
&lt;&#x2F;span&gt;&lt;span&gt;weight&#x2F;codec        无 IO 的 reference decode 与布局转换
&lt;&#x2F;span&gt;&lt;span&gt;backend + kernel    权重驻留、格式分派与设备直接计算
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这套分层有一个关键结果：&lt;strong&gt;模型架构不等于权重编码&lt;&#x2F;strong&gt;。同一个线性层可以来自 BF16、
FP8、NVFP4、W4A16 或 Q4_K；格式层只解释字节，模型适配层只认名字和 shape，
runtime 只描述数据流，backend 才根据设备能力选择 kernel。&lt;&#x2F;p&gt;
&lt;p&gt;这也避免了两种常见污染：格式解析代码里写满某个模型的张量名，或者 GPU kernel
里硬编码 &lt;code&gt;6144&lt;&#x2F;code&gt;、&lt;code&gt;256&lt;&#x2F;code&gt; 之类的模型维度。前者使新格式无法跨模型复用，后者使
“支持一种模型”伪装成了“实现一个通用算子”。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;model-quantization&#x2F;format-paths.svg&quot; alt=&quot;常见量化格式的数值编码，以及 W4A16、W4A8 的权重与 activation 计算路径&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;er-liang-hua-dao-di-jie-jue-shen-me&quot;&gt;二、量化到底解决什么&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;1-di-yi-mu-biao-rang-quan-zhong-zhuang-de-xia&quot;&gt;1. 第一目标：让权重装得下&lt;&#x2F;h3&gt;
&lt;p&gt;忽略 scale、元数据和未量化张量，一个 (N) 参数模型仅权重的理论体积约为：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;BF16 &#x2F; FP16：2N bytes
&lt;&#x2F;span&gt;&lt;span&gt;INT8：       1N bytes
&lt;&#x2F;span&gt;&lt;span&gt;INT4：       0.5N bytes
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;因此 70B 模型的裸 BF16 权重约 140 GB，理想 4-bit 约 35 GB。实际 GGUF Q4_K_M
会高于严格的 4 bit&#x2F;weight，因为分块 scale、minimum、对齐，以及被保留为更高精度
的敏感张量都占空间。量化也只压缩它所覆盖的数据：KV Cache、activation、临时
buffer 和运行时本身仍要单独预算。&lt;&#x2F;p&gt;
&lt;p&gt;在离散 GPU 上，这决定模型能否进入显存；在 Apple UMA 上，它决定模型、KV、
系统和应用能否共享同一物理内存而不触发严重内存压力。UMA 省掉了传统的
Host→VRAM staging，不代表内存容量变成无限。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-di-er-mu-biao-jian-shao-ban-yun-huan-qu-su-du&quot;&gt;2. 第二目标：减少搬运，换取速度&lt;&#x2F;h3&gt;
&lt;p&gt;单 token decode 的大矩阵乘常接近 GEMV：计算复用低，瓶颈往往是读取权重，而不是
乘加次数。若 4-bit 权重保持 packed，kernel 在寄存器或局部块内解码并立即累加，
一次读取能带来接近四倍于 BF16 的有效权重密度。这是量化可能加速的根本原因。&lt;&#x2F;p&gt;
&lt;p&gt;但加速有三个前提：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;设备有适合该格式的直接 kernel；&lt;&#x2F;li&gt;
&lt;li&gt;解码、scale 处理、activation 转换和调度开销没有吃掉带宽收益；&lt;&#x2F;li&gt;
&lt;li&gt;测的是完整 prefill&#x2F;decode，而不是孤立的解码函数。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;zLLM 在真实 Qwen3.8 CPU decode 路径中，Q4_K packed AVX2&#x2F;FMA 直接点积曾把
16-token decode 从 107.483 秒降到 7.578 秒；继续融合相邻 gate&#x2F;up 后为
7.183 秒。这个约 15 倍的变化包含“删除逐块 F32 展开”和专用向量化的共同收益，
&lt;strong&gt;不能解释成 4-bit 对 BF16 的普遍加速倍数&lt;&#x2F;strong&gt;。反例同样重要：Gemma 4 的某个
Q4_K 双输出 kernel 虽通过 CPU oracle，完整链路却从 35.9 降到 35.5 tok&#x2F;s，最终
被回退。量化只提供更低的带宽下限，能否兑现要看整条路径。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;san-ji-ben-si-lu-bu-shi-quan-ju-she-ru-er-shi-li-yong-quan-zhong-ju-bu-xing&quot;&gt;三、基本思路：不是全局舍入，而是利用权重局部性&lt;&#x2F;h2&gt;
&lt;p&gt;最粗糙的量化，是拿整张矩阵的最大最小值建立一个比例尺：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;q = round(w &#x2F; scale)              # 对称量化
&lt;&#x2F;span&gt;&lt;span&gt;w_hat = scale * q
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;q = round((w - offset) &#x2F; scale)   # 非对称量化
&lt;&#x2F;span&gt;&lt;span&gt;w_hat = offset + scale * q
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;问题在于，权重分布并不均匀。少数离群值会把全局量程拉大，让大部分普通值挤在
少数几个整数格子里。现代权重量化因此依赖三种“分层、分级、局部”的选择。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;第一层是块内局部性。&lt;&#x2F;strong&gt; 不给整张矩阵共用一个 scale，而是按 32、64、128 或
256 个权重分组。每组有自己的 scale，组越小越能贴合局部分布，但元数据占比、
访存复杂度和 kernel 成本也越高。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;第二层是张量和通道的敏感度。&lt;&#x2F;strong&gt; attention 的 value&#x2F;output、FFN down projection、
embedding、LM head 对误差的敏感度可能不同，甚至同一张量不同通道也不同。
因此 “Q4_K_M” 里的 M 不是所有权重都机械使用同一精度，而是让重要张量用更高
档位。AWQ 的核心观察也类似：由 activation 分布识别少量 salient weights，保护
它们比只最小化权重自身误差更有效。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;第三层是层级局部性。&lt;&#x2F;strong&gt; 误差会沿深度传播，前层造成的 hidden 偏移会改变后层
路由、attention 和最终 token 排序。逐层或逐块校准比一次全局量化更能控制累积
误差；MoE 还要覆盖真实的 expert 路由分布，否则“没有在校准集里被选中的专家”
可能被草率量化。&lt;&#x2F;p&gt;
&lt;p&gt;这就是为什么量化格式通常不仅保存低 bit code，还保存分组 scale、minimum，
甚至 importance matrix。有效 bit 数也因此不是标签上的整数：以 llama.cpp 的
Llama 2 表为例，Q4_K_M 的实际平均约为 4.80–4.84 bits&#x2F;weight，而不是正好 4。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;model-quantization&#x2F;scale-layouts.svg&quot; alt=&quot;逐张量、逐通道、分组量化与 GGUF K-quant 的 scale 层级结构&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;chang-jian-gguf-liang-hua-ge-shi-zen-me-xuan&quot;&gt;常见 GGUF 量化格式怎么选&lt;&#x2F;h3&gt;
&lt;p&gt;先澄清一个经常被混用的概念：&lt;strong&gt;GGUF 是容器格式，不是某一种量化算法&lt;&#x2F;strong&gt;。一个
GGUF 文件保存 metadata、tokenizer、tensor directory 和张量数据；其中不同张量
可以分别使用 F32、F16、Q4_K、Q6_K、IQ4_XS 等类型。因此文件名写着
&lt;code&gt;Q4_K_M&lt;&#x2F;code&gt;，不代表里面每个 tensor 都是同一种 4-bit block。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;model-quantization&#x2F;gguf-formats.svg&quot; alt=&quot;常见 GGUF 量化格式从 IQ2、Q3、Q4 到 Q5、Q6、Q8 的容量质量谱系&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;常见格式可以分成四组：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;家族&lt;&#x2F;th&gt;&lt;th&gt;常见名称&lt;&#x2F;th&gt;&lt;th&gt;结构与用途&lt;&#x2F;th&gt;&lt;th&gt;选择提示&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;经典 block&lt;&#x2F;td&gt;&lt;td&gt;Q4_0、Q5_0、Q8_0&lt;&#x2F;td&gt;&lt;td&gt;一组整数 code 配一个 scale，布局简单、kernel 覆盖广&lt;&#x2F;td&gt;&lt;td&gt;Q4_0 适合兼容优先；Q8_0 常作高质量对照或量化点积的 activation 格式&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;K-quant&lt;&#x2F;td&gt;&lt;td&gt;Q3_K、Q4_K、Q5_K、Q6_K&lt;&#x2F;td&gt;&lt;td&gt;256 权重 super-block 内再保存子块 scale&#x2F;minimum&lt;&#x2F;td&gt;&lt;td&gt;Q4_K_M 是常见起点；Q5_K_M、Q6_K 用容量换质量&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;I-quant&lt;&#x2F;td&gt;&lt;td&gt;IQ2_&lt;em&gt;、IQ3_&lt;&#x2F;em&gt;、IQ4_NL、IQ4_XS&lt;&#x2F;td&gt;&lt;td&gt;importance-aware、非线性码本或查表解码&lt;&#x2F;td&gt;&lt;td&gt;同 BPW 下质量可能更好，但必须确认目标 backend 有直接 kernel&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;混合配方&lt;&#x2F;td&gt;&lt;td&gt;Q3_K_S&#x2F;M&#x2F;L、Q4_K_S&#x2F;M、Q5_K_S&#x2F;M、UD-*&lt;&#x2F;td&gt;&lt;td&gt;按 tensor 敏感度分配不同类型&lt;&#x2F;td&gt;&lt;td&gt;后缀描述整模型配方；实际类型应读取 GGUF tensor directory 确认&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;&lt;code&gt;S&#x2F;M&#x2F;L&lt;&#x2F;code&gt; 最容易被误读。它们通常表示 Small、Medium、Large 的混合精度方案：
例如某些 attention 或 FFN 张量升到更高 bit，其他张量保持主档位。它们不是简单的
“同一种 Q4 多三个压缩级别”，实际 BPW 也会随模型结构而变。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;IQ4_XS&lt;&#x2F;code&gt; 与 &lt;code&gt;Q4_K_M&lt;&#x2F;code&gt; 都在 4-bit 附近，却不是可随意互换的同一布局。前者使用
非线性量化&#x2F;码本思路，把有限 code 更贴近真实权重分布；后者使用成熟的 K-quant
分层 scale。前者可能以更低 BPW 获得很强质量，后者通常拥有更广泛、稳定的设备
kernel。选择时要同时看模型质量、文件大小和&lt;strong&gt;当前 backend 的直接执行白名单&lt;&#x2F;strong&gt;。&lt;&#x2F;p&gt;
&lt;p&gt;实际选型可以从下面的顺序开始：资源非常紧才进入 IQ2&#x2F;Q2&#x2F;Q3；大多数本地部署先测
Q4_K_M 或已有高质量校准的 IQ4_XS；质量敏感且内存允许则测 Q5_K_M&#x2F;Q6_K；Q8_0
适合作为接近高精度的量化基线。任何发布者自定义的 &lt;code&gt;UD-Q4_K_XL&lt;&#x2F;code&gt; 等名称，都应先
检查内部 tensor types——它可能在不同层混用 Q4_K、Q5_K、Q6_K 和 IQ4_XS，
只支持 &lt;code&gt;Q4_K&lt;&#x2F;code&gt; 的 kernel 并不一定能加载整份模型。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;si-wei-shen-me-4-bit-chang-shi-tian-dian-wei&quot;&gt;四、为什么 4-bit 常是甜点位&lt;&#x2F;h2&gt;
&lt;p&gt;“甜点位”不是说 4-bit 永远最好，而是说它经常处在三条曲线的交点：容量已经大幅
下降，设备仍容易高效解码，质量损失还没有像 2&#x2F;3-bit 那样陡增。&lt;&#x2F;p&gt;
&lt;p&gt;llama.cpp 对 Mistral 7B、WikiText-2、512 context 的一组同口径测试很直观：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;格式&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;perplexity&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;相对 FP16 增幅&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Q3_K_S&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;6.0021&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;5.44%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Q3_K_M&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;5.8489&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2.75%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Q4_K_S&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;5.7349&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.75%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Q4_K_M&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;5.7259&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.59%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Q5_K_S&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;5.7100&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.31%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;从 Q3_K_M 到 Q4_K_M，困惑度增幅从 2.75% 降到 0.59%；再从 Q4_K_M 加到
Q5_K_S，只改善到 0.31%，但权重带宽和体积继续增加。这正是典型的“膝点”。
另一组 Llama 33B 数据中，FP16、Q6_K、Q5_K_M、Q4_K_M、Q3_K_M 的 perplexity
分别为 4.1557、4.1598、4.1675、4.2081、4.3594，也呈现相似趋势。&lt;&#x2F;p&gt;
&lt;p&gt;这些数字只能证明&lt;strong&gt;该模型、该数据集、该 context、该量化器&lt;&#x2F;strong&gt;下的趋势。perplexity
不是聊天质量、代码正确率或长思考稳定性的同义词；0.59% 也不表示每个回答只差
0.59%。接近决策边界的 token、长链条中的早期分叉、MoE router 的 Top-K 变化，
都可能把很小的局部误差放大成完全不同的输出。&lt;&#x2F;p&gt;
&lt;p&gt;因此更稳妥的经验是：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;资源够时，Q5&#x2F;Q6 更接近基线；&lt;&#x2F;li&gt;
&lt;li&gt;容量和 decode 带宽都紧时，优先从现代 grouped&#x2F;mixed 4-bit 开始；&lt;&#x2F;li&gt;
&lt;li&gt;3-bit 以下需要更强的校准、重要性加权或训练补偿，不能只做朴素舍入；&lt;&#x2F;li&gt;
&lt;li&gt;embedding、LM head、norm、router、视觉投影等敏感部分是否保留高精度，要由
模型结构与任务测试决定。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;wu-wei-shen-me-mo-xing-ke-yi-bei-liang-hua&quot;&gt;五、为什么模型“可以被量化”&lt;&#x2F;h2&gt;
&lt;p&gt;神经网络不是一套每一步都要求 bit-exact 的符号程序。大量参数共同形成冗余表示，
相邻的权重值常能映射到相同量化格点而不改变宏观能力；LayerNorm&#x2F;RMSNorm、残差
连接和过参数化也给小扰动留下了容忍空间。量化利用的是这种统计冗余。&lt;&#x2F;p&gt;
&lt;p&gt;生成本身又是概率过程。模型输出的是下一个 token 的 logits，经 softmax、temperature、
top-k&#x2F;top-p 和随机采样才成为文本。温度大于零时，即使权重完全相同，两次回答也
可能不同；即使 greedy 解码，两个非常接近的 logit 也可能因微小数值扰动交换顺序。&lt;&#x2F;p&gt;
&lt;p&gt;但这不能推出“量化误差无所谓”。正确说法是：开放式生成通常没有唯一字符串答案，
评价对象往往是“好”和“更好”，而不只是“对”和“错”；量化质量必须用分布和任务
指标判断，而不能只做逐字比较。数学、代码、工具参数、JSON schema 等任务仍然有
硬正确性边界。&lt;&#x2F;p&gt;
&lt;p&gt;量化模型偶尔还会在某个 benchmark 上高于浮点基线。这可能来自量化噪声打破了
原有错误偏好，也可能只是有限样本、采样方差或评测器噪声。它说明质量不是逐位
单调的，不说明量化普遍提升模型。只有跨 seed、跨数据集、带置信区间的重复结果，
才值得称为反向收益。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;liu-xun-lian-yu-xiao-zhun-zai-bu-chang-shen-me&quot;&gt;六、训练与校准在补偿什么&lt;&#x2F;h2&gt;
&lt;p&gt;训练后量化（PTQ）不改模型参数，只用少量代表性样本估计 scale、clipping、通道
重要性或 Hessian 近似。GPTQ 逐层补偿量化一个权重对其余权重造成的误差；AWQ
利用 activation 识别并保护显著权重。它们的共同点是：优化目标不再是“每个权重
离原值最近”，而是“这一层在真实输入上输出尽量不变”。&lt;&#x2F;p&gt;
&lt;p&gt;量化感知训练（QAT）则在训练&#x2F;微调时模拟舍入与裁剪，让参数主动适应低精度格点。
它成本更高，但在 3-bit、2-bit、activation 量化或高敏感任务上更重要。校准集也必须
贴近部署分布：代码模型用纯百科文本校准，长推理模型只用短句校准，MoE 模型遗漏
大量 expert，都可能得到漂亮的平均误差和糟糕的真实输出。&lt;&#x2F;p&gt;
&lt;p&gt;还要区分权重量化、activation 量化与 KV Cache 量化。W4A16 只把权重降到 4-bit，
activation 仍以 16-bit 参与计算；W4A8 会进一步减少流量，却引入动态范围和融合
kernel 的新约束；KV 量化影响长上下文容量，也可能持续扰动 attention。三者不能
用一个“4-bit 模型”标签混在一起。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;qi-wei-lan-yue-shu-jie-gou-dan-bu-yao-wei-zao-zheng-que-xing&quot;&gt;七、围栏：约束结构，但不要伪造正确性&lt;&#x2F;h2&gt;
&lt;p&gt;量化后最危险的质量问题，不一定表现为乱码。更常见的是 logit margin 变小、思考链
提前分叉、长推理逐步漂移，或者工具调用在某个标点上选错 token。&lt;&#x2F;p&gt;
&lt;p&gt;工程上需要三层围栏：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;算子围栏&lt;&#x2F;strong&gt;：reference decode、CPU oracle、逐层 hidden&#x2F;logit 误差、真实 shape
校验，先证明低精度路径实现正确；&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;生成围栏&lt;&#x2F;strong&gt;：JSON Schema、工具名、结束标签和 stop token 在采样前进入 token
fence，保证结构合法；重复循环由独立的 generation guard 处理；&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;质量围栏&lt;&#x2F;strong&gt;：固定 prompt 的 greedy 回归、perplexity、任务集、长上下文、
多 seed 开放式评测，以及关键任务的人工审查。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;生成围栏只能保证“格式允许”，不能保证参数语义正确。更不能因为加了 JSON 修复器，
就把量化造成的 tool selection 漂移藏起来。对于思考链，也不应把“逐字一致”设为唯一
目标：chain-of-thought 本来就可能因采样而变化，而且可见推理文本不等于模型内部
计算。更可靠的办法是同时检查最终答案&#x2F;工具结果、关键中间约束、结束原因、循环率、
长度分布，以及同一解码策略下的成功率变化。&lt;&#x2F;p&gt;
&lt;p&gt;尤其要防止一种假通过：短题、greedy、最终答案碰巧一致，于是宣布量化无损。长链条
中每一步的小 logit 偏移都可能累积；应加入需要数千 token 的推理样本，记录量化前后
的答案正确率、异常重启、重复、提前 EOS 和超长尾。围栏负责拦住协议级灾难，评测
负责发现语义退化，两者不能相互替代。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ba-gong-cheng-zhong-zui-rong-yi-cai-de-keng&quot;&gt;八、工程中最容易踩的坑&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;zhi-chi-bi-xu-chai-cheng-si-chong-zhuang-tai&quot;&gt;“支持”必须拆成四种状态&lt;&#x2F;h3&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;能解析文件
&lt;&#x2F;span&gt;&lt;span&gt;≠ CPU reference 能解码
&lt;&#x2F;span&gt;&lt;span&gt;≠ 设备存在 packed 直接 kernel
&lt;&#x2F;span&gt;&lt;span&gt;≠ 某模型默认端到端路径已经使用
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这四种状态必须分别记录。zLLM 在装配期验证完整执行路径：量化权重没有目标设备
kernel 就加载失败，Metal 路径禁止偷偷反量化成 F16 来掩盖能力缺口。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;packed-bi-xu-yi-zhi-bao-chi-dao-kernel&quot;&gt;packed 必须一直保持到 kernel&lt;&#x2F;h3&gt;
&lt;p&gt;加载时展开 F32 也许最容易写，却把体积和访存优势清零。正确的数据生命周期是：
容器定位 packed bytes，format 解释布局，model 校验名字和 shape，backend 让它驻留，
kernel 按块读取 code&#x2F;scale 并直接累加。临时解码只在寄存器、SIMD lane 或短生命周期
scratch 中发生。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;bu-yao-hu-lue-fei-quan-zhong-nei-cun&quot;&gt;不要忽略非权重内存&lt;&#x2F;h3&gt;
&lt;p&gt;权重从 16-bit 降到 4-bit，不代表峰值内存恰好除以四。长上下文 KV、prefill
activation、MoE expert 工作集、视觉 encoder、output logits 和 command buffer 都可能
成为新峰值。容量规划必须列出每份数据的位置、格式、消费者和生命周期。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;xing-neng-bi-xu-duan-dao-duan-ce&quot;&gt;性能必须端到端测&lt;&#x2F;h3&gt;
&lt;p&gt;分别记录 TTFT、prefill tok&#x2F;s、decode tok&#x2F;s、峰值内存、SSD 等待、GPU 利用率与温度。
量化 GEMV 快了，不代表 command submission、反量化 activation 或 output head 没有成为
新瓶颈。移动设备和无风扇 Mac 还要区分冷机与热稳态。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;zheng-que-xing-bu-neng-zhi-kan-shu-chu-tong-shun&quot;&gt;正确性不能只看“输出通顺”&lt;&#x2F;h3&gt;
&lt;p&gt;至少要依次验证：codec 对拍、单算子 oracle、逐层差分、固定 token 回归、完整任务集。
shape 与 dtype 以 checkpoint 实测为准；norm 是一维还是二维、某个投影是 BF16 还是
FP8，任何“按道理应该”都不如加载时断言。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;jie-yu&quot;&gt;结语&lt;&#x2F;h2&gt;
&lt;p&gt;量化的真正价值不是把模型文件做小，而是重新安排“精度、容量、带宽和计算”之间的
预算。4-bit 常成为甜点位，是因为它在许多模型上跨过了容量门槛，也仍能保留接近
高精度的统计质量，并且适合现代设备做 packed 直算；它不是一个脱离模型、数据、
硬件和任务的魔法数字。&lt;&#x2F;p&gt;
&lt;p&gt;一条可信的量化路径应当同时回答四个问题：哪些权重以什么粒度被量化，哪些敏感部分
被保护；设备是否真的直接消费 packed 格式；速度收益是否出现在完整请求中；质量
围栏是否覆盖长思考、结构化输出和真实任务。只有这四个答案都明确，“模型能跑”才
能升级为“模型值得被部署”。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;can-kao-zi-liao&quot;&gt;参考资料&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;2210.17323&quot;&gt;GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;2306.00978&quot;&gt;AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ggml-org&#x2F;llama.cpp&#x2F;discussions&#x2F;4364&quot;&gt;llama.cpp：Mistral 7B 量化困惑度对比&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ggml-org&#x2F;llama.cpp&#x2F;discussions&#x2F;406&quot;&gt;llama.cpp：不同模型与 K-quant 的困惑度记录&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ggml-org&#x2F;llama.cpp&#x2F;blob&#x2F;master&#x2F;tools&#x2F;quantize&#x2F;quantize.cpp&quot;&gt;llama.cpp quantize：当前格式、体积与 Llama 3 8B 困惑度增量&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="zh">
        <title>用 Rust 统一管理内存与显存所有权：从 Arc&lt;Tensor&gt; 到 GPU Completion</title>
        <published>2026-09-02T00:00:00+00:00</published>
        <updated>2026-09-02T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhuai.tech/blog/rust-unified-memory-ownership/"/>
        <id>https://zhuai.tech/blog/rust-unified-memory-ownership/</id>
        
        <content type="html" xml:base="https://zhuai.tech/blog/rust-unified-memory-ownership/">&lt;p&gt;在 GPU 推理代码里，最难维护的往往不是矩阵乘法，而是一个更朴素的问题：这块
内存到底属于谁，它什么时候可以释放？&lt;&#x2F;p&gt;
&lt;p&gt;Rust 的借用、&lt;code&gt;Arc&lt;&#x2F;code&gt;、&lt;code&gt;Mutex&lt;&#x2F;code&gt; 和 &lt;code&gt;Drop&lt;&#x2F;code&gt; 能把这个问题变得非常直接，但前提是先
分清三件不同的事：&lt;strong&gt;谁拥有资源、谁可以修改资源、GPU 什么时候使用完资源&lt;&#x2F;strong&gt;。
如果不做区分，给所有对象统一套上 &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;_&amp;gt;&amp;gt;&lt;&#x2F;code&gt;，代码虽然能够通过编译，却
会引入不必要的锁，甚至仍然可能在异步 kernel 尚未完成时提前释放显存。&lt;&#x2F;p&gt;
&lt;p&gt;zLLM 要建立一条贯穿所有 backend 的完整所有权链：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;模型&#x2F;文件中的数据
&lt;&#x2F;span&gt;&lt;span&gt;    │  Arc&amp;lt;File&amp;gt; &#x2F; Vec&amp;lt;u8&amp;gt; &#x2F; Vec&amp;lt;f32&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    ▼
&lt;&#x2F;span&gt;&lt;span&gt;RocmWeight 或 RocmTensor
&lt;&#x2F;span&gt;&lt;span&gt;    │  Arc&amp;lt;DeviceBuffer&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    ▼
&lt;&#x2F;span&gt;&lt;span&gt;DeviceBuffer（唯一显存 allocation owner，Drop 负责回收或释放）
&lt;&#x2F;span&gt;&lt;span&gt;    │  backend mempool 决定复用策略；Arc clone 被 in-flight 状态保留
&lt;&#x2F;span&gt;&lt;span&gt;    ▼
&lt;&#x2F;span&gt;&lt;span&gt;DeviceCompletion + HIP event（GPU 真正完成后才退休资源）
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这套机制的关键不是“到处使用智能指针”，而是让每一种类型准确表达一种生命周期。
本文说明这套机制的定义、用法、优势和边界。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;1-xian-hui-da-jie-lun-bu-shi-arc-mutex-tensor&quot;&gt;1. 先回答结论：不是 &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;Tensor&amp;gt;&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;对于只读权重和大多数不可变 Tensor，推荐形态是：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;Arc&amp;lt;DeviceBuffer&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;Arc&amp;lt;RocmCooperativeMlaWeights&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 只有确实需要独立共享整个权重组时
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;而不是：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;Arc&amp;lt;Mutex&amp;lt;DeviceBuffer&amp;gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;Arc&amp;lt;Mutex&amp;lt;RocmCooperativeMlaWeights&amp;gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;Arc&lt;&#x2F;code&gt; 回答“有多少个使用者共同拥有它”，&lt;code&gt;Mutex&lt;&#x2F;code&gt; 回答“多个线程如何独占修改它”。
权重加载后通常只读，克隆 &lt;code&gt;Arc&lt;&#x2F;code&gt; 就能共享底层 allocation，没有必要为了读取指针
而加锁。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;&amp;amp;RocmCooperativeMlaWeights&lt;&#x2F;code&gt; 也不是资源 owner。它只表示当前函数临时借用一个
已经存在的权重组：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;run_attention&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;weights&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;RocmCooperativeMlaWeights) {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 函数调用期间可以读取权重；不取得整个权重组的所有权。
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;zLLM 把 cooperative MLA 权重保存在 &lt;code&gt;RocmPrefillExperts&lt;&#x2F;code&gt; 的
&lt;code&gt;HashMap&amp;lt;usize, RocmCooperativeMlaWeights&amp;gt;&lt;&#x2F;code&gt; 中，查询时返回引用：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;super&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;cooperative_mla_layer&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;layer&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;(RocmContext, &amp;amp;RocmCooperativeMlaWeights), BackendError&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; weights = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.cooperative_mla.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;layer)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;ok_or_else&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* ... *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;)?;
&lt;&#x2F;span&gt;&lt;span&gt;    Ok((peer.context, weights))
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这里的顶层引用已经足够，因为 &lt;code&gt;RocmPrefillExperts&lt;&#x2F;code&gt; 拥有权重组，调用者只在当前
计算中读取它。权重组内部的每个 &lt;code&gt;RocmWeight&lt;&#x2F;code&gt; 又通过 &lt;code&gt;Arc&amp;lt;DeviceBuffer&amp;gt;&lt;&#x2F;code&gt; 拥有
真正的显存。因此即使 &lt;code&gt;RocmWeight&lt;&#x2F;code&gt; 被 &lt;code&gt;Clone&lt;&#x2F;code&gt;，显存也不会重复上传或复制。&lt;&#x2F;p&gt;
&lt;p&gt;只有当整个权重组要脱离 &lt;code&gt;RocmPrefillExperts&lt;&#x2F;code&gt;，进入后台任务、队列或多个独立执行器
长期共享时，才值得把 map 的值升级为 &lt;code&gt;Arc&amp;lt;RocmCooperativeMlaWeights&amp;gt;&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;2-di-yi-ceng-cpu-nei-cun-he-wen-jian-ju-bing-de-suo-you-quan&quot;&gt;2. 第一层：CPU 内存和文件句柄的所有权&lt;&#x2F;h2&gt;
&lt;p&gt;所有权链从模型文件开始，并遵循三种典型模式。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-1-du-zhan-zi-jie-vec-u8-vec-f32&quot;&gt;2.1 独占字节：&lt;code&gt;Vec&amp;lt;u8&amp;gt;&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;Vec&amp;lt;f32&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Safetensors 的 &lt;code&gt;TensorData&lt;&#x2F;code&gt; 直接拥有读取出来的字节：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;derive&lt;&#x2F;span&gt;&lt;span&gt;(Clone, Debug)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;TensorData {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;: String,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;dtype&lt;&#x2F;span&gt;&lt;span&gt;: String,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;shape&lt;&#x2F;span&gt;&lt;span&gt;: Vec&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;data&lt;&#x2F;span&gt;&lt;span&gt;: Vec&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;Vec&lt;&#x2F;code&gt; 是最直接的 RAII owner：结构存在，字节存在；结构被释放，字节也被释放。
需要转换时，&lt;code&gt;to_f32()&lt;&#x2F;code&gt; 返回一个新的 &lt;code&gt;Vec&amp;lt;f32&amp;gt;&lt;&#x2F;code&gt;，所有权随返回值移动给调用者。
这种数据没有共享需求，不需要 &lt;code&gt;Arc&lt;&#x2F;code&gt;，更不需要 &lt;code&gt;Mutex&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-2-gong-xiang-wen-jian-arc-file&quot;&gt;2.2 共享文件：&lt;code&gt;Arc&amp;lt;File&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;GGUF matrix 不会为每个 tensor 重新打开模型文件，而是保存共享文件句柄：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;GgufMatrix {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;: String,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;rows&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;columns&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;file&lt;&#x2F;span&gt;&lt;span&gt;: Arc&amp;lt;File&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;offset&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;bytes_len&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;bytes&lt;&#x2F;span&gt;&lt;span&gt;: Arc&amp;lt;OnceLock&amp;lt;Vec&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;矩阵切片通过 &lt;code&gt;Arc::clone(&amp;amp;self.file)&lt;&#x2F;code&gt; 共享文件，同时拥有独立的 offset 和 shape。
最后一个 matrix 释放后，文件句柄自然关闭。&lt;code&gt;OnceLock&amp;lt;Vec&amp;lt;u8&amp;gt;&amp;gt;&lt;&#x2F;code&gt; 则表达“原始字节
最多惰性加载一次”；读路径无需每次竞争互斥锁。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-3-gong-xiang-ke-bian-suo-yin-arc-mutex-hashmap&quot;&gt;2.3 共享可变索引：&lt;code&gt;Arc&amp;lt;Mutex&amp;lt;HashMap&amp;lt;...&amp;gt;&amp;gt;&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Safetensors shard cache 是 &lt;code&gt;Mutex&lt;&#x2F;code&gt; 真正适合出现的地方：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;SharedShards = Arc&amp;lt;Mutex&amp;lt;HashMap&amp;lt;String, Arc&amp;lt;CachedShard&amp;gt;&amp;gt;&amp;gt;&amp;gt;;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这里有一个会被多个 store 共同修改的 &lt;code&gt;HashMap&lt;&#x2F;code&gt;，所以既需要 &lt;code&gt;Arc&lt;&#x2F;code&gt; 共享目录，也
需要 &lt;code&gt;Mutex&lt;&#x2F;code&gt; 串行化插入。map 中的 shard 自身是 &lt;code&gt;Arc&amp;lt;CachedShard&amp;gt;&lt;&#x2F;code&gt;；拿到 shard
以后即可释放 map 锁，后续读取不必一直持锁。&lt;&#x2F;p&gt;
&lt;p&gt;全局缓存只保存 &lt;code&gt;Weak&lt;&#x2F;code&gt;，防止全局表永久拥有所有模型：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;SharedShardCaches =
&lt;&#x2F;span&gt;&lt;span&gt;    HashMap&amp;lt;PathBuf, Weak&amp;lt;Mutex&amp;lt;HashMap&amp;lt;String, Arc&amp;lt;CachedShard&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这是一种值得统一采用的缓存模式：&lt;strong&gt;全局 registry 用 &lt;code&gt;Weak&lt;&#x2F;code&gt; 定位，真实使用者用
&lt;code&gt;Arc&lt;&#x2F;code&gt; 拥有，&lt;code&gt;Mutex&lt;&#x2F;code&gt; 只保护 registry 的结构变化。&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;3-di-er-ceng-devicebuffer-shi-xian-cun-de-wei-yi-owner&quot;&gt;3. 第二层：&lt;code&gt;DeviceBuffer&lt;&#x2F;code&gt; 是显存的唯一 owner&lt;&#x2F;h2&gt;
&lt;p&gt;ROCm 路径中最重要的资源类型是 &lt;code&gt;DeviceBuffer&lt;&#x2F;code&gt;：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;DeviceBuffer {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;device_id&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;pointer&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;*mut&lt;&#x2F;span&gt;&lt;span&gt; c_void,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;bytes&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;capacity_bytes&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;recyclable&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;bool&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;async_allocated&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;bool&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;owner&lt;&#x2F;span&gt;&lt;span&gt;: Option&amp;lt;Arc&amp;lt;DeviceBuffer&amp;gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 省略 stage completion 与 deferred upload 状态
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;它同时记录设备、裸地址、逻辑大小、真实 allocation 容量、分配方式和可选上游
owner。HIP 裸指针只被关在这个底层类型里；backend 和 runtime 不直接负责调用
&lt;code&gt;hipFree&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;p&gt;真正的释放策略集中在 &lt;code&gt;Drop for DeviceBuffer&lt;&#x2F;code&gt;：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;Drop &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;DeviceBuffer {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;drop&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.owner.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;is_some&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; view 不释放 owner 的 allocation
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.async_allocated {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 优先在正确 stream 上 hipFreeAsync
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.recyclable {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 能安全复用时归还显式 pool
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 其余情况最终 hipFree
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;从上层看，分配和释放因此成为普通 Rust 值的创建与离开作用域：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; buffer = Arc::new(DeviceBuffer::allocate(device_id, bytes)?);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; another_user = Arc::clone(&amp;amp;buffer);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;drop&lt;&#x2F;span&gt;&lt;span&gt;(buffer);       &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; allocation 仍存在
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;drop&lt;&#x2F;span&gt;&lt;span&gt;(another_user); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 最后一个 Arc 消失，才进入 DeviceBuffer::drop
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;RAII 在这里带来的不仅是“自动 free”，还把同步分配、异步分配、显式复用池和 view
统一到了同一个释放入口。调用者不需要记住这块地址最初来自 &lt;code&gt;hipMalloc&lt;&#x2F;code&gt;、
&lt;code&gt;hipMallocAsync&lt;&#x2F;code&gt; 还是 pool。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;4-di-san-ceng-tensor-ba-luo-ji-xin-xi-yu-wu-li-cun-chu-zu-he-qi-lai&quot;&gt;4. 第三层：Tensor 把逻辑信息与物理存储组合起来&lt;&#x2F;h2&gt;
&lt;p&gt;ROCm Tensor 的定义是：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;derive&lt;&#x2F;span&gt;&lt;span&gt;(Debug, Clone)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;RocmTensor {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;data&lt;&#x2F;span&gt;&lt;span&gt;: Vec&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;f32&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;rows&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;cols&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;dtype&lt;&#x2F;span&gt;&lt;span&gt;: RocmTensorDType,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;layout&lt;&#x2F;span&gt;&lt;span&gt;: RocmTensorLayout,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;device&lt;&#x2F;span&gt;&lt;span&gt;: Option&amp;lt;Arc&amp;lt;DeviceBuffer&amp;gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;它区分两类信息：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rows&lt;&#x2F;code&gt;、&lt;code&gt;cols&lt;&#x2F;code&gt;、&lt;code&gt;dtype&lt;&#x2F;code&gt;、&lt;code&gt;layout&lt;&#x2F;code&gt; 是逻辑 Tensor 描述；&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;data&lt;&#x2F;code&gt; 是必要时存在的 host shadow；&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;device&lt;&#x2F;code&gt; 是可选的设备 resident allocation。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;构造一个纯设备 Tensor 时，上层只移动或共享 buffer：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;device_tensor_with_arc&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;buffer&lt;&#x2F;span&gt;&lt;span&gt;: Arc&amp;lt;DeviceBuffer&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;rows&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;cols&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;dtype&lt;&#x2F;span&gt;&lt;span&gt;: RocmTensorDType,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; RocmTensor {
&lt;&#x2F;span&gt;&lt;span&gt;    debug_assert_eq!(
&lt;&#x2F;span&gt;&lt;span&gt;        buffer.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;bytes&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;        rows * cols * dtype.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;element_bytes&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;    );
&lt;&#x2F;span&gt;&lt;span&gt;    RocmTensor {
&lt;&#x2F;span&gt;&lt;span&gt;        data: Vec::new(),
&lt;&#x2F;span&gt;&lt;span&gt;        rows,
&lt;&#x2F;span&gt;&lt;span&gt;        cols,
&lt;&#x2F;span&gt;&lt;span&gt;        dtype,
&lt;&#x2F;span&gt;&lt;span&gt;        layout: RocmTensorLayout::RowMajor,
&lt;&#x2F;span&gt;&lt;span&gt;        device: Some(buffer),
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;RocmTensor::clone()&lt;&#x2F;code&gt; 会复制少量 metadata、按需复制 host &lt;code&gt;Vec&lt;&#x2F;code&gt;，并克隆设备
buffer 的 &lt;code&gt;Arc&lt;&#x2F;code&gt;；它不会复制显存内容。只要任意 Tensor、权重、cache 或 in-flight
任务还持有这个 &lt;code&gt;Arc&lt;&#x2F;code&gt;，allocation 就不会进入 &lt;code&gt;Drop&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;p&gt;这一层让 kernel 接口可以借用：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;kernel&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;input&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;RocmTensor, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;weight&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;RocmWeight) -&amp;gt; Result&amp;lt;RocmTensor, Error&amp;gt;;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;借用负责限制当前 CPU 调用栈中的访问；Tensor 内部的 &lt;code&gt;Arc&amp;lt;DeviceBuffer&amp;gt;&lt;&#x2F;code&gt; 负责底层
allocation 的共享所有权。两者职责不同，组合起来才完整。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;5-di-si-ceng-quan-zhong-shi-bu-ke-bian-zi-yuan-ji-he-bu-shi-hu-chi-zhuang-tai&quot;&gt;5. 第四层：权重是不可变资源集合，不是互斥状态&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;code&gt;RocmWeight&lt;&#x2F;code&gt; 用枚举表达权重真正的 resident 形态。Dense 权重可以拥有 host F32
和设备副本；W4A16、W8A16、FP8、MXFP4、GGUF 则直接拥有 packed code、scale
等 &lt;code&gt;Arc&amp;lt;DeviceBuffer&amp;gt;&lt;&#x2F;code&gt;：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;enum &lt;&#x2F;span&gt;&lt;span&gt;RocmWeightInner {
&lt;&#x2F;span&gt;&lt;span&gt;    Quantized(RocmQuantizedWeight),
&lt;&#x2F;span&gt;&lt;span&gt;    Dense {
&lt;&#x2F;span&gt;&lt;span&gt;        data: Vec&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;f32&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        resident: Option&amp;lt;Arc&amp;lt;DeviceBuffer&amp;gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        resident_bf16: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;bool&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        router_bf16: Arc&amp;lt;OnceLock&amp;lt;Arc&amp;lt;DeviceBuffer&amp;gt;&amp;gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    },
&lt;&#x2F;span&gt;&lt;span&gt;    Gguf(GgufMatrix),
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;RocmCooperativeMlaWeights&lt;&#x2F;code&gt; 再把同一执行能力所需的六个权重组合起来：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;derive&lt;&#x2F;span&gt;&lt;span&gt;(Clone)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;RocmCooperativeMlaWeights {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;owner_q_b&lt;&#x2F;span&gt;&lt;span&gt;: RocmWeight,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;peer_q_b&lt;&#x2F;span&gt;&lt;span&gt;: RocmWeight,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;owner_kv_b&lt;&#x2F;span&gt;&lt;span&gt;: RocmWeight,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;peer_kv_b&lt;&#x2F;span&gt;&lt;span&gt;: RocmWeight,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;owner_o&lt;&#x2F;span&gt;&lt;span&gt;: RocmWeight,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;peer_o&lt;&#x2F;span&gt;&lt;span&gt;: RocmWeight,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这个 struct 的价值是表达“一层 cooperative MLA 的完整 resident 权重集合”，不是
提供并发控制。字段加载完成后保持不变，所以只读借用或 &lt;code&gt;Arc&lt;&#x2F;code&gt; 共享即可。&lt;&#x2F;p&gt;
&lt;p&gt;惰性生成的派生权重使用 &lt;code&gt;OnceLock&lt;&#x2F;code&gt;，例如 BF16 router cache：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;Arc&amp;lt;OnceLock&amp;lt;Arc&amp;lt;DeviceBuffer&amp;gt;&amp;gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这比 &lt;code&gt;Mutex&amp;lt;Option&amp;lt;Arc&amp;lt;DeviceBuffer&amp;gt;&amp;gt;&amp;gt;&lt;&#x2F;code&gt; 更精确：类型直接保证只初始化一次，初始化
完成后的热路径只读取结果。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;6-view-zi-tensor-bi-xu-fan-xiang-yong-you-yuan-allocation&quot;&gt;6. View：子 Tensor 必须反向拥有原 allocation&lt;&#x2F;h2&gt;
&lt;p&gt;GPU Tensor 经常只使用大 buffer 中的一段。如果 view 只保存偏移后的裸指针，原
buffer 一旦离开作用域就会释放，view 立即悬空。&lt;&#x2F;p&gt;
&lt;p&gt;zLLM 的 &lt;code&gt;DeviceBuffer::view&lt;&#x2F;code&gt; 把上游 owner 存进 view：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;owner&lt;&#x2F;span&gt;&lt;span&gt;: Arc&amp;lt;DeviceBuffer&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;offset&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;bytes&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;DeviceBuffer, String&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; pointer = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;unsafe &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        owner.pointer.cast::&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;add&lt;&#x2F;span&gt;&lt;span&gt;(offset).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;cast&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    };
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(DeviceBuffer {
&lt;&#x2F;span&gt;&lt;span&gt;        device_id: owner.device_id,
&lt;&#x2F;span&gt;&lt;span&gt;        pointer,
&lt;&#x2F;span&gt;&lt;span&gt;        bytes,
&lt;&#x2F;span&gt;&lt;span&gt;        owner: Some(owner),
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; view 自己不回收 allocation
&lt;&#x2F;span&gt;&lt;span&gt;        ..
&lt;&#x2F;span&gt;&lt;span&gt;    })
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;所有权关系变成：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;RocmTensor
&lt;&#x2F;span&gt;&lt;span&gt;  └─ Arc&amp;lt;View DeviceBuffer&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;       └─ Arc&amp;lt;Owner DeviceBuffer&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;            └─ HIP allocation
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;view 的 &lt;code&gt;Drop&lt;&#x2F;code&gt; 发现 &lt;code&gt;owner.is_some()&lt;&#x2F;code&gt; 后不会释放裸地址；它只减少 owner 的引用
计数。最后一个 view 和原 owner 都消失后，真实 allocation 才会被释放或放回池。&lt;&#x2F;p&gt;
&lt;p&gt;这是用 Rust 封装显存后非常典型的收益：原本需要人工约定的“slice 不能活得比
base buffer 久”，现在由结构本身保证。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;7-zui-rong-yi-lou-diao-de-yi-ceng-gpu-yi-bu-sheng-ming-zhou-qi&quot;&gt;7. 最容易漏掉的一层：GPU 异步生命周期&lt;&#x2F;h2&gt;
&lt;p&gt;到这里仍然没有完全解决问题。HIP kernel launch 通常是异步的：Rust 函数已经
返回、局部 &lt;code&gt;Arc&lt;&#x2F;code&gt; 已经 drop，并不代表 GPU 已经读完对应地址。&lt;&#x2F;p&gt;
&lt;p&gt;危险的伪代码如下：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;launch&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;input&lt;&#x2F;span&gt;&lt;span&gt;: Arc&amp;lt;DeviceBuffer&amp;gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;unsafe &lt;&#x2F;span&gt;&lt;span&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;hip_launch&lt;&#x2F;span&gt;&lt;span&gt;(input.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;device_pointer&lt;&#x2F;span&gt;&lt;span&gt;()) };
&lt;&#x2F;span&gt;&lt;span&gt;} &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; input 在 CPU 侧释放，但 GPU 可能仍在执行
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;因此完整机制必须增加“完成所有权”。zLLM 用 &lt;code&gt;DeviceCompletion&lt;&#x2F;code&gt; 记录当前 stream
上的 HIP event，并持有这批异步工作仍依赖的资源：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;DeviceCompletion {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;device_id&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;event&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;p2p_sources&lt;&#x2F;span&gt;&lt;span&gt;: Mutex&amp;lt;Vec&amp;lt;PendingP2pSource&amp;gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;stage_inputs&lt;&#x2F;span&gt;&lt;span&gt;: Vec&amp;lt;Arc&amp;lt;DeviceBuffer&amp;gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;recycled&lt;&#x2F;span&gt;&lt;span&gt;: Mutex&amp;lt;Vec&amp;lt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;)&amp;gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;retired&lt;&#x2F;span&gt;&lt;span&gt;: AtomicBool,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;提交阶段形成的临时输入与 P2P 来源被转移进 completion：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; completion = DeviceCompletion::record(device_id)?;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;while &lt;&#x2F;span&gt;&lt;span&gt;!completion.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;is_complete&lt;&#x2F;span&gt;&lt;span&gt;()? {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; CPU 可以处理别的请求
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; event 完成后，p2p source、stage input 和待回收 allocation 才退休
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;对于 ordered P2P，代码会克隆来源 buffer 的 &lt;code&gt;Arc&lt;&#x2F;code&gt;，把它放进
&lt;code&gt;PendingP2pSource&lt;&#x2F;code&gt;，再由消费端 completion 持有。于是来源卡上的 allocation 至少
存活到复制或 peer read 真正完成，而不是只活到 &lt;code&gt;hipMemcpyPeerAsync&lt;&#x2F;code&gt; 返回。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;Mutex&lt;&#x2F;code&gt; 在 &lt;code&gt;DeviceCompletion&lt;&#x2F;code&gt; 中也是局部使用：它保护可能被轮询、等待或析构路径
共同退休的两个可变队列。它没有包住 Tensor，也没有包住 GPU 数据本身。&lt;&#x2F;p&gt;
&lt;p&gt;这给出一个非常重要的公式：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;安全的 GPU 资源生命周期
&lt;&#x2F;span&gt;&lt;span&gt;  = Rust 所有权（Arc &#x2F; Drop）
&lt;&#x2F;span&gt;&lt;span&gt;  + GPU 顺序（stream &#x2F; event）
&lt;&#x2F;span&gt;&lt;span&gt;  + in-flight 引用保留（completion owns Arc）
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;缺少任何一项都不完整。&lt;code&gt;Arc&lt;&#x2F;code&gt; 不理解 GPU event，HIP event 也不会自动增加 Rust
引用计数；&lt;code&gt;DeviceCompletion&lt;&#x2F;code&gt; 是把两个世界接起来的桥。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;8-fu-yong-chi-shi-fang-bu-deng-yu-ma-shang-hipfree&quot;&gt;8. 复用池：释放不等于马上 &lt;code&gt;hipFree&lt;&#x2F;code&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;高性能推理不能让每个临时 Tensor 都直接触发同步 &lt;code&gt;hipFree&lt;&#x2F;code&gt;。&lt;code&gt;DeviceBuffer&lt;&#x2F;code&gt;
的 &lt;code&gt;Drop&lt;&#x2F;code&gt; 会依据来源和状态选择：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;view：只释放对 owner 的引用；&lt;&#x2F;li&gt;
&lt;li&gt;async allocation：优先 &lt;code&gt;hipFreeAsync&lt;&#x2F;code&gt;，保持 stream 顺序；&lt;&#x2F;li&gt;
&lt;li&gt;recyclable buffer：进入 stage 延迟回收或设备池；&lt;&#x2F;li&gt;
&lt;li&gt;其他独占 allocation：最终调用 &lt;code&gt;hipFree&lt;&#x2F;code&gt;。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;池本身用 &lt;code&gt;Mutex&amp;lt;DeviceBufferPool&amp;gt;&lt;&#x2F;code&gt; 保护 metadata：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;DeviceBufferPool {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;buffers&lt;&#x2F;span&gt;&lt;span&gt;: HashMap&amp;lt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;), Vec&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;pending&lt;&#x2F;span&gt;&lt;span&gt;: HashMap&amp;lt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;), Vec&amp;lt;PendingDeviceBuffer&amp;gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;bytes&lt;&#x2F;span&gt;&lt;span&gt;: HashMap&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;available_events&lt;&#x2F;span&gt;&lt;span&gt;: Vec&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;需要锁的是空闲地址列表、pending event 和计账，而不是 allocation 中的数十 MiB
数据。锁的作用域仅覆盖一次取出或归还，kernel 执行期间不会持锁。&lt;&#x2F;p&gt;
&lt;p&gt;这种设计把“逻辑释放”和“物理释放”分开：最后一个 &lt;code&gt;Arc&lt;&#x2F;code&gt; 消失代表业务不再需要
Tensor；&lt;code&gt;Drop&lt;&#x2F;code&gt; 再根据 event 与 pool 状态决定 allocation 是立即 free、异步 free
还是安全复用。上层算法无需知道这些差异。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;9-backend-wu-guan-de-zi-yuan-chi-qi-yue&quot;&gt;9. Backend 无关的资源池契约&lt;&#x2F;h2&gt;
&lt;p&gt;只分析 &lt;code&gt;DeviceBufferPool&lt;&#x2F;code&gt; 会产生一个错误印象：仿佛内存池是 ROCm&#x2F;HIP 的特殊
优化。zLLM 把资源池定义为所有 backend 共同遵守的资源能力：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;ROCm 的 &lt;code&gt;DeviceBufferPool&lt;&#x2F;code&gt; 保存 available 与 pending allocation，并用 HIP
event 判断何时可重新使用；&lt;&#x2F;li&gt;
&lt;li&gt;Metal 的 &lt;code&gt;MetalContext::scratch_pool&lt;&#x2F;code&gt; 按 &lt;code&gt;(tag, len, slot)&lt;&#x2F;code&gt; 保存
&lt;code&gt;metal::Buffer&lt;&#x2F;code&gt;，利用同一 command queue 上的提交顺序复用 workspace；&lt;&#x2F;li&gt;
&lt;li&gt;CPU Tensor 主要由 &lt;code&gt;Vec&amp;lt;T&amp;gt;&lt;&#x2F;code&gt; 拥有，普通 allocator 承担大部分工作，并不一定
需要 GPU 式 event pool。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;所以 pool 的架构归属是 &lt;strong&gt;backend 资源管理能力&lt;&#x2F;strong&gt;，而不是 ROCm kernel 的公共
语义。&lt;code&gt;backend&#x2F;mod.rs&lt;&#x2F;code&gt; 用统一的 &lt;code&gt;MemoryPool&lt;&#x2F;code&gt; trait、&lt;code&gt;MemoryRequest&lt;&#x2F;code&gt;、
&lt;code&gt;MemoryKind&lt;&#x2F;code&gt; 与 &lt;code&gt;MemoryLifetime&lt;&#x2F;code&gt; 定义契约。ROCm 的物理 pool 位于
&lt;code&gt;kernel&#x2F;rocm&#x2F;hip&#x2F;device_buffer.rs&lt;&#x2F;code&gt;，Metal 的物理 pool 位于
&lt;code&gt;backend&#x2F;metal&#x2F;context.rs&lt;&#x2F;code&gt;；公共契约只统一请求和 RAII handle，不移动平台实现。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;9-1-tong-yong-de-ying-gai-shi-sheng-ming-zhou-qi-yu-yi-bu-shi-luo-zhi-zhen-shi-xian&quot;&gt;9.1 通用的应该是生命周期语义，不是裸指针实现&lt;&#x2F;h3&gt;
&lt;p&gt;HIP、Metal、CUDA 和 CPU 的 allocation 不能被强行塞进一个最低公分母：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;HIP 有 device id、stream-ordered allocation、跨卡 event 和 P2P 生命周期；&lt;&#x2F;li&gt;
&lt;li&gt;Metal 的 &lt;code&gt;MTLBuffer&lt;&#x2F;code&gt; 本身是引用计数对象，使用 command buffer&#x2F;queue 表达完成；&lt;&#x2F;li&gt;
&lt;li&gt;CUDA 有 stream、event、&lt;code&gt;cudaMallocAsync&lt;&#x2F;code&gt; 和设备 mempool；&lt;&#x2F;li&gt;
&lt;li&gt;CPU 没有 GPU completion，alignment、NUMA、pinned memory 才是主要差异。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;因此 backend 无关层不应持有 &lt;code&gt;*mut c_void&lt;&#x2F;code&gt;，也不应直接规定 &lt;code&gt;hipFreeAsync&lt;&#x2F;code&gt; 一类
平台动作。它真正需要统一的是五个问题：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;申请：需要多少字节、对齐、用途和位置？
&lt;&#x2F;span&gt;&lt;span&gt;拥有：返回的 storage 由哪个 RAII handle 持有？
&lt;&#x2F;span&gt;&lt;span&gt;借出：同一 allocation 如何形成 Tensor&#x2F;view？
&lt;&#x2F;span&gt;&lt;span&gt;退休：最后一个逻辑使用者退出后，设备是否已经完成？
&lt;&#x2F;span&gt;&lt;span&gt;复用：满足哪个 completion 条件后，allocation 可以重新进入 available？
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;统一接口这样表达这条边界：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;trait &lt;&#x2F;span&gt;&lt;span&gt;MemoryPool {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Memory: Clone + Send + Sync + &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;static&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;allocate_memory&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;request&lt;&#x2F;span&gt;&lt;span&gt;: MemoryRequest,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Memory, BackendError&amp;gt;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;memory_bytes&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;memory&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Memory) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MemoryRequest {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;bytes&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;alignment&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;kind&lt;&#x2F;span&gt;&lt;span&gt;: MemoryKind,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;lifetime&lt;&#x2F;span&gt;&lt;span&gt;: MemoryLifetime,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tag&lt;&#x2F;span&gt;&lt;span&gt;: Option&amp;lt;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;static str&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;slot&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;enum &lt;&#x2F;span&gt;&lt;span&gt;MemoryKind {
&lt;&#x2F;span&gt;&lt;span&gt;    Scratch,
&lt;&#x2F;span&gt;&lt;span&gt;    Activation,
&lt;&#x2F;span&gt;&lt;span&gt;    Cache,
&lt;&#x2F;span&gt;&lt;span&gt;    Weight,
&lt;&#x2F;span&gt;&lt;span&gt;    Transfer,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;enum &lt;&#x2F;span&gt;&lt;span&gt;MemoryLifetime {
&lt;&#x2F;span&gt;&lt;span&gt;    Operation,
&lt;&#x2F;span&gt;&lt;span&gt;    Stage,
&lt;&#x2F;span&gt;&lt;span&gt;    Session,
&lt;&#x2F;span&gt;&lt;span&gt;    Model,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Metal 的 &lt;code&gt;cached_zero_buffer_slot()&lt;&#x2F;code&gt; 通过这一 trait 申请 Scratch；ROCm 的
&lt;code&gt;concat_token_rows()&lt;&#x2F;code&gt; 和 reserved concat allocation 通过同一 trait 申请
Operation&#x2F;Stage Activation。接口刻意不增加统一 &lt;code&gt;view()&lt;&#x2F;code&gt; 或 &lt;code&gt;retire_after()&lt;&#x2F;code&gt;：view
由平台 storage 保留 owner，retirement 接到 &lt;code&gt;StageExecutionBackend::Completion&lt;&#x2F;code&gt;
和 backend RAII &lt;code&gt;Drop&lt;&#x2F;code&gt;，避免复制一套完成协议。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;9-2-pool-yu-arc-ge-guan-yi-ban&quot;&gt;9.2 Pool 与 &lt;code&gt;Arc&lt;&#x2F;code&gt; 各管一半&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;Arc&lt;&#x2F;code&gt; 与 mempool 不能相互替代：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;Arc strong_count &amp;gt; 0
&lt;&#x2F;span&gt;&lt;span&gt;    → 仍有逻辑 owner，不能退休
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Arc strong_count == 0
&lt;&#x2F;span&gt;&lt;span&gt;    → 进入 Storage::drop &#x2F; retire
&lt;&#x2F;span&gt;&lt;span&gt;    → completion 未完成：进入 pending
&lt;&#x2F;span&gt;&lt;span&gt;    → completion 已完成：进入 available
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;下一次同类 allocation
&lt;&#x2F;span&gt;&lt;span&gt;    → 从 available best-fit&#x2F;tag-slot 复用
&lt;&#x2F;span&gt;&lt;span&gt;    → 没有合适块才向设备申请新 allocation
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;Arc&lt;&#x2F;code&gt; 决定“业务是否仍然引用资源”，pool 决定“业务释放后物理 allocation 去哪里”。
GPU completion 位于两者之间，决定资源何时从 pending 变成 available。这三者组合
才是一套完整的 backend 内存管理。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;9-3-rocm-yu-metal-de-ce-lue-wei-shen-me-bu-tong&quot;&gt;9.3 ROCm 与 Metal 的策略为什么不同&lt;&#x2F;h3&gt;
&lt;p&gt;ROCm &lt;code&gt;DeviceBufferPool&lt;&#x2F;code&gt; 按 &lt;code&gt;(device_id, capacity)&lt;&#x2F;code&gt; 管理裸 allocation，并区分
&lt;code&gt;buffers&lt;&#x2F;code&gt; 与 &lt;code&gt;pending&lt;&#x2F;code&gt;。这是因为 host 端 drop 时 GPU 可能尚未执行完成，必须用
event 推进 pending；同时 best-fit 容量复用能适应不同 prefill shape。&lt;&#x2F;p&gt;
&lt;p&gt;Metal &lt;code&gt;scratch_pool&lt;&#x2F;code&gt; 按 &lt;code&gt;(tag, len, slot)&lt;&#x2F;code&gt; 管理 &lt;code&gt;Buffer&lt;&#x2F;code&gt;。&lt;code&gt;tag&lt;&#x2F;code&gt; 防止语义上可能同时
活跃的 workspace 意外别名，&lt;code&gt;slot&lt;&#x2F;code&gt; 则允许多个同尺寸切片同时存在。同一 queue 上
“写入、消费、下一次写入”的顺序为复用提供安全边界。&lt;&#x2F;p&gt;
&lt;p&gt;因此，backend 无关不等于所有 backend 使用相同 key 和相同容器：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层次&lt;&#x2F;th&gt;&lt;th&gt;应统一&lt;&#x2F;th&gt;&lt;th&gt;不应统一&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;runtime&lt;&#x2F;td&gt;&lt;td&gt;Tensor 用途、生命周期等级、stage&#x2F;session 边界&lt;&#x2F;td&gt;&lt;td&gt;HIP event、Metal command buffer&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;backend capability&lt;&#x2F;td&gt;&lt;td&gt;allocate&#x2F;view&#x2F;retire&#x2F;completion 的行为契约&lt;&#x2F;td&gt;&lt;td&gt;pool 的 key、best-fit 算法、平台句柄&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;backend 实现&lt;&#x2F;td&gt;&lt;td&gt;本平台内一致的 owner 与回收路径&lt;&#x2F;td&gt;&lt;td&gt;另一平台的同步原语&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;kernel&lt;&#x2F;td&gt;&lt;td&gt;借用输入、写入输出&lt;&#x2F;td&gt;&lt;td&gt;决定全局资源驻留和 pool 策略&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h3 id=&quot;9-4-quan-zhong-activation-kv-yu-scratch-bu-neng-gong-yong-yi-chong-hui-shou-ce-lue&quot;&gt;9.4 权重、activation、KV 与 scratch 不能共用一种回收策略&lt;&#x2F;h3&gt;
&lt;p&gt;统一 pool 还必须保留数据用途，而不能只看字节数：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;resident 权重&lt;&#x2F;strong&gt;：模型生命周期内常驻，通常不进入短期复用池；&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;KV&#x2F;DSA cache&lt;&#x2F;strong&gt;：属于 session，可增长、持久化或迁移，不能被 stage 结束回收；&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;activation&lt;&#x2F;strong&gt;：跨若干 kernel 或 stage，由 completion 决定退休；&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;scratch&#x2F;workspace&lt;&#x2F;strong&gt;：生命周期最短，最适合按 tag&#x2F;shape 高频复用；&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;pinned host staging&lt;&#x2F;strong&gt;：受 DMA completion 约束，不能按普通 &lt;code&gt;Vec&amp;lt;u8&amp;gt;&lt;&#x2F;code&gt; 处理。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;也就是说，backend 无关的 mempool 首先需要统一“生命周期等级”，然后让 ROCm、
Metal、CUDA 或其他 backend 选择物理实现。若只提供 &lt;code&gt;alloc(bytes)&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;free(ptr)&lt;&#x2F;code&gt;，
上层仍然无法表达权重、KV 和临时 workspace 的根本区别。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;9-5-tong-yi-jia-gou-bian-jie&quot;&gt;9.5 统一架构边界&lt;&#x2F;h3&gt;
&lt;p&gt;zLLM 的 mempool 遵守以下不变量：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;保留 &lt;code&gt;RocmTensor&lt;&#x2F;code&gt;、&lt;code&gt;MetalTensor&lt;&#x2F;code&gt; 等 backend associated type，不制造统一
&lt;code&gt;dyn Tensor&lt;&#x2F;code&gt;；&lt;&#x2F;li&gt;
&lt;li&gt;backend 生命周期边界统一描述 allocation 用途、stage completion 和 session
retirement；&lt;&#x2F;li&gt;
&lt;li&gt;ROCm 用 event 驱动 &lt;code&gt;pending → available&lt;&#x2F;code&gt;，Metal 利用 command queue 顺序与
retained &lt;code&gt;Buffer&lt;&#x2F;code&gt;；&lt;&#x2F;li&gt;
&lt;li&gt;Tensor&#x2F;storage handle 持有 pool return token，或在 &lt;code&gt;Drop&lt;&#x2F;code&gt; 中回到所属 pool；&lt;&#x2F;li&gt;
&lt;li&gt;公共 capability 只表达真实共性，不建立通用裸指针 allocator，不让平台差异
反向污染 runtime。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;完整关系是：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;Backend
&lt;&#x2F;span&gt;&lt;span&gt;  ├─ Tensor &#x2F; Weight &#x2F; Cache associated types
&lt;&#x2F;span&gt;&lt;span&gt;  ├─ Completion（平台自己的 event&#x2F;command buffer&#x2F;fence）
&lt;&#x2F;span&gt;&lt;span&gt;  └─ Memory lifecycle policy
&lt;&#x2F;span&gt;&lt;span&gt;       ├─ allocate
&lt;&#x2F;span&gt;&lt;span&gt;       ├─ retain in flight
&lt;&#x2F;span&gt;&lt;span&gt;       ├─ retire to pending
&lt;&#x2F;span&gt;&lt;span&gt;       └─ promote&#x2F;reuse&#x2F;release
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;runtime 只表达资源用途与生命周期，不知道底层使用 HIP pool、Metal buffer
cache、CUDA mempool 还是普通 CPU allocator。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;10-yi-tao-tong-yi-de-shi-yong-gui-ze&quot;&gt;10. 一套统一的使用规则&lt;&#x2F;h2&gt;
&lt;p&gt;所有 backend 代码统一遵守下面的规则。&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;场景&lt;&#x2F;th&gt;&lt;th&gt;推荐类型&lt;&#x2F;th&gt;&lt;th&gt;原因&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;函数内临时读取 Tensor&#x2F;权重&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;&amp;amp;RocmTensor&lt;&#x2F;code&gt;、&lt;code&gt;&amp;amp;RocmWeight&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;不转移所有权，作用域最短&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;多个 Tensor&#x2F;权重共享一块显存&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;Arc&amp;lt;DeviceBuffer&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;最后一个使用者负责触发回收&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;共享整组只读权重&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;Arc&amp;lt;Weights&amp;gt;&lt;&#x2F;code&gt;，或由单一 manager 拥有并返回 &lt;code&gt;&amp;amp;Weights&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;不需要锁&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;只初始化一次的派生权重&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;OnceLock&amp;lt;Arc&amp;lt;DeviceBuffer&amp;gt;&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;语义比互斥 Option 更准确&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;可变 cache&#x2F;registry metadata&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;Mutex&amp;lt;HashMap&amp;lt;...&amp;gt;&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;只保护结构变化&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;全局但不应永久保活的 cache&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;Weak&amp;lt;T&amp;gt;&lt;&#x2F;code&gt; registry + &lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;&#x2F;code&gt; user&lt;&#x2F;td&gt;&lt;td&gt;自动随最后一个用户退出&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;大 buffer 的逻辑切片&lt;&#x2F;td&gt;&lt;td&gt;view 内保存 &lt;code&gt;Arc&amp;lt;owner&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;防止 base allocation 提前释放&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;异步 kernel&#x2F;P2P 依赖&lt;&#x2F;td&gt;&lt;td&gt;completion 持有 &lt;code&gt;Arc&amp;lt;DeviceBuffer&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;直到 event 完成才退休&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;独占、短生命周期 CPU 数据&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;Vec&amp;lt;T&amp;gt;&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;Box&amp;lt;T&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;避免无意义引用计数&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;新增 GPU 接口时，可以按下面的顺序审查：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;裸 pointer 是否只存在于 &lt;code&gt;DeviceBuffer&lt;&#x2F;code&gt; 或 kernel FFI 边界？&lt;&#x2F;li&gt;
&lt;li&gt;返回 view 时是否保留了 owner？&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;Arc&lt;&#x2F;code&gt; 的最后一个 CPU 引用消失时，GPU 是否可能仍在使用它？&lt;&#x2F;li&gt;
&lt;li&gt;如果可能，哪个 completion&#x2F;event 持有它？&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;Mutex&lt;&#x2F;code&gt; 是否只保护确实可变的 metadata，锁内是否包含 I&#x2F;O、同步或 kernel 等待？&lt;&#x2F;li&gt;
&lt;li&gt;cache 是否需要 &lt;code&gt;Weak&lt;&#x2F;code&gt; 防止永久驻留？&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;11-yi-ge-cong-jia-zai-dao-yi-bu-zhi-xing-de-wan-zheng-li-zi&quot;&gt;11. 一个从加载到异步执行的完整例子&lt;&#x2F;h2&gt;
&lt;p&gt;下面的简化例子展示这套机制如何连起来：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 1. 上传后，Arc 成为 resident allocation 的共享 owner。
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; q_b = Arc::new(DeviceBuffer::upload(device_id, q_b_bytes)?);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 2. 权重只读，不加 Mutex。
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; weights = Arc::new(MlaWeights {
&lt;&#x2F;span&gt;&lt;span&gt;    q_b: Arc::clone(&amp;amp;q_b),
&lt;&#x2F;span&gt;&lt;span&gt;});
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 3. Tensor view 反向持有 q_b，避免基址先释放。
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; shard = Arc::new(DeviceBuffer::view(
&lt;&#x2F;span&gt;&lt;span&gt;    Arc::clone(&amp;amp;weights.q_b),
&lt;&#x2F;span&gt;&lt;span&gt;    shard_offset,
&lt;&#x2F;span&gt;&lt;span&gt;    shard_bytes,
&lt;&#x2F;span&gt;&lt;span&gt;)?);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 4. kernel 只借用资源。
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;launch_mla&lt;&#x2F;span&gt;&lt;span&gt;(input_buffer.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;as_ref&lt;&#x2F;span&gt;&lt;span&gt;(), shard.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;as_ref&lt;&#x2F;span&gt;&lt;span&gt;())?;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 5. 如果 GPU 工作越过当前 Rust 作用域，submission 必须取得 Arc。
&lt;&#x2F;span&gt;&lt;span&gt;submission.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;retain&lt;&#x2F;span&gt;&lt;span&gt;(Arc::clone(&amp;amp;input_buffer));
&lt;&#x2F;span&gt;&lt;span&gt;submission.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;retain&lt;&#x2F;span&gt;&lt;span&gt;(Arc::clone(&amp;amp;shard));
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; completion = DeviceCompletion::record(device_id)?;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 6. 业务 owner 可以先退出；in-flight owner 仍保活 allocation。
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;drop&lt;&#x2F;span&gt;&lt;span&gt;(weights);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;drop&lt;&#x2F;span&gt;&lt;span&gt;(shard);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 7. event 完成后，completion 释放引用；最后一个 Arc 触发 Drop，
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;    DeviceBuffer 决定归还 pool、hipFreeAsync 或 hipFree。
&lt;&#x2F;span&gt;&lt;span&gt;completion.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;wait&lt;&#x2F;span&gt;&lt;span&gt;()?;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这里的 &lt;code&gt;submission.retain()&lt;&#x2F;code&gt; 表达统一语义；各 backend 把它落实到自己的 in-flight
资源集合。ROCm 由 active stage、ordered P2P pending source 和
&lt;code&gt;DeviceCompletion&lt;&#x2F;code&gt; 完成保活，Metal 由 command buffer 的 retained resources
完成保活。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;12-zhe-tao-ji-zhi-dai-lai-de-shi-ji-you-shi&quot;&gt;12. 这套机制带来的实际优势&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;sheng-ming-zhou-qi-cong-yue-ding-bian-cheng-lei-xing-guan-xi&quot;&gt;生命周期从约定变成类型关系&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;RocmTensor → Arc&amp;lt;DeviceBuffer&amp;gt;&lt;&#x2F;code&gt;、&lt;code&gt;View → Arc&amp;lt;Owner&amp;gt;&lt;&#x2F;code&gt;、
&lt;code&gt;Completion → Vec&amp;lt;Arc&amp;lt;DeviceBuffer&amp;gt;&amp;gt;&lt;&#x2F;code&gt; 都可以直接从字段看出来。开发者不再依赖
“调用者应该记得晚一点 free”这样的口头约定。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;xian-zhu-jian-shao-zhong-fu-shang-chuan-he-fu-zhi&quot;&gt;显著减少重复上传和复制&lt;&#x2F;h3&gt;
&lt;p&gt;克隆 &lt;code&gt;RocmWeight&lt;&#x2F;code&gt; 或 &lt;code&gt;RocmTensor&lt;&#x2F;code&gt; 只增加设备 buffer 的引用计数，权重显存不会
被复制。量化 codes、scales 和派生 BF16 cache 也能独立共享。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;cuo-wu-lu-jing-zi-dong-shou-lian&quot;&gt;错误路径自动收敛&lt;&#x2F;h3&gt;
&lt;p&gt;函数在中途使用 &lt;code&gt;?&lt;&#x2F;code&gt; 返回时，已经构造成功的 &lt;code&gt;Vec&lt;&#x2F;code&gt;、&lt;code&gt;Arc&lt;&#x2F;code&gt;、Tensor 和 buffer 会按
逆序析构。大部分错误分支不再需要手写多段 cleanup。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;zi-yuan-ce-lue-zai-backend-nei-du-li-shi-xian&quot;&gt;资源策略在 backend 内独立实现&lt;&#x2F;h3&gt;
&lt;p&gt;runtime 只使用 backend 的 Tensor&#x2F;Weight&#x2F;Completion 契约；ROCm 使用
&lt;code&gt;hipFreeAsync&lt;&#x2F;code&gt;、显式 pool、stage 批量回收和 event 驱动退休，Metal 使用 command
queue 与 scratch pool，两者都不改变模型算法。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;bing-fa-bian-jie-geng-qing-chu&quot;&gt;并发边界更清楚&lt;&#x2F;h3&gt;
&lt;p&gt;不可变数据用 &lt;code&gt;Arc&lt;&#x2F;code&gt;，一次性初始化用 &lt;code&gt;OnceLock&lt;&#x2F;code&gt;，可变 metadata 才用 &lt;code&gt;Mutex&lt;&#x2F;code&gt;。
锁竞争因此集中在小型 registry 和队列，而不是扩散到权重读取热路径。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;13-reng-ran-xu-yao-bao-chi-jing-ti-de-bian-jie&quot;&gt;13. 仍然需要保持警惕的边界&lt;&#x2F;h2&gt;
&lt;p&gt;这套机制很强，但不是自动正确。&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;unsafe impl Send&#x2F;Sync for DeviceBuffer&lt;&#x2F;code&gt; 是对底层 HIP 行为的人工承诺，编译器无法
替我们证明跨线程和跨设备操作安全；每条新 FFI 路径仍需遵守 device&#x2F;stream
规则。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;Arc&lt;&#x2F;code&gt; 只能防止 Rust 对象析构，不能自动建立 producer&#x2F;consumer stream 顺序。
数据依赖仍要用 stream ordering 或 event 表达。&lt;&#x2F;li&gt;
&lt;li&gt;host shadow 与 device resident 同时存在时，要明确谁是最新副本；引用计数不能
解决数据一致性。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;Arc&lt;&#x2F;code&gt; 环会导致资源永久不释放。owner 方向应保持单向：view 指向 allocation，
allocation 不反向拥有 view；registry 需要时使用 &lt;code&gt;Weak&lt;&#x2F;code&gt;。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;Drop&lt;&#x2F;code&gt; 不适合返回错误。真正要求“确认 GPU 已完成”的边界，应显式调用
&lt;code&gt;wait()&lt;&#x2F;code&gt; 或由 scheduler 轮询 completion，析构只作为最后的安全网。&lt;&#x2F;li&gt;
&lt;li&gt;不应为了形式统一把所有 Tensor 都改成 &lt;code&gt;Arc&amp;lt;Tensor&amp;gt;&lt;&#x2F;code&gt;。通常共享的是大块 storage，
Tensor metadata 本身很小，按值移动或克隆更简单。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;jie-yu&quot;&gt;结语&lt;&#x2F;h2&gt;
&lt;p&gt;Rust 可以极大降低 CPU 内存与 GPU 显存的生命周期管理复杂度。zLLM 的统一答案是：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-text &quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span&gt;Vec &#x2F; Arc&amp;lt;File&amp;gt; 管理 host 数据与文件
&lt;&#x2F;span&gt;&lt;span&gt;RocmWeight &#x2F; RocmTensor 描述逻辑资源
&lt;&#x2F;span&gt;&lt;span&gt;Arc&amp;lt;DeviceBuffer&amp;gt; 共享物理显存
&lt;&#x2F;span&gt;&lt;span&gt;Drop 统一回收、异步释放与复用策略
&lt;&#x2F;span&gt;&lt;span&gt;owner Arc 保证 view 不悬空
&lt;&#x2F;span&gt;&lt;span&gt;DeviceCompletion + HIP event 保证 GPU 用完以后才退休
&lt;&#x2F;span&gt;&lt;span&gt;Mutex 只保护真正可变的 registry 与回收队列
&lt;&#x2F;span&gt;&lt;span&gt;backend mempool 统一生命周期语义，各平台保留自己的分配与完成原语
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;因此，真正值得推广的不是 &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;Tensor&amp;gt;&amp;gt;&lt;&#x2F;code&gt; 这个单一写法，而是这套分层的
所有权协议：&lt;strong&gt;借用表达当前调用，&lt;code&gt;Arc&lt;&#x2F;code&gt; 表达共享存活，&lt;code&gt;Mutex&lt;&#x2F;code&gt; 表达可变互斥，
&lt;code&gt;Drop&lt;&#x2F;code&gt; 表达资源归还，event&#x2F;completion 表达设备完成。&lt;&#x2F;strong&gt; 每个原语只做自己擅长的
一件事，组合起来才能同时得到安全、性能和较低的开发复杂度。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh">
        <title>解耦：让模型、算法、后端与算子独立演进</title>
        <published>2026-09-01T00:00:00+00:00</published>
        <updated>2026-09-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhuai.tech/blog/zllm-decoupled-architecture/"/>
        <id>https://zhuai.tech/blog/zllm-decoupled-architecture/</id>
        
        <content type="html" xml:base="https://zhuai.tech/blog/zllm-decoupled-architecture/">&lt;p&gt;日期：2026-08-28 初稿；2026-09-01 重写。本文基于当前源码中的 &lt;code&gt;model_spec&#x2F;&lt;&#x2F;code&gt;、
&lt;code&gt;runtime&#x2F;&lt;&#x2F;code&gt;、&lt;code&gt;backend&#x2F;&lt;&#x2F;code&gt;、&lt;code&gt;kernel&#x2F;&lt;&#x2F;code&gt;、&lt;code&gt;weight&#x2F;&lt;&#x2F;code&gt; 与 &lt;code&gt;server&#x2F;&lt;&#x2F;code&gt; 整理，描述已经存在的
依赖边界，不把尚未落地的通用 Planner 或远端 Expert RPC 写成现状。&lt;&#x2F;p&gt;
&lt;p&gt;zLLM 的解耦目标不是“目录看起来整齐”，而是让变化沿正确的轴发生：模型架构变化
主要进入 spec 与 runtime，新的数学语义先进入领域层，设备差异停在 backend，局部
性能优化落到 kernel。一次完整推理仍由这些层共同完成，但任何一层都不需要知道
所有下层实现。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;1-fen-ceng-quan-jing&quot;&gt;1. 分层全景&lt;&#x2F;h2&gt;
&lt;p&gt;主依赖沿中轴自上而下。权重格式是一条侧向数据通道：&lt;code&gt;model_spec&lt;&#x2F;code&gt; 提供 shape，
&lt;code&gt;weight&lt;&#x2F;code&gt; 解析文件与编码，backend 决定最终 resident 形态；它不反向控制模型编排。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;zllm-decoupling&#x2F;architecture.zh.png&quot; alt=&quot;zLLM 解耦架构：平台后端与参数化算子在设备实现区域并列，共同实现后端能力&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;图中的五层主干和一个设备实现区域不是六个运行时进程，而是六类职责。设备实现
内部，&lt;code&gt;backend&#x2F;&amp;lt;platform&amp;gt;&lt;&#x2F;code&gt; 与 &lt;code&gt;kernel&#x2F;&amp;lt;platform&amp;gt;&lt;&#x2F;code&gt; 是并列协作关系：前者管理
tensor、权重、cache、提交和迁移，后者实现参数化局部计算；两者共同兑现上层
capability，而不是 backend 层层调用一个更低级的 kernel“平台”。生产请求仍然执行完整的
New Prefill、Append Prefill 或 Decode Round；解耦只改变代码依赖与资源所有权，
不把完整模型拆成彼此不知道上下文的局部演示流程。&lt;&#x2F;p&gt;
&lt;p&gt;各层一句话职责(与 README &quot;核心边界&quot;一致):&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;bin&#x2F;runtime、bin&#x2F;tools&lt;&#x2F;strong&gt;:平台入口组合,一切 &lt;code&gt;--config &amp;lt;yaml&amp;gt;&lt;&#x2F;code&gt; 驱动;无模型
专属、验证或 profiling binary(验证与性能归独立的 zllm-bench 项目)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;embedded&lt;&#x2F;strong&gt;:进程内 &lt;code&gt;Engine&lt;&#x2F;code&gt;,复用与服务模式相同的结构化 JSON 语义,不启动
HTTP、scheduler 或 iroh(详见 06 篇 §6)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;server&#x2F;&lt;&#x2F;strong&gt;:HTTP&#x2F;H3 协议(Chat &#x2F; Anthropic &#x2F; Responses)、node、scheduler 与
iroh stage transport;网络服务生命周期不散落在 crate 根或模型 runtime&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;model_spec&#x2F;&amp;lt;model&amp;gt;&lt;&#x2F;strong&gt;:平台与执行无关的架构配置——Config 纯常量,自身零
依赖;是 runtime 编排与 weight 装配共同依赖的单一规格定义&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;runtime&#x2F;&amp;lt;model&amp;gt;&lt;&#x2F;strong&gt;:LayerSpec 展开、平台无关执行编排与同目录
&lt;code&gt;&amp;lt;backend&amp;gt;_*.rs&lt;&#x2F;code&gt; 平台组合;Config 经 re-export 保持定义靠近使用点&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;attention&#x2F;、moe&#x2F;、kv_cache&#x2F;、norm.rs&lt;&#x2F;strong&gt;:领域规格、通用算法与 f32
reference 语义(设备无关)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;backend&#x2F;&lt;&#x2F;strong&gt;:平台资源、cache、completion、stream&#x2F;command buffer、驻留与调度；
按 capability 和数据格式选择 kernel&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;kernel&#x2F;&lt;&#x2F;strong&gt;:参数化算子，模型维度不得硬编码；ROCm 的 HIP 主体已拆成独立
&lt;code&gt;source.hip&lt;&#x2F;code&gt;，Rust launcher 负责加载、校验与提交&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;weight&#x2F;&lt;&#x2F;strong&gt;:标准格式解析与生命周期;模型专属部分只做命名 &#x2F; shape 适配&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;2-yi-lai-gui-ze-qiang-zhi-de-fang-xiang&quot;&gt;2. 依赖规则(强制的方向)&lt;&#x2F;h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;runtime::prefill&lt;&#x2F;code&gt; 等通用调度不依赖具体模型或具体 backend&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;model_spec&lt;&#x2F;code&gt; 只含架构常量,不依赖 crate 内任何模块;runtime 与 weight 都
依赖它,彼此之间不互相依赖&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;runtime&#x2F;&amp;lt;model&amp;gt;&#x2F;mod.rs&lt;&#x2F;code&gt; 平台无关算法只依赖 capability trait;同目录
&lt;code&gt;&amp;lt;backend&amp;gt;_*.rs&lt;&#x2F;code&gt; 才允许依赖具体平台&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;backend&#x2F;&lt;&#x2F;code&gt; 与 &lt;code&gt;kernel&#x2F;&lt;&#x2F;code&gt; 不依赖、命名或分支到具体模型；双卡 cooperative
MoE、Metal replay 等优化只能表达设备能力，不能把 GLM 层号写进 kernel&lt;&#x2F;li&gt;
&lt;li&gt;领域层(attention&#x2F;moe&#x2F;kv_cache)只存规格&#x2F;算法&#x2F;reference;具体存储、kernel、
同步在 backend&lt;&#x2F;li&gt;
&lt;li&gt;weight&#x2F; 只做格式解析与加载;算法不进权重目录,平台资源不进格式解析&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;违反方向通常意味着依赖倒挂。例外必须是明确的平台组合文件或可测量的真实执行
路径，不能为了“将来也许复用”增加空 flow、factory 或多层协议。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;3-jie-ou-de-san-ge-jiao-jie-mian&quot;&gt;3. 解耦的三个交界面&lt;&#x2F;h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;交界面&lt;&#x2F;th&gt;&lt;th&gt;上游表达&lt;&#x2F;th&gt;&lt;th&gt;下游承担&lt;&#x2F;th&gt;&lt;th&gt;当前约束&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;A · 模型 × 领域&lt;&#x2F;td&gt;&lt;td&gt;layer 数据流、完整 prefill&#x2F;decode 顺序&lt;&#x2F;td&gt;&lt;td&gt;attention、MoE、KV 等数学语义与 reference&lt;&#x2F;td&gt;&lt;td&gt;新语义先有可验证定义，再写设备加速&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;B · 领域 × 后端&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;where&lt;&#x2F;code&gt; 子句声明所需 capability&lt;&#x2F;td&gt;&lt;td&gt;tensor&#x2F;cache&#x2F;权重驻留、提交和迁移&lt;&#x2F;td&gt;&lt;td&gt;runtime 不接触 HIP event 或 Metal command buffer&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;C · 能力 × 实现&lt;&#x2F;td&gt;&lt;td&gt;capability 与资源生命周期契约&lt;&#x2F;td&gt;&lt;td&gt;并列的平台 backend 和参数化 kernel&lt;&#x2F;td&gt;&lt;td&gt;两条轨道共同实现能力；kernel 无模型名和模型常量&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;&lt;strong&gt;交界面 A:模型 × 领域&lt;&#x2F;strong&gt;。模型编排层调用领域层纯函数(如
&lt;code&gt;attention::dsa::select_rows&lt;&#x2F;code&gt;、&lt;code&gt;moe::topk_moe::decode_shared_experts&lt;&#x2F;code&gt;),并把
backend 作为泛型参数传入——领域算法描述&quot;做什么&quot;,backend 决定&quot;怎么做&quot;。
新算子(如 kpool)先进领域层钉语义 + 单测,后端的 GPU kernel 只是 reference
的加速替身;跨后端一致性(CPU oracle,atol=rtol=1e-2)保证语义不漂移。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;交界面 B:领域 × 后端(capability trait)&lt;&#x2F;strong&gt;。&lt;code&gt;Backend&lt;&#x2F;code&gt; 基础 trait 只放全
模型共用算子;领域能力拆成独立 trait(KdaKernel、HyperConnectionKernel、
DsaPrefillBackend、ExpertPrefillBackend...),模型层的 where 子句精确声明
所需能力集。新能力以默认拒绝进入——如 &lt;code&gt;append_dsa_keys_gated&lt;&#x2F;code&gt; 默认返回
&quot;backend 未实现 kpool 打包追加&quot;错误——不强迫既有后端同步实现。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;交界面 C：能力 × 设备实现&lt;&#x2F;strong&gt;。&lt;code&gt;backend&#x2F;&amp;lt;platform&amp;gt;&lt;&#x2F;code&gt; 与 &lt;code&gt;kernel&#x2F;&amp;lt;platform&amp;gt;&lt;&#x2F;code&gt;
位于边界的同一侧且彼此并列。backend 持有资源、resident 表示、shape&#x2F;format
分派和提交语义；kernel 持有参数化的局部计算。权重经 &lt;code&gt;LinearWeight&lt;&#x2F;code&gt; 枚举
(F32 &#x2F; F16 &#x2F; Bf16Bytes &#x2F; Quantized，后者覆盖 FP8、MXFP8、MXFP4、NVFP4、
W4A16、W8A16、GGUF 等量化族)进入设备实现，backend 的 &lt;code&gt;linear()&lt;&#x2F;code&gt; 选择同平台
kernel；量化路径在 kernel 内解码，不展开 F32。这里是协作关系，不是架构层级。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;4-guan-jian-jue-ce-yu-yi-ju&quot;&gt;4. 关键决策与依据&lt;&#x2F;h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;决策&lt;&#x2F;th&gt;&lt;th&gt;依据&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;不设行为型薄 model 层;Config 作为纯常量收敛进 model_spec&#x2F;&lt;&#x2F;td&gt;&lt;td&gt;编排(runtime)与装配(weight)共同依赖单一规格定义,二者因此不互相依赖;model_spec 只有数据没有行为,不是旧&quot;薄 model 层&quot;的复活。LayerSpec 展开与编排仍在 runtime&#x2F;&amp;lt;model&amp;gt;,定义靠近使用点&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;associated types(Tensor&#x2F;Weight&#x2F;Cache)而非统一对象&lt;&#x2F;td&gt;&lt;td&gt;后端间 tensor 形态差异是本质的(CPU 值&#x2F;Metal 句柄&#x2F;ROCm 双态);统一对象必然 Arc&amp;lt;dyn&amp;gt;+最低公分母&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;capability trait 按领域拆分&lt;&#x2F;td&gt;&lt;td&gt;模型依赖面可读;新领域能力渐进进入不破坏存量&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;CPU 是 oracle,reference 先行&lt;&#x2F;td&gt;&lt;td&gt;跨后端一致性可测(atol=rtol=1e-2);GPU kernel 可疑时退化 reference 继续跑,正确性不阻塞工程&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;唯一执行流程在 runtime,平台组合在模型目录内&lt;&#x2F;td&gt;&lt;td&gt;避免 N 模型 × M 平台的复制;glm53_flash 的 rocm_node.rs 是&quot;组合&quot;而非&quot;另一份流程&quot;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;无模型专属 binary,一切 --config yaml 驱动&lt;&#x2F;td&gt;&lt;td&gt;入口收敛;验证&#x2F;profiling 归 zllm-bench 项目&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h2 id=&quot;5-you-shi&quot;&gt;5. 优势&lt;&#x2F;h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;组合扩展线性化&lt;&#x2F;strong&gt;:新模型 × 既有算子 = 零后端代码;新算子 × 新后端 =
一个 kernel 文件 + 一个 trait 实现。GLM-5.3-Flash(mHC + KDA + kpool 三个
新领域件)从零到 Amd-1 八卡真机 prefill 数日,再扩到双机 16 卡持续
stage,是这条曲线的直接验证。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;正确性有锚&lt;&#x2F;strong&gt;:reference 与单测先行;ROCm kpool 第一版&quot;全量注意力
退化&quot;能上真机先跑通,语义由 CPU reference 钉死,性能后补。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;平台演进互不阻塞&lt;&#x2F;strong&gt;:Metal 的优化(minicpm5 异步流水、q6k 向量化)、
ROCm 的 stage 流水、CUDA 与华为 NPU AscendC 算子的接入并行推进,互不
污染。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;审计边界清晰&lt;&#x2F;strong&gt;:依赖规则使&quot;谁该改哪&quot;几乎唯一确定,评审成本随规模
增长慢。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;6-dai-jia-yu-lie-shi-cheng-shi-qing-dan&quot;&gt;6. 代价与劣势(诚实清单)&lt;&#x2F;h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;kernel 语义 N 份实现&lt;&#x2F;strong&gt;:Rust reference + MSL + HIP + CUDA + AscendC,
热门路径每个后端都要高性能版,绝对工作量不小。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;泛型实例化膨胀&lt;&#x2F;strong&gt;:每模型编排对每后端实例化;capability 叠加后 where
子句变长,编译时间与二进制体积付出代价。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;trait 蔓延需要自律&lt;&#x2F;strong&gt;:每个新架构概念都倾向往共享 trait 加方法,&quot;基础
Backend 保持最小&quot;靠 AGENTS.md 纪律维持而非机制保证。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;跨后端行为差异要文档化&lt;&#x2F;strong&gt;:同一算子退化路径不同(如 nope-MLA 零宽
split,CPU 通过、ROCm 拒绝),散在实现注释,集中索引依赖本系列文档。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;7-xi-lie-dao-hang&quot;&gt;7. 系列导航&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;02 模型层:格式支持 &#x2F; Config &#x2F; 权重装配 &#x2F; 量化&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;blog&#x2F;algorithm-layer&#x2F;&quot;&gt;03 算法层：attention 家族 &#x2F; MoE &#x2F; KV cache &#x2F; prefill &#x2F; decode 编排&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;04 backend:设备抽象 &#x2F; 资源与提交 &#x2F; 各平台特性&lt;&#x2F;li&gt;
&lt;li&gt;05 算子:基础与融合算子 &#x2F; 精度选择 &#x2F; 硬件映射 &#x2F; 并行基础&lt;&#x2F;li&gt;
&lt;li&gt;06 服务层:围栏 &#x2F; 协议 &#x2F; 输入输出;§6 嵌入式库形态
(&lt;code&gt;zllm::Engine&lt;&#x2F;code&gt;,进程内 generate(json))&lt;&#x2F;li&gt;
&lt;li&gt;07 多节点:scheduler &#x2F; 负载均衡 &#x2F; 跨节点 KV 与 stage 流水 &#x2F; iroh&lt;&#x2F;li&gt;
&lt;li&gt;08 投机解码:事务化 verify &#x2F; MTP 与 DSpark 形态 &#x2F; 围栏交互 &#x2F; 实证纪律&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="zh">
        <title>为什么我只输入了一句话，却消耗了数十万 Token？</title>
        <published>2026-08-30T00:00:00+00:00</published>
        <updated>2026-08-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhuai.tech/blog/http-api-prefix-cache-tool-fence/"/>
        <id>https://zhuai.tech/blog/http-api-prefix-cache-tool-fence/</id>
        
        <content type="html" xml:base="https://zhuai.tech/blog/http-api-prefix-cache-tool-fence/">&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;http-token-cache&#x2F;hero-v2.png&quot; alt=&quot;一句话扩展为庞大上下文，进入推理核心和显存；显存淘汰的缓存备份到 SSD，并可按需恢复&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;一句简短输入背后，是系统指令、工具定义、项目上下文和历史组成的巨大 token 流。热状态留在显存，淘汰时备份到 SSD，需要时再恢复回显存。&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;你明明只输入了一句话，账单或用量统计里为什么会出现数万、十几万甚至数十万 input token？&lt;&#x2F;p&gt;
&lt;p&gt;因为 Claude Code、Codex 这类 Agent 调用后台大模型时，发送的从来不只有输入框里那句话。在真正请求模型之前，客户端会先组装一份完整的“工作上下文”：系统指令、开发者规则、工具说明、JSON Schema、当前目录和项目约束、对话历史、之前的工具调用与结果，以及为了继续任务而保留的文件片段或摘要。你的话可能只有 20 个 token，但它只是一个巨大 prompt 的最后一小段。&lt;&#x2F;p&gt;
&lt;p&gt;换句话说，Agent 每一轮都要先让后台模型知道三件事：&lt;strong&gt;你是谁、你能做什么、现在已经发生了什么。&lt;&#x2F;strong&gt; 这些知识不是写进模型参数的训练，也不会被模型永久记住；它们是本次推理请求中的上下文注入，下一轮通常还要再次携带。工具越多、规则越长、历史越深，请求的 input token 就越大。&lt;&#x2F;p&gt;
&lt;p&gt;这也是为什么前缀缓存如此重要。虽然客户端每轮看起来都重新发送了数十 K 的知识，但其中绝大部分与上一轮完全相同。如果服务端保存了这段 token 执行后的模型状态，就不必每次从第一个字重新计算。&lt;&#x2F;p&gt;
&lt;p&gt;大模型服务表面上很像一个普通的 HTTP JSON 服务：客户端发来消息，服务端不断返回 token。但真正开始兼容不同客户端、复用长对话、支持工具调用以后，问题很快就不再是“加几个路由”那么简单。&lt;&#x2F;p&gt;
&lt;p&gt;我们在 zLLM 中实现了三套当前最常见的接口：OpenAI Chat Completions、OpenAI Responses 和 Anthropic Messages；在执行层实现了显存与 SSD 两级前缀缓存；在生成层又为 DeepSeek DSML 工具调用加入了 token 级围栏。它们表面上是三个功能，实际都在解决同一个问题：&lt;strong&gt;协议状态、模型看到的 token，以及设备中保存的计算状态必须严格对齐。&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;这篇文章不只介绍最终设计，也会把过程中踩过的坑讲明白。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;yi-ju-hua-bei-hou-de-shu-shi-k-shang-xia-wen&quot;&gt;一句话背后的数十 K 上下文&lt;&#x2F;h2&gt;
&lt;p&gt;一次典型的 Claude Code 或 Codex 请求，可以粗略拆成下面几部分：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;http-token-cache&#x2F;request-context-flow.svg&quot; alt=&quot;一句用户输入与系统指令、工具定义、工作区知识和历史组合成完整 Prompt，再由显存或 SSD 前缀缓存复用&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;上下文组成&lt;&#x2F;th&gt;&lt;th&gt;里面有什么&lt;&#x2F;th&gt;&lt;th&gt;为什么每轮可能重复出现&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;系统与开发者指令&lt;&#x2F;td&gt;&lt;td&gt;身份、安全边界、回答格式、编码规范、工作流程&lt;&#x2F;td&gt;&lt;td&gt;模型本身不会跨 HTTP 请求记住这些规则&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;工具定义&lt;&#x2F;td&gt;&lt;td&gt;shell、文件、搜索、浏览器等工具的名称、描述和参数 Schema&lt;&#x2F;td&gt;&lt;td&gt;模型必须知道有哪些动作以及怎样合法调用&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;工作区知识&lt;&#x2F;td&gt;&lt;td&gt;当前目录、仓库规则、AGENTS.md、环境与权限信息&lt;&#x2F;td&gt;&lt;td&gt;Agent 需要在正确项目和约束下行动&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;对话历史&lt;&#x2F;td&gt;&lt;td&gt;用户要求、模型分析、已完成步骤与中间结论&lt;&#x2F;td&gt;&lt;td&gt;保持多轮任务连续性&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;工具调用和结果&lt;&#x2F;td&gt;&lt;td&gt;执行过的命令、读取的文件、错误日志和返回数据&lt;&#x2F;td&gt;&lt;td&gt;后续判断依赖这些观察结果&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;当前输入&lt;&#x2F;td&gt;&lt;td&gt;你刚刚键入的那一句话&lt;&#x2F;td&gt;&lt;td&gt;通常只是整个 prompt 的尾部&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;“数十 K 的知识先灌输给模型”说的就是这个过程。这里的 K 是千 token：10K 约等于一万个 token。它不等于一万个汉字，因为 tokenizer 会把文本、标点、路径、代码和 JSON 按各自规则切分。尤其是工具 Schema，字段名、描述、枚举、嵌套对象和协议样板都会计入 input token；同时启用几十个工具时，仅工具说明就可能占据非常可观的上下文。&lt;&#x2F;p&gt;
&lt;p&gt;客户端界面往往只展示用户消息和最终回答，所以这部分成本很容易被误解为“模型偷偷生成了几十万 token”。实际上，用量通常至少分成三类：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;input_tokens&lt;&#x2F;code&gt;：本轮送进模型的完整上下文，不只是用户新输入；&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;cached_tokens&lt;&#x2F;code&gt;：input 中已由服务端复用计算状态的前缀；&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;output_tokens&lt;&#x2F;code&gt;：模型本轮新生成的 reasoning、正文或工具调用。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;不同服务商如何计费要以各自规则为准，但在计算语义上，&lt;code&gt;cached_tokens&lt;&#x2F;code&gt; 仍然属于输入上下文。它表示这些 token 确实在 prompt 中，只是服务端没有重新完成同样的 prefill 计算。于是会出现一个看似矛盾、其实完全合理的现象：&lt;strong&gt;本轮 input token 仍有十万，但真正重新计算的输入可能只有最后几百个 token。&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;如果缓存没有命中，这十万 token 就要重新经过完整模型的每一层，首 token 延迟会明显增加；如果缓存命中，服务端恢复旧状态，只对新追加的用户消息和 assistant 起始部分做 append prefill。这正是本文后半部分显存与 SSD 两级前缀缓存要解决的问题。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;san-chong-zhu-liu-jie-kou-chai-yi-bu-zhi-shi-json-zi-duan-ming&quot;&gt;三种主流接口，差异不只是 JSON 字段名&lt;&#x2F;h2&gt;
&lt;p&gt;zLLM 对外提供三个主要入口：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;接口&lt;&#x2F;th&gt;&lt;th&gt;典型客户端&lt;&#x2F;th&gt;&lt;th&gt;输入核心&lt;&#x2F;th&gt;&lt;th&gt;流式输出&lt;&#x2F;th&gt;&lt;th&gt;会话续接&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;POST &#x2F;v1&#x2F;chat&#x2F;completions&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;OpenAI SDK、通用聊天前端&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;messages&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Chat chunk SSE&lt;&#x2F;td&gt;&lt;td&gt;客户端重传完整历史，可携带 cache 标识&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;POST&#x2F;GET &#x2F;v1&#x2F;responses&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Codex、ZCode、新版 Agent 客户端&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;input&lt;&#x2F;code&gt; item&lt;&#x2F;td&gt;&lt;td&gt;Responses SSE 或 WebSocket event&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;previous_response_id&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;POST &#x2F;v1&#x2F;messages&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Claude Code、Anthropic SDK&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;system&lt;&#x2F;code&gt; + &lt;code&gt;messages&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Anthropic event SSE&lt;&#x2F;td&gt;&lt;td&gt;客户端重传历史&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;三者最终都会变成同一种内部任务：结构化消息经过模型自己的 chat template 变成 token，随后执行 &lt;code&gt;NewPrefill&lt;&#x2F;code&gt;、&lt;code&gt;AppendPrefill&lt;&#x2F;code&gt; 和 &lt;code&gt;DecodeRound&lt;&#x2F;code&gt;。但协议适配层不能只做字段重命名。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;chat-completions-zui-zhi-jie-ye-zui-rong-yi-cheng-wei-nei-bu-gong-gong-xing-tai&quot;&gt;Chat Completions：最直接，也最容易成为内部公共形态&lt;&#x2F;h3&gt;
&lt;p&gt;Chat Completions 的核心是有序 &lt;code&gt;messages&lt;&#x2F;code&gt;。工具统一采用 OpenAI function tool 形态：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;json&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-json &quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;model&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;deepseek-v4&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;messages&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: [{&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;role&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;user&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;content&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;查一下上海天气&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}],
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;tools&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: [{
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: {
&lt;&#x2F;span&gt;&lt;span&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;get_weather&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;parameters&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: {
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;object&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;properties&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: {&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;city&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: {&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;}},
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;required&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: [&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;city&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]
&lt;&#x2F;span&gt;&lt;span&gt;      }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;  }],
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;stream&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;true
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;非流式响应把文本和 &lt;code&gt;tool_calls&lt;&#x2F;code&gt; 收拢到一个 assistant message；流式响应则要保持 tool call 的 &lt;code&gt;index&lt;&#x2F;code&gt;、&lt;code&gt;id&lt;&#x2F;code&gt;、函数名和参数增量稳定。这里有一个容易忽略的问题：工具调用 ID 不能只由工具名生成，否则同一工具在不同请求或同一响应中多次调用会发生碰撞。我们的做法是把请求级 scope、调用序号和工具名一起哈希。&lt;&#x2F;p&gt;
&lt;p&gt;Chat Completions 很适合作为服务内部的规范形态，因为角色消息、工具定义和采样参数都比较直接。Responses 和 Anthropic 请求进入服务后，会先严谨地转换成这一公共结构，再交给 scheduler 和 runtime。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;responses-ta-shi-shi-jian-xie-yi-bu-shi-huan-pi-de-chat&quot;&gt;Responses：它是事件协议，不是换皮的 Chat&lt;&#x2F;h3&gt;
&lt;p&gt;Responses 的输入不是单纯的消息数组，而是 &lt;code&gt;input&lt;&#x2F;code&gt; item 流。它可以包含普通 message、&lt;code&gt;function_call&lt;&#x2F;code&gt;、&lt;code&gt;function_call_output&lt;&#x2F;code&gt;、图片输入，以及 Codex Responses Lite 放在输入前缀中的 &lt;code&gt;additional_tools&lt;&#x2F;code&gt;。后者还可能用 namespace 包裹真正可执行的 function，因此转换时要展开 namespace，同时跳过当前无法本地执行的托管工具类型。&lt;&#x2F;p&gt;
&lt;p&gt;输出同样不是一个不断增长的 &lt;code&gt;delta.content&lt;&#x2F;code&gt;。文本、reasoning 和 function call 是不同的 output item，各自有生命周期事件：创建、增量、完成，最后才是整个 response 的 completed 或 failed。尤其是 reasoning，必须作为独立的 &lt;code&gt;output[type=reasoning]&lt;&#x2F;code&gt; 暴露，不能混进正文，否则 Codex 一类客户端无法正确渲染和恢复状态。&lt;&#x2F;p&gt;
&lt;p&gt;我们同时支持两种传输：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP POST + SSE，适合普通 SDK；&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;GET &#x2F;v1&#x2F;responses&lt;&#x2F;code&gt; 升级为 WebSocket，一条连接串行复用多次 &lt;code&gt;response.create&lt;&#x2F;code&gt;，适合长时间运行的 Agent 客户端。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;code&gt;previous_response_id&lt;&#x2F;code&gt; 也值得单独说明。zLLM 会持久化上一轮的协议消息和 cache key，因此进程重启后仍能恢复对话链；但它本身&lt;strong&gt;不持有模型 KV&lt;&#x2F;strong&gt;。协议历史恢复成功，只代表我们能重建完整输入，不代表设备缓存一定还在。随后是否命中显存或 SSD cache，仍由 runtime 独立判断。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;anthropic-messages-nei-rong-kuai-he-shi-jian-shun-xu-bi-xu-yuan-yang-cheng-li&quot;&gt;Anthropic Messages：内容块和事件顺序必须原样成立&lt;&#x2F;h3&gt;
&lt;p&gt;Anthropic Messages 把 &lt;code&gt;system&lt;&#x2F;code&gt; 放在顶层，正文由 content block 组成。工具定义使用 &lt;code&gt;name&lt;&#x2F;code&gt;、&lt;code&gt;description&lt;&#x2F;code&gt;、&lt;code&gt;input_schema&lt;&#x2F;code&gt;，工具调用和结果则分别是 &lt;code&gt;tool_use&lt;&#x2F;code&gt; 与 &lt;code&gt;tool_result&lt;&#x2F;code&gt; block。适配层需要把它们双向转换成内部 function tool，而不是把 block 粗暴拼成字符串。&lt;&#x2F;p&gt;
&lt;p&gt;流式响应的事件顺序也有严格语义：先 &lt;code&gt;message_start&lt;&#x2F;code&gt;，再打开一个 &lt;code&gt;content_block_start&lt;&#x2F;code&gt;，发送对应 delta，关闭 block，最终发送 &lt;code&gt;message_delta&lt;&#x2F;code&gt; 和 &lt;code&gt;message_stop&lt;&#x2F;code&gt;。文本与多个工具调用可能占据不同 block index；如果只把 Chat SSE 的字段名改掉，客户端会得到顺序错误或无法闭合的内容块。&lt;&#x2F;p&gt;
&lt;p&gt;Claude CLI 还会调用 &lt;code&gt;&#x2F;v1&#x2F;messages&#x2F;count_tokens&lt;&#x2F;code&gt; 判断何时自动压缩上下文。这个接口即使只是估算，也必须读入 system、messages 和 tools，而不能只数用户正文，否则压缩触发点会严重偏后。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;cache-hit-dao-di-yi-wei-zhao-shen-me&quot;&gt;cache hit 到底意味着什么&lt;&#x2F;h2&gt;
&lt;p&gt;在推理服务里，cache hit 不是“找到了相似问题”，也不是“直接返回上次答案”。它的精确定义是：&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;新请求 token 序列的开头，与某个已保存会话的 token 序列逐 token 完全相同，因此可以恢复这一前缀执行完后各层的 KV、DSA&#x2F;MTP 等模型状态，从前缀末尾继续计算。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;假设上一轮终点状态对应：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;[system][tools][user-1][assistant-1]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;新一轮输入是：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;[system][tools][user-1][assistant-1][user-2][assistant-prefix]
&lt;&#x2F;span&gt;&lt;span&gt;|&amp;lt;----------- cached_tokens -----------&amp;gt;|&amp;lt;-- append prefill --&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;命中后，前面 &lt;code&gt;cached_tokens&lt;&#x2F;code&gt; 个 token 不再重新通过完整 Transformer；只对新增后缀执行 append prefill，再进入 decode。它降低的是 TTFT 和重复 prefill 计算，不会消除新问题的 decode，也不会缓存答案。&lt;&#x2F;p&gt;
&lt;p&gt;这一定义有几个重要后果：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;必须是 token 前缀相等，不是文本“看起来一样”。&lt;&#x2F;strong&gt; 空格、JSON 序列化方式、工具 schema 顺序、thinking 开关、chat template 或特殊 token 变化，都可能让前缀分叉。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;cache ID 是定位提示，不是正确性凭证。&lt;&#x2F;strong&gt; 即使客户端给出精确 ID，runtime 仍要检查 token 前缀与 namespace；不匹配就回退到最长前缀或冷启动。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;命中长度必须小于新 prompt 长度。&lt;&#x2F;strong&gt; 如果直接把一个完整终点当成待执行输入，却没有新增 token，模型没有合法的 append 边界。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;cache hit 不是 cache resident。&lt;&#x2F;strong&gt; 命中的状态可能在显存，也可能已经换出到 SSD。二者节省的计算相同，但恢复延迟完全不同。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;我们为 terminal cache 计算稳定身份时，不只包含用户消息，也包含模型、模板相关请求、assistant 输出和结构化 tool calls。否则“屏幕上看起来相同”的两轮请求可能错误共享不兼容的模型状态。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wei-shen-me-yao-zuo-xian-cun-yu-ssd-liang-ji-cun-chu&quot;&gt;为什么要做显存与 SSD 两级存储&lt;&#x2F;h2&gt;
&lt;p&gt;长上下文 KV 很贵。以数万到数十万 token 的 Agent 对话为例，保留每个历史分支的设备状态能显著减少重复 prefill，但显存不可能无限增长。只做 LRU 删除又意味着刚淘汰的长对话下一轮要从头算起。&lt;&#x2F;p&gt;
&lt;p&gt;我们的两级机制可以概括为：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;请求到达
&lt;&#x2F;span&gt;&lt;span&gt;  ├─ 精确 cache_id 命中显存 ────────┐
&lt;&#x2F;span&gt;&lt;span&gt;  ├─ 显存中搜索最长 token 前缀 ─────┤
&lt;&#x2F;span&gt;&lt;span&gt;  ├─ 精确 cache_id 命中 SSD ────────┤→ append prefill → decode
&lt;&#x2F;span&gt;&lt;span&gt;  ├─ SSD 中搜索最长 token 前缀 ─────┤
&lt;&#x2F;span&gt;&lt;span&gt;  └─ 都未命中 → new prefill ────────┘
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;显存层：可直接继续计算，容量小，延迟最低
&lt;&#x2F;span&gt;&lt;span&gt;SSD 层：保存压缩快照，容量大，需要读盘和 H2D&#x2F;设备恢复
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;yi-ji-xian-cun-zhong-de-resident-terminal-cache&quot;&gt;一级：显存中的 resident terminal cache&lt;&#x2F;h3&gt;
&lt;p&gt;每次请求成功结束后，引擎保留终点 session，其中包括 token 序列、namespace、父节点&#x2F;轮次关系和模型私有执行状态。公共 cache 层只负责所有权、前缀图和淘汰；具体有哪些 KV、压缩环、DSpark target cache，由模型 runtime 自己定义。&lt;&#x2F;p&gt;
&lt;p&gt;这里必须有真实的驻留预算，而不能只限制“最多保存 N 个会话”。两个 token 数接近的会话，占用也可能相差数倍：除线性 KV 外，还有 recent&#x2F;compressed 环、batch scratch、推测解码 block 和 target cache 等固定或阶梯式开销。我们最终按真实 &lt;code&gt;allocated_bytes&lt;&#x2F;code&gt; 做 admission 和诊断，并在请求开始前预留后续 decode 仍会增长的容量。&lt;&#x2F;p&gt;
&lt;p&gt;缓存还不是一条简单 LRU 链。Agent 对话会从同一个历史点分叉，因此内存层维护共享前缀 block graph：父状态可以被多个后继复用，只有不再需要的分支才真正释放。&lt;code&gt;terminal_cache_prefix_rounds&lt;&#x2F;code&gt; 控制保留多少轮祖先，避免每个中间终点永久占据设备。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;er-ji-ssd-shang-de-ke-hui-fu-kuai-zhao&quot;&gt;二级：SSD 上的可恢复快照&lt;&#x2F;h3&gt;
&lt;p&gt;显存淘汰一个值得保留的终点时，runtime 下载模型状态，按模型版本编码成快照，写入 SSD。索引中保存 cache ID、完整 token 序列、namespace、轮次、head 标志、设备驻留字节数和 blob generation；大块 KV 数据采用分块存储。&lt;&#x2F;p&gt;
&lt;p&gt;写入必须具有提交边界。manifest 只能在数据 blob 完整落盘后切换到新 generation，这样进程崩溃时不会留下“索引存在、内容不完整”的半份缓存。读取端把磁盘内容当作不可信输入：所有长度、计数、版本和剩余字节都要检查，不能让损坏快照触发越界或巨量分配。&lt;&#x2F;p&gt;
&lt;p&gt;SSD 命中后，引擎优先复用已经分配过的空 session buffer，把各 stage cache 上传回设备，并恢复 DSpark 等附属状态。恢复完成后设置 &lt;code&gt;next_prefill = cached_tokens.len()&lt;&#x2F;code&gt;，只计算新增部分。&lt;&#x2F;p&gt;
&lt;p&gt;SSD 层也支持最长前缀搜索，而不是只能依赖 ID。搜索条件仍然是严格的 &lt;code&gt;tokens.starts_with(snapshot.tokens)&lt;&#x2F;code&gt;，并且要隔离 namespace。为了排查“几乎命中却没命中”的线上问题，我们会记录公共前缀超过 1024 token 且覆盖旧快照 90% 以上的 near miss；这种日志通常能直接暴露模板、工具回显或序列化分叉。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;liang-ji-huan-cun-shi-xian-zhong-zhen-zheng-ji-shou-de-wen-ti&quot;&gt;两级缓存实现中真正棘手的问题&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;1-tao-tai-shun-xu-yu-admission-si-suo&quot;&gt;1. 淘汰顺序与 admission 死锁&lt;&#x2F;h3&gt;
&lt;p&gt;最早的实现先为新请求申请显存，再淘汰旧 cache。结果是明明有可淘汰状态，却因为预算不足拒绝新请求。正确顺序是先识别可牺牲的 resident cache，完成必要的换出和释放，再做 admission；同时为 decode 增长预留空间，避免 prefill 恢复成功后在生成中途耗尽显存。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-yi-bu-hui-fu-bing-bu-deng-yu-ke-yi-ti-qian-kai-shi-ji-suan&quot;&gt;2. 异步恢复并不等于可以提前开始计算&lt;&#x2F;h3&gt;
&lt;p&gt;SSD 读取、CPU 解码和多 GPU H2D 可以并行，但依赖关系不能省略。我们遇到过 restore 任务仍在写设备 cache，计算流已经开始读取的情况；也遇到 cache open 命令没有 drain 就复用资源。修复不是“多加一个全局同步”，而是为每个 stage 明确 restore 完成事件，只让互不依赖的 I&#x2F;O 与上传并行，在第一次消费前等待对应依赖。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-reusable-pool-ye-hui-xie-lou&quot;&gt;3. reusable pool 也会泄漏&lt;&#x2F;h3&gt;
&lt;p&gt;如果每次 SSD 恢复都新建 session，旧设备 buffer 虽然进入 reusable pool，却长期没有机会被拿出来，显存仍然越堆越高。恢复路径必须优先从 pool 取一个 session 并覆盖其 cache；失败和取消路径也必须 reset 后归还，而不是只处理成功请求。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;4-gong-ju-hui-xian-shi-huan-cun-lian-zui-chang-jian-de-duan-dian&quot;&gt;4. 工具回显是缓存链最常见的断点&lt;&#x2F;h3&gt;
&lt;p&gt;工具调用经历“模型原生文本 → API 结构化 tool call → 客户端执行 → 下一轮历史重新编码”。只要正向解析和历史重放不互为逆操作，下一轮 token 就会在工具位置分叉。我们曾遇到 DSML terminal cache 续接失败，根因正是工具标签、参数字符串&#x2F;JSON 类型或大小写规范化后的回显不一致。&lt;&#x2F;p&gt;
&lt;p&gt;解决方法是让每种 &lt;code&gt;ToolDialect&lt;&#x2F;code&gt; 同时拥有四个能力：注入 instructions、渲染历史 tool call、渲染 tool result、解析模型输出。它们必须来自同一个协议实现，不能散落在 HTTP 层和各个 backend 中。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;gong-ju-diao-yong-wei-lan-wei-shen-me-shi-hou-jie-xi-huan-bu-gou&quot;&gt;工具调用围栏：为什么事后解析还不够&lt;&#x2F;h2&gt;
&lt;p&gt;仅靠 prompt 告诉模型“请输出合法 JSON”并不可靠。模型可能生成不存在的工具名、重复参数、漏掉 required 字段、在 JSON 中提前输出闭合标签，或在长推理后陷入重复。等整段文本生成完再解析，只能宣布失败，无法挽回已经浪费的 decode。&lt;&#x2F;p&gt;
&lt;p&gt;因此我们把工具支持分成三层：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;协议提示与历史渲染&lt;&#x2F;strong&gt;：把统一 function schema 编码为模型训练时熟悉的 GLM XML、ChatML JSON 或 DeepSeek DSML。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;增量解析&lt;&#x2F;strong&gt;：流式识别正文和工具区域，把合法调用转换成统一 &lt;code&gt;tool_calls&lt;&#x2F;code&gt;，同时避免把半个标签提前泄露给客户端。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;token 级生成围栏&lt;&#x2F;strong&gt;：在采样之前，根据当前语法状态强制或排除候选 token，使结构化区域只能沿合法路径生成。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;第三层才是“围栏”的核心。以一个指定的 DeepSeek DSML 工具为例，状态机依次经过：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;固定 tool_calls&#x2F;invoke 前缀
&lt;&#x2F;span&gt;&lt;span&gt;        ↓
&lt;&#x2F;span&gt;&lt;span&gt;选择一个尚未出现的参数
&lt;&#x2F;span&gt;&lt;span&gt;        ↓
&lt;&#x2F;span&gt;&lt;span&gt;按该参数 JSON Schema 生成值
&lt;&#x2F;span&gt;&lt;span&gt;        ↓
&lt;&#x2F;span&gt;&lt;span&gt;参数闭合；required 是否齐全？
&lt;&#x2F;span&gt;&lt;span&gt;        ├─ 否：只能继续选择参数
&lt;&#x2F;span&gt;&lt;span&gt;        └─ 是：允许闭合 invoke&#x2F;tool_calls
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;固定标签、工具名和参数名由围栏强制下一个 token；参数值由 &lt;code&gt;JsonSchemaFence&lt;&#x2F;code&gt; 给出允许集合；每个参数只能出现一次；required 未齐全时，结束标签根本不会进入候选集。字符串参数还要排除会意外打开 markup 的 token，终止 token 在结构未完成时同样被禁止。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;tool_choice=auto&lt;&#x2F;code&gt; 更复杂：模型在普通正文阶段应保持自由，只有检测到 DSML trigger 后围栏才激活。激活时可能仍有多个候选工具，状态机并行推进所有仍与已生成 token 相容的分支；候选收敛到一个后，再使用该工具的参数 schema。这里不能一开始就强迫模型调用工具，否则 &lt;code&gt;auto&lt;&#x2F;code&gt; 会被错误实现成 &lt;code&gt;required&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;p&gt;围栏位于 output head 与采样之间。普通 decode、batch 中的每一行、MTP&#x2F;DSpark draft 和 speculative verify 都必须应用同一份请求级围栏状态。漏掉任何一条路径，就会出现“不开推测解码时正常，一开就偶发非法工具 JSON”的问题。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wei-lan-shi-xian-zhong-cai-guo-de-keng&quot;&gt;围栏实现中踩过的坑&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;tokenizer-bian-jie-bu-shi-zi-fu-chuan-bian-jie&quot;&gt;tokenizer 边界不是字符串边界&lt;&#x2F;h3&gt;
&lt;p&gt;标签 &lt;code&gt;&amp;lt;&#x2F;...&amp;gt;&lt;&#x2F;code&gt; 在 tokenizer 中可能跨 token，也可能共享一个 &lt;code&gt;&amp;lt;&#x2F;&lt;&#x2F;code&gt; token。围栏不能假设每个字符或标签都是独立 token。初始化时要用当前模型 tokenizer 编码所有 literal，并验证闭合片段的首 token 与预期边界一致；不一致就拒绝启用，而不是在生成中途进入不可达状态。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;auto-fen-zhi-bu-neng-qu-cuo-wu-de-jiao-ji&quot;&gt;auto 分支不能取错误的交集&lt;&#x2F;h3&gt;
&lt;p&gt;多个候选工具的下一 token 可能不同。正确的允许集合是所有仍合法分支的并集；只有所有分支都禁止某 token 时才能排除它。如果把排除集合简单合并，就会过早杀死仍然合法的工具分支。每生成一个 token 后，还要淘汰与它不相容的候选状态机。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;draft-token-ye-yao-zhu-ge-tui-jin-lin-shi-zhuang-tai&quot;&gt;draft token 也要逐个推进临时状态&lt;&#x2F;h3&gt;
&lt;p&gt;推测解码一次提出多个 token。不能只拿当前围栏验证整段，也不能让草稿直接推进正式状态。正确做法是 clone 一份 guard，逐 token 计算该位置的 fence 并推进 probe；target verify 通过的前缀提交后，再推进正式状态。若围栏已经强制某些 token，还可以直接构造这些位置，避免无意义的 logits 计算。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;xie-yi-xiu-fu-bu-neng-zhi-kao-parser-da-bu-ding&quot;&gt;协议修复不能只靠 parser 打补丁&lt;&#x2F;h3&gt;
&lt;p&gt;我们处理过大小写不一致、转义的 DSML 标签和游离闭合标签。parser 的容错能兼容已有模型输出，但如果问题属于可预知的结构错误，更根本的修复仍然是采样前围栏。否则非流式看似“被修好了”，流式过程中却可能已经把错误片段发给客户端。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;wei-lan-huan-yao-yu-shi-kong-xun-huan-bao-hu-zu-he&quot;&gt;围栏还要与失控循环保护组合&lt;&#x2F;h3&gt;
&lt;p&gt;工具语法约束解决“结构非法”，不解决模型在 thinking 中重复同一段内容。zLLM 的 &lt;code&gt;GenerationGuard&lt;&#x2F;code&gt; 会检测重复 token、重复模式和带证据的语义重启，在下一次采样时排除已知会继续循环的 token；若 speculative batch 内已经发现循环，则截断已验证行，必要时用 reasoning-end token 收口。工具围栏和循环保护最终合并成同一个 &lt;code&gt;TokenFence&lt;&#x2F;code&gt;，但两者保持独立状态机，避免协议逻辑污染通用生成层。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;yi-tiao-qing-qiu-zui-zhong-ru-he-chuan-guo-zheng-ge-xi-tong&quot;&gt;一条请求最终如何穿过整个系统&lt;&#x2F;h2&gt;
&lt;p&gt;把三套接口、两级缓存和工具围栏放在一起，一条真实请求的路径是：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;Chat &#x2F; Responses &#x2F; Anthropic request
&lt;&#x2F;span&gt;&lt;span&gt;        ↓  协议校验与规范化
&lt;&#x2F;span&gt;&lt;span&gt;统一 messages + function tools
&lt;&#x2F;span&gt;&lt;span&gt;        ↓  模型 dialect&#x2F;template&#x2F;tokenizer
&lt;&#x2F;span&gt;&lt;span&gt;完整 prompt tokens + cache identity
&lt;&#x2F;span&gt;&lt;span&gt;        ↓
&lt;&#x2F;span&gt;&lt;span&gt;显存精确&#x2F;最长前缀 → SSD 精确&#x2F;最长前缀 → 冷启动
&lt;&#x2F;span&gt;&lt;span&gt;        ↓
&lt;&#x2F;span&gt;&lt;span&gt;NewPrefill 或 AppendPrefill
&lt;&#x2F;span&gt;&lt;span&gt;        ↓
&lt;&#x2F;span&gt;&lt;span&gt;output head → 请求级 token fence → sampling&#x2F;speculative verify
&lt;&#x2F;span&gt;&lt;span&gt;        ↓
&lt;&#x2F;span&gt;&lt;span&gt;原生工具流增量解析
&lt;&#x2F;span&gt;&lt;span&gt;        ↓
&lt;&#x2F;span&gt;&lt;span&gt;Chat chunks &#x2F; Responses events &#x2F; Anthropic content blocks
&lt;&#x2F;span&gt;&lt;span&gt;        ↓
&lt;&#x2F;span&gt;&lt;span&gt;成功终点写入 resident cache，淘汰时持久化到 SSD
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这条链路中，HTTP 层不拥有 KV，cache 层不理解模型协议，backend 不认识工具名，模型 runtime 也不负责某一种外部 SSE 格式。每一层都只保存自己必须知道的语义，但边界上传递的是可验证的真实状态，而不是一个含糊的“会话 ID”。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;zui-hou-de-pan-duan-biao-zhun&quot;&gt;最后的判断标准&lt;&#x2F;h2&gt;
&lt;p&gt;前缀缓存是否正确，不看日志里有没有打印 &lt;code&gt;hit=true&lt;&#x2F;code&gt;，而要验证恢复后的 token、position、各层 KV 和附属状态与完整 prefill 一致；收益则看真实 TTFT，而不是只算跳过了多少 token。&lt;&#x2F;p&gt;
&lt;p&gt;工具调用是否正确，也不看最终字符串能否被一次 JSON parser 接受，而要验证流式事件顺序、历史回放、下一轮 cache 续接，以及普通 decode、batch、speculative 路径是否都执行了同一语法约束。&lt;&#x2F;p&gt;
&lt;p&gt;我们最终得到的经验很朴素：&lt;strong&gt;接口兼容的终点不是 JSON 长得像，缓存命中的终点不是 ID 对得上，结构化生成的终点也不是事后能修。&lt;&#x2F;strong&gt; 真正可靠的实现，必须一直落到模型实际看到的 token 和设备实际保存的状态上。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh">
        <title>MacBook Air M5：零依赖运行多模态大模型！</title>
        <published>2026-08-29T00:00:00+00:00</published>
        <updated>2026-08-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhuai.tech/blog/macbook-air-m5-native-multimodal/"/>
        <id>https://zhuai.tech/blog/macbook-air-m5-native-multimodal/</id>
        
        <content type="html" xml:base="https://zhuai.tech/blog/macbook-air-m5-native-multimodal/">&lt;blockquote&gt;
&lt;p&gt;不安装 Python、PyTorch、Conda、Docker，也不启动常驻模型服务：一个 Rust 对象、一份 GGUF 权重，就能把原生多模态推理放进自己的进程。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;这篇文章在一台 &lt;strong&gt;24 GB 内存、10 核 Apple M5 的 MacBook Air&lt;&#x2F;strong&gt; 上完成。当前 release 版 &lt;code&gt;zllm-metal&lt;&#x2F;code&gt; 是 8.1 MB；配合 Gemma 4 E4B 的 Q4_K_M 主模型和视觉投影，它可以直接通过 Metal 完成多轮对话和图像理解。&lt;&#x2F;p&gt;
&lt;p&gt;先说明标题中的“零依赖”：它指的是&lt;strong&gt;交付和运行时不依赖第三方推理框架&lt;&#x2F;strong&gt;。最终用户不需要 Python 环境、PyTorch 动态库、CUDA 工具包或单独管理的 C++ runtime；模型权重仍然是推理所需的数据，macOS 自带的 Metal 与系统框架也仍然存在。从源码构建时，Cargo 会正常编译 zLLM 使用的 Rust crates，并把它们链接进原生程序。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ku-mo-shi-shi-zen-yang-gong-zuo-de&quot;&gt;库模式是怎样工作的&lt;&#x2F;h2&gt;
&lt;p&gt;zLLM 不是对另一个本地服务的 HTTP 包装。&lt;code&gt;zllm::embedded::Engine&lt;&#x2F;code&gt; 直接持有模型、Metal backend、KV Cache 和生成状态，在调用方进程内走完整推理路径：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;Rust 应用
&lt;&#x2F;span&gt;&lt;span&gt;  └─ Engine::from_config（装载一次）
&lt;&#x2F;span&gt;&lt;span&gt;       ├─ GGUF 权重与 chat template
&lt;&#x2F;span&gt;&lt;span&gt;       ├─ Metal backend &#x2F; kernels
&lt;&#x2F;span&gt;&lt;span&gt;       └─ terminal KV Cache
&lt;&#x2F;span&gt;&lt;span&gt;            ↓
&lt;&#x2F;span&gt;&lt;span&gt;     Engine::generate（重复调用）
&lt;&#x2F;span&gt;&lt;span&gt;       ├─ OpenAI Chat JSON → template → tokenize
&lt;&#x2F;span&gt;&lt;span&gt;       ├─ 可选 image_url → mmproj → 视觉 embedding
&lt;&#x2F;span&gt;&lt;span&gt;       ├─ prefill &#x2F; append prefill &#x2F; decode
&lt;&#x2F;span&gt;&lt;span&gt;       └─ 每个 token 通过 Rust 回调返回
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这条路径与 zLLM 的 Metal node 共用模型 runtime、权重装配和生成状态机，只是没有启动 HTTP、Scheduler 或 iroh。&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;接口&lt;&#x2F;th&gt;&lt;th&gt;用途&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Engine::from_config(path)&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;从 &lt;code&gt;kind: standalone&lt;&#x2F;code&gt; YAML 装载模型和 backend，但不启动服务&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Engine::load(...)&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;已经解析配置时直接装载&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Engine::generate(...)&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;接受结构化 Chat JSON，通过回调流式返回 token&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Engine::cancellation()&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;创建可复制到 UI 或控制线程的取消句柄&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Engine::kv_resources()&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;查看 KV capacity、active、resident、available 和单会话占用&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;GenerationResult&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;返回结束原因、输入&#x2F;输出 token 数和后续轮次可复用的 &lt;code&gt;cache_id&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;回调返回 &lt;code&gt;false&lt;&#x2F;code&gt;，或从另一线程调用 &lt;code&gt;Cancellation::cancel()&lt;&#x2F;code&gt;，生成就会结束。引擎释放时还会执行优雅关闭；调用方不需要管理 Metal command buffer，也不会接触平台 KV 对象。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;zai-ren-yi-rust-cheng-xu-zhong-qian-ru&quot;&gt;在任意 Rust 程序中嵌入&lt;&#x2F;h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;zLLM 源码已在 &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;zllm-lab&#x2F;zllm&quot;&gt;GitHub&lt;&#x2F;a&gt; 公开。&lt;&#x2F;strong&gt; 可以直接使用 Git 依赖，也可以 clone 后改成本地 &lt;code&gt;path&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;在现有项目的 &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt; 中加入：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;toml&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-toml &quot;&gt;&lt;code class=&quot;language-toml&quot; data-lang=&quot;toml&quot;&gt;&lt;span&gt;[dependencies]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;serde_json &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;zllm &lt;&#x2F;span&gt;&lt;span&gt;= { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;git &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;zllm-lab&#x2F;zllm&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; }
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;把路径换成 zLLM 源码在本机的实际位置。下面是一个完整的最小程序：引擎只加载一次，之后可以反复调用 &lt;code&gt;generate&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;std::io::{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, Write};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;serde_json::json;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;zllm::embedded::Engine;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Result&amp;lt;(), String&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; engine = Engine::from_config(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;gemma4-metal.yaml&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; cancellation = engine.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;cancellation&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; result = engine.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;generate&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;amp;json!({
&lt;&#x2F;span&gt;&lt;span&gt;            &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;model&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;gemma4&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;            &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;messages&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: [{
&lt;&#x2F;span&gt;&lt;span&gt;                &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;role&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;user&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;                &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;content&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;用一句话解释为什么推理引擎适合嵌入应用&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;            }],
&lt;&#x2F;span&gt;&lt;span&gt;            &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;max_completion_tokens&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;128
&lt;&#x2F;span&gt;&lt;span&gt;        }),
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;amp;cancellation,
&lt;&#x2F;span&gt;&lt;span&gt;        |&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;_token&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;text&lt;&#x2F;span&gt;&lt;span&gt;| {
&lt;&#x2F;span&gt;&lt;span&gt;            print!(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{text}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;            io::stdout().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;flush&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;is_ok&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        },
&lt;&#x2F;span&gt;&lt;span&gt;    )?;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    println!(
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;finish=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; prompt=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; completion=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; cache=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{:?}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;        result.finish_reason,
&lt;&#x2F;span&gt;&lt;span&gt;        result.prompt_tokens,
&lt;&#x2F;span&gt;&lt;span&gt;        result.completion_tokens,
&lt;&#x2F;span&gt;&lt;span&gt;        result.cache_id
&lt;&#x2F;span&gt;&lt;span&gt;    );
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(())
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;后续轮次继续传完整的 &lt;code&gt;messages&lt;&#x2F;code&gt;。Runtime 会校验 cache identity 和真实 token 前缀；匹配时恢复 terminal session，只为新增后缀执行 append prefill，不匹配时安全回退到最长公共前缀或完整 prefill。&lt;&#x2F;p&gt;
&lt;p&gt;图像输入仍然使用同一个接口，只需把 user content 改成有序的图文 part：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; cancellation = engine.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;cancellation&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; result = engine.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;generate&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;amp;json!({
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;model&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;gemma4&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;messages&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: [{
&lt;&#x2F;span&gt;&lt;span&gt;            &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;role&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;user&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;            &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;content&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: [
&lt;&#x2F;span&gt;&lt;span&gt;                {&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;text&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;text&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;请描述这张图片&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;},
&lt;&#x2F;span&gt;&lt;span&gt;                {&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;image_url&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;image_url&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: {
&lt;&#x2F;span&gt;&lt;span&gt;                    &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;url&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;absolute&#x2F;path&#x2F;to&#x2F;photo.png&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;                }}
&lt;&#x2F;span&gt;&lt;span&gt;            ]
&lt;&#x2F;span&gt;&lt;span&gt;        }],
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;max_completion_tokens&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;256
&lt;&#x2F;span&gt;&lt;span&gt;    }),
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;amp;cancellation,
&lt;&#x2F;span&gt;&lt;span&gt;    |&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;_token&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;text&lt;&#x2F;span&gt;&lt;span&gt;| {
&lt;&#x2F;span&gt;&lt;span&gt;        print!(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{text}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;        io::stdout().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;flush&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;is_ok&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    },
&lt;&#x2F;span&gt;&lt;span&gt;)?;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;本地路径、HTTP(S) URL 和 &lt;code&gt;data:&lt;&#x2F;code&gt; URL 会进入同一套图像物化与视觉编码路径。Gemma 4 E4B 的 &lt;code&gt;mmproj&lt;&#x2F;code&gt; 在第一次图像请求时懒加载；只做文字对话时不必先装载视觉塔。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;zhun-bei-gemma-4-e4b&quot;&gt;准备 Gemma 4 E4B&lt;&#x2F;h2&gt;
&lt;p&gt;本文使用 instruction-tuned 的 Gemma 4 E4B GGUF：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;unsloth&#x2F;gemma-4-E4B-it-GGUF&quot;&gt;Hugging Face：unsloth&#x2F;gemma-4-E4B-it-GGUF&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;modelscope.cn&#x2F;models&#x2F;unsloth&#x2F;gemma-4-E4B-it-GGUF&quot;&gt;魔搭 ModelScope：unsloth&#x2F;gemma-4-E4B-it-GGUF&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;下载下面两个文件，并放在同一个目录：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;models&#x2F;gemma-4-E4B-it-GGUF&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;├── gemma-4-E4B-it-Q4_K_M.gguf   # 主模型，4.98 GB
&lt;&#x2F;span&gt;&lt;span&gt;└── mmproj-F16.gguf               # 视觉编码器&#x2F;投影，990 MB
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;可以在上面的文件页直接下载，也可以临时使用 Hugging Face CLI；这个下载工具不是 zLLM 的运行时依赖：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;hf&lt;&#x2F;span&gt;&lt;span&gt; download unsloth&#x2F;gemma-4-E4B-it-GGUF \
&lt;&#x2F;span&gt;&lt;span&gt;  gemma-4-E4B-it-Q4_K_M.gguf mmproj-F16.gguf \
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;  --local-dir&lt;&#x2F;span&gt;&lt;span&gt; .&#x2F;models&#x2F;gemma-4-E4B-it-GGUF
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;国内网络可以从魔搭页面选择同名文件。Q4_K_M 在 24 GB MacBook Air 上兼顾体积和质量；视觉投影约 1 GB，保留 F16 可以避免再引入一层视觉量化误差。&lt;&#x2F;p&gt;
&lt;p&gt;库模式使用的最小 &lt;code&gt;gemma4-metal.yaml&lt;&#x2F;code&gt; 如下：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-yaml &quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;version&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;kind&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;standalone
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;http&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;listen&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;127.0.0.1:8000
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;public_base_url&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;http:&#x2F;&#x2F;127.0.0.1:8000
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;artifacts&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;directory&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;.&#x2F;artifacts
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;node&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;cache_directory&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;.&#x2F;cache&#x2F;gemma4
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;persist_kv_cache&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;false
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;max_concurrency&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;model&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;architecture&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;gemma4
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;weights_directory&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;.&#x2F;models&#x2F;gemma-4-E4B-it-GGUF&#x2F;gemma-4-E4B-it-Q4_K_M.gguf
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;lm_head_quantization&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;native
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;max_sequence_length&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;49152
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;execution&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;prefill_chunk_size&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;2048
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;backend&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;kind&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;metal
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;device&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;default
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;配置里保留 &lt;code&gt;http&lt;&#x2F;code&gt; 和 &lt;code&gt;artifacts&lt;&#x2F;code&gt; 是为了与 standalone 配置结构兼容；&lt;code&gt;Engine::from_config&lt;&#x2F;code&gt; 不会监听端口。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;yi-ge-8-mb-de-metal-ming-ling-xing-cheng-xu&quot;&gt;一个 8 MB 的 Metal 命令行程序&lt;&#x2F;h2&gt;
&lt;p&gt;只想在终端里马上对话时，不需要写 YAML。直接从 GitHub 获取源码并构建：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;git&lt;&#x2F;span&gt;&lt;span&gt; clone https:&#x2F;&#x2F;github.com&#x2F;zllm-lab&#x2F;zllm.git
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;cd&lt;&#x2F;span&gt;&lt;span&gt; zllm
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;cargo&lt;&#x2F;span&gt;&lt;span&gt; build&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; --release --bin&lt;&#x2F;span&gt;&lt;span&gt; zllm-metal
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;然后把主模型路径直接传给 8.1 MB 的 release 程序：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;.&#x2F;target&#x2F;release&#x2F;zllm-metal &lt;&#x2F;span&gt;&lt;span&gt;\
&lt;&#x2F;span&gt;&lt;span&gt;  .&#x2F;models&#x2F;gemma-4-E4B-it-GGUF&#x2F;gemma-4-E4B-it-Q4_K_M.gguf
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;zllm-metal&lt;&#x2F;code&gt; 会自动完成这些工作：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;从 GGUF metadata 识别 Gemma 4 架构；&lt;&#x2F;li&gt;
&lt;li&gt;发现同目录的 &lt;code&gt;mmproj-*.gguf&lt;&#x2F;code&gt;；&lt;&#x2F;li&gt;
&lt;li&gt;如果存在匹配的 &lt;code&gt;mtp-*.gguf&lt;&#x2F;code&gt;，自动启用 MTP 投机解码；&lt;&#x2F;li&gt;
&lt;li&gt;根据统一内存、权重和安全余量推导 KV 预算与上下文；&lt;&#x2F;li&gt;
&lt;li&gt;装载 Metal runtime，进入多轮终端对话并复用 terminal KV Cache。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;这台机器的一次实际启动输出是：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;[zllm-metal] gemma4 (hybrid GQA, KV f16) | 权重 4.64 GiB |
&lt;&#x2F;span&gt;&lt;span&gt;  KV 预算 7.96 GiB | ≈172032 B&#x2F;token | 上下文 49152 (模型上限 131072)
&lt;&#x2F;span&gt;&lt;span&gt;[zllm-metal] 模型加载完成(1.4s),直接输入消息开始对话
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;直接输入文字即可对话：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;&amp;gt; 请用一句话介绍你自己，并说明你正在本地 Mac 上运行。
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;识别本地图片时，把路径和问题放在同一条消息里即可；裸路径、引号、反引号和带空格路径都支持，路径本身不会进入模型提示词：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;&amp;gt; 请用两句话描述这张图片 `&#x2F;Users&#x2F;me&#x2F;Pictures&#x2F;demo.png`
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;也可以先复制图片，再输入：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;&amp;gt; &#x2F;paste 请描述剪贴板中的图片
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;常用命令如下：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;&#x2F;stats    查看上下文与 KV 内存
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;reset    清空当前对话
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;compact  压缩较早的对话历史
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;paste    读取 macOS 剪贴板中的 PNG 图片
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;exit     退出
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;zhen-ji-jie-guo-yu-shi-pin-yan-shi&quot;&gt;真机结果与视频演示&lt;&#x2F;h2&gt;
&lt;p&gt;下面的数据均来自这台 MacBook Air M5（24 GB）和同一份 Q4_K_M 权重，不是理论估算：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;项目&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;实测结果&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;zllm-metal&lt;&#x2F;code&gt; release 文件&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;8.1 MB&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;CLI 自动上下文&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;49,152 token&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;模型加载（本地热文件缓存，多次启动）&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.9–1.6 秒&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;文本 &lt;code&gt;tg50&lt;&#x2F;code&gt;，三次冷启动均值&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;40.47 token&#x2F;s&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;同一 115-token 回复的全段 decode&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;38.1–38.5 token&#x2F;s&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;323-token 图文请求 prefill&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;6.469 秒&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;同一图文请求 TTFT&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;7.312 秒&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;下面的 23 秒短片把同一次终端流程排成更易阅读的画面：启动 &lt;code&gt;zllm-metal&lt;&#x2F;code&gt;、进行文字对话、把刚才那张本地图片路径交给模型、查看 KV 状态并退出。命令、启动数据、token 数和回答文字均取自实机终端输出；画面只做了换行和节奏编排。&lt;&#x2F;p&gt;
&lt;video class=&quot;post-video&quot; controls playsinline preload=&quot;metadata&quot; poster=&quot;&#x2F;videos&#x2F;gemma4-e4b-terminal-poster.png&quot;&gt;
  &lt;source src=&quot;&#x2F;videos&#x2F;gemma4-e4b-terminal-demo.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
  当前浏览器不支持内嵌视频，请直接下载演示文件。
&lt;&#x2F;video&gt;
&lt;blockquote&gt;
&lt;p&gt;这段视频验证的是从模型装载、文字对话到视觉输入的完整调用链，不是模型精度评测。速度会随模型版本、上下文、采样参数、文件缓存和机器散热状态变化。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;zui-hou-de-bian-jie&quot;&gt;最后的边界&lt;&#x2F;h2&gt;
&lt;p&gt;zLLM 的目标不是把 Python 环境换成另一套庞大 runtime，而是让模型执行真正成为应用的一部分：一个 Rust 对象、一份模型、一个原生 backend。应用拥有自己的线程、取消、缓存和生命周期，不必把核心能力委托给另一个进程。&lt;&#x2F;p&gt;
&lt;p&gt;对 Rust 开发者来说，“给现有程序增加本地 AI”因此不再是一次服务部署，而是一次普通的库调用；对终端用户来说，它就是一个 8 MB 级原生程序，在 MacBook Air 上完成文字与图像推理。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh">
        <title>为什么我们要用 Rust 重新写一个推理引擎</title>
        <published>2026-08-29T00:00:00+00:00</published>
        <updated>2026-08-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhuai.tech/blog/welcome/"/>
        <id>https://zhuai.tech/blog/welcome/</id>
        
        <content type="html" xml:base="https://zhuai.tech/blog/welcome/">&lt;blockquote&gt;
&lt;p&gt;zLLM 的定位不是给既有框架增加一个 Rust 外壳，而是以 Rust 为主体、以最少且必要的第三方依赖，从模型执行、数据生命周期和硬件资源出发，重新设计一个原生推理引擎。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;why-rust&#x2F;resource-flow.svg&quot; alt=&quot;zLLM 完整推理任务与资源流&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;这篇文章回答三个问题：zLLM 为什么必须拥有自己的引擎边界，为什么 Rust 恰好适合表达这些边界，以及这种选择如何落实到真实代码与完整推理路径中。&lt;&#x2F;p&gt;
&lt;p&gt;今天再写一个大模型推理引擎，很容易被问到两个问题：为什么不用已经成熟的引擎？为什么偏偏选择 Rust？&lt;&#x2F;p&gt;
&lt;p&gt;如果目标只是尽快让一个模型跑起来，复用现有框架通常是更经济的选择。但 zLLM 要解决的不是“把模型跑起来”这一件事。我们希望同一套引擎能够面对 Apple UMA、CPU、CUDA、ROCm、Vulkan、NPU、本地 SSD、大模型 MoE 权重、长上下文 KV Cache，以及未来多机分层执行。这些资源有不同的存储位置、同步方式、带宽、生命周期和失败边界。真正困难的部分，不是再封装一次算子 API，而是让整个推理过程的资源关系足够清楚，清楚到可以被验证、优化和长期演进。&lt;&#x2F;p&gt;
&lt;p&gt;因此，zLLM 选择从头设计。不是因为现有项目不够好，也不是为了追求“自研”本身，而是因为我们想要的边界与许多现有系统的历史边界不同。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wo-men-suo-shuo-de-cong-tou-xie-shi-shen-me&quot;&gt;我们所说的“从头写”是什么&lt;&#x2F;h2&gt;
&lt;p&gt;从头写不等于拒绝标准，也不等于重复制造所有基础设施。&lt;&#x2F;p&gt;
&lt;p&gt;zLLM 继续读取 Safetensors、GGUF&#x2F;GGML、compressed-tensors 和 ModelOpt 等标准权重格式，继续使用 Metal、CUDA、ROCm、Vulkan 等平台能力，也会采用经过验证的通用 Rust 库。我们不重新发明文件格式、网络协议或操作系统接口。&lt;&#x2F;p&gt;
&lt;p&gt;我们重新设计的是推理引擎本身：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;模型规格如何表达；&lt;&#x2F;li&gt;
&lt;li&gt;模型算法如何只写一份；&lt;&#x2F;li&gt;
&lt;li&gt;backend 应该提供哪些能力；&lt;&#x2F;li&gt;
&lt;li&gt;kernel 如何保持参数化而不绑定模型；&lt;&#x2F;li&gt;
&lt;li&gt;权重、KV Cache、activation、expert 和 scratch 分别由谁拥有；&lt;&#x2F;li&gt;
&lt;li&gt;prefill、append prefill 和 decode 的完整执行路径如何调度；&lt;&#x2F;li&gt;
&lt;li&gt;单机、嵌入式服务和多机分层如何共享同一套语义；&lt;&#x2F;li&gt;
&lt;li&gt;正确性如何由 CPU reference 和跨后端一致性验证锚定。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;换句话说，zLLM &lt;strong&gt;复用开放标准与平台能力，但自己拥有模型执行和资源语义&lt;&#x2F;strong&gt;：&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;复用&lt;&#x2F;th&gt;&lt;th&gt;自己拥有&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Safetensors、GGUF&#x2F;GGML、compressed-tensors、ModelOpt&lt;&#x2F;td&gt;&lt;td&gt;模型规格、算法编排与权重装配&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Metal、CUDA、ROCm、Vulkan、NPU 平台接口&lt;&#x2F;td&gt;&lt;td&gt;backend capability、资源驻留与同步&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;成熟的序列化、网络和异步 Rust 库&lt;&#x2F;td&gt;&lt;td&gt;KV、Expert、Activation、Scratch 的生命周期&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;平台原生 GPU kernel 语言&lt;&#x2F;td&gt;&lt;td&gt;完整 Prefill、Append Prefill 与 Decode Round&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;这也是“重新设计”与“重写”的区别。重写容易把旧结构换一种语言再实现一次；重新设计则要求回到数据依赖、资源位置和真实任务，重新判断每一个模块是否有存在的必要。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;zui-xiao-yin-qing-bu-shi-zui-shao-gong-neng&quot;&gt;最小引擎，不是最少功能&lt;&#x2F;h2&gt;
&lt;p&gt;zLLM 追求的“最小”，不是做一个只能演示的小程序，而是让系统中的概念数量保持最小。&lt;&#x2F;p&gt;
&lt;p&gt;我们的长期准则是 &lt;strong&gt;Simple &#x2F; Short &#x2F; Straight&lt;&#x2F;strong&gt;：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simple&lt;&#x2F;strong&gt;：能由一个清楚模块解决的问题，不拆成多层抽象；&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Short&lt;&#x2F;strong&gt;：代码、作用域和对象生命周期都尽可能短；&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Straight&lt;&#x2F;strong&gt;：定义靠近使用点，控制流和依赖方向可以顺着代码直接读懂。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;目录、文件、trait、wrapper、manager、registry 和 context 都不是免费的。每增加一个实体，读代码的人就要多记住一个名字、多理解一层转发、多追踪一段生命周期。只有当一个实体确实拥有独立的数据、行为、生命周期或依赖边界时，它才值得存在。&lt;&#x2F;p&gt;
&lt;p&gt;所以 zLLM 不建立没有真实实现的通用 flow，不为了“未来可能有用”提前设计抽象工厂，也不保留只做转发的空壳 facade。模型执行流程只有一份；平台差异由 capability trait 和具体 backend 承接；kernel 只表达算子，不知道模型名字。&lt;&#x2F;p&gt;
&lt;p&gt;最小化的对象不是能力，而是理解系统所需的心智负担。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;zui-shao-qie-bi-yao-de-di-san-fang-yi-lai&quot;&gt;最少且必要的第三方依赖&lt;&#x2F;h2&gt;
&lt;p&gt;“最小第三方依赖”同样不意味着零依赖。密码哈希、序列化、异步运行时、HTTP、标准格式解析等成熟能力，没有理由为了形式上的纯粹重新实现。&lt;&#x2F;p&gt;
&lt;p&gt;我们的原则是：依赖可以提供通用能力，但不能替我们拥有推理引擎的核心语义。&lt;&#x2F;p&gt;
&lt;p&gt;模型执行、资源驻留、KV 生命周期、权重装配、调度策略和跨后端能力边界，应当在 zLLM 自己的代码中清晰可见。第三方库不应在不透明的内部替我们决定张量放在哪里、什么时候复制、什么时候同步、如何回收，或者某个模型如何执行。&lt;&#x2F;p&gt;
&lt;p&gt;这带来三个直接收益：&lt;&#x2F;p&gt;
&lt;p&gt;第一，依赖图更容易审计。升级一个网络库，不应改变模型数值；增加一种权重格式，不应侵入 runtime；替换某个平台绑定，不应迫使模型算法重写。&lt;&#x2F;p&gt;
&lt;p&gt;第二，性能成本更可见。推理系统最怕“方便的抽象”背后出现一次隐式拷贝、一次设备同步或一次临时反量化。核心路径由自己拥有，才能追踪每一份大数据的来源、去向和生命周期。&lt;&#x2F;p&gt;
&lt;p&gt;第三，长期演进不受上游框架结构限制。zLLM 可以复用标准和通用库，但不会把自己的架构方向交给某个大型运行时、张量框架或 Python 扩展体系决定。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wei-shen-me-rust-gua-he-zhe-jian-shi&quot;&gt;为什么 Rust 适合这件事&lt;&#x2F;h2&gt;
&lt;p&gt;Rust 的价值不只是“没有 GC”或“比 Python 快”。对于推理引擎，Rust 最重要的优势是：它能把资源关系写进程序结构，并在编译阶段消灭大量不合法状态。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-suo-you-quan-yu-sheng-ming-zhou-qi-zheng-hao-dui-ying-tui-li-zi-yuan&quot;&gt;1. 所有权与生命周期正好对应推理资源&lt;&#x2F;h3&gt;
&lt;p&gt;推理引擎处理的不是抽象数字，而是一组昂贵资源：mmap 权重、设备 buffer、KV page、command buffer、异步 I&#x2F;O、跨节点 session、临时 scratch。它们都在回答相同的问题：谁拥有、谁可以借用、什么时候能够释放、异步任务结束前是否仍然有效。&lt;&#x2F;p&gt;
&lt;p&gt;Rust 的所有权、借用和生命周期不是额外约束，而是这类问题的直接语言表达。一个异步预取任务不能引用已经释放的目标 buffer；一份 cache 不能在仍被执行队列使用时被回收；共享状态必须明确同步边界。这些错误在 C&#x2F;C++ 中往往依赖约定和审查，在带 GC 的语言中又容易被延迟释放或外部资源生命周期绕开。Rust 让其中相当一部分成为编译问题。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-wu-gc-de-ke-yu-ce-zhi-xing&quot;&gt;2. 无 GC 的可预测执行&lt;&#x2F;h3&gt;
&lt;p&gt;decode 是持续、细粒度、对尾延迟敏感的循环。不可预测的暂停、隐式对象分配和延迟析构会让性能分析变得困难。&lt;&#x2F;p&gt;
&lt;p&gt;Rust 没有垃圾回收器，值的生命周期通常由作用域决定。配合预分配和 scratch 复用，我们可以让热路径中的分配、释放和同步更可预测。这并不自动保证高性能，但它提供了构建可预测系统所需要的基础。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-ling-cheng-ben-chou-xiang-gua-he-biao-da-backend-neng-li&quot;&gt;3. 零成本抽象适合表达 backend 能力&lt;&#x2F;h3&gt;
&lt;p&gt;不同 backend 的差异是真实存在的。CPU tensor 可以是普通内存，Metal tensor 是设备资源句柄，ROCm 可能有完全不同的驻留与提交模型。把它们强行塞进统一的动态对象，往往只会得到最低公分母，以及遍布热路径的间接调用和运行时检查。&lt;&#x2F;p&gt;
&lt;p&gt;zLLM 使用 trait 和 associated type 表达能力：模型 runtime 声明自己需要什么，具体 backend 在编译期提供实现。泛型会带来编译时间和二进制体积成本，但换来的是静态组合、可读的能力集合，以及热路径上更少的动态分派。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;4-mei-ju-yu-mo-shi-pi-pei-rang-ge-shi-zhuang-tai-xian-shi&quot;&gt;4. 枚举与模式匹配让格式状态显式&lt;&#x2F;h3&gt;
&lt;p&gt;权重可能是 F32、F16、BF16，也可能保持 FP8、FP4、W4A16 或 GGUF block 编码直到 kernel。用明确的 enum 表达这些状态，可以要求每个 backend 对每种合法输入作出处理或明确拒绝，而不是在字符串、指针和隐式约定之间猜测。&lt;&#x2F;p&gt;
&lt;p&gt;这对量化尤其重要：量化不是“加载时先转成浮点”的附属功能，而是与模型规格、平台 kernel 正交的一条数据路径。状态越显式，隐式展开和错误分派就越难混入系统。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;5-rust-tong-shi-fu-gai-di-ceng-yu-fu-wu-ceng&quot;&gt;5. Rust 同时覆盖底层与服务层&lt;&#x2F;h3&gt;
&lt;p&gt;许多推理系统天然分裂为两部分：底层由 C&#x2F;C++ 或 GPU 语言实现，上层由 Python、Go 或另一套服务框架组织。边界两侧各自合理，但组合后往往出现重复的数据模型、跨语言 FFI、两套错误处理和难以统一的生命周期。&lt;&#x2F;p&gt;
&lt;p&gt;Rust 可以同时承担权重解析、CPU reference、backend 绑定、runtime、调度器、HTTP&#x2F;SSE 服务和嵌入式库接口。GPU kernel 仍然使用平台原生语言——Metal Shading Language、HIP&#x2F;CUDA、WGSL 或 AscendC——但 kernel 之外的引擎主体由一套类型系统、一套错误模型和一套资源语义贯穿。&lt;&#x2F;p&gt;
&lt;p&gt;这才是 zLLM 所说的“纯 Rust”：不是否认平台原生 kernel 的存在，而是不依赖 Python 控制面、不把核心执行委托给另一个大型张量运行时，Rust 直接拥有整个推理引擎。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;6-gong-ju-lian-rang-kua-ping-tai-gong-cheng-bao-chi-yi-zhi&quot;&gt;6. 工具链让跨平台工程保持一致&lt;&#x2F;h3&gt;
&lt;p&gt;Cargo 的构建、feature、测试和依赖管理，使 CPU、Metal、CUDA、ROCm、Vulkan 等路径可以在同一个工程中保持清晰边界。平台能力按 feature 和目标系统启用，CPU 单元测试不依赖真实权重，GPU 路径则由 CPU oracle 做数值锚定。&lt;&#x2F;p&gt;
&lt;p&gt;Rust 不能替代真机测试，也不能在编译期发现运行时编译的 MSL&#x2F;HIP kernel ABI 错误。但它能把平台相关的不安全边界压缩在较小范围内，让其余绝大部分系统继续享受静态检查。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;zllm-de-jia-gou-ru-he-ti-xian-zhe-xie-xuan-ze&quot;&gt;zLLM 的架构如何体现这些选择&lt;&#x2F;h2&gt;
&lt;p&gt;zLLM 把系统分成几个职责明确、依赖单向的区域：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;why-rust&#x2F;architecture.svg&quot; alt=&quot;zLLM 分层架构&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;这里最关键的不是目录长什么样，而是依赖方向：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;模型规格不依赖平台；&lt;&#x2F;li&gt;
&lt;li&gt;模型算法只写一份，不复制到各个 backend；&lt;&#x2F;li&gt;
&lt;li&gt;backend 和 kernel 不知道具体模型；&lt;&#x2F;li&gt;
&lt;li&gt;attention、MoE、KV Cache 等领域层保存设备无关语义和 reference；&lt;&#x2F;li&gt;
&lt;li&gt;权重层只负责标准格式、命名、shape 和生命周期，不接管模型算法；&lt;&#x2F;li&gt;
&lt;li&gt;CPU 是正确性 oracle，GPU kernel 是经过一致性验证的加速实现。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;这种设计避免了最常见的组合爆炸：不是每增加一个模型，就为每个平台复制一套执行流程；也不是每增加一个后端，就重新理解所有模型。新增模型主要增加规格、编排和权重适配；新增 backend 主要实现已有 capability；只有出现新的通用算子语义时，才扩展领域能力。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;cong-shu-ju-liu-er-bu-shi-yu-yi-biao-qian-chu-fa&quot;&gt;从数据流，而不是语义标签出发&lt;&#x2F;h2&gt;
&lt;p&gt;从头设计给了我们一个重要机会：按真实数据依赖组织系统，而不是照搬论文章节或模型名词。&lt;&#x2F;p&gt;
&lt;p&gt;如果 A 的输出是 B 的输入，它们就是必须串行的 pipeline，应当靠近并共同管理中间状态。如果 A 和 B 独立读取 hidden，它们就应当分开，让 backend 有机会并行调度。权重的结构、kernel 的融合和资源的生命周期都遵循同一原则。&lt;&#x2F;p&gt;
&lt;p&gt;同样，生产推理只承认三类完整任务：新会话 prefill、已有会话 append prefill，以及完整 decode round。截断到某一层、只跑一个 kernel、只比较局部输出，都可以是诊断手段，但不能替代端到端任务。优化必须最终回到 TTFT、decode latency、吞吐、内存峰值、SSD 等待和设备利用率。&lt;&#x2F;p&gt;
&lt;p&gt;这让系统不容易被漂亮但无关的局部数字带偏。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;rust-bu-hui-zi-dong-dai-lai-zheng-que-xing-he-xing-neng&quot;&gt;Rust 不会自动带来正确性和性能&lt;&#x2F;h2&gt;
&lt;p&gt;选择 Rust 不是宣称语言可以替代工程纪律。&lt;&#x2F;p&gt;
&lt;p&gt;Rust 无法证明 GPU kernel 的数值正确，无法阻止错误的算法设计，也无法保证一个看起来优雅的抽象没有性能代价。多 backend 意味着同一 kernel 语义可能需要 Rust reference、MSL、HIP、CUDA、WGSL 或 AscendC 多份高性能实现；泛型会增加编译成本；capability trait 也可能在缺乏约束时不断膨胀。&lt;&#x2F;p&gt;
&lt;p&gt;所以 zLLM 把以下纪律与语言能力放在一起：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;新算子先有 reference 语义和单元测试；&lt;&#x2F;li&gt;
&lt;li&gt;CPU 与设备 backend 做一致性验证；&lt;&#x2F;li&gt;
&lt;li&gt;kernel 维度全部参数化，不写入模型常量；&lt;&#x2F;li&gt;
&lt;li&gt;不用孤立 kernel benchmark 代替完整路径指标；&lt;&#x2F;li&gt;
&lt;li&gt;没有测量证据，不为性能增加复杂度；&lt;&#x2F;li&gt;
&lt;li&gt;源码、编译通过、oracle 通过和真机端到端验证必须明确区分；&lt;&#x2F;li&gt;
&lt;li&gt;只做完成当前任务所需的最小改动，不借机重构无关代码。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Rust 提供的是一块坚实地基。真正决定引擎质量的，仍然是清楚的边界、可复现的验证和对复杂度的持续克制。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wei-shen-me-zhi-de-cong-tou-kai-shi&quot;&gt;为什么值得从头开始&lt;&#x2F;h2&gt;
&lt;p&gt;从头写的代价很高。权重格式要自己理解，kernel 要逐个平台优化，服务与调度要自己打磨，错误也没有上游框架替我们兜底。短期看，这条路一定比接入一个成熟运行时更慢。&lt;&#x2F;p&gt;
&lt;p&gt;但它换来了一个长期价值：系统中的每一个关键决定都有明确所有者。&lt;&#x2F;p&gt;
&lt;p&gt;当模型变了，我们知道应该修改规格、编排还是领域能力；当平台变了，我们知道应该修改 backend 还是 kernel；当性能下降，我们能够沿着权重、activation、KV、expert、scratch 和同步点逐一追踪；当系统扩展到多机，我们传输的是层边界 activation，而不是把模型内部状态和远端 RPC 混成新的热路径。&lt;&#x2F;p&gt;
&lt;p&gt;这就是 zLLM 的目标：不是成为依赖最庞大、抽象最完整的通用 AI 框架，而是成为一个足够小、足够直接、能够真正拥有硬件资源和模型执行的原生推理引擎。&lt;&#x2F;p&gt;
&lt;p&gt;我们选择 Rust，因为它与这套目标一致：安全，但不放弃底层控制；抽象，但不必支付运行时成本；跨平台，但不抹平平台差异；能写 kernel 周围最靠近硬件的代码，也能一路写到服务和分布式调度。&lt;&#x2F;p&gt;
&lt;p&gt;我们选择从头设计，因为只有这样，Simple、Short、Straight 才不是对旧系统的局部修补，而能成为整个引擎从第一行代码开始遵守的结构原则。&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
