算法层回答的不是“在哪块 GPU 上跑”,而是“这一步数学上应该得到什么”。在
zLLM 中,它首先是一组设备无关的规格、数据流和 CPU f32 reference:CPU
oracle 给出可读、可测的正确性基准,Metal、CUDA、ROCm、Vulkan 与 NPU kernel
则是在同一语义上的加速实现。
这一区分很重要。高性能 kernel 可以换布局、融合算子、降低精度或并行提交,但 不能悄悄改变 causal mask、RoPE、Top-K 路由、KV 写入位置或数值定义。新算法先 在 CPU/reference 上钉住 shape、边界与结果,再与设备实现做 oracle 对拍;通过 oracle 只证明局部语义一致,最终仍要执行完整 prefill/decode 并验证输出与性能。
1. 从 token 到下一 token
可以把一层 Transformer 类比为一次有结构的思考,但不要把类比当成生物学结论: 模型的“角度、形状、视觉、语义、历史、关系”并不是六个预先命名的槽位,而是训练 后分布在高维向量中的特征方向。一个维度通常也没有独立、稳定的人类解释。
完整路径是:
- 分词:tokenizer 把文本或多模态占位符变成 token id。token 是词、子词、 字节片段或特殊符号,不必等同于一个汉字或单词。
- 向量化:embedding 按 id 查表,把离散 token 变成
hidden_size维向量; 位置编码再让注意力知道顺序与相对位置。 - 逐层加工:每层先用 attention 从当前与历史 token 聚合相关信息,再用 FFN 或 MoE 对每个 token 的表示做非线性特征变换;归一化与残差连接贯穿其中。
- 输出:final norm 与 LM head 把 hidden 投影到词表大小的 logits,采样或 贪心选择一个 token。decode 把它作为下一轮输入,直到 EOS 或达到长度上限。
“升维、矩阵乘法、合并、降维”究竟是什么
若一行 hidden 写成 x ∈ R^d,线性层本质是 y = xW。矩阵乘法把旧坐标的
加权组合映射到一组新坐标:它既可把 d 维投影到更宽的 d_ff 维,也可投影成
Q/K/V,或把多头结果映射回 d 维。
- 升维不是凭空增加事实,而是提供更宽的中间工作空间,使门控与非线性能够 表达更多特征组合。
- 多头把同一 hidden 投影到多个子空间。不同 head 可能学到局部、语义、位置、 指代或视觉关系,但并没有人工规定“第 3 头就是形状”。
- 合并通常是对 value 的加权求和,再拼接多个 head;它是在聚合信息,不是把 原句复制一遍。
- 降维用输出投影或 FFN down projection 回到
hidden_size,从而能与残差相加 并送入下一层。它是学习得到的投影,不等于简单删除末尾若干维。
因此“一层的实际思考过程”更准确地说,是 attention 的跨 token 读取与 FFN 的 逐 token 特征变换交替进行;层数提供连续的表示修正,而向量维度提供每一步的表达 空间。
2. Attention:决定此刻应读取哪些信息
经典 scaled dot-product attention 为:
Attention(Q,K,V) = softmax(QKᵀ / √dₖ + mask)V
可以用三个问题理解它:Q 是“当前在找什么”,K 是“每段历史用什么索引被找到”, V 是“找到后取回什么内容”。causal mask 保证生成第 N 个 token 时看不到未来。 《Attention Is All You Need》用全局 self-attention 替代了当时序列模型中的循环主干,但今天的主流模型已经发展出多种计算与存储折中。
这些方案并不都是“门限分组”,应按压缩发生的位置区分:
| 方案 | 核心办法 | 主要减少什么 | zLLM 对应 |
|---|---|---|---|
| MHA,多头注意力 | 每个 Q head 有自己的 K/V head | 基线方案,表达力强但 KV 较大 | 通用多头几何与 block/reference 语义 |
| MQA | 所有 Q head 共享一组 K/V | KV Cache 与 K/V 带宽 | 可视为 num_kv_heads = 1 的 GQA 退化形态 |
| GQA,分组查询注意力 | 一组 Q heads 共享一个 K/V head | KV Cache、K/V 投影和带宽 | attention/gqa.rs,支持 full/sliding window 与 hybrid layer |
| MLA / Gated MLA | 把 KV 压到低秩 latent,需要时重建;可带输出门 | KV 表示与投影开销 | attention/mla.rs,用于 GLM-5.2、DeepSeek-V3、Kimi-K3 等编排 |
| Sliding / Block Attention | 只看最近窗口或显式可见块 | 长上下文 attention 计算 | gqa::CausalWindow、attention/block.rs |
| DSA / MSA | 学习索引器或块索引,每个 query 只选 Top-K token/块 | 长上下文的 QK 与 AV 计算 | attention/dsa.rs、attention/msa.rs |
| 压缩稀疏注意力 | 保留近期窗口,把更早历史池化压缩后选择或全读 | 远端历史的存储与计算 | attention/compressed_sparse.rs |
| Gated DeltaNet / KDA | 用短卷积和固定大小 recurrent state 递推历史 | 避免随上下文线性增长的完整 KV | attention/gated_delta_net.rs、attention/kda.rs |
门控、分组、低秩和稀疏选择是四件不同的事。门控控制信息通过多少;GQA 让多个 query head 共享 KV;MLA 压缩表示;DSA/MSA 只让当前 query 聚焦一部分历史。 它们都像人类思考中的“聚焦”:当前问题不必同时、同强度地翻阅全部记忆,但工程上 必须明确这种聚焦是否仍扫描全历史来做选择,否则“稀疏结果”未必带来稀疏计算。
3. KV Cache:历史信息的可复用表示
自回归生成每轮只多一个 token。如果每轮都重新计算此前全部 token 的 K 和 V, 大量矩阵乘法会被重复。KV Cache 保存各层已经得到的历史 K/V 或等价状态,decode 只计算新 token,再让它查询历史并追加新记录。
它可类比为“工作记忆的索引和内容”,但不是原始对话的无损副本:
- GQA 保存较少的 KV heads;MHA/GQA 的容量通常仍随 token 数线性增长。
- MLA 保存归一化后的低秩 latent 与 RoPE 分量,读取时再重建所需表示。
- zLLM 的 MLA cache 支持
F16,也定义了 latent INT8 per-group、RoPE 保持 F16 的布局;量化减少容量,但会引入可测的数值误差。 - sliding window 只需保留有效窗口;DSA/MSA 还要维护索引信息。
- Gated DeltaNet 与 KDA 保存固定大小 recurrent/短卷积 state,它们与 full attention KV 的更新语义不同,不能强塞进同一种存储抽象。
kv_cache/mod.rs 负责逻辑层到 cache 槽的映射、GQA/MLA 形态、格式、步长、容量
和有效长度等设备无关语义;backend 才负责 buffer 分配、驻留位置、量化 kernel
与同步。KV 跟随负责该层计算的设备驻留,多机按连续完整层切分时也不跨网络搬运
每层 KV。
4. FFN:每层内部的特征加工
Attention 解决“从别的 token 取什么”,FFN 解决“当前 token 取到信息后怎样变换”。 主流 gated MLP 可概括为:
y = W_down(act(xW_gate) ⊙ (xW_up))
gate/up 把 hidden 投影到更宽的 intermediate 空间,激活函数与逐元素乘法形成非线性
选择,down 再投影回 hidden。zLLM 的 moe/dense_mlp.rs 保存这一平台无关数据流,
当前激活规格覆盖 SiLU、clamped SiLU、SiTU、OpenAI 风格 SwiGLU 与 GELU-Tanh;
矩阵乘法仍由 backend capability 实现。
Dense FFN 与 MoE
Dense FFN 的整套 gate/up/down 权重对每个 token 都参与计算。它可被口语化为“一层 的全部专家都工作”,但源码里 dense MLP 是一个完整网络块,并不是先存在许多专家 再全部选中。
MoE 则放置多组 expert FFN,由 router 给每个 token 打分并只激活 Top-K routed experts;shared experts 可始终执行。这个设计更接近大脑中“不同区域按任务活跃”的 类比:总参数容量很大,但单个 token 的活跃计算量较小。这个类比只解释稀疏激活, 不意味着 Transformer expert 对应固定脑区或具备可命名的人格。
zLLM 的 FFN/MoE 模块边界如下:
| 模块 | 功能 |
|---|---|
moe/dense_mlp.rs | dense gated MLP 的规格、激活与 gate/up → activation → down 数据流 |
moe/routing.rs | 路由分数、Top-K、分组 assignment 与活跃 expert 统计 |
moe/topk_moe.rs | routed/shared expert 的组合、路由后缩放和输出累加 |
moe/latent_moe.rs | 原 hidden 做路由,低维 latent 进入 routed expert,shared MLP 仍读原 hidden |
moe/prefill.rs | 多 token prefill 的 expert 分组执行与合并 |
moe/expert_predictor.rs | 根据真实历史路由预测后续 expert,服务异步预取;不持权重、不执行 I/O |
MoE 首先降低的是每 token 的计算量,不会自动降低模型总权重。高吞吐、低延迟场合 通常需要让大量甚至全部 expert 权重驻留内存/显存,所以显存占用仍接近完整 MoE 模型;router 省下 FLOPs,却没有让未选 expert 从文件中消失。
在容量受限且性能要求较低的场合,可以把冷 expert 放在 CPU 内存或 SSD,路由后 按需流式加载;zLLM 也把 expert source、prefetch 与预测反馈放在 backend/runtime 边界上。但这会引入 I/O 延迟、预取命中率、并发工作集和抖动问题。只有加载与当前 层计算真正重叠,且命中率足够高,流式 expert 才可能在可接受延迟下换取较低驻留量。
5. zLLM 算法层的模块地图
| 目录/模块 | 它定义什么 | 它不负责什么 |
|---|---|---|
attention/mod.rs | 注意力家族的设备无关规格入口 | 设备 buffer 与 command submission |
attention/rope.rs | 位置旋转、布局与 reference | 模型 tokenizer |
attention/gqa.rs、mla.rs | GQA/MLA 几何、窗口、投影关系与 f32 reference | 平台专属融合 kernel |
attention/dsa.rs、msa.rs | token/block 评分、因果 Top-K 与稀疏选择语义 | 宣称所有选择路径天然是 O(K) |
attention/compressed_sparse.rs | 滑窗、压缩历史、可见位置与选择计划 | 具体压缩 buffer 的物理驻留 |
attention/gated_delta_net.rs、kda.rs | recurrent/conv state 的形态、更新与 reference | 将其伪装成普通 KV Cache |
attention/hybrid.rs | full attention 与线性/recurrent attention 的混合层状态 | 决定设备放置策略 |
attention/hyper_connection.rs、attn_res.rs | 多残差流、attention residual 等层内连接语义 | HTTP 会话或跨节点传输 |
moe/* | dense FFN、Top-K/latent MoE、路由、共享 expert 与预取反馈语义 | SSD/显存分配和 DMA |
kv_cache/* | cache 逻辑布局、格式、容量、有效长度与持久化数据边界 | 某平台的分配、同步和 kernel |
runtime/prefill.rs | 通用 chunk、batch、stage 与完整层循环 | 某个模型的 layer 顺序 |
runtime/generation.rs | token 生成、EOS、位置推进生命周期 | 具体 attention/FFN 数学 |
runtime/<model>/ | 按模型规格组合 embedding、每层 attention + FFN、norm、LM head | 复制 backend 算法实现 |
生产执行只有三种完整任务:NewPrefill 创建新 session 与 cache,AppendPrefill
在已有历史后追加一段 token,DecodeRound 输入一个 token、跑完所有层、追加状态并
产生下一个 token。算法层规定正确顺序,runtime 组合完整模型,backend 管资源与
提交,kernel 加速局部计算。
6. CPU oracle 如何守住正确性
CPU oracle 的价值不是“CPU 也能勉强跑模型”,而是把复杂设备优化拆成可验证问题:
- 用小而真实的 shape 验证分词后位置、mask、RoPE、矩阵维度和 cache append。
- 用确定输入对比 CPU
f32reference 与设备 kernel,检查 shape、有限值、绝对/ 相对误差,以及稀疏索引和 MoE expert id 这类离散结果。 - 逐层比较 hidden、路由与 logits,定位误差从哪一层开始放大。
- 固定 token 序列做 prefill/decode 回归,并测试 cache 边界、追加前缀和长上下文。
- 最后跑完整模型任务;编译通过、单算子 oracle 通过、固定 token 一致与真机性能 达标是四种不同状态,不能互相替代。
容差必须按 dtype、量化方式和误差传播设定,不能把统一的宽松阈值当作正确。对于
Top-K 路由、causal 选择、cache 长度和提交位置,很多不变量必须精确相等;对于
F16/BF16/量化矩阵输出,才使用有依据的 atol/rtol。即使局部 kernel 对拍通过,
也可能因为额外转换、同步或不合适的 shape 让端到端性能倒退,因此正确性 oracle
之后仍需要完整 prefill、decode 吞吐、峰值内存与稳定性验证。
这就是算法层在 zLLM 中的定位:它不是又一套设备实现,而是所有设备实现共同遵守 的数学合同。CPU reference 让合同可执行、可回归;attention/KV 定义怎样读取历史, FFN/MoE 定义怎样加工当前表示,runtime 再把它们组合成一次完整而可验证的生成。
English:zhuai.tech/en/blog/alg…
zLLM GitHub:github.com/zllm-lab/zl…