18 KiB
Calculet NPU 新模型适配与算子优化教程
日期:2026-08-02
适用基线:CalRT 0.7.6、calculet-llama:v0.4.8-release、llama.cpp fd9bd632
参考模型:Qwen3-30B-A3B,双芯粒,40960 context,batch 1
实施下钻:新模型的逐 Gate 输入/产物/命令/回退见《Calculet NPU 新模型适配工程 Runbook》;GEMM、Flash Attention、KV、MoE、D2D、fusion 与 sampling 的逐项 A/B 见《Calculet NPU 逐算子优化实验手册》。本文作为方法总览,不替代两份实施规范。
1. 本教程能做什么
当前资料足以完整定义新模型适配工程、准备模型和测试资产、修改服务适配层、分析现有 calbin,并与厂商共同验收新产物;但不足以在本地独立生成 NPU calbin。缺失项包括 calcc、ONNX passes、量化工具、kernel/codegen、C oplib、打包器及其版本化容器。
本文对每个阶段使用统一状态:
| 状态 | 含义 | 本教程中的处理方式 |
|---|---|---|
| C0 | 当前产品或产物已经确认 | 可以直接复现、回归或改进 |
| C1 | SDK 0.7.6 存在,生产未使用 | 先做隔离实验,不能直接宣称产品支持 |
| C2 | 用现有源码和公开接口可工程实现 | 我方可以开发,但要经过回归和压测 |
| C3 | 依赖厂商编译器、kernel 或 Runtime | 准备明确输入、验收标准和问题单交给厂商 |
| C4 | 当前版本不支持或不应开放 | 不进入普通推理产品面 |
当前最重要的边界是:生产系统不是把 llama.cpp 的 GGML 节点逐个下发 NPU,而是由 CalRT 加载预编译的 prefill/decode 整图。新模型适配的主线因此是“模型导出和编译成 calbin + llama.cpp 运行时契约适配”,不是简单增加一个 GGML backend 算子。
2. 先建立三套基线
在申请 NPU 编译前先冻结三套基线,避免后续把模型误差、编译问题和服务问题混在一起。
2.1 模型语义基线(C0/C2)
归档以下不可变信息:
- Hugging Face 仓库、revision/commit、license 和所有权重 SHA-256。
config.json、generation config、tokenizer、chat template、special token ID。- 层数、hidden size、Q/KV heads、head dim、FFN/MoE 参数、vocab、RoPE 参数、训练 context。
- 权重 dtype、tie embeddings、attention bias、norm epsilon、activation、sliding window。
- 至少 100 组固定 prompt/token IDs,以及 FP32/BF16 参考 logits 和 greedy token 序列。
参考模型 tokenizer 声明 131072 context,但当前 calbin 固定为 40960。运行时可用上限必须取“calbin 容量、服务 slot 容量、tokenizer/模型训练上限”的最小值,而不能只读 tokenizer。
2.2 CPU/GPU 参考基线(C2)
保留原生 Transformers 或上游 llama.cpp 基线,至少覆盖:
| 测试 | 目的 |
|---|---|
| 单 token embedding/第一层/末层 logits | 快速定位模型结构或权重映射错误 |
| prompt 长度 1、2、15、16、17 | 覆盖 D0=16 布局边界 |
| 255、256、257 和 4095、4096、4097 | 覆盖块边界与长序列算法切换 |
| 接近 context 上限 | 验证 RoPE、KV 地址和越界处理 |
| 多语言、代码、特殊 token、空 prompt | 验证 tokenizer 和模板一致性 |
| greedy 逐 token logits/top-k | 将采样随机性排除在数值检查之外 |
2.3 当前 NPU 产品基线(C0)
先在现有 Qwen3 calbin 上固化:
- 服务、Runtime、driver、firmware、calbin 和源代码版本。
- TTFT、prefill tokens/s、decode tokens/s、端到端 tokens/s。
- H2D、NPU infer、D2H、BF16 转 FP32、sampling、排队各阶段耗时。
- prompt/output 长度、并发、KV 占用、功耗/温度、错误和 reset 次数。
- 输出 token 和日志的可复现性。
历史 CSV 的所谓 TTFT 是 predicted_ms + prompt_ms,且 model=deepseek 被脚本硬编码,不能直接作为新模型验收基线。应重写采集器,以客户端首字节、服务分层计时和 Runtime 计时三套时钟互证。
3. 端到端适配流水线
flowchart LR
A["HF 权重和 tokenizer"] --> B["框架参考模型"]
B --> C["规范化 ONNX"]
C --> D["量化和校准"]
D --> E["算子清单与融合"]
E --> F["芯粒切分和内存规划"]
F --> G["calcc/codegen/kernel"]
G --> H["calbin 和 golden"]
H --> I["CalRT 冒烟与数值验收"]
I --> J["llama.cpp/服务接入"]
J --> K["正确性、性能、并发、稳定性"]
阶段 0:选择第一个新模型(C2)
优先选一个 dense decoder-only 模型,而不是再选 MoE。建议满足:
- 架构接近 Llama/Qwen,RMSNorm、RoPE、GQA、SwiGLU 均为常见形式。
- 1B 到 7B,单芯粒参数和 KV 明显能放下。
- 不使用 sliding-window、MLA、自定义 attention、视觉 encoder 或动态 control flow。
- vocab 不大于当前 151936,context 先选 4K/8K。
- 有公开参考权重和标准 benchmark。
这样能先验证导出、量化、编译、打包和服务契约,减少同时调试 MoE route、双芯粒通信和长上下文的变量。
阶段 1:HF/tokenizer 固化(C2)
执行项:
- 下载到内容寻址目录,生成文件清单和 SHA-256。
- 将 tokenizer 输出固定成整数 token IDs,记录 BOS/EOS/PAD、chat template 和 add-special-tokens 行为。
- 建立 greedy 解码脚本,每一步保存 top-20 token ID/logit。
- 导出模型结构 manifest,不依赖文件名猜测模型。
- 明确 context、batch 和 KV dtype 的产品目标。
交付物建议:
model-source/
manifest.json
config.json
tokenizer/
weights.sha256
reference/
prompts.jsonl
token_ids.npz
logits_bf16_or_fp32.npz
greedy_tokens.jsonl
阶段 2:规范化 ONNX(C2 准备,C3 最终兼容)
我方可以用 PyTorch/Transformers 导出 ONNX 并做标准检查,但无法确认 calcc 的私有输入 dialect。应同时保存原始 ONNX 和交给厂商的规范化 ONNX。
至少明确下列逻辑输入:
- token IDs 或 input embeddings。
- position IDs。
- attention mask 或等价 sequence-length CSR。
- past K/V 和 present K/V,或厂商 Runtime 管理的 KV 句柄。
- prefill 与 decode 的动态轴/固定 shape。
推荐拆分为两个契约:
| 子图 | 输入 shape | 输出 | 目标 |
|---|---|---|---|
| prefill | [B,S],S 在约定范围内 |
最后有效 token logits + KV | 吞吐和 TTFT |
| decode | [B,1] |
每 batch logits + 更新 KV | 每 token 时延和并发吞吐 |
导出后执行 ONNX checker、shape inference、常量折叠前后等价性,并生成逐 op type 计数。任何自定义 op 都要写明语义、shape、dtype、广播和误差要求。
阶段 3:量化和校准(C3)
参考 calbin 使用 dynamic W8A8/W4AF16 混合量化,但现有资料没有量化器或量化规则。厂商必须提供可重放的配置和容器。
建议的第一轮策略:
| 模块 | 首轮 dtype | 原因 |
|---|---|---|
| embedding、norm、RoPE | BF16 | 敏感且占比不高 |
| Q/K/V/O、dense FFN | W8A8 | 先追求稳定可验收 |
| lm_head | W8A8 或 BF16 对照 | 大 vocab 对精度和 D2H 都敏感 |
| KV cache | BF16 | 当前已确认基线 |
| MoE experts | dense 成功后再试 W8A8/W4AF16 | 隔离 route 与量化误差 |
| router/gate | BF16 或高精度 W8A8 | top-k 翻转会放大输出差异 |
校准集必须覆盖真实语言、代码、长上下文、边界 token 和异常输入。验收不应只有最终文本;至少比较 layer probes、logits cosine/最大绝对误差、top-k overlap、greedy token 一致率和任务指标。
阶段 4:算子盘点与支持矩阵(C2/C3)
从 ONNX 自动生成:
- op type、数量、shape family、dtype 和常量/动态属性。
- 每个 op 的 producer/consumer 和 fusion 候选。
- 是否有 NPU kernel、是否 CPU fallback、是否需要新 kernel。
- prefill/decode 是否共享实现,动态 shape 是否受支持。
对 decoder-only dense 模型,优先确认:embedding、RMSNorm、MatMul/GEMM、RoPE、Flash Attention、SiLU、Mul、Add、KV scatter/gather 和 lm_head。对 MoE 额外确认 router、top-k、dispatch、grouped GEMM、combine/reduce。
现有 Qwen3 图提供一个可对照的编译结果:prefill 1496 个节点,decode 1398 个节点;每个图含 48 个 Flash Attention、144 个 grouped MoE dense、193 个 fused RMSNorm 和 193 个 quantized fused matmul/add。它证明这些融合形态存在,但不提供复现这些融合的 pass 或 kernel 源码。
阶段 5:图融合与 kernel 优化(C3)
优先级按端到端收益排序:
- RMSNorm + quantize + GEMM,减少中间 BF16/INT8 往返。
- QKV projection + RoPE + KV scatter,减少 layout conversion。
- Flash Attention,分别优化短 prefill、长 prefill 和单 token decode。
- SwiGLU gate/up 融合及 down projection + residual。
- MoE top-k + dispatch + grouped GEMM + combine。
- lm_head + top-k/sampling,减少每 token 303872 B BF16 logits D2H。
每个融合必须提供:未融合 golden、融合 golden、支持 shape/dtype、SRAM/DRAM 用量、数值误差、fallback、性能和故障边界。不要仅以 kernel 微基准判断收益;还要测跨芯粒同步、H2D/D2H 和服务排队。
阶段 6:芯粒分配(C3)
编译输入应显式包含:芯粒数、每芯粒 CalCore 数、DRAM/SRAM 容量、互联带宽、参数/KV/工作区预算和 batch/context 组合。
候选切分:
| 方案 | 适合场景 | 主要风险 |
|---|---|---|
| 单芯粒 | 小型 dense 模型、多模型并存 | 单芯粒 kernel 吞吐和容量 |
| 逐层 tensor parallel | 大 dense/MoE;当前 Qwen3 方案 | 每层 gather/reduce 和 D2D |
| pipeline parallel | 层数多、microbatch 足够 | 单请求气泡、KV/调度复杂 |
| expert parallel | MoE 且专家权重/路由适合分布 | all-to-all、负载不均、热点专家 |
| 混合 TP+EP/PP | 大模型高并发 | 编译器和 Runtime 复杂度最高 |
当前 Qwen3 两芯粒都执行 48 层并各持有 128-expert 权重分片,是 tensor parallel,不是 expert parallel。新的 MoE 方案不能把当前文件名中的 2_chips 当成已经支持 EP 的证据。
阶段 7:内存规划与 calbin 生成(C3)
厂商输出必须包含:
param_blk*.bin及 chip mask。- 全局和子模型 memory reservation。
- prefill/decode I/O、CSR、ping/pong 地址和 shape/dtype。
- 每芯粒 profile/op list、op I/O 地址图。
- CPU/PLD command binaries 与可读文本。
- CCU ELF、必要的 dynamic D2D helper。
- 编译器/TVM/kernel/oplib commit、命令、配置、输入 ONNX hash。
- golden input/output 或可计算 hash。
容量验算至少包括:
parameter_bytes_per_chip
+ KV_bytes_per_chip(batch, context, dtype)
+ peak_workspace_per_chip
+ IO_and_command_buffers
+ allocator_guard
<= physical_usable_memory_per_chip
BF16 KV 的基础公式:
KV bytes = layers * kv_heads * 2(K,V) * head_dim * context * batch * 2 bytes
参考模型为 3.750 GiB 总 KV、1.875 GiB/芯粒。S8 V-cache 若只压 V,节省量不能简单按整个 KV 减半,必须按实际 K/V dtype 和布局重算。
阶段 8:calbin 静态验收(C2/C3)
在上卡前运行一个无设备解析器:
- 所有 manifest 文件存在,hash 和 size 正确。
- 只允许明确的 prefill/decode/kv_update 类型。
- 模型名精确匹配,不用 substring。
- batch、context、vocab、chip mask、dtype 与服务 manifest 一致。
- tensor 地址不越过 reservation,参数 offset 不越过文件。
- ping/pong buffer 不发生不允许的别名。
device_memory_required使用可解释单位。- prefill metadata 不能错误标成
llm_decode。
当前产物中的 device_memory_required: 16777216 MB 单位明显不合理,prefill metadata 还存在类型标记问题。新产物必须把这两项作为阻断式检查,而不是继续容忍。
阶段 9:CalRT 独立冒烟(C0/C1/C2)
先绕过 llama.cpp,写一个最小 C++ runner:
CreateVDevice(PCIE),检查Status()、Runtime/firmware 信息。CreateCalbin(),打印版本、模型列表和 LLM info。configure(),记录时间和内存占用。- 按精确 model name 创建 InputBuf/OutputBuf。
- 填 token/position,设置
cur_seq_len和past_kv_cur_seq_len。 - 提交 prefill,
Wait(),比较 golden logits。 - 连续提交 decode,逐 token 比较参考。
- 覆盖 KV allocate/apply/free/clear 和容量不足。
- 在隔离测试中再验证 parallel mode、fixed ping/pong 和状态机。
正确性门槛建议:解析/配置零错误;所有 tensor shape/dtype 完全一致;greedy 序列达到约定一致率;错误路径有限时返回,不得无限忙等或 abort() 整个服务。
阶段 10:llama.cpp 与服务接入(C2)
不要继续复制当前适配层中的硬编码。新增一个版本化 manifest 和严格 adapter:
schema: 1
architecture: qwen3
calbin_version: ...
runtime_abi: 0.7.6
context: 40960
batch: 1
vocab: 151936
models:
prefill: exact-prefill-name
decode: exact-decode-name
tensors:
token: inputs[0]
position: inputs[1]
logits: outputs[0]
csr:
current: cur_seq_len[0]
past_current: past_kv_cur_seq_len[0]
接入时必须:
- 将 calbin 能力作为真实
n_ctx/n_batch/n_ubatch来源。 - 启动时校验 vocab、tokenizer、batch、context、model type 和 logits shape。
- 去掉 output tile 的
D0=16业务硬编码,读取 metadata/layout。 - 给每个 in-flight job 独占或引用计数的 input/output/backing buffers。
- 用 RAII 管理 MapBuf/UnMapBuf、slice reset、output completion 和 KV 生命周期。
- 将 Runtime 错误映射为请求级、模型级和设备级故障,不直接
abort()。 - 修复非
USE_CALRT构建中无条件使用cal_ctx的代码。 - 将 sampling 与整 vocab logits 转换单独计时。
阶段 11:测试金字塔(C2)
| 层级 | 内容 | 通过标准 |
|---|---|---|
| 静态 | manifest、hash、地址、shape、单位 | 全部阻断项通过 |
| 单 op | kernel/fusion golden、边界 shape | 达到数值和性能门槛 |
| 子图 | prefill/decode/KV | 与参考逐步对齐 |
| Runtime | sync/async/ping/pong/error/recovery | 无挂死、无 buffer 复用错误 |
| 服务 | tokenizer、slot、stream、cancel、context shift | API 行为正确 |
| 并发 | 1/2/4/8/16 请求、混合长短 prompt | 无串扰,公平性可控 |
| 稳定性 | 1h/8h/24h soak、反复加载卸载 | 无持续内存增长或设备失联 |
| 性能 | TTFT/TPOT/吞吐/功耗/利用率 | 相对冻结基线达标 |
测试矩阵必须同时固定 prompt tokens、generated tokens、并发和采样参数。报告 P50/P90/P99,不只报告平均值。
4. Dense-first 到 MoE 的两阶段路线
4.1 第一阶段:小型 dense 闭环
目标不是最高速度,而是第一次可重复地完成:
HF -> ONNX -> quantization -> calcc -> calbin -> golden -> CalRT -> llama.cpp -> server
建议产出单芯粒 batch 1 和 batch 4 两套 decode calbin。单芯粒验证多模型常驻的基础,batch 4 验证硬件 batch 和 buffer/KV 调度。首轮不做 NPU sampling,不做多物理板,不做复杂 context shift。
4.2 第二阶段:MoE 与双芯粒
在 dense 闭环稳定后增加:
- router logits 和 top-k 的高精度 reference。
- token-to-expert 分布、热点专家和负载不均指标。
- grouped GEMM 的 token bucket 边界。
- 当前 tensor parallel 与候选 expert parallel 的 A/B calbin。
- 跨芯粒有效字节、同步等待和每层时间。
- W8A8 与 W4AF16 对 router/expert 的分层消融。
只有 vendor compiler 能改变当前专家权重和计算的芯粒分配,因此 EP 属于 C3,不是修改 llama.cpp 调度就能实现。
5. 算子优化的实验方法
每次只改变一个编译或 Runtime 因素,并保存同一输入下的完整结果:
| 因素 | A/B 例子 |
|---|---|
| kernel/fusion | fused vs unfused RMSNorm+GEMM |
| quantization | W8A8 vs W4AF16;BF16 router 对照 |
| attention | standard vs FA;短/长 prompt 分桶 |
| partition | single-chip vs TP2;TP2 vs EP2 |
| batch | 1/4/8/16 decode calbin |
| KV | BF16 vs S8 V-cache;shift on/off |
| output | full logits D2H vs NPU top-k/sampling |
| runtime | immediate Wait vs ping/pong in-flight=2 |
每个实验记录模型 hash、calbin hash、编译 commits、driver/firmware/Runtime、环境温度、运行顺序和原始 trace。性能提升必须同时给出数值回归结果;吞吐提升但 P99、正确性或恢复能力恶化时不能直接合入。
6. 厂商必须补齐的工具和契约
要让我们具备独立新模型适配能力,至少需要:
- 固定 digest 的 compiler container,内含 calcc、TVM、codegen、oplib 和 kernel。
- 当前 Qwen3 从 ONNX 到 calbin 的完整命令、配置和输入 ONNX。
- 支持 op/shape/dtype/量化矩阵与 fallback 规则。
- 单芯粒和双芯粒 partition 约束、内存/通信 cost model。
- batch 4/8/16 与动态 batch 的支持方式。
- golden 生成、逐层 dump、trace/counter 的稳定 API。
- calbin/Runtime/driver/firmware 兼容矩阵和错误码说明。
- kernel 开发、profiling 和自定义 op 接入文档。
- metadata schema,包括单位、model type、地址和版本字段。
- 可发布的 cal-llm EngineCore SDK、API/ABI 和迁移说明;Git 历史分支不能替代 SDK。
7. 第一个新模型项目的完成定义
只有同时满足以下条件才算完成:
- 所有输入、工具和产物可由固定版本容器重放。
- calbin 静态检查通过,版本/shape/context/batch/tokenizer 无歧义。
- 独立 runner 和服务端逐 token 正确性达标。
- sync 和 async 路径都无挂死、越界、串扰或 buffer 生命周期问题。
- batch 1 以及至少一个 batch > 1 的产物通过并发压测。
- 1h、8h、24h 稳定性测试无持续资源泄漏。
- TTFT、TPOT、吞吐、P99、功耗和精度都有可追溯报告。
- 所有 C3 能力都有厂商版本、负责人、交付件和回归用例,不再依赖口头说明。