# Calculet NPU 总体研究报告 日期:2026-08-02 当前生产源码:`fd9bd632f346085920d523dd307a3e4c11cc0a05` CalRT:0.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 sampling;CalRT 加载预编译 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` 文件显示: - decode:chip0 1254 行,chip1 1253 行。 - prefill:chip0 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 KiB;122880 x 32 KiB;75968 x 1 KiB | | chip1 | 444,608 | 18.822 GiB | 245760 x 64 KiB;122880 x 32 KiB;75968 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 GiB;chip1 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`,不是多物理设备管理器。 - 当前 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 prefill:184.27 token/s。 - 246 token decode:58.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 需要读取不断增长的 KV,Attention/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 基础长度矩阵 - prompt:1、16、128、512、2048、4096、8192、16384、32768、40900。 - output:1、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/s,batch 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 能力写入产品规格。