381 lines
18 KiB
Markdown
381 lines
18 KiB
Markdown
# 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. 端到端适配流水线
|
||
|
||
```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/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)
|
||
|
||
执行项:
|
||
|
||
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 和布局重算。
|
||
|
||
### 阶段 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:
|
||
|
||
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()` 整个服务。
|
||
|
||
### 阶段 10:llama.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 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. 厂商必须补齐的工具和契约
|
||
|
||
要让我们具备独立新模型适配能力,至少需要:
|
||
|
||
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 能力都有厂商版本、负责人、交付件和回归用例,不再依赖口头说明。
|