Files
calculet-npu-research-archive/reports/Calculet-NPU-环境源码与算子优化分析-20260801.md

17 KiB
Raw Permalink Blame History

Calculet NPU 环境、源码与算子优化分析

日期:2026-08-01
源码版本:fd9bd632f346085920d523dd307a3e4c11cc0a05
RuntimeCalRT 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 > 1n_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 回传到 CPUCPU 完成采样和下一 token 调度。

这套架构已经能稳定跑 Qwen3-30B-A3B,但当前 calbin 的硬件 batch 固定为 1,新模型接入依赖重新导出 ONNX、编译 prefill/decode 图和生成 calbin,不是仅在 llama.cpp 中新增几个 kernel 即可。

2. 硬件与系统环境

项目 当前值 影响
PCIe 设备 20f5:c001 Calculet NPU
PCIe 链路 Gen4 x416 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 MiB1000 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.04Linux 7.0.0-28-generic。
  • 宿主编译器:GCC 13.3CMake 3.28.3。
  • 镜像编译器:GCC 11.4。
  • llama.cppRelease、-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 cache48 层、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.cppCalRT decode 接入 slot 调度和性能计时。
  • src/models/qwen3moe.cppQwen3-MoE 的标准 GGML 图定义,用于模型结构和 CPU/GGML 路径。
  • ggml/src/ggml-calrt/*:未启用的实验 backend。

关键行为:

  • 设备初始化固定使用 PCIe VirtualDevice。
  • Runtime 初始化时直接读取两个固定寄存器获得 CalcoreRT commit。
  • n_tokens > 1 选择 prefilln_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 prefill184.27 token/s。
  • 246 token decode58.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。

P0KV 资源等待无超时

while(!Apply()) 会在资源耗尽或状态错误时永久占用 CPU。应增加超时、指数退避、请求取消和明确的 HTTP 429/503。

P1:每次推理创建 Buffer 对象

虽然底层数据内存复用,但 InputBuf/OutputBuf 对象、tensor lookup 和 hyperparameter lookup 仍重复发生。应按 phase 和 slot 建立 buffer pool,并缓存 tensor/CSR handle。

P1logits D2H 与 CPU sampling

每个 decode token 都回传完整 vocab logits。Qwen3 vocab 为 151936BF16 全量约 297 KiB/token58 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++ 分支。

P2PCIe 和驱动等待策略

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:两芯片各放一组 expertsattention 权重复制或按层切分。

8.2 Attention/KV

  • 保留 fused Flash Attention。
  • decode 使用 GQA 专用 kernel4 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. 固化模型 hparamslayers、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 schemainputs[0]inputs[1]outputs[0] 只是当前约定,建议改为语义名称和版本化 manifest。

阶段 B:算子覆盖分析

生成 op inventory,分为:

  • NPU 原生支持。
  • 可由现有 kernel 组合。
  • 值得融合的新 kernel。
  • 暂时回退 CPU 的低占比 op。
  • 阻塞模型接入的 unsupported op。

对每个 op 记录 dtype、shape 范围、layout、动态轴、精度容差和预期占比。

阶段 Ckernel 开发

每个新 kernel 至少包含:

  • correctness golden test。
  • 边界 shape 和动态 shape。
  • BF16/INT8/INT4 精度比较。
  • 单芯片和双芯片布局。
  • 性能基线:吞吐、延迟、SRAM、DRAM、DMA。
  • 与融合前图的端到端收益。

阶段 Dcalbin 编译

  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* 接口上做自动验证。

阶段 Ellama/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、取消请求。
  • APIcompletion/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。