Files
calculet-npu-research-archive/reports/Calculet-NPU-新模型适配与算子优化教程-20260802.md
T

381 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Calculet NPU 新模型适配与算子优化教程
日期:2026-08-02
适用基线:CalRT 0.7.6、`calculet-llama:v0.4.8-release`、llama.cpp `fd9bd632`
参考模型:Qwen3-30B-A3B,双芯粒,40960 contextbatch 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. 端到端适配流水线
```mermaid
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/QwenRMSNorm、RoPE、GQA、SwiGLU 均为常见形式。
- 1B 到 7B,单芯粒参数和 KV 明显能放下。
- 不使用 sliding-window、MLA、自定义 attention、视觉 encoder 或动态 control flow。
- vocab 不大于当前 151936context 先选 4K/8K。
- 有公开参考权重和标准 benchmark。
这样能先验证导出、量化、编译、打包和服务契约,减少同时调试 MoE route、双芯粒通信和长上下文的变量。
### 阶段 1HF/tokenizer 固化(C2
执行项:
1. 下载到内容寻址目录,生成文件清单和 SHA-256。
2. 将 tokenizer 输出固定成整数 token IDs,记录 BOS/EOS/PAD、chat template 和 add-special-tokens 行为。
3. 建立 greedy 解码脚本,每一步保存 top-20 token ID/logit。
4. 导出模型结构 manifest,不依赖文件名猜测模型。
5. 明确 context、batch 和 KV dtype 的产品目标。
交付物建议:
```text
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
优先级按端到端收益排序:
1. RMSNorm + quantize + GEMM,减少中间 BF16/INT8 往返。
2. QKV projection + RoPE + KV scatter,减少 layout conversion。
3. Flash Attention,分别优化短 prefill、长 prefill 和单 token decode。
4. SwiGLU gate/up 融合及 down projection + residual。
5. MoE top-k + dispatch + grouped GEMM + combine。
6. 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。
容量验算至少包括:
```text
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 的基础公式:
```text
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 和布局重算。
### 阶段 8calbin 静态验收(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 还存在类型标记问题。新产物必须把这两项作为阻断式检查,而不是继续容忍。
### 阶段 9CalRT 独立冒烟(C0/C1/C2
先绕过 llama.cpp,写一个最小 C++ runner
1. `CreateVDevice(PCIE)`,检查 `Status()`、Runtime/firmware 信息。
2. `CreateCalbin()`,打印版本、模型列表和 LLM info。
3. `configure()`,记录时间和内存占用。
4. 按精确 model name 创建 InputBuf/OutputBuf。
5. 填 token/position,设置 `cur_seq_len``past_kv_cur_seq_len`
6. 提交 prefill`Wait()`,比较 golden logits。
7. 连续提交 decode,逐 token 比较参考。
8. 覆盖 KV allocate/apply/free/clear 和容量不足。
9. 在隔离测试中再验证 parallel mode、fixed ping/pong 和状态机。
正确性门槛建议:解析/配置零错误;所有 tensor shape/dtype 完全一致;greedy 序列达到约定一致率;错误路径有限时返回,不得无限忙等或 `abort()` 整个服务。
### 阶段 10llama.cpp 与服务接入(C2
不要继续复制当前适配层中的硬编码。新增一个版本化 manifest 和严格 adapter
```yaml
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 闭环
目标不是最高速度,而是第一次可重复地完成:
```text
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 闭环稳定后增加:
1. router logits 和 top-k 的高精度 reference。
2. token-to-expert 分布、热点专家和负载不均指标。
3. grouped GEMM 的 token bucket 边界。
4. 当前 tensor parallel 与候选 expert parallel 的 A/B calbin。
5. 跨芯粒有效字节、同步等待和每层时间。
6. W8A8 与 W4AF16 对 router/expert 的分层消融。
只有 vendor compiler 能改变当前专家权重和计算的芯粒分配,因此 EP 属于 C3,不是修改 llama.cpp 调度就能实现。
## 5. 算子优化的实验方法
每次只改变一个编译或 Runtime 因素,并保存同一输入下的完整结果:
| 因素 | A/B 例子 |
| --- | --- |
| kernel/fusion | fused vs unfused RMSNorm+GEMM |
| quantization | W8A8 vs W4AF16BF16 router 对照 |
| attention | standard vs FA;短/长 prompt 分桶 |
| partition | single-chip vs TP2TP2 vs EP2 |
| batch | 1/4/8/16 decode calbin |
| KV | BF16 vs S8 V-cacheshift 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. 厂商必须补齐的工具和契约
要让我们具备独立新模型适配能力,至少需要:
1. 固定 digest 的 compiler container,内含 calcc、TVM、codegen、oplib 和 kernel。
2. 当前 Qwen3 从 ONNX 到 calbin 的完整命令、配置和输入 ONNX。
3. 支持 op/shape/dtype/量化矩阵与 fallback 规则。
4. 单芯粒和双芯粒 partition 约束、内存/通信 cost model。
5. batch 4/8/16 与动态 batch 的支持方式。
6. golden 生成、逐层 dump、trace/counter 的稳定 API。
7. calbin/Runtime/driver/firmware 兼容矩阵和错误码说明。
8. kernel 开发、profiling 和自定义 op 接入文档。
9. metadata schema,包括单位、model type、地址和版本字段。
10. 可发布的 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 能力都有厂商版本、负责人、交付件和回归用例,不再依赖口头说明。