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

399 lines
17 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-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 > 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 回传到 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.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 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 重叠。
### P1prefill 输出切片存在硬编码 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 schema`inputs[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。