Files
calculet-npu-research-archive/reports/Calculet-NPU-总体研究报告-20260802.md
T

403 lines
19 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-02
当前生产源码:`fd9bd632f346085920d523dd307a3e4c11cc0a05`
CalRT0.7.6
镜像:`calculet-llama:v0.4.8-release`
当前模型:Qwen3-30B-A3B,双芯粒,40960 context
实施说明:本文用于建立全貌。需要直接拆研发任务时,下钻到《异步执行器与生产稳定性实施规格》《性能压测故障注入与验收规范》《新模型适配工程 Runbook》《逐算子优化实验手册》和《Batch、KV 与多模型调度实现规格》;这些文档包含类/方法、数据 schema、实验矩阵、故障注入和验收阈值。
## 1. 结论先行
当前产品不是 GGML 逐算子调度到 NPU 的通用 backend,而是一个“llama.cpp 服务层 + CalRT 预编译整图”的系统:llama.cpp 负责 tokenizer、请求/slot 调度和 CPU samplingCalRT 加载预编译 calbin,按 prefill/decode 执行完整 Transformer 图。
当前系统的真实能力边界是:
| 维度 | 当前状态 | 结论 |
| --- | --- | --- |
| 物理设备 | 1 个 PCIe VirtualDevice | 0.7.6 头文件明确写“temporary only support one physical device” |
| 板内计算 | 2 个芯粒,MCHIP | 当前模型已经双芯粒运行 |
| 并行方式 | 逐层张量并行 | 两芯粒都执行 48 层;不是 pipeline parallel,也不是 expert parallel |
| 模型 | 1 套完整 Qwen3-30B-A3B | 68 个文件是同一模型的分片、命令、ELF、元数据和 tokenizer,不是 68 个模型 |
| 硬件 batch | 1 | `max_batch_size=1`,无法形成硬件 continuous batching |
| 子模型 | prefill、decode | 两者不能在一次提交中混跑 |
| 上下文 | calbin 40960 | tokenizer 配置中的 131072 不是当前 NPU 可用上限 |
| 调用方式 | `infer()` 后立即 `Wait()` | Runtime 非阻塞和 ping/pong 能力没有转化为并发 |
| 采样 | CPU | 每 token 回传 151936 个 BF16 logits,再转 FP32 采样 |
| 多卡 | 未支持 | 不能把“多芯粒”表述为“多板多卡” |
| 新模型编译 | 当前材料不能独立完成 | 缺 calcc、ONNX pass、kernel/codegen、oplib 和 calbin 打包工具 |
性能优化优先级不是先写更多单算子,而是:
1. 补齐分层指标和正确基准口径。
2. 修复 KV 无限忙等、错误恢复和 manifest 校验。
3. 让 Runtime 的异步、ping/pong 和 buffer 复用真正进入服务路径。
4. 由厂商重新编译 batch 4/8/16 decode calbin,建立 continuous batching。
5. 优化长上下文 Attention/KV 带宽,以及 MoE grouped GEMM/dispatch。
6. 最后再评估 expert parallel、多模型常驻和 NPU 侧 sampling。
## 2. 证据口径与状态定义
本文综合了 Runtime PDF、0.7.6 SDK 头文件、非 strip 动态库符号、生产源码、完整 Git 历史、OCI image history、构建缓存、完整模型产物、首轮实测和历史性能 CSV。
为避免把“接口存在”误写为“产品可用”,全文采用以下状态:
| 状态 | 含义 |
| --- | --- |
| C0 当前已确认 | 当前生产代码、产物或实测直接证明 |
| C1 SDK 存在但未接入 | 0.7.6 头文件和动态符号存在,当前 llama 路径没有使用或未验证 |
| C2 可工程实现 | 基于现有 Runtime/服务能力可以开发,但当前尚不存在 |
| C3 依赖厂商工具链 | 需要重新编译模型、开发 kernel、修改 Runtime 或获得私有工具 |
| C4 当前不支持/不应开放 | 版本或架构明确不支持,或接口只适合受控运维面 |
## 3. 构建与供应链边界
OCI history 完整记录了镜像构建过程:
1. 基础镜像是 Ubuntu 22.04 amd64。
2. 安装 GCC/G++ 11、CMake、Git、ELF、调试工具和 Miniconda。
3.`/home/user1/CalCulet/calrt/hostrt` 以 Release/shared 方式编译、安装 Runtime。
4. 安装完成后执行 `rm -rf calrt/`,因此最终镜像不保留 HostRT 源码。
5. 构建 SystemC、fmt、yaml-cpp 等依赖。
6. 使用以下核心配置编译 llama.cpp:
```text
cmake -B build \
-DLLAMA_BUILD_COMMON=ON \
-DLLAMA_USE_CALRT=ON \
-DLLAMA_CURL=OFF \
-DCALRT_INSTALL_DIR=/usr/local/
cmake --build build -j
```
构建缓存进一步确认:
- `CMAKE_BUILD_TYPE=Release``-O3 -DNDEBUG`
- `LLAMA_USE_CALRT=ON`
- `GGML_CALRT=OFF`
- 最终链接 `/usr/local/lib/libcalrt-linux-x86_64.so.0.7.6``libelf`
- 当前 Git HEAD 与 `origin/dev` 都是 `fd9bd632...`
由此得到三条边界:
- 我们能修改 llama.cpp 服务/适配层并重新构建。
- 我们能调用和封装 CalRT 0.7.6 的公开头文件/动态符号。
- 我们不能仅凭现有材料重建 calcc 图优化、NPU kernel/codegen 或生成一个新 calbin。
模型 YAML 记录的编译器版本是 `calcc 575c2d708e306a60f10bc34f5087477073b58caa`、TVM `7aebd1c21fa67d942a1fd93e39e8a67007f91c0c`;原 ONNX 只留下路径和 SHA-256,文件本身未归档。
## 4. 当前端到端执行链
```mermaid
sequenceDiagram
participant Client as OpenAI API Client
participant Server as llama-server
participant Llama as llama.cpp Scheduler
participant CalRT as CalRT 0.7.6
participant NPU as 2-chiplet NPU
Client->>Server: chat/completions
Server->>Llama: tokenize + select slot
Llama->>Llama: split request by sequence
Llama->>CalRT: select prefill/decode model
Llama->>CalRT: set current/past sequence CSR
Llama->>CalRT: MapBuf + SliceTensor
CalRT->>NPU: H2D + submit whole graph
NPU-->>CalRT: logits + updated KV
CalRT-->>Llama: D2H + Wait completion
Llama->>Llama: BF16 to FP32 + CPU sampling
Llama-->>Server: next token
Server-->>Client: stream/non-stream response
```
生产源码中的具体行为:
1. `VirtualDevice::CreateVDevice(PCIE)` 创建设备。
2. 从固定寄存器读取 CalcoreRT commit,并读取 `calrt_version()`
3. `Calbin::CreateCalbin(path)` 解析模型并 `configure()`
4. 根据第一个 sequence 的 `n_tokens > 1` 选 prefill,否则选 decode。
5. 子模型通过名称子串查找,而不是严格 manifest。
6. 每次 `calrt_infer()` 都创建 InputBuf/OutputBuf 对象,但映射到长期 vector backing store。
7. 写入 `past_kv_cur_seq_len[0]``cur_seq_len[0]`
8. prefill 对 token/position 和末行 logits 做 slice。
9. 对 sequence 数组逐个执行 `infer()`、立即 `Wait()`
10. 将 BF16 logits 转成 FP32,并交还 llama.cpp CPU sampling。
这里最关键的判断是:`infer()` 本身是非阻塞提交,但当前代码紧接 `Wait()`,所以应用层表现仍是同步串行。
## 5. Qwen3-30B-A3B 结构与计算图
| 参数 | 值 |
| --- | ---: |
| 层数 | 48 |
| hidden size | 2048 |
| Q heads | 32 |
| KV heads | 4 |
| head dim | 128 |
| experts | 128 |
| 每 token 激活 experts | 8 |
| expert intermediate | 768 |
| dense intermediate 配置值 | 6144 |
| vocab | 151936 |
| RoPE theta | 1,000,000 |
| calbin context | 40960 |
| batch | 1 |
| 量化 | W8A8/W4AF16 混合 |
| Flash Attention | 已开启 |
产物中的算子计数:
| 算子 | Prefill | Decode | 说明 |
| --- | ---: | ---: | --- |
| fused RMSNorm | 193 | 193 | 每层 attention/FFN 多处及尾部 |
| quantized fused matmul/add | 193 | 193 | QKV、O、router、lm_head 等 |
| sync CPU/CCU D2D | 146 | 146 | 芯粒/执行单元同步与数据交接 |
| gather tensor | 145 | 145 | 分片结果合并 |
| grouped MoE dense | 144 | 144 | 每层 gate/up/down 三组 |
| KV scatter | 96 | 96 | 每层 K/V |
| fused RoPE | 96 | 96 | 每层 Q/K |
| add/relu/residual | 96 | 96 | attention/FFN residual |
| dynamic D2D | 96 | 0 | prefill 动态跨芯粒移动 |
| SiLU/multiply/reduce | 各 48 | 各 48 | 每层 MoE |
| gate pre/post | 各 48 | 各 48 | top-k、dispatch 与恢复 |
| Flash Attention | 48 | 48 | 每层 attention |
| embedding | 1 | 1 | 只在 chip0 profile 中执行 |
| layout conversion | 2 | 0 | prefill mat/hw pattern 转换 |
| 总节点 | 1496 | 1398 | YAML 顶层 op 数 |
Decode MoE 权重 shape 的第一维是 128,且 chip0/chip1 的 profile 都包含相同的 144 个 `nbu_moe_dense_group`。这说明两边都有 128 experts 的权重分片;当前是矩阵维度张量并行,不是把 experts 分组放到不同芯粒。
## 6. 双芯粒分配与通信
四个 `.profparts` 文件显示:
- decodechip0 1254 行,chip1 1253 行。
- prefillchip0 1352 行,chip1 1351 行。
- 唯一稳定的一行差异是 embedding 只在 chip0。
- 两芯粒都执行全部 48 层的 attention、MoE、norm、residual 和 output path。
- Q、K/V、投影矩阵、MoE 权重与 vocab 输出按编译器布局分片,然后 gather/reduce。
- 每颗芯粒承担 2 个 KV heads,合计 4 个。
这属于 tensor parallel:每层都有跨芯粒同步,优势是每层计算并行,代价是每层通信。它不具备 pipeline parallel 的阶段隔离,也没有 expert parallel 的按专家路由。
最大 prefill 静态命令描述:
| 芯粒 | `data_moving` 条目 | 描述字节 | 主要块大小 |
| --- | ---: | ---: | --- |
| chip0 | 444,610 | 18.979 GiB | 245760 x 64 KiB122880 x 32 KiB75968 x 1 KiB |
| chip1 | 444,608 | 18.822 GiB | 245760 x 64 KiB122880 x 32 KiB75968 x 1 KiB |
注意:prefill 配置为 `dynamic_d2d=true`,且 `libcpu_x86.so` 导出 `Process`。因此这些数字是 40960 上限图的静态命令描述,不是短 prompt 的实测传输量。必须通过 Runtime trace 或 PCIe/D2D counter 测量实际长度相关流量。
## 7. 内存基线
| 项目 | 总量 | 每芯粒/分片 | 口径 |
| --- | ---: | ---: | --- |
| 参数文件 | 16.938 GiB | chip0 8.759 GiBchip1 8.179 GiB 文件大小 | 两个 `param_blk*.bin` |
| 参数映射到 DRAM | 16.910 GiB | 8.745 / 8.165 GiB | memory reservation 逐段求和 |
| 参数映射到 SRAM 窗口 | 28.762 MiB | 各 14.381 MiB | memory reservation 逐段求和 |
| BF16 KV | 3.750 GiB | 1.875 GiB | 48 x 4 heads x K/V x 128 x 40960 x 2 bytes |
| DRAM reservation | 推断 28.150 GiB/双芯粒 | 14.075 GiB | 同一虚拟地址计划按芯片 mask 应用 |
| SRAM reservation | 推断 33.688 MiB/双芯粒 | 16.844 MiB | 接近每芯粒 18 MiB 物理 SRAM |
| decode tensor 地址并集 | 10.634 GiB | 每芯粒地址计划 | 含常量、KV、I/O 和 workspace;非峰值活跃内存 |
| prefill tensor 地址并集 | 14.091 GiB | 每芯粒地址计划 | 与 DRAM+SRAM reservation 基本闭合 |
| 单 token logits | 303,872 B BF16 | Host D2H | 151936 x 2 bytes |
参数、KV、workspace 的区间存在别名和生命周期复用,不能把各角色的地址覆盖量直接相加。完整映射见《模型产物与内存映射》。
`device_memory_required: 16777216 MB` 明显存在单位/字段问题;按字面是 16,777,216 MiB,不可能成立。它更像 16 GiB 的 KiB 数值被错误标成 MB,需要厂商确认解析口径。
## 8. Runtime 已有能力与当前使用差距
### C0 当前已使用
- PCIe VirtualDevice 创建与状态检查。
- calbin 解析、configure、按名称获取子模型。
- Input/Output buffer、tensor MapBuf、slice。
- CSR 动态序列长度。
- `infer()``Wait()`、H2D/D2H/infer 计时。
- KV Apply/Free/RemoveTokensAtEnd/Clear 的部分路径。
- 双芯粒 MCHIP 整图执行。
### C1 SDK 有但生产未利用
- `EnableParallelMode(bool)`
- `SubmitJob()``infer_with_fixed_task_type()` 的 ping/pong 控制。
- OutputBuf pending/running/done/CCU exception 状态。
- pending/finished/left job counters。
- trace 和配置 metadata dump。
- golden input/output 读取。
- KV `canAllocate`、显式 Allocate、DoShift、batch layout、S8 V-cache。
- per-chip memory/register 访问。
- C API 的 block/non-block inference 封装。
### C4 当前限制
- VirtualDevice 内部只有一个 `shared_ptr<CalrtDevice>`,不是多物理设备管理器。
- 当前 calbin batch=1、只有 prefill/decode。
- `RelocateTensorAddress()``AllocDevMem()` 只出现在 PDF,0.7.6 头文件与动态符号都不存在。
- 任意寄存器、设备地址、reset、ClearAllDevMem 不应暴露到普通推理 API。
## 9. 当前集成的工程问题
### P0 正确性与稳定性
1. KV 申请使用无限 `do/while(!Apply())`,没有超时、退避、取消和资源耗尽错误。
2. KV 更新失败直接 `abort()`;初始化失败使用 `GGML_ABORT`
3. KV adapter 的 `seq_cp``seq_keep``seq_add``seq_div`、state save/load 基本为空。
4. `seq_rm` 只支持尾部删除,common-prefix 与任意区间删除不成立。
5. 子模型通过名称包含匹配,重复名称时最后一个命中覆盖前一个。
6. CSR setter、`infer()``OutputBuf::Wait()` 都返回错误码,但当前直接调用没有检查,提交、执行或 D2H 失败可能继续使用无效状态/输出。
7. calbin prefill 的 `smodel_type` 错标为 `llm_decode`
8. tokenizer 的 `model_max_length=131072` 与模型/calbin 40960 冲突。
9. `server.cpp` 只在成员定义处保护 `cal_ctx`,部分指标读取在 `#ifdef` 外,非 CalRT 构建存在编译风险。
### P0 吞吐
1. `max_batch_size=1`
2. sequence 逐个循环,`infer()` 后立即 `Wait()`
3. 所有 slot 共享一套 prefill/decode backing vector,不能直接多 in-flight。
4. prefill/decode 不能混合提交。
### P1 延迟与 CPU 开销
1. 每次调用重建 InputBuf/OutputBuf,并重复查 tensor/CSR。
2. Decode 全 vocab BF16 logits 回传后,先构造完整 FP32 临时 vector 再复制。
3. output untile 硬编码 `D0=16` 和 shape 分支。
4. MapBuf 使用普通 vector,尚未证明是 pinned/registered host memory。
5. 固定寄存器读取版本不具备芯片/固件兼容抽象。
## 10. 性能证据与正确口径
首轮当前版本实测:
- 27 token prefill184.27 token/s。
- 246 token decode58.25 token/s。
- 请求端到端:4.41 s。
历史 CSV 有 163 条数据,但有两个口径问题:
- `model=deepseek``client_new.py` 第 183 行的硬编码字符串,不能证明真实模型。
- CSV 的 `TTFT` 实际是 `predicted_ms + prompt_ms`,是完整生成时长,不是真正首 token 延迟。
仍然可信的趋势是:
| 上下文示例 | Decode TPS | 观察 |
| --- | ---: | --- |
| 短上下文,2026-06/08 | 58-64 | 当前短请求基线 |
| 约 2K | 38-46 | KV 长度增加后明显下降 |
| 约 4K | 28-40 | 4K 附近继续退化 |
| 约 8K-16K | 20-30 为主 | 不同版本混合,不能直接横比 |
| 约 33K | 12 左右 | 长上下文已明显带宽受限 |
| 约 40K | 10-16 | 接近上限时最慢 |
这与 GQA decode 的理论特征一致:每个新 token 需要读取不断增长的 KVAttention/KV 带宽是长上下文的第一瓶颈。MoE 权重读取则是所有 decode 长度都存在的主成本。
后续必须将指标定义为:
- TTFT:请求进入到第一个可见 token。
- TPOT:首 token 后每个输出 token 的间隔。
- ITL:逐 token inter-token latency 分布。
- E2E:请求进入到结束。
- Goodput:满足 SLO 的完成 token/s,而不是只看设备 TPS。
## 11. 优化路线
### 阶段 0:建立可信基线
- 修复 TTFT 统计和 model 标签。
- 暴露 infer/H2D/D2H/framework、KV 等待、队列等待和 sampling 时间。
- 每次运行记录 llama commit、CalRT/驱动/固件、calcc/TVM、calbin hash。
- 固定 prompt/output/tokenizer/采样参数,保留原始逐 token 时间。
### 阶段 1:稳定性和低风险收益
- KV 申请加入超时、退避、取消和 429/503。
- 严格 model manifest,启动时检查 I/O、dtype、layout、batch、context、chip topology。
- 建立 InputBuf/OutputBuf pool 和 handle cache。
- 复用 BF16->FP32 缓冲,或直接转换到目标 logits 区。
- interrupt/polling、普通/pinned host memory、单/双 buffer 做 A/B。
### 阶段 2:异步流水
- 每个 in-flight 请求独立 backing storage。
- 使用 OutputBuf 状态和 Wait,将 submit 与 completion 分离。
- 使用 ping/pong job,并测试 `EnableParallelMode`
- 将 CPU sampling 与下一请求/下一阶段 NPU 工作重叠。
- prefill、decode 建立独立队列和优先级。
### 阶段 3:真正批处理
- 由厂商生成 batch 4/8/16 decode calbin。
- 完成 batch KV layout、slot-to-hardware-batch 映射和压实。
- continuous batching 每 step 填充活跃 sequence。
- 验证 fairness、取消、EOS、资源回收和长短请求隔离。
### 阶段 4:算子与图优化
- 长上下文 GQA Flash Attention、KV layout、S8 V-cache。
- router + top-k + dispatch 融合。
- 8 active experts 的 decode 专用 grouped GEMM。
- RMSNorm+QKV、Q/K norm+RoPE、O-proj+residual、FFN+residual 融合。
- NPU top-k 或 sampling,减少 303,872 B/token 的 logits 往返和 CPU 串行。
### 阶段 5:拓扑演进
- 单芯粒小模型/embedding,另一芯粒大模型或 reranker。
- 双芯粒 tensor parallel 大模型。
- expert parallel 的实验性新 calbin。
- 多模型常驻、动态装卸和显式内存预算。
- 多物理板卡需要 Runtime 新版本或进程级设备隔离,0.7.6 不能直接实现。
## 12. 下一轮实验矩阵
### 12.1 基础长度矩阵
- prompt1、16、128、512、2048、4096、8192、16384、32768、40900。
- output1、32、128、512。
- 重复:预热 3 次,正式至少 20 次;报告 P50/P95/P99。
- 指标:TTFT、TPOT、E2E、infer、H2D、D2H、framework、sampling、KV wait、CPU、DRAM。
### 12.2 当前 batch=1 下的并发行为
- 客户端并发:1、2、4、8、16。
- 分别测试全短、全长、10% 长+90% 短。
- 只声明队列吞吐和排队延迟,不宣称硬件 batch。
- 验证取消后 KV 是否释放,资源耗尽是否有界返回。
### 12.3 Runtime A/B
- interrupt vs `poll_mode=1`
- 默认 submit vs `EnableParallelMode(true)`
- 自动 task vs 固定 ping/pong。
- 普通 vector vs pinned/registered buffer。
- 当前对象创建 vs buffer pool。
- 全 logits vs NPU top-k 原型。
### 12.4 稳定性
- 30 分钟、2 小时、24 小时。
- context 逼近 40960。
- 客户端断开、请求取消、模型服务重启。
- 非法 calbin、超长 prompt、KV 耗尽、CCU exception。
- 设备 reset 后恢复只在独占维护窗口执行。
## 13. 决策建议
1. 近期主线应是“稳定性 + 可观测 + 异步流水”,不要先投入多板抽象。
2. 吞吐目标若高于单请求 58-64 token/sbatch decode calbin 是必要条件。
3. 长上下文性能目标必须单独立项,不能用短上下文 TPS 代表。
4. 新模型适配应先让厂商交付可复现的 compiler container 和一套 dense 模型示例,再做 MoE。
5. `origin/runtime_replace` 的 cal-llm EngineCore 方向值得吸收,但它是厂商演进分支,不是当前生产能力;应先拿到匹配的 `c_api.h``libcal_llm.so`、兼容矩阵和测试结果。
6. 对外接口应分为推理面、只读观测面和受控运维面;寄存器/任意内存/reset 不进入普通服务 API。
## 14. 研究后的可执行起点
后续项目研究建议从三个并行工作包进入:
- 工作包 A:在当前 commit 上修复 KV busy wait、非 CalRT 条件编译、buffer pool 和分层 metrics。
- 工作包 B:建立标准性能 harness,重测当前 calbin 的长度/并发/Runtime A/B 矩阵。
- 工作包 C:向厂商索取 compiler toolchain、batch calbin、cal-llm SDK 和接口问题答复,启动一个小型 dense 新模型适配试点。
未经新实验或厂商确认,不应将 C1/C2/C3 能力写入产品规格。