# 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 转为 FP32,CPU 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 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: 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 模型 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 仍有两个可行方案: 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 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`。因此: - 一个 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 历史与驱动证据分析》。