399 lines
17 KiB
Markdown
399 lines
17 KiB
Markdown
# 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。
|