# 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 能力都有厂商版本、负责人、交付件和回归用例,不再依赖口头说明。