Files
calculet-npu-research-archive/reports/Calculet-NPU-双芯粒双模型独立服务可行性与实施规格-20260802.md
T

860 lines
36 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`、一张 PCIe 板、两个芯粒、每芯粒两个 CalCore
目标:chip0 常驻模型 A,chip1 常驻模型 B;每个模型的 prefill 和 decode 全部在自己的芯粒完成,两个模型同时对外提供服务
## 0. 执行结论
这个目标需要先澄清“核心”的含义:
| “核心”指什么 | 当前结论 | 原因 | 状态 |
| --- | --- | --- | --- |
| 每芯粒内的一个 CalCore/CCU | 当前不可直接实现 | 完整图会为同一芯粒同时生成 `ccu0``ccu1` 程序;公开 API 没有 CalCore affinity、独立配置、内存配额或 reset domain | C40.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 maskB 只带 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 GiBchip1 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/syncprefill 有 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 和 decodesequence 不跨芯粒迁移。
- 任何提交都带 model/session 身份,不能依赖“当前 calbin”隐式选择。
- 如果 Runtime 只能组合 calbinDeviceSupervisor 负责唯一 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 的两个 CalCoreB 同理。
### 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 maskB 反之。
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. 路径 BRuntime 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 可否使用相同 vaddrIOMMU/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/503B 正常 |
| 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 configurechip1 保持空闲。
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 在 chip1tokenizer和权重版本相同。
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 历史与驱动证据分析》。