Files
calculet-npu-research-archive/reports/Calculet-NPU-多芯粒多模型动态加载与并发演进设计-20260802.md
T

402 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
基线: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 最终仍串行:
1. batch 先按唯一 sequence 拆成多个 ubatch。
2. 根据第一个 ubatch 的 token 数选 prefill 或 decode;硬件不允许同次提交混合两者。
3. 创建一对 InputBuf/OutputBuf,然后循环各 sequence。
4. 每次调用 `infer()` 后立即 `Wait()`
5. prefill/decode 各自映射到 `calrt_context` 中的一组长期 vector。
6. 输出完成后将整 vocab BF16 logits 转为 FP32CPU sampling。
7. 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. 目标架构
```mermaid
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"]
```
核心对象:
```text
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 至少包含:
```text
(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 状态机
```mermaid
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
先不追求性能:
1. 请求线程构建 JobContext 后入 submit queue。
2. device submitter 调用 `infer()` 并检查返回码。
3. completion worker 调用 `Wait()`
4. 结果通过 future/callback 回到 slot scheduler。
5. 队列、超时、取消、错误和 buffer 生命周期有单测。
这一步验证异步架构本身,不改变 NPU 执行顺序。
### 5.2 Phase Bping/pongin-flight=2C1/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
1. 从 decode-ready 选择最多 B 个 sequence。
2. 为每个 sequence 分配稳定 hardware batch lane/KV slot。
3.`[B,1]` token/position 和每 lane sequence length。
4. inactive lane 按厂商定义 mask,不能伪造 token。
5. 提交一次 decode,返回 `[B,1,vocab]` 或等价 layout。
6. 按 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 更新拆成三阶段:
```text
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 模型 Achip1 模型 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
仍有两个可行方案:
1. 让厂商把多个子模型打进同一个组合 calbin,前提是地址和命令由编译器统一规划。
2. 进程级单模型,使用受控 unload/configure/load 切换;切换期间 drain,不追求同时常驻。
不能通过手工拼接两个模型目录或改子模型名称实现组合 calbin。
## 10. 动态 load/unload 生命周期
### 10.1 模型状态机
```mermaid
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
1. 在非设备线程验证 manifest、hash、文件权限、版本和容量。
2. 进入设备写锁,停止 admission。
3. 若替换模型,先 drain 当前 generation。
4. `CreateCalbin`、configure、创建 buffer pool/KV manager。
5. 运行 golden prefill/decode 和固定 warmup。
6. 原子发布新 model generation,恢复 admission。
### 10.3 unload
1. model alias 从 Ready 变为 Draining,新请求拒绝或转移。
2. 等待/取消业务,但始终等待底层 DMA 完成。
3. 释放 KV、buffer pool、Calbin/context。
4. 按厂商契约 ResetConfiguration/ClearDynamicMem;不默认全设备 reset。
5. 检查 Runtime 内存用量回落和 device health。
6. 只有清理确认后进入 Unloaded。
所有 job 携带 generation。旧 completion 到达时不得写入新模型的 slot/KV/buffer。
## 11. 双芯粒并行演进
### 11.1 当前 tensor parallelC0
两芯粒均执行 48 层 attention、MoE、norm 和 residualembedding 只在 chip0。每层有 gather/reduce/syncprefill 还有 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/BTP2、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 rollbackbuffer 归还或隔离 | 连续失败进入 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 历史与驱动证据分析》。