19 KiB
Calculet NPU 多芯粒、多模型、动态加载与并发演进设计
日期:2026-08-02
基线:CalRT 0.7.6、llama.cpp fd9bd632、单物理 VirtualDevice、双芯粒 Qwen3 calbin
实施下钻:执行器类、锁和 buffer/KV 所有权见《Calculet NPU 异步执行器与生产稳定性实施规格》;B4/B8/B16 lane、continuous batching、KV 状态、公平队列和 generation load/unload 见《Calculet NPU Batch、KV 与多模型调度实现规格》。本文保留演进决策和能力边界。
1. 结论
当前可安全实现的第一步不是“同时跑很多模型”,而是把单模型的同步请求路径改造成有明确所有权、可观测、可取消、最多两个 in-flight job 的异步执行器。完成后再引入厂商编译的 batch 4/8/16 decode calbin,才有真正的硬件 continuous batching。
能力边界:
| 目标 | 当前状态 | 所需工作 | 状态 |
|---|---|---|---|
| 单请求单模型双芯粒 | 已运行 | 稳定性修复 | C0 |
| 同一模型 ping/pong 两任务在途 | SDK 有基础,产品未用 | buffer/job/KV 重构和验证 | C1/C2 |
| 同模型 batch 4/8/16 | 当前 calbin batch 1 | 厂商新 calbin + scheduler | C3 |
| 单芯粒运行小模型 | 当前产物固定双芯粒 | 厂商单芯粒 calbin | C3 |
| 两芯粒各跑一个小模型 | 无相应产物/隔离契约 | 单芯粒 calbin + chip affinity/隔离 API | C3 |
| 同板多个模型常驻 | 0.7.6 未证明可同时 configure 多 calbin | 内存规划、命名空间、厂商确认 | C3 |
| 动态 load/unload | 可设计服务生命周期 | Runtime 释放语义和恢复验证 | C2/C3 |
| 多物理 NPU 板 | 0.7.6 明确只支持一个物理设备 | 新 Runtime/多设备管理层 | C4(0.7.6)/C3 |
| expert parallel / pipeline parallel | 当前不是这两类并行 | compiler partition + Runtime 调度 | C3 |
| cal-llm EngineCore 路径 | 仅 Git 历史分支 | 正式 SDK、ABI、产物和迁移测试 | C3 |
2. 当前执行模型为什么不能直接并发
当前服务可以有多个 llama.cpp slot,但 NPU adapter 最终仍串行:
- batch 先按唯一 sequence 拆成多个 ubatch。
- 根据第一个 ubatch 的 token 数选 prefill 或 decode;硬件不允许同次提交混合两者。
- 创建一对 InputBuf/OutputBuf,然后循环各 sequence。
- 每次调用
infer()后立即Wait()。 - prefill/decode 各自映射到
calrt_context中的一组长期 vector。 - 输出完成后将整 vocab BF16 logits 转为 FP32,CPU sampling。
- CSR setter、
infer()和Wait()的错误码均未检查。
因此有四个硬限制:
- 当前 calbin
max_batch_size=1,服务聚合并不等于 NPU batch。 - 同一 prefill/decode backing vector 会被后续任务覆盖。
- KV Apply 与提交/完成没有事务边界,失败时可能无限循环或
abort()。 - 立即 Wait 使 Runtime 的非阻塞、ping/pong 和 parallel mode 没有形成 pipeline。
只把 Wait() 移走会产生 use-after-reuse、输出串扰和 KV 状态提前提交,不是有效优化。
3. 目标架构
flowchart LR
API["API/slot scheduler"] --> ADM["Admission + KV allocator"]
ADM --> BATCH["Prefill/decode batch builder"]
BATCH --> SUB["Per-device submitter"]
SUB --> Q0["JobContext ping"]
SUB --> Q1["JobContext pong"]
Q0 --> NPU["CalRT / NPU"]
Q1 --> NPU
NPU --> CMP["Completion worker"]
CMP --> KV["KV commit/rollback"]
CMP --> POST["logits postprocess/sampling"]
POST --> API
CMP --> OBS["metrics + trace + health"]
核心对象:
DeviceExecutor
device_id / vdevice
lifecycle_state
configured_calbin
submit_queue
completion_worker
buffer_pool[model, task_type, engine_slot]
kv_allocator
health/recovery controller
JobContext
job_id / request_ids / sequence_ids
model_generation
task_type(prefill|decode)
engine_slot(auto|ping|pong)
exclusive input/output buffers
exclusive host backing storage
tensor slices and CSR snapshot
KV reservation transaction
submit/deadline/cancel state
completion status and timings
每个 JobContext 从构造到 completion 期间独占所有会被 DMA 访问的 host memory。只有 OutputBuf::Wait() 返回并完成 D2H 后才能归还池。取消不能立即释放 buffer;它只标记“不再交付结果”,仍要等待 Runtime 完成或执行经过验证的 reset。
4. Buffer 所有权模型
4.1 池化单位
池 key 至少包含:
(calbin_generation, exact_submodel_name, task_type, engine_slot, shape_profile)
一个池项包含 InputBuf、OutputBuf、token/position/embedding/logits backing memory、slice 初始状态和固定 tensor pointers。不能只池化 Runtime 对象而继续共享 std::vector。
4.2 状态机
stateDiagram-v2
[*] --> Free
Free --> Preparing: acquire
Preparing --> Submitted: infer succeeds
Preparing --> Free: validation fails
Submitted --> Running: output status
Submitted --> Completing: done fast path
Running --> Completing: done/CCU exception
Completing --> Free: D2H + postprocess + reset
Submitted --> Quarantined: timeout/reset
Running --> Quarantined: timeout/reset
Quarantined --> Free: device generation changed and buffers rebuilt
归还前必须:UndoSlice/ResetAllTensors、清理 CSR、确认无 DMA、清理 request 引用和敏感数据。CCU exception、timeout 或 device reset 后的 buffer 不直接复用,而是绑定新 device/calbin generation 重建。
4.3 零拷贝边界
MapBuf 可以避免 adapter 内额外复制,但不代表 host memory 可以任意移动。backing storage 在任务完成前必须地址稳定。std::vector::resize、模型切换或 pool 扩容不得作用于 in-flight 项。
5. 异步提交与完成
5.1 Phase A:保持 in-flight=1,先解耦线程(C2)
先不追求性能:
- 请求线程构建 JobContext 后入 submit queue。
- device submitter 调用
infer()并检查返回码。 - completion worker 调用
Wait()。 - 结果通过 future/callback 回到 slot scheduler。
- 队列、超时、取消、错误和 buffer 生命周期有单测。
这一步验证异步架构本身,不改变 NPU 执行顺序。
5.2 Phase B:ping/pong,in-flight=2(C1/C2)
在隔离 runner 中先验证:
EnableParallelMode(true)的准确语义。infer_with_fixed_task_type(...PING/PONG)或SubmitJob(forceEngineMode)。- ping/pong 是否只表示命令/buffer bank,是否能并行 H2D、compute、D2H。
- OutputBuf status 的线程安全和转换顺序。
- pending/finished/left job counter 的含义。
- 两个 task 使用不同 buffer、不同 sequence 时的输出和 KV 一致性。
产品中 in-flight 上限必须来自经验证的 Runtime 能力,不由请求并发直接决定。初始固定为 2;发现 counter、状态或正确性异常时自动降级为 1。
5.3 Phase C:多队列调度(C2)
建议三类队列:
| 队列 | 优先级 | 策略 |
|---|---|---|
| decode-ready | 高 | 延迟敏感,按 deadline/轮转 |
| short prefill | 中 | 长度分桶,限制连续占用 |
| long prefill | 低但防饥饿 | token budget,定期强制服务 |
若 Runtime/图不能抢占,长 prefill 会阻塞 decode。调度器应限制单次 prefill token chunk,但只有 calbin 支持相应长度/增量语义时才能切分;不能擅自把一个整 prompt 切成多个 prefill 而假设 KV 等价。
6. Batch 4/8/16 与 continuous batching
6.1 为什么必须有新 calbin
当前子模型的 max_batch_size=1,KV layout 也是相应配置。llama.cpp 把多个 slot 放入软件 batch 后仍逐 sequence 调用 NPU,不会自动变成硬件 batch。需要厂商至少输出 decode B4/B8/B16,最好同时提供 prefill 的可行 batch/shape 组合。
6.2 调度模型
每个 decode tick:
- 从 decode-ready 选择最多 B 个 sequence。
- 为每个 sequence 分配稳定 hardware batch lane/KV slot。
- 填
[B,1]token/position 和每 lane sequence length。 - inactive lane 按厂商定义 mask,不能伪造 token。
- 提交一次 decode,返回
[B,1,vocab]或等价 layout。 - 按 lane 完成 sampling,将仍活跃的请求放回 ready queue。
6.3 选择 B4、B8、B16
不能假设 batch 越大越快。分别测:
- 单请求 TPOT 及 P99。
- 满 batch aggregate tokens/s。
- 不满 batch 的有效率。
- 长短 sequence 混合时的 padding/mask 成本。
- KV DRAM、workspace 和每 token D2H。
- 热/功耗稳定状态。
Runtime KV 头文件提到 batch 16 layout 和 ONLY_VALID/ALL/AUTO,但只是能力线索;需要对应 calbin、明确 inactive-lane 规则和数值测试后才是产品能力。
7. KV 事务与 admission control
将 KV 更新拆成三阶段:
reserve(sequence, target_length) -> submit(job) -> commit(job)
| error/cancel
v
rollback(job)
规则:
- admission 先调用
canAllocate或等价容量模型;不足时排队/拒绝,不忙等。 - reserve 记录旧长度、目标长度、hardware batch 和 generation。
- submit 失败直接 rollback。
- Runtime 完成且输出有效才 commit 新长度。
- CCU exception/timeout 后若 KV 一致性未知,隔离相关 sequence;必要时 reset configuration 并清空整个 generation。
- context shift、seq copy/keep/add/div 在 CalRT adapter 完整实现并通过 reference 前禁用。
当前 Apply() 失败前循环无 sleep/timeout,完成后失败则 abort(),必须在任何并发扩展前修复。
8. 单芯粒小模型
8.1 需要的厂商产物(C3)
单芯粒运行不是在当前双芯粒 calbin 上传 chipId=0。必须重新编译:
num_chips=1、chip mask、参数文件和 reservation。- 单芯粒 kernel/layout、无跨芯粒 gather/reduce/D2D。
- batch/context profiles。
- 单芯粒 golden 和性能报告。
8.2 可能的部署形态
| 形态 | 价值 | 依赖 |
|---|---|---|
| chip0 单模型,chip1 空闲 | 功耗/容量验证 | 单芯粒 calbin、power/affinity |
| chip0 模型 A,chip1 模型 B | 两租户/两模型并行 | 独立地址空间、命令队列、reset domain |
| 双芯粒大模型与单芯粒小模型切换 | 峰谷调度 | 可靠 unload/load、内存重配置 |
0.7.6 虽有 per-chip memory access,不能据此推断 configure 和 job engine 支持两个独立 chip-scoped VirtualDevice。必须向厂商取得 chip affinity、隔离和 reset 语义。
9. 多模型常驻
9.1 当前障碍
参考 Qwen3 参数 16.938 GiB、BF16 KV 3.750 GiB,总 DRAM reservation 推断为 28.150 GiB/双芯粒,已经占用大部分板上计划空间。CalrtDevice 暴露“current calbin”,现有代码也只有一个 calrt_context/calbin。没有证据表明 0.7.6 能同时 configure 多套独立 calbin 并隔离地址。
9.2 需要的 Runtime 契约(C3)
- 多 calbin 的 model namespace 和地址重定位。
- 每 calbin 参数/KV/workspace reservation 与 quota。
- job 中显式 model handle,不依赖全局 current calbin。
- load/configure 与在途任务的原子性。
- 单模型 unload 是否影响其他模型。
- chip/core affinity、优先级、公平性和 reset domain。
- 参数共享、prefix/KV 共享是否允许及其生命周期。
9.3 如果 0.7.6 只能单 calbin
仍有两个可行方案:
- 让厂商把多个子模型打进同一个组合 calbin,前提是地址和命令由编译器统一规划。
- 进程级单模型,使用受控 unload/configure/load 切换;切换期间 drain,不追求同时常驻。
不能通过手工拼接两个模型目录或改子模型名称实现组合 calbin。
10. 动态 load/unload 生命周期
10.1 模型状态机
stateDiagram-v2
[*] --> Unloaded
Unloaded --> Validating: load request
Validating --> Configuring: manifest/hash/compat pass
Validating --> Failed: reject
Configuring --> Warming: configure succeeds
Configuring --> Failed: configure/reset fails
Warming --> Ready: golden + warmup pass
Ready --> Draining: unload/replace/fault
Draining --> Unloading: in-flight=0
Unloading --> Unloaded: release + memory verified
Failed --> Unloaded: cleanup verified
10.2 load
- 在非设备线程验证 manifest、hash、文件权限、版本和容量。
- 进入设备写锁,停止 admission。
- 若替换模型,先 drain 当前 generation。
CreateCalbin、configure、创建 buffer pool/KV manager。- 运行 golden prefill/decode 和固定 warmup。
- 原子发布新 model generation,恢复 admission。
10.3 unload
- model alias 从 Ready 变为 Draining,新请求拒绝或转移。
- 等待/取消业务,但始终等待底层 DMA 完成。
- 释放 KV、buffer pool、Calbin/context。
- 按厂商契约 ResetConfiguration/ClearDynamicMem;不默认全设备 reset。
- 检查 Runtime 内存用量回落和 device health。
- 只有清理确认后进入 Unloaded。
所有 job 携带 generation。旧 completion 到达时不得写入新模型的 slot/KV/buffer。
11. 双芯粒并行演进
11.1 当前 tensor parallel(C0)
两芯粒均执行 48 层 attention、MoE、norm 和 residual;embedding 只在 chip0。每层有 gather/reduce/sync,prefill 还有 dynamic D2D。它适合单个大模型,但跨芯粒通信会随层数重复。
11.2 expert parallel 候选(C3)
按专家把权重分到芯粒,router 后对 token 做 all-to-all。潜在收益是降低每芯粒专家权重和 grouped GEMM 工作;风险是热点专家、少 token 时利用率低、通信和动态 dispatch。必须测每层 expert histogram、跨芯粒 token bytes、all-to-all 时间和负载不均。
当前两芯粒都含 128 experts 的权重分片,不是 EP。EP 需要 compiler 重新 partition 和生成通信命令。
11.3 pipeline parallel 候选(C3)
例如 chip0 负责 0-23 层、chip1 负责 24-47 层。参数只跨边界传 activation,通信频率较低;但单请求两个阶段串行、microbatch 小会产生气泡,KV 也按层分布。只有 batch/并发足够时可能优于逐层 TP。
11.4 混合方案
候选必须由编译器产出不同 calbin 做 A/B:TP2、PP2、EP2、TP+EP。llama.cpp 只负责请求和 batch 调度,不能在 Host 侧重排当前静态命令流来改变 partition。
12. 多物理板
CalRT 0.7.6 的 VirtualDevice 头文件明确注明临时只支持一个物理设备,内部仅有一个 shared_ptr<CalrtDevice>。因此:
- 一个 VirtualDevice 不是多板资源池。
GetDeviceInfo(idx)的idx参数不构成多板证据。- per-chip
ReadMem/WriteMem是板内芯粒访问,不是板间通信。 - 当前双芯粒 calbin 不能跨两张 PCIe 卡部署。
短期横向扩展只能采用“一进程/一板/一模型实例 + 上层负载均衡”,前提是 Runtime/driver 能以独立进程稳定绑定各板;本地资料尚不能确认设备选择 API,需要厂商支持。统一多板 continuous batching、KV 迁移和模型切分属于新 Runtime 设计。
13. origin/runtime_replace 与 cal-llm
Git 历史中 origin/runtime_replace 包含 cal-llm EngineCore、profile、capacity 和错误处理方向。这说明厂商正在探索用更高层推理 Runtime 替换当前直接 CalRT adapter,但它不是当前生产:
- 当前 HEAD/origin/dev 是
fd9bd632。 - 当前镜像链接 CalRT 0.7.6 并使用
llama-calrt.cpp。 - 分支代码不等于可发布库、头文件、ABI、calbin 或支持承诺。
评估 cal-llm 前要求:SDK/header/library、版本与 ABI、支持模型/产物格式、batch/KV/sampling/取消/错误契约、性能 trace、迁移指南和至少一套可运行镜像。它可作为长期替换候选,不能写入当前 C0 能力。
14. 故障恢复
| 故障 | 首选处理 | 升级处理 |
|---|---|---|
| admission/KV 不足 | 排队、429/503、有界超时 | 不 reset |
| submit 返回错误 | job rollback,buffer 归还或隔离 | 连续失败进入 drain |
| output CCU exception | 相关 job 失败,KV 标记未知 | ResetCCU/Configuration,重新 warmup |
| Wait 超时 | 停止新提交,保留 buffer | 厂商定义的 cancel;否则 reset generation |
| output 数值异常 | 隔离 calbin generation | golden 复测,回滚旧模型 |
| device unhealthy | 全实例 drain | device reset/进程重启/摘流 |
reset 不是普通错误的第一反应。所有 reset 必须记录原因、在途任务、前后版本/health 和恢复结果,并限制重试次数,避免 reset storm。
15. 指标与验收
必须增加:
- submit queue 深度、pending/finished/left jobs、in-flight。
- buffer pool 使用/等待、KV slot 使用/等待、admission reject。
- 每任务 queue/H2D/infer/D2H/postprocess/sampling。
- prefill/decode 分桶 P50/P90/P99。
- batch fill ratio、每 tick active lanes、tokens/s。
- ping/pong 利用率和 overlap 率。
- per-model/chip memory、D2D bytes/time、CCU exception、reset。
- generation、calbin hash、Runtime/driver/firmware 版本标签。
验收顺序:正确性 -> 资源生命周期 -> 错误恢复 -> 并发 -> 性能 -> 24h 稳定性。吞吐达标但请求串扰、KV 不一致或 reset 后不稳定时视为失败。
16. 建议实施里程碑
| 里程碑 | 工作 | 完成条件 |
|---|---|---|
| M0 稳定基线 | 修 KV 忙等/abort、manifest、metrics guard、计时 | 现有 batch1 24h 稳定 |
| M1 异步骨架 | JobContext、buffer pool、submit/completion | in-flight=1 行为等价 |
| M2 ping/pong | parallel/fixed task 隔离验证 | in-flight=2 无串扰,收益可测 |
| M3 batch decode | 厂商 B4/B8/B16 + scheduler/KV lanes | continuous batching 正确且 P99 可控 |
| M4 单芯粒小模型 | dense 单芯粒产物 | 与双芯粒模型可切换,资源可回收 |
| M5 多模型 | 组合 calbin 或 Runtime 多模型契约 | load/unload soak 无泄漏/串扰 |
| M6 新并行 | TP/EP/PP A/B | 用实测而非架构推断选择 |
| M7 多板 | 新 Runtime 或多进程绑定 | 明确设备选择、隔离和恢复 |
前三个里程碑不应并行跳跃:M0 不稳定会污染 M1/M2 的所有结果,M1 的所有权模型又是 M2/M3 的前置条件。
17. Git 历史对演进顺序的校正
完整 CalRT 历史确认了 async/serial mode、thread-safe job engine、multi-chip configure、ResetConfiguration 和连续同模型 fast path 的演进;它们分别对应 M1/M2/M5 的候选实现基础。但历史同时出现 async nullptr、多线程同步、job id、设备状态、内存释放和 CCU reset 修复,说明并发/切换功能的回归面必须覆盖:
- 两线程 Submit/Wait 与 ping/pong 交错。
- timeout、cancel、CCU exception 后 buffer/KV/generation 隔离。
- 同模型连续运行与 A/B 模型切换两条状态路径。
- ResetConfiguration、Release、soft reset 的真实作用域。
- queue counters、jobs left 和 device owner 在多线程/多进程下的一致性。
llama.cpp 历史还明确区分了三类证据:dev 主线是当前生产基线;origin/runtime_replace 是未合入的 cal-llm 集成方向;当前/过期 stash 和不可达 commit 是实验。实现时只允许主线作为基线,其他内容按独立 patch 重建,不直接把历史分支合并到产品。
M5 增加硬门:取得驱动 1.0.0 对应 Git commit 和 Runtime/firmware 兼容矩阵。现有 0.9.0 源码可以做设计分析,不能替换运行模块。完整资产、恢复 refs 和历史风险清单见《源码、Git 历史与驱动证据分析》。