860 lines
36 KiB
Markdown
860 lines
36 KiB
Markdown
# 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 历史与驱动证据分析》。
|