# Calculet NPU 环境、源码与算子优化分析 日期:2026-08-01 源码版本:`fd9bd632f346085920d523dd307a3e4c11cc0a05` Runtime:CalRT 0.7.6 镜像:`calculet-llama:v0.4.8-release` ## 1. 结论摘要 当前方案不是通用的逐算子 GGML NPU backend,而是“llama.cpp 负责服务、调度和采样,预编译 calbin 负责整段 Transformer 推理”的混合架构。 主路径如下: 1. llama.cpp 完成 tokenizer、请求调度、slot 管理和采样。 2. 根据 `n_tokens > 1` 或 `n_tokens == 1` 选择 prefill/decode calbin 子模型。 3. Runtime 设置 `past_kv_cur_seq_len[0]` 和 `cur_seq_len[0]` 两个 CSR。 4. 输入 token/position 复制到 Runtime buffer。 5. 同步执行 `calrt::infer()` 和 `OutputBuf::Wait()`。 6. logits 从 NPU 回传到 CPU,CPU 完成采样和下一 token 调度。 这套架构已经能稳定跑 Qwen3-30B-A3B,但当前 calbin 的硬件 batch 固定为 1,新模型接入依赖重新导出 ONNX、编译 prefill/decode 图和生成 calbin,不是仅在 llama.cpp 中新增几个 kernel 即可。 ## 2. 硬件与系统环境 | 项目 | 当前值 | 影响 | | --- | --- | --- | | PCIe 设备 | `20f5:c001` | Calculet NPU | | PCIe 链路 | Gen4 x4,16 GT/s x4 | 理论单向有效带宽约 7.9 GB/s,需重点控制 Host/Device 拷贝 | | BAR | 64 MB + 64 MB + 4 MB | BAR 空间有限,主要数据应走 DMA/设备 DRAM | | 驱动 | `calculet_pci 1.0.0` | DKMS 模块,已签名 | | 设备节点 | `/dev/calculet0` | 当前权限为全用户读写,建议收紧到专用组 | | Host CPU | 32 逻辑核 | tokenizer、采样和 server 调度资源充足 | | Host RAM | 约 96 GB | 模型文件和服务缓存空间充足 | | NPU DRAM | 32768 MiB | 当前 Runtime 报告常驻使用 1024 MiB | | NPU SRAM | 18 MiB,1000 MHz | 算子 tile 和中间缓存的关键约束 | | Podman | 4.9.3 rootless | 短 SSH 会话会破坏 pause-process 状态 | 驱动可调参数: - `poll_mode`:中断与硬件轮询切换,可用于 decode 低延迟 A/B。 - `dma_timeout_ms`:长序列、大块参数或压力测试时的 DMA 超时控制。 - `strict_validation`:开发阶段建议开启,性能基准阶段再对比关闭开销。 - `calculet_log_level`:用于驱动级错误和 DMA 定位。 - `max_boards`:未来多卡场景的设备枚举上限。 ## 3. 软件与构建状态 - 宿主机:Ubuntu 24.04,Linux 7.0.0-28-generic。 - 宿主编译器:GCC 13.3,CMake 3.28.3。 - 镜像编译器:GCC 11.4。 - llama.cpp:Release、`-O3 -DNDEBUG`、shared libraries。 - `LLAMA_USE_CALRT=ON`。 - `CALRT_INSTALL_DIR=/usr/local/`。 - 实际链接 `/usr/local/lib/libcalrt-linux-x86_64.so.0.7.6`。 - `GGML_CALRT=OFF`,因此 `ggml/src/ggml-calrt` 不是当前生产推理路径。 风险:源码中同时存在旧 hostrt backend、新 CalRT 整图路径和系统预编译 Runtime 三种集成痕迹。建议建立一个版本清单,明确 llama commit、CalRT SO、驱动、CalcoreRT、模型编译器和 calbin schema 的兼容矩阵。 ## 4. 当前 calbin 能力 实测解析: - calbin 版本:`v0.1`。 - `max_batch_size=1`。 - `max_seq_len=40960`。 - 子模型数量:2。 - 子模型:prefill、decode。 - 芯片模式:双芯片 `MCHIP`。 - 参数分片:9.40 GB + 8.78 GB。 - KV cache:48 层、4 KV heads、head dimension 128、BF16 路径。 - 量化:主模型 W8A8/W4AF16 混合配置。 - Flash Attention:已编译进 calbin。 calbin 元数据问题: 1. prefill 子模型的 `modelType` 被标记为 `llm_decode`。 2. 两个子模型 workload 均为 0。 3. 双芯片模型的 `tarChipN` 为空。 4. 这些字段会妨碍自动调度、模型自检和多卡拓扑选择,应由编译工具链修复。 ## 5. 源码调用链 核心文件: - `src/llama-calrt.cpp`:生产 CalRT 整图执行路径。 - `src/llama-kv-cache-calrt-adapter.cpp`:把 llama memory API 适配到 `KvManager`。 - `tools/server/server.cpp`:CalRT decode 接入 slot 调度和性能计时。 - `src/models/qwen3moe.cpp`:Qwen3-MoE 的标准 GGML 图定义,用于模型结构和 CPU/GGML 路径。 - `ggml/src/ggml-calrt/*`:未启用的实验 backend。 关键行为: - 设备初始化固定使用 PCIe VirtualDevice。 - Runtime 初始化时直接读取两个固定寄存器获得 CalcoreRT commit。 - `n_tokens > 1` 选择 prefill,`n_tokens == 1` 选择 decode。 - prefill/decode 通过子模型名称子串查找,缺乏严格 schema 和唯一性校验。 - 每次 `calrt_infer()` 创建新的 InputBuf 和 OutputBuf。 - 实际数据区使用长期 `std::vector` 并通过 `MapBuf()` 复用。 - prefill 对输入和 logits 使用 `SliceTensor()` 降低传输量。 - 每个 sequence 在 C++ 循环里依次执行同步 infer,当前没有真正的多 sequence 并行。 - KV 资源不足时使用 `do/while(!ok)` 忙等,没有超时、退避或取消机制。 - 推理错误主要通过 exception、abort 和 GGML_ASSERT 处理,不适合生产服务降级。 ## 6. 已暴露但未充分利用的性能数据 CalRT 已提供并且源码已采集: - `GetWaitTime()`:纯 NPU infer 时间。 - `GetInputTransferTime()`:H2D 时间。 - `GetOutputTransferTime()`:D2H 时间。 - framework unknown 时间:总同步时间减去以上三项。 - prefill 与 decode 分项累计。 建议新增稳定的 `/calrt/metrics` 或并入 Prometheus `/metrics`: - `calrt_infer_ms{phase=prefill|decode}` - `calrt_h2d_ms{phase=...}` - `calrt_d2h_ms{phase=...}` - `calrt_framework_ms{phase=...}` - `calrt_kv_used_batches` - `calrt_kv_allocation_wait_ms` - `calrt_device_dram_used_bytes` - `calrt_device_status` - `calrt_errors_total{code=...}` 这是优先级最高的改造,因为没有分层时间就无法判断应该优化算子、DMA、Runtime 调度还是 server。 ## 7. 当前性能与主要瓶颈 已测 Qwen3-30B-A3B: - 27 token prefill:184.27 token/s。 - 246 token decode:58.25 token/s。 - 246 token 请求端到端:4.41 s。 瓶颈优先级判断: ### P0:硬件 batch 固定为 1 `max_batch_size=1` 使 `-np > 1` 无法形成硬件批处理,只能排队或时间复用。要提升吞吐,必须重新编译支持 batch 4/8/16 的 decode calbin,并同步修改 KV layout 和 server batching。 ### P0:同步执行和逐 sequence 循环 每个 sequence 都执行 `infer()` 后立即 `Wait()`。应改为: 1. 创建多个 in-flight OutputBuf。 2. prefill/decode 分队列。 3. 先提交可并行任务,再批量等待。 4. 用事件/回调替代 host busy wait。 ### P0:KV 资源等待无超时 `while(!Apply())` 会在资源耗尽或状态错误时永久占用 CPU。应增加超时、指数退避、请求取消和明确的 HTTP 429/503。 ### P1:每次推理创建 Buffer 对象 虽然底层数据内存复用,但 InputBuf/OutputBuf 对象、tensor lookup 和 hyperparameter lookup 仍重复发生。应按 phase 和 slot 建立 buffer pool,并缓存 tensor/CSR handle。 ### P1:logits D2H 与 CPU sampling 每个 decode token 都回传完整 vocab logits。Qwen3 vocab 为 151936,BF16 全量约 297 KiB/token;58 token/s 时仅 logits 就约 17 MiB/s,带宽不是 Gen4 x4 的上限,但延迟、DMA 启动和 CPU sampling 会形成串行链。 可选优化: - NPU 侧 top-k,返回 token id、score 和少量候选。 - 只在 logprobs 请求时回传更多 logits。 - NPU 侧 fused lm_head + top-k + sampling。 - 双缓冲 logits,使下一次 decode 与上一次 sampling 重叠。 ### P1:prefill 输出切片存在硬编码 tile 代码硬编码 `D0_tmp=16`,并依据 output rank 选择多套 untile 函数。应把 layout、tile 和有效输出范围放入 calbin tensor metadata,避免新模型/新编译器版本依赖 C++ 分支。 ### P2:PCIe 和驱动等待策略 Gen4 x4 足以承载当前 token/position/logits,但小 DMA 的固定启动成本可能影响 decode。需要用 Runtime 分项计时做以下 A/B: - interrupt vs `poll_mode=1` - pageable host vector vs pinned/registered host memory - 单缓冲 vs 双缓冲 - 每 token DMA vs 批量/候选压缩 ## 8. Qwen3-MoE 算子优化方向 Qwen3-30B-A3B 每层主要结构: 1. RMSNorm 2. Q/K/V projection 3. Q/K RMSNorm 4. RoPE 5. GQA attention + KV cache 6. output projection + residual 7. RMSNorm 8. MoE router softmax/top-k 9. 8 个激活 expert 的 gate/up/down projection 10. SiLU 门控和 residual 建议优先优化: ### 8.1 MoE dispatch 与 expert GEMM - 将 router softmax、top-k、token permutation 和 expert offset 生成融合。 - 对 decode batch=1 使用专用 sparse expert path,避免为 128 experts 建立大 dispatch 表。 - 将 8 个激活 expert 的小 GEMM 合并成 grouped GEMM。 - 按 expert 热度调整权重布局,减少跨芯片访问。 - 对 prefill 使用 token-expert 分桶和负载均衡;对 decode 使用固定低延迟路径。 - 评估 expert parallel:两芯片各放一组 experts,attention 权重复制或按层切分。 ### 8.2 Attention/KV - 保留 fused Flash Attention。 - decode 使用 GQA 专用 kernel,4 KV heads 不应展开成 32 heads。 - KV cache layout 应让 batch、head 和 sequence tile 与 DMA/compute tile 一致。 - 支持 S8 V-cache 是 Runtime 头文件已有能力,可作为长上下文容量特性开放。 - KV shift/rope 更新应异步执行,并与下一层或下一请求重叠。 ### 8.3 Norm/Residual/RoPE 融合 - RMSNorm + QKV projection 融合。 - attention output projection + residual 融合。 - FFN output + residual 融合。 - Q/K norm + RoPE 融合。 - 对 decode 的单 token 路径生成独立 kernel,避免复用 prefill 的通用调度。 ### 8.4 量化 - 当前 W8A8/W4AF16 应按层做敏感度分析,而不是全局固定。 - attention projection、router 和 lm_head 通常比 expert FFN 更敏感。 - 优先尝试 expert W4、attention W8、activation BF16/动态 INT8。 - 建立 perplexity、关键任务准确率和 token/s 的三维回归门槛。 ## 9. 新模型算子优化与接入流程 ### 阶段 A:模型结构归一化 1. 确认 tokenizer、chat template 和特殊 token。 2. 固化模型 hparams:layers、heads、KV heads、head dim、rope、experts、top-k、activation。 3. 将 Hugging Face 图导出为 prefill 与 decode 两张 ONNX。 4. 明确动态维:prompt length、past KV length、batch。 5. 保持稳定 I/O schema:`inputs[0]`、`inputs[1]`、`outputs[0]` 只是当前约定,建议改为语义名称和版本化 manifest。 ### 阶段 B:算子覆盖分析 生成 op inventory,分为: - NPU 原生支持。 - 可由现有 kernel 组合。 - 值得融合的新 kernel。 - 暂时回退 CPU 的低占比 op。 - 阻塞模型接入的 unsupported op。 对每个 op 记录 dtype、shape 范围、layout、动态轴、精度容差和预期占比。 ### 阶段 C:kernel 开发 每个新 kernel 至少包含: - correctness golden test。 - 边界 shape 和动态 shape。 - BF16/INT8/INT4 精度比较。 - 单芯片和双芯片布局。 - 性能基线:吞吐、延迟、SRAM、DRAM、DMA。 - 与融合前图的端到端收益。 ### 阶段 D:calbin 编译 1. 分别编译 prefill 和 decode。 2. 生成 batch 1 低延迟 decode。 3. 生成 batch 4/8/16 吞吐 decode。 4. 为常用 context bucket 生成 prefill 变体,或验证真正动态 shape。 5. 把 tile、layout、I/O、KV 和芯片映射写入版本化 metadata。 6. 生成 golden input/output,并在 Runtime `GetGolden*` 接口上做自动验证。 ### 阶段 E:llama/server 适配 - 不再用子串选择模型,改用明确的 role/type manifest。 - 按 batch、phase、context bucket 选择 calbin 子模型。 - 增加 buffer pool 和 async queue。 - 实现失败回滚、超时和可观测错误码。 - 对不兼容特性在启动时 fail-fast,而不是运行中 abort。 ### 阶段 F:验证 - 单 op golden。 - 子图 golden。 - prefill/decode logits 对齐。 - 长上下文 KV 正确性。 - 多 slot 并发。 - soak test、设备 reset 后恢复、PCIe 错误注入。 - 性能回归:TTFT、TPOT、tokens/s、P50/P95/P99。 ## 10. 可开放特性矩阵 ### 可以较快开放 | 特性 | 当前基础 | 所需工作 | | --- | --- | --- | | Prometheus CalRT 分项指标 | 时间已采集 | 接入 `/metrics` | | 设备状态/内存只读 API | Runtime 已提供 | 鉴权、单位修复、限频 | | tokenizer/detokenizer | server 已有端点 | 接口测试与文档化 | | slots/health/models/props | server 已有端点 | 增加 CalRT 字段 | | 流式与非流式 completion | 已验证 | 补协议回归测试 | | 固定 40960 长上下文 | calbin 支持 | 长上下文稳定性与 KV 压测 | | prefix/prompt cache | server 已有 | 验证 CalRT KV adapter 一致性 | | 性能 debug dump | Runtime 有 debug/replay | 增加安全路径、空间配额和自动清理 | ### 需要 Runtime/server 改造后开放 | 特性 | 当前阻塞 | | --- | --- | | 真正多并发 | calbin max batch=1、同步 infer | | continuous batching | server 有逻辑,硬件侧没有 batch calbin | | dynamic batching | prefill/decode 不能混合,模型选择只看 token 数 | | speculative decoding | 需要 draft model calbin、双模型 KV 协同和调度 | | LoRA/aLoRA | 当前整图权重固化在 calbin,标准 llama LoRA 不会自动作用于 NPU 图 | | embeddings/rerank | 需要对应 encoder/ranker calbin 和 pooling 输出 | | multimodal | 源码有 vision 子模型入口,但当前 calbin 只有 prefill/decode | | tool-use/json grammar | CPU sampling 层可用,但需验证 reasoning/chat template 行为 | | KV quantization | Runtime 有 S8 V-cache 枚举,需模型编译和精度验证 | | multi-card scaling | 元数据芯片列表为空,缺设备拓扑和调度策略 | | NPU sampling | 当前完整 logits 回 CPU,需要新增 top-k/sampling kernel 和 API | ### 不应直接开放 - 未鉴权的设备 reset、清配置和寄存器读写。 - 任意设备地址的内存读写。 - 用户可控 debug dump 路径。 - 未限流的 power mode 与设备管理接口。 这些接口应只放在受控运维面,并提供 allowlist、审计日志和互斥锁。 ## 11. 建议的实施顺序 ### 第一阶段:可观测与稳定性 1. 暴露 infer/H2D/D2H/framework/KV 指标。 2. 修复 `GetMemoryUsage()` total 值异常。 3. 修复 KV busy wait,加入超时和 429/503。 4. 统一版本与 calbin metadata schema。 5. 修复 Podman 持久会话,建议为 user2 开启 linger 或使用 systemd user service。 ### 第二阶段:低风险性能收益 1. Buffer object pool 和 tensor/CSR handle cache。 2. pinned host memory 与双缓冲。 3. decode interrupt/polling A/B。 4. logits top-k 压缩。 5. prefill context bucket 基准。 ### 第三阶段:吞吐架构 1. 编译 batch 4/8/16 decode calbin。 2. prefill/decode 分队列和异步 submit/wait。 3. 多 slot KV 资源管理和背压。 4. continuous batching 端到端验证。 ### 第四阶段:新模型平台化 1. 自动 op inventory 和 unsupported report。 2. 版本化 model manifest。 3. 自动 calbin golden test。 4. kernel microbenchmark 与精度回归平台。 5. 以一个 dense 模型和一个 MoE 模型验证通用流程。 ## 12. 下一轮测试矩阵 - prompt 长度:1、16、128、512、2048、8192、32768、40960。 - output 长度:1、32、128、512。 - slot:1、2、4;当前用于验证排队行为,不宣称硬件并发。 - 指标:TTFT、TPOT、总吞吐、infer、H2D、D2H、framework、CPU、NPU memory。 - 驱动模式:interrupt 与 polling。 - 稳定性:30 分钟、2 小时、24 小时。 - KV:创建/删除/复用/shift、上下文接近 40960、取消请求。 - API:completion/chat/stream/tokenize/detokenize/slots/metrics/props。 - 错误:非法 calbin、超长 context、资源耗尽、客户端断连、容器重启。 ## 13. 当前明确问题清单 1. `GetMemoryUsage().second` 返回约 `3.05e-05 MiB`,与 32768 MiB 冲突。 2. calbin prefill model type 错标为 decode。 3. calbin workload 为 0、目标芯片列表为空。 4. `chat_template_kwargs.enable_thinking=false` 未生效。 5. 短 `max_tokens` 会全部消耗在 reasoning,最终 content 为空。 6. rootless Podman pause process 随短 SSH session 消失。 7. KV allocation 使用无限忙等。 8. Runtime 错误路径存在 abort/assert,缺服务级恢复。 9. 子模型选择使用字符串包含匹配,存在歧义。 10. 未启用的 `ggml-calrt` backend 报告固定 16 KiB memory,支持/执行 op 列表还不一致,不能作为生产能力声明。 11. `/dev/calculet0` 权限过宽。 12. user2 实际属于 sudo 组,与接入文档描述不一致。 ## 14. 保持状态 - VPN 已重新连接并保持运行。 - SSH 可用。 - 远端保留聚焦源码归档,便于继续分析。 - 本地保留聚焦源码、测试程序和分析材料。 - 未再次启动模型服务,避免无测试时占用 NPU。