# Calculet NPU 双芯粒双模型独立服务可行性与实施规格 日期:2026-08-02 基线:CalRT 0.7.6、llama.cpp `fd9bd632`、一张 PCIe 板、两个芯粒、每芯粒两个 CalCore 目标:chip0 常驻模型 A,chip1 常驻模型 B;每个模型的 prefill 和 decode 全部在自己的芯粒完成,两个模型同时对外提供服务 ## 0. 执行结论 这个目标需要先澄清“核心”的含义: | “核心”指什么 | 当前结论 | 原因 | 状态 | | --- | --- | --- | --- | | 每芯粒内的一个 CalCore/CCU | 当前不可直接实现 | 完整图会为同一芯粒同时生成 `ccu0`、`ccu1` 程序;公开 API 没有 CalCore affinity、独立配置、内存配额或 reset domain | C4(0.7.6 公共能力)/C3(需厂商新能力) | | 板上的一颗芯粒 | 架构上可行,当前不能直接上线 | calbin 已有单/多芯粒描述和 chip mask,但缺少单芯粒完整模型产物,以及双 session/队列/KV/reset 的正式隔离契约 | C3 | 因此,建议把目标正式定义成: ```text chip0: Model A = A.prefill + A.decode + A.params + A.KV + A.workspace chip1: Model B = B.prefill + B.decode + B.params + B.KV + B.workspace ``` 不是: ```text CalCore0: Model A CalCore1: Model B ``` 推荐两条实现路径: 1. **最短硬件验证路径:厂商组合 calbin。** 由 compiler 在一个 calbin 内统一放入 A/B 四个子图,A 的所有 section 只带 chip0 mask,B 只带 chip1 mask;整个 calbin 只 configure 一次,再按 `CalbinModel*` 提交。它最贴近 0.7.6 的“单 current calbin”模型,但能否真正双芯粒并行仍需厂商 runner 和 trace 证明,状态 C3。 2. **正式产品路径:Runtime chip session。** Runtime 显式提供 `chip_session/model_handle/job_handle`,每颗芯粒独立 load、quota、queue、KV、health、drain、unload 和 reset。服务层再实现两个 ModelWorker 和一个 Router,状态 C3。 当前 Qwen3-30B-A3B 双芯粒产物不能通过删文件、改 mask 或只传 `chipId` 改成两个单芯粒模型;该产物本身是逐层双芯粒 tensor parallel。第一轮应选择 **1B-3B、decoder-only、dense、4K/8K context** 的模型,优先选择厂商已有算子覆盖、与现有模型架构接近的模型。 ## 1. 状态和验收口径 | 状态 | 本文使用规则 | | --- | --- | | C0 | 当前源码、产物或样机记录直接证明 | | C1 | 0.7.6 头文件/符号存在,但生产未接入或未测 | | C2 | 我方能用已确认接口实现,仍需开发和硬件验收 | | C3 | 需要厂商 compiler、Runtime、kernel 或新 calbin | | C4 | 当前接口明确不支持,或不应作为产品路径 | “两个模型同时服务”再分两层: | 等级 | 定义 | 本项目目标 | | --- | --- | --- | | S1 服务并存 | 两个 alias 同时 Ready,能同时接收请求,但板内可能串行执行 | 中间里程碑 | | S2 硬件并发 | chip0/chip1 的计算区间真实重叠,联合吞吐接近两颗芯粒隔离运行之和 | 最终目标 | 只有 Router 同时暴露两个模型不能证明 S2。必须通过 per-chip trace、任务时间线和联合吞吐证明硬件重叠。 ## 2. 当前证据为什么支持这个判断 ### 2.1 板卡是一个 PCIe 设备,不是两个独立设备 保存的 `lspci` 只看到一项 Calculet 设备 `01:00.0`,由 `calculet` 驱动管理。CalRT 0.7.6 的 `VirtualDevice` 头文件也明确写着“temporary only support one physical device”,内部只有一个 `m_phyDevices`。 这意味着: - 两颗芯粒是同一物理设备内部资源。 - 不能用“两个 PCIe device id + 两个进程”绕过 Runtime 隔离问题。 - 在厂商确认前,不允许两个进程分别打开同一设备并各自 configure 一个 calbin;全局配置、地址和 reset 可能互相覆盖。 ### 2.2 CalCore 是编译器执行单元,不是应用可分配的模型槽 当前 prefill 和 decode 各包含四个 ELF: ```text ccu0_chip0.elf ccu1_chip0.elf ccu0_chip1.elf ccu1_chip1.elf ``` 根 manifest 对两个子图都声明: ```yaml device_cfg: chip_nums: 2 calcore_num: 2 ``` 所以一个完整子图在每颗芯粒上会同时使用两个 CalCore。`calcore_num: 2` 只能证明 codegen 生成了两个执行程序,不能证明应用能把两个 host job 分别绑定到两个 CalCore。 公开 API 的 `TASK_PING/TASK_PONG` 是 job engine/bank 选择,不是 `CalCore0/CalCore1` 亲和性。`SubmitJob()` 也没有 `core_id`。 ### 2.3 calbin 有芯粒描述能力,但不等于多模型隔离已经可用 0.7.6 头文件包含: - `ModelChipMode_e { SCHIP, MCHIP }`。 - `CalbinModel::tarChipN`。 - 每个 `CalbinSection_s::chipMask`。 - `TaskInfo_s::m_taskChipArch` 的 `SINGLE_CHIP_TASK/MULTIPLE_CHIP_TASK`。 - `ReadMem/WriteMem(..., chipId)` 和 remote chip memory API。 这些是单芯粒编译和按芯粒部署的有力线索,但不能证明: - 两个模型可以同时 configure。 - 两个 job queue 独立。 - KV manager 按模型/芯粒隔离。 - reset 一颗芯粒不会影响另一颗。 - 两个线程或进程可以同时提交而无全局锁。 ### 2.4 0.7.6 仍是单 current-calbin 设备状态 `CalrtDevice` 当前只有: ```text Calbin *pmCurrentCalbin uint64_t mCurCalbinHash SetConfigModel(Calbin*) GetCurrentCalbinOnDevice() ``` `configure(VirtualDevice*, Calbin*)` 对整个 calbin 生效,`SubmitJob(model, input, output, engine)` 虽选择子模型,但没有 `chip_session`、`model_handle`、`memory_quota` 或 `reset_scope`。 这正是组合 calbin 可能成为短期路径的原因:A/B 可以由 compiler 放进同一个全局 calbin;也是不能由应用随意加载两个独立 calbin 的原因。 ### 2.5 当前生产适配层只有一个设备、一个 calbin、一个 KV manager `calrt_context` 只持有一份: ```text VirtualDevice Calbin KvManager prefill/decode backing vectors ``` 当前请求路径按第一个 ubatch 选择 prefill/decode,创建 buffer,`infer()` 后立即 `Wait()`;多个 sequence 仍逐个提交。生产代码没有 model instance、chip worker、独立 KV namespace 或双模型 router。 历史 `origin/runtime_replace` 分支也只有一个 `CalrtEngineConfig.calbin_path` 和一个 EngineCore 实例,不能作为双模型已实现的证据。 ## 3. 当前模型为什么不能直接拆开 当前 Qwen3 产物的关键事实: | 项目 | 当前值 | 含义 | | --- | ---: | --- | | 参数块 | 16.938 GiB 合计 | chip0 8.759 GiB,chip1 8.179 GiB,是张量分片,不是两套完整权重 | | BF16 KV | 3.750 GiB 合计 | 与 48 层、4 KV heads、40960 context 对应 | | DRAM reservation | 14.075 GiB/芯粒地址计划(推断) | 已接近可能的单芯粒本地容量,物理口径仍需厂商确认 | | 图分配 | 两颗芯粒都覆盖 48 层 attention/MoE | 当前是逐层 tensor parallel | | 通信 | 每层 gather/reduce/sync;prefill 有 dynamic D2D | 删除另一芯粒后图不完整 | | batch | 1 | 不能由服务端聚合自动变成硬件 batch | 以下做法全部禁止: - 把 `param_blk0.bin` 当成 chip0 的完整模型,把 `param_blk1.bin` 当成 chip1 的另一个模型。 - 删除 `chip1` ELF/command 后运行 chip0。 - 手工把 section 的 mask 从 `3` 改成 `1` 或 `2`。 - 把两套模型目录拼到一起并改子图名。 - 创建两个进程分别 configure 同一物理板。 - 用 `ReadMem(..., chipId)` 推断 job 也能按 chip 独立提交。 这些操作会破坏权重、通信、同步、地址和命令流的一致性,轻则配置失败,重则错误输出、DMA 越界或整板 reset。 ## 4. 推荐目标架构 ```mermaid flowchart LR C["OpenAI-compatible clients"] --> R["Model Router"] R -->|"model=A"| WA["ModelWorker A"] R -->|"model=B"| WB["ModelWorker B"] WA --> QA["A admission/queues"] QA --> KVA["A KV manager"] QA --> BFA["A buffer pool"] KVA --> SA["chip0 session"] BFA --> SA WB --> QB["B admission/queues"] QB --> KVB["B KV manager"] QB --> BFB["B buffer pool"] KVB --> SB["chip1 session"] BFB --> SB SA --> DS["One DeviceSupervisor / one board"] SB --> DS DS --> NPU["Calculet NPU: chip0 + chip1"] ``` 硬约束: - 进程内只有一个 `DeviceSupervisor` 拥有板卡生命周期。 - 每个 ModelWorker 只访问自己的 session、KV、buffer、队列和 generation。 - Router 按稳定 `model_alias` 路由,不把 `chip_id` 暴露给普通客户端。 - 默认模型 A/B 都各自包含 prefill 和 decode,sequence 不跨芯粒迁移。 - 任何提交都带 model/session 身份,不能依赖“当前 calbin”隐式选择。 - 如果 Runtime 只能组合 calbin,DeviceSupervisor 负责唯一 configure;两个 worker 只能借用其中已绑定 chip mask 的子模型。 ## 5. 三条实现路径对比 | 路径 | 0.7.6 适配度 | 改动 | 优点 | 主要风险 | 建议 | | --- | --- | --- | --- | --- | --- | | A. 厂商组合 calbin | 最高 | compiler 生成 A/B 统一地址计划;我方单 DeviceSupervisor + 双 worker | 避开多 current-calbin;最快证明芯粒并发 | unload 粒度可能是整包;queue/reset 是否隔离未知 | 第一优先 PoC,C3 | | B. Runtime chip session | 需新版本/API | Runtime、driver 和我方适配层共同升级 | 生命周期清楚,可独立 load/unload/quota/reset | 厂商开发量最大,ABI/兼容性风险 | 正式产品目标,C3 | | C. 两张物理板各一模型 | 0.7.6 头文件仍称单物理设备 | 新多设备选择 API,或两台主机 | 隔离最强,运维简单 | 需要第二张板;0.7.6 多板 API 不完整 | 业务兜底,C3/C4 | 不采用“同一板两个进程各占一个芯粒”。在芯粒被 Runtime 显式虚拟化成可独占设备之前,这种方案没有安全边界。 ## 6. 路径 A:组合 calbin 的精确要求 ### 6.1 逻辑内容 一个组合包至少包含四个业务子图: ```text model_a.prefill -> tarChipN=[0], SCHIP model_a.decode -> tarChipN=[0], SCHIP model_b.prefill -> tarChipN=[1], SCHIP model_b.decode -> tarChipN=[1], SCHIP ``` 以及每个模型独立的: - 参数块和参数映射。 - KV 地址、容量、sequence id namespace。 - prefill/decode workspace。 - input/output/CSR metadata。 - chip-local `ccu0/ccu1` ELF。 - CPU/PLD ping/pong command。 - golden input/output、tokenizer、模型配置和 hash。 “A 在 chip0”不代表 A 只使用 `ccu0`。单芯粒 A 仍可由 compiler 同时使用 chip0 的两个 CalCore;B 同理。 ### 6.2 compiler 必须统一解决的内容 1. A/B 的虚拟地址允许在不同芯粒使用相同数值,但 mask、DMA route 和 host mapping 必须无歧义。 2. 同一芯粒内 A.prefill/A.decode 可以按生命周期复用 workspace;A 与 B 不能共享可写状态。 3. 两模型的 KV base、sync unit、job id 和 completion 信息必须独立。 4. A 的任何 section 不得带 chip1 mask;B 反之。 5. 不得生成跨模型或跨芯粒 gather/reduce/D2D。 6. prefill/decode 的 tensor shape、dtype、CSR、batch、context 必须分别写入 manifest。 7. 组合包必须由 compiler/packer 正式生成,不能由我方脚本拼接。 ### 6.3 建议目录合同 ```text dual-model-calbin/ manifest.yaml model_memory_reserved_info.txt model-a/ prefill/... decode/... params/... golden/... model-b/ prefill/... decode/... params/... golden/... tokenizer/model-a/... tokenizer/model-b/... checksums.sha256 compatibility.json build-provenance.json ``` 实际文件名可沿用厂商 schema,但 manifest 必须能无字符串模糊匹配地确定:`alias -> prefill/decode -> chip -> profile`。 ### 6.4 组合 calbin runner PoC runner 必须只做以下固定流程: ```text CreateVDevice(PCIE) CreateCalbin(combined_path) validate exact four model names and chip targets configure(vdev, combined_calbin) exactly once create A/B independent input/output buffers run A golden, run B golden run A+B concurrent submissions with separate buffers Wait both, verify output and per-chip trace drain, ResetConfiguration, release ``` `SubmitJob`/`infer` 没有 chip 参数,所以 chip 选择必须完全来自经过签名/校验的 `CalbinModel` metadata。runner 启动时发现任何模型 mask 越界、跨 chip section 或名字重复,应在 configure 前退出。 ## 7. 路径 B:Runtime chip session 的最小 API 契约 建议厂商提供等价于下面的能力;具体命名可以不同,但语义不能缺失: ```cpp struct ChipSessionOptions { uint32_t chip_id; uint64_t dram_quota_bytes; uint64_t sram_quota_bytes; uint32_t max_inflight; }; Topology QueryTopology(DeviceHandle); ChipSessionHandle CreateChipSession(DeviceHandle, ChipSessionOptions); ModelHandle LoadModel(ChipSessionHandle, const CalbinPath&, LoadOptions); JobHandle Submit( ChipSessionHandle, ModelHandle, GraphKind, // prefill | decode InputBufferHandle, OutputBufferHandle, JobOptions); JobStatus Query(JobHandle); Status Wait(JobHandle, Deadline); Status Cancel(JobHandle); // 必须明确是否只取消交付,还是终止设备执行 Status Drain(ChipSessionHandle, Deadline); Status Unload(ModelHandle); Status ResetSession(ChipSessionHandle); MemoryUsage GetMemoryUsage(ChipSessionHandle); Health GetHealth(ChipSessionHandle); ``` ### 7.1 必须写进正式契约的语义 | 契约 | 必须回答的问题 | | --- | --- | | 地址空间 | 两 session 可否使用相同 vaddr;IOMMU/DMA 如何区分;host mapping 生命周期 | | configure | chip0 load 是否会改变 chip1 的 current model、命令表或 sync unit | | 提交 | 两线程并发 Submit 是否线程安全;每芯粒最大 in-flight;完成顺序 | | 队列 | 全板共享队列还是 per-chip queue;是否存在全局 head-of-line blocking | | KV | key 必须含 `(session, model_generation, sequence_id)`;失败后的 commit/rollback | | 内存 | quota 的硬/软语义;allocation 失败是否只影响本 session | | reset | CCU/session/chip/device 四种 reset 各影响哪些任务、KV、参数和另一芯粒 | | unload | 只释放目标 model/session;在途 DMA 未完成时必须拒绝或 drain | | 错误 | timeout/crash/invalid calbin 的污染范围和恢复顺序 | | 观测 | per-chip 温度、功耗、频率、内存、queue、job、trace 的单位和时钟 | 若 Runtime 只有 `ResetConfiguration()` 和整板 `Reset()`,两个模型可以做到并发服务,但不能宣称具备强故障隔离;任一模型故障可能导致两个 alias 同时摘流。 ## 8. 模型选择和内存预算 ### 8.1 容量不是只看参数文件 每颗芯粒的稳态预算至少为: ```text M_required = W_compiled + KV(batch, context) + workspace_peak(prefill, decode) + code_and_commands + input_output_staging + runtime_metadata + allocator_fragmentation + safety_guard ``` BF16 KV 的理论下界: ```text KV_bytes = 2(K,V) * n_layers * n_kv_heads * head_dim * context_tokens * batch_or_active_sequences * bytes_per_element ``` 真实值还受 layout、对齐、batch lane、page/block 和 compiler reservation 影响,最终只能以新 calbin 的 metadata 加设备实测为准。 ### 8.2 推荐安全线 在厂商没有提供可靠 per-chip memory API 前: - 不以 `GetMemoryUsage()` 的总量字段做容量承诺。 - 静态 `M_required` 不超过厂商确认的本地 DRAM 的 75%-80%。 - 至少保留 `max(1 GiB, 15% local DRAM)` 作为 Runtime、碎片、瞬时 workspace 和恢复余量。 - 以 prefill 峰值 workspace 而非 decode 常态决定是否可部署。 - A/B 分别验算,不能拿整板 32 GiB 作为任一芯粒可独占容量。 当前 14.075 GiB/芯粒 reservation 已说明大模型地址计划非常紧;当前 Qwen3 不适合作为单芯粒完整模型候选。 ### 8.3 第一轮模型范围 推荐: | 维度 | 第一轮选择 | 原因 | | --- | --- | --- | | 架构 | decoder-only dense | 去掉 MoE router/expert 和跨芯粒通信变量 | | 参数规模 | 1B-3B | 为权重、KV、workspace 和并发留余量 | | context | 4K,随后 8K | 快速闭环,再扩大 KV | | batch | B1 | 先证明双芯粒隔离;B4/B8 是后续独立课题 | | 量化 | 厂商已有稳定 W8A8;必要时比较 W4 | 避免模型、量化、双模型同时引入未知量 | | tokenizer | 各模型独立 | 避免 alias 路由到错误词表 | 若希望先隔离“不同模型算子覆盖”变量,Phase 2 应先把同一个小模型复制到两颗芯粒;通过后再换成不同模型 A/B。 ### 8.4 容量验收表 每个模型必须提交并实测: | 项 | 静态来源 | 运行验证 | | --- | --- | --- | | 参数 | parameter map/文件区间 | load 前后 per-chip memory delta | | KV/sequence | config 公式和 reservation | 1/2/... active sequence 增量 | | workspace | compiler peak report | prefill/decode 峰值采样 | | code/command | section 文件和映射 | configure delta | | allocator 碎片 | 不可纯静态确定 | 100 次 load/unload 后回落 | | guard | 项目策略 | OOM 前必须 admission reject,不允许设备 crash | ## 9. 每芯粒的 prefill/decode 调度 ### 9.1 同一模型的基本顺序 一个 sequence 永远遵循: ```text Queued -> PrefillReady -> PrefillRunning -> DecodeReady -> DecodeRunning -> DecodeReady ... -> Completed ``` prefill 成功后才提交第一次 decode;KV 更新使用 `reserve -> submit -> commit/rollback`。任务失败或取消不能立即复用仍被 DMA 引用的 buffer。 ### 9.2 每个 ModelWorker 的队列 ```text decode_ready_queue 高优先级,降低 TPOT short_prefill_queue 例如 <=512 token medium_prefill_queue 例如 <=4096 token long_prefill_queue 低优先级但必须防饥饿 ``` 每个 worker 独立做 admission: - 最大等待请求数。 - 最大等待 prompt token 数。 - KV 可分配量。 - 请求 deadline。 - 模型 context/batch profile。 - 当前 chip health 和 generation。 模型 A 过载只能返回 A 的 429/503,不应阻塞模型 B 的 admission。 ### 9.3 长 prefill 对 decode 的影响 即使 A/B 在不同芯粒,单个模型内部仍可能被长 prefill 阻塞 decode。只有 compiler/Runtime 明确支持 chunked prefill、抢占或安全 ping/pong 时才能切分/重叠。第一版采用: - decode 优先。 - 限制长 prefill 的 admission 和并发数。 - 不擅自把一个 prompt 拆成多个 prefill 图调用。 - 每颗芯粒初始 `max_inflight=1`;完成独立 buffer 和 ping/pong 验证后才升到 2。 ### 9.4 两芯粒并发的四种干扰组合 必须分别测: | chip0 | chip1 | 主要观察 | | --- | --- | --- | | A prefill | B prefill | DRAM/DMA/功耗峰值、TTFT | | A prefill | B decode | 长任务是否拖慢 B 的 TPOT | | A decode | B prefill | 对称性、共享队列偏置 | | A decode | B decode | 稳态吞吐、带宽、温度、频率 | 两颗芯粒可能共享 PCIe、板级功耗/散热和部分 Runtime 锁,因此即使计算单元独立,也不能假定联合性能等于孤立性能简单相加。 ## 10. 服务层实施规格 ### 10.1 对外接口 保持 OpenAI-compatible `model` 字段路由: ```text GET /v1/models POST /v1/chat/completions body.model = "model-a" | "model-b" POST /v1/completions body.model = "model-a" | "model-b" DELETE /internal/requests/{request_id} ``` `GET /v1/models` 只在对应 worker 完成 configure、golden 和 warmup 后返回 Ready。普通接口不暴露 chip id、calbin 路径、虚拟地址、寄存器或 reset。 ### 10.2 内部对象 ```text DeviceSupervisor board_handle topology lifecycle mutex chip_sessions[2] board health/recovery ModelWorker model_alias chip_id model_generation prefill/decode graph handles tokenizer/config admission controller scheduler queues KV manager buffer pools submit/completion workers health and metrics RequestContext request_id/model_alias/generation sequence_id/deadline/cancel prompt/output counters KV transaction current JobContext JobContext session/model/graph/job handles exclusive input/output/backing storage CSR snapshot submit/deadline/status/timings ``` ### 10.3 锁和所有权 - `DeviceSupervisor.lifecycle_mutex` 只用于 load/unload/reset,不包住正常 `Wait()`。 - A/B 各有 scheduler mutex;绝不同时持有两个 worker mutex。 - JobContext 从 prepare 到 completion 独占 host backing memory。 - KV commit 发生在 OutputBuf 完成且输出有效之后。 - reset 增加 session/device generation;旧 generation 的 completion 只能被丢弃,不能写入新请求。 - Router 只保存 worker 的稳定引用,不直接持有 Runtime handle。 ### 10.4 建议配置 ```yaml schema: calculet-dual-model/v1 device: type: pcie expected_chips: 2 combined_calbin: /models/dual-a-b models: - alias: model-a chip_id: 0 prefill_graph: model_a_prefill decode_graph: model_a_decode context_limit: 4096 max_inflight: 1 max_waiting_requests: 64 - alias: model-b chip_id: 1 prefill_graph: model_b_prefill decode_graph: model_b_decode context_limit: 4096 max_inflight: 1 max_waiting_requests: 64 recovery: allow_session_reset: false restart_process_on_unknown_dma: true ``` loader 必须将配置与签名 manifest 交叉验证。chip、graph、context、batch、tensor 或 hash 不一致时启动失败,不能自动猜测或按子字符串匹配。 ## 11. 生命周期和故障状态机 ### 11.1 单 worker 生命周期 ```mermaid stateDiagram-v2 [*] --> Unloaded Unloaded --> Validating: load Validating --> Configuring: manifest/hash/compat pass Validating --> Failed: reject Configuring --> Warming: configure succeeds Configuring --> Failed: configure fails Warming --> Ready: golden + warmup pass Warming --> Failed: mismatch/fault Ready --> Draining: unload/replace/session fault Draining --> Unloading: in-flight=0 Draining --> Recovering: deadline/unknown DMA Unloading --> Unloaded: memory released Recovering --> Warming: scoped reset + reload Recovering --> Failed: reset scope unknown/fails ``` ### 11.2 组合 calbin 的特殊限制 组合 calbin 很可能只能整包 configure/reset。此时 A/B 的逻辑状态虽独立,但物理生命周期如下: ```text 任一模型需要重新 configure -> Router 同时停止 A/B admission -> drain 两个 worker -> 整包 ResetConfiguration -> configure 新组合包 -> 分别 golden/warmup -> 两者都 Ready 后恢复接流 ``` 不能在没有 Runtime 保证时只释放 A 的 Calbin/buffer 并让 B 继续提交。 ### 11.3 故障范围 | 故障 | 默认隔离范围 | 动作 | | --- | --- | --- | | 非法输入/shape/context | request | 提交前 400,不碰设备 | | A admission/KV 满 | A worker | A 有界排队后 429/503,B 正常 | | submit DeviceBusy | 对应 queue,待确认 | 有界 backoff,不立刻 reset | | 输出数值不符 | model generation | 摘目标 alias;若共享配置不确定则摘 A/B | | Wait timeout/CCU exception | session 或全板未知 | 停止新提交、drain、保存 trace,按厂商契约 reset | | chip-scoped reset 失败 | device | 两个 alias 摘流,进程/设备恢复 | | 板级 reset/驱动异常 | device | A/B 同时 503,重建全部 generation | 在故障注入证明之前,所有 Runtime timeout/crash 按“可能污染整板”处理。 ## 12. 分阶段执行计划 ### Phase 0:冻结合同和基线 输入:当前 SDK、板卡、服务、模型和性能 harness。 工作: 1. 固定 driver/firmware/CalRT/compiler/calbin/server 的版本与 hash。 2. 获取 per-chip DRAM、SRAM、队列、reset topology 的书面口径。 3. 建立 `model_alias -> graph -> chip mask` 的版本化 manifest schema。 4. 对现有单模型跑正确 TTFT/TPOT、功耗、温度、内存和 1h 稳定性基线。 出口:版本可重放,设备健康,旧模型行为可对比。当前可做,C0/C2。 ### Phase 1:单芯粒单模型 输入:厂商提供一个单芯粒小模型 calbin。 工作: 1. 静态校验 `SCHIP`、mask、ELF、command、参数、KV、地址和 hash。 2. chip0 configure;chip1 保持空闲。 3. 跑 prefill/decode golden、1/16/512/4K prompt 边界和 32/128 token 输出。 4. 用 trace 证明只在 chip0 执行,无跨芯粒 D2D。 5. 重复 chip1 版本,验证对称性。 出口:两颗芯粒分别能独立完成同一模型的 prefill+decode。C3 -> 实测后 C0。 ### Phase 2:同一模型复制到两芯粒 输入:组合 calbin 或两个正式 chip session。 工作: 1. A0 在 chip0、A1 在 chip1,tokenizer和权重版本相同。 2. 先串行 golden,再同时提交不同 prompt。 3. 交叉使用不同 sequence id、不同长度和不同取消时机。 4. 比较两侧输出和 reference,检查 KV、buffer、job completion 无串扰。 5. 用 trace/时间线证明执行重叠。 出口:先证明资源隔离,不把不同模型算子覆盖混入问题。 ### Phase 3:两个不同模型 输入:模型 A/B 均已单独通过 Phase 1。 工作: 1. A/B 各自 tokenizer、sampling config、context、KV 和 buffer pool。 2. 跑四种 prefill/decode 干扰组合。 3. open-loop 分别压 A、B 和联合压测,验证各自 backpressure。 4. A 过载、取消、非法请求时检查 B 的成功率和 P99。 5. 分别验证 load/warmup/ready;组合 calbin 则验证整包生命周期。 出口:S1 服务并存与 S2 硬件并发都通过。 ### Phase 4:故障隔离 输入:厂商书面 reset/error 契约。 工作: 1. 注入 A 的 invalid tensor、KV exhausted、submit busy、Wait timeout、CCU exception。 2. 注入 A 的 drain deadline 和 unload/reload。 3. 检查 B 是否继续生成、延迟是否在门槛内、KV 是否不变。 4. 分别测试 session/chip/config/device reset。 5. 若任何故障实际影响全板,将产品能力降级为“共享故障域”,并按 A/B 同时摘流设计。 出口:故障域有实测结论,不以 API 名称猜测隔离。 ### Phase 5:生产化和 24h soak 工作: 1. 补齐 Router、鉴权、限流、metrics、trace、审计和 health。 2. 混合流量 1h、8h、24h;记录功耗/温度/频率和内存回落。 3. 100 次 load/unload;组合 calbin 跑 100 次整包切换。 4. 灰度、自动降级、旧 generation 回滚和冷启动测试。 出口:满足第 13 节门槛后才对外声明双模型并发。 ## 13. 验收矩阵和建议门槛 下面是第一版工程门槛;业务 SLA 更严格时取更严格值。 ### 13.1 正确性 | 项目 | 通过条件 | | --- | --- | | 单模型 golden | 厂商定义的 logits tolerance/top-k/token 序列全部通过 | | A/B 并发 | 至少 1000 请求无跨模型 token、logits、KV、sequence id 串扰 | | tokenizer | alias 与 tokenizer/hash 一一匹配,错配启动失败 | | context 边界 | 0、max、max+1 在提交前正确处理,无越界提交 | | cancel | 取消只停止结果交付;在途 buffer 等待完成后回收 | | generation | reload 后旧 completion 不得交付给新 generation | ### 13.2 并发和性能 定义: ```text T_serial = T(A isolated workload) + T(B isolated workload) T_joint = wall time when A and B workloads run together overlap_ratio = 1 - T_joint / T_serial throughput_efficiency = joint_successful_tokens_per_s / (A_isolated_tokens_per_s + B_isolated_tokens_per_s) ``` 建议通过条件: - per-chip trace 显示 A/B compute 区间真实重叠。 - `overlap_ratio > 0` 且置信区间不跨 0;不能只看一次运行。 - 稳态 `throughput_efficiency >= 0.80`。 - 联合负载下每个模型 P99 TTFT/TPOT 相比各自隔离基线退化不超过 25%。 - A 满载时 B 的成功率不低于 99.9%,除非触发明确的板级热/电/故障保护。 若吞吐未达门槛,先区分 compiler、共享 DMA、全局 Runtime 锁、功耗降频和服务调度,不能直接归因于芯粒算力。 ### 13.3 生命周期和稳定性 - 100 次 load/unload 或整包 reconfigure 无泄漏、无 `AlreadyConfigured`、无旧 generation 完成。 - 24h 混合 soak 无 crash、hang、错误 token 或未知状态。 - 空闲恢复后设备内存回到基线;稳定漂移小于 `max(64 MiB, 1% local DRAM)`。 - pending/finished/left job counters 最终闭合。 - timeout 和 reset 后必须重新 golden+warmup 才能 Ready。 - A 队列/KV 满不使 B 的 queue depth、admission 或错误率同步恶化。 ### 13.4 故障隔离等级 | 等级 | 能力 | | --- | --- | | F0 | 任一 Runtime 故障都需要整板恢复;A/B 同时摘流 | | F1 | 请求和资源耗尽隔离,但 CCU/timeout/reset 共享故障域 | | F2 | session/chip reset、unload 和 KV 污染均不影响另一模型 | 组合 calbin 很可能先达到 F1;只有逐项故障注入通过后才能声明 F2。 ## 14. 指标和日志 每条请求: ```text request_id, model_alias, model_generation, chip_id(internal only), prompt_tokens, output_tokens, queue_ms, ttft_ms, tpot_ms, e2e_ms, finish_reason, error_class ``` 每个 job: ```text job_id, session_id, model_generation, graph(prefill|decode), engine(auto|ping|pong), submit_ns, done_ns, h2d_ms, infer_ms, d2h_ms, output_status, kv_before, kv_after, reset_generation ``` 每颗芯粒: ```text memory_used/total, queue_depth, in_flight, completed, failed, temperature, power, frequency, throttle, reset_count ``` 取不到的 per-chip 指标写 `null` 并列入厂商缺口,不能用 0 或整板值冒充。`request_id/job_id` 进入 log/trace,不作为 Prometheus 高基数 label。 ## 15. 厂商必须交付的 P0 清单 ### 15.1 compiler/calbin 1. 一个 1B-3B dense 模型的 chip0 单芯粒 calbin,含 prefill/decode、参数、KV、workspace、golden 和 tokenizer。 2. 同一模型的 chip1 对称版本。 3. 同模型双副本组合 calbin,用于先验证隔离。 4. 两个不同模型 A/B 的组合 calbin。 5. 每个包的 ONNX/hash、量化配置、compiler 容器/commit、完整命令和 compatibility matrix。 6. 正式 manifest schema,明确 `SCHIP/tarChipN/chipMask/calc_mchip` 语义。 7. 静态/峰值内存报告,分别列权重、KV、workspace、code、command、I/O 和 guard。 8. 证明单芯粒图无跨芯粒 D2D/gather/reduce 的 compiler report。 ### 15.2 Runtime/driver 1. `CalbinModel` 的 chip target 如何驱动 configure 和 SubmitJob。 2. 一个 calbin 内两个 SCHIP 模型能否同时执行,以及官方最小 runner。 3. per-chip queue、job id、completion、DMA 和 sync unit 是否隔离。 4. 两线程同时向不同芯粒 Submit/Wait 的线程安全保证。 5. per-chip DRAM/SRAM 的真实容量、预留和查询 API。 6. KV manager 的 model/session namespace 和失败回滚语义。 7. session/chip/config/device reset 的精确影响范围。 8. 独立 load/unload 是否支持;若不支持,组合 calbin 整包生命周期的正式流程。 9. per-chip trace/counter/temperature/power/frequency API 和时钟域。 10. Runtime/driver/firmware/compiler/calbin 的版本兼容矩阵。 ### 15.3 验收材料 - 单模型孤立和双模型联合性能原始数据。 - A/B 并发时间线,能看到 chip0/chip1 overlap。 - 错误注入与 reset 影响范围报告。 - 24h soak 报告和内存回落数据。 - 已知限制:最大并发、context、batch、不可抢占、共享功耗/带宽和恢复限制。 只给两个单芯粒目录、没有组合 runner 和 reset/queue 契约,不足以开始产品接入。 ## 16. 我方代码拆分 厂商 P0 产物到位后建议按以下 PR 推进: | PR | 内容 | 依赖 | 退出条件 | | ---: | --- | --- | --- | | 1 | exact model manifest 和静态验证 | 单芯粒/组合样包 | 坏 mask/hash/shape 启动前拒绝 | | 2 | DeviceSupervisor + generation 生命周期 | Runtime 契约 | 单 configure、可 drain/recover | | 3 | ModelWorker 抽象,拆 tokenizer/KV/buffer/queue | PR2 | A/B 可独立单测 | | 4 | Router 按 alias 路由和 per-model admission | PR3 | A 过载不阻塞 B | | 5 | JobContext、独占 buffer、异步 completion | PR3 | in-flight=1 无串扰 | | 6 | 组合 calbin 双 worker 提交 | 厂商 runner | 同模型双副本并发通过 | | 7 | 不同模型 A/B 和 metrics/trace | PR6 | S1/S2 验收通过 | | 8 | 故障注入、灰度、回滚、24h soak | reset 契约 | F0/F1/F2 等级落定 | 在厂商产物和 API 到位后,我方纯软件首个 PoC 约需 5-8 个工程日,完整故障/性能/24h 验收再需 5-7 个工程日。厂商 compiler/Runtime 新能力的交付周期目前无法从本地资料可靠估算,应让对方分别给组合 calbin 和 chip session 两条排期。 ## 17. Go/No-Go 决策门 | 决策点 | Go 条件 | No-Go/降级动作 | | --- | --- | --- | | G0 单芯粒容量 | 静态预算和 load 峰值均留足 guard | 缩模型/context/batch,禁止硬塞 | | G1 单芯粒正确性 | chip0/chip1 分别 golden 通过 | 回到 compiler/kernel 调试 | | G2 双副本隔离 | A0/A1 无 buffer/KV/job 串扰 | 不进入不同模型阶段 | | G3 S2 并发 | trace overlap + 联合性能达标 | 只声明 S1,调查全局锁/共享瓶颈 | | G4 故障域 | 至少 F1,恢复流程确定 | 两 alias 绑定同一健康状态并同时摘流 | | G5 生产 | 24h、100 次生命周期、回滚通过 | 保留单模型或两板方案 | ## 18. 最终建议 1. 把需求名称改为“**双芯粒双模型独立服务**”,不要写“每个 CalCore 一个模型”。 2. 立即向厂商索取“同一小模型双副本组合 calbin + 官方并发 runner”;这是最少变量、最快验证硬件隔离的路径。 3. 第一轮用 1B-3B dense、B1、4K context;先 chip0/chip1 单独跑,再双副本,最后不同模型。 4. 产品架构从一开始按两个 ModelWorker 设计,但板卡只由一个 DeviceSupervisor 管理。 5. 若厂商确认 0.7.6 组合 calbin 能独立 dispatch,则先以它达到 S2/F1;独立 load/unload 和 F2 继续要求 chip session API。 6. 如果厂商不能提供组合 calbin或芯粒级 queue/dispatch,当前单板方案 No-Go;业务上改用两张板/两台主机,每板一个模型。 ## 19. 证据入口 - [研究导航](./Calculet-NPU-研究导航-20260802.md) - [模型产物与内存映射](./Calculet-NPU-模型产物与内存映射-20260802.md) - [多芯粒、多模型、动态加载与并发演进设计](./Calculet-NPU-多芯粒多模型动态加载与并发演进设计-20260802.md) - [Batch、KV 与多模型调度实现规格](./Calculet-NPU-Batch-KV与多模型调度实现规格-20260802.md) - [Runtime API 精确契约与错误恢复手册](./Calculet-NPU-Runtime-API精确契约与错误恢复手册-20260802.md) - [calbin 产物字段字典与静态验收规范](./Calculet-NPU-calbin产物字段字典与静态验收规范-20260802.md) - [新模型适配工程 Runbook](./Calculet-NPU-新模型适配工程Runbook-20260802.md) - [性能压测、故障注入与验收规范](./Calculet-NPU-性能压测故障注入与验收规范-20260802.md) 本文只使用已下载到本地的 SDK、产物、源码和环境记录;本轮没有连接或修改远端测试样机。 ## 20. 完整 Git 历史带来的新增约束 完整 CalRT/llama.cpp 历史没有证明当前 Runtime 已有芯粒级独立 session,但补强了三个实施判断: 1. CalRT 历史中存在 async/serial、job engine thread-safe、重配其他 calbin、多芯片 configure 和 reset 演进,因此路径 A/B 具备可研究的底层基础,不是从零开始。 2. 同一历史也记录过 async 空指针、多线程同步、job id 和 release/reset 修复,因此所有并发能力必须在独立 runner 中按 commit 风险回归,不能仅靠 API 签名判定可用。 3. 当前可读驱动源码是 0.9.0,而运行模块是 1.0.0;在取得上游 driver mirror 和 reset/DMA/lease 差异前,不能把改驱动当作短期生产路径。 llama.cpp `1bec0db` 的“2 calbins”只是 benchmark 顺序加载;`origin/runtime_replace` 只有一个 EngineCore 和一个 `calbin_path`;两者均不构成 S1/S2 或 F1/F2 证据。三个过期 stash和 `calrt: add slice` 不可达实验已经恢复,可用于重建 buffer slice、计时和版本输出实验,但必须在当前基线上重新验收。 新增 P0 厂商交付项:驱动 1.0.0 的完整 Git mirror、0.9.0->1.0.0 变更、PR/Issue/CI、Runtime/firmware 兼容矩阵,以及 `cal-llm` SDK/ABI。详见《源码、Git 历史与驱动证据分析》。