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

19 KiB
Raw Blame History

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:
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.6libelf
  • 当前 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. 当前端到端执行链

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_cpseq_keepseq_addseq_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=deepseekclient_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.hlibcal_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 能力写入产品规格。