Files
calculet-npu-research-archive/reports/Calculet-NPU-Runtime-API精确契约与错误恢复手册-20260802.md

383 lines
18 KiB
Markdown
Raw Permalink 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 Runtime API 精确契约与错误恢复手册
日期:2026-08-02
真实 ABI 基线:归档的 CalRT 0.7.6 headers + `libcalrt-linux-x86_64.so.0.7.6`
## 1. 使用规则
1. 编译和实现以 0.7.6 头文件为准;PDF 用来解释概念,不能覆盖真实签名。
2. 所有 C++ `CalrtError_e` 都必须检查;`void` API 不能假设成功,需通过后续 status/health 验证。
3. `unique_ptr` 拥有 Calbin/VirtualDevice/InputBuf/OutputBuf`CalbinModel*``CalrtTensor*` 是借用指针,不得跨所属对象生命周期。
4. 当前 `VirtualDevice` 只支持一个物理设备;per-chip 参数不代表多卡。
5. reset、任意寄存器/内存读写不属于普通业务 API。
## 2. PDF 与 SDK 关键不兼容
| 主题 | PDF 写法 | 0.7.6 真实写法 | 实现结论 |
| --- | --- | --- | --- |
| 创建解析器 | 第 7 页示例为 `CreateParser`,第 15 页 API 参考为 `CreateCalbin` | `Calbin::CreateCalbin(const std::string &)` | PDF 内部版本漂移;0.7.6 只能使用后者 |
| 全部模型 | 返回 vector | `std::vector<CalbinModel> *GetAllModels()` | 返回借用指针,判空并限制生命周期 |
| 按名找模型 | `GetModelInfo(name)` | `GetModelByName(std::string_view)` | exact name |
| infer 返回值 | 文档示例为 void | C++ 返回 `CalrtError_e` | 必须检查 |
| CSR setter | 第 28 页写作 `void SetCsrByName(...)` | 返回 `CalrtError_e` | 0.7.6 必须检查返回码 |
| VirtualDevice 物理设备数 | 第 29 页泛称 one or multiple | header 注释明确临时只支持一个,成员为单个 device | 多物理板保持 C4/C3 |
| tensor 重定位 | `RelocateTensorAddress` | header 和 SO 无接口 | C4,不得调用 |
| 设备内存分配 | `AllocDevMem` | 无同名公共接口 | C4,不用底层 `CreateBuf` 冒充 |
## 3. C++ 生命周期接口
### 3.1 创建、配置和销毁
真实签名:
```cpp
static std::unique_ptr<calrt::VirtualDevice>
calrt::VirtualDevice::CreateVDevice(calrt::CalrtDeviceType_e type);
static std::unique_ptr<calrt::Calbin>
calrt::Calbin::CreateCalbin(const std::string &path);
calrt::CalrtError_e
calrt::configure(calrt::VirtualDevice *vdev, calrt::Calbin *calbin,
bool dumpIni = false);
```
调用契约:
| 输入 | 前置条件 | 输出/后置条件 | 失败处理 |
| --- | --- | --- | --- |
| `type=PCIE` | driver/设备存在 | 返回非空 vdev,随后 `Status()==Success` | 判空;非 success 停止启动 |
| calbin path | 受信目录、目录完整、hash 已验证 | 返回 parser,内部模型借用指针有效 | 捕获异常/判空;不得 configure |
| configure | vdev/calbin 非空,设备未被其他 generation 使用 | 参数/命令/内存 reservation 部署完成 | 按错误分类回滚;不接流 |
| `dumpIni` | 仅诊断环境 | 可能写出地址/路径 | 生产默认 false,输出目录受控 |
销毁顺序必须是:停止 admission -> 等待所有 in-flight -> 销毁 buffer/KV manager -> reset configuration(仅按厂商契约)-> 销毁 Calbin -> Shutdown/Release vdev。不能先销毁 vdev 再让 OutputBuf 析构。
### 3.2 最小启动代码
```cpp
StatusOr<RuntimeObjects> open_runtime(const std::string &calbin_dir) {
auto vdev = calrt::VirtualDevice::CreateVDevice(calrt::PCIE);
if (!vdev) return Status::Unavailable("CreateVDevice returned null");
CAL_RETURN_IF_ERROR(check_rc(vdev->Status(), "vdev.Status"));
auto calbin = calrt::Calbin::CreateCalbin(calbin_dir);
if (!calbin) return Status::InvalidArtifact("CreateCalbin returned null");
CAL_RETURN_IF_ERROR(validate_calbin(*calbin));
CAL_RETURN_IF_ERROR(check_rc(calrt::configure(vdev.get(), calbin.get(), false),
"configure"));
CAL_RETURN_IF_ERROR(check_rc(vdev->Status(), "vdev.Status.after_configure"));
return RuntimeObjects{std::move(vdev), std::move(calbin)};
}
```
`CAL_RETURN_IF_ERROR` 是我方包装,不是 SDK API。
## 4. Calbin 查询精确契约
真实接口:
```cpp
CalrtCalbin &GetCalbinBrief();
CalbinModel *GetModelByName(std::string_view modelName);
std::vector<CalbinModel> *GetAllModels();
std::vector<CalbinModel *> GetModelByType(std::string_view type);
std::vector<Parsed_Elf_s> &GetPairedElf(const std::string &, const std::vector<uint32_t> &);
CalbinModel &GetGlobalMemInfo();
const CalbinLLM_s &GetLLMInfo() const;
void Report();
CalbinSectionPlace_e GetStackLoc(const std::string &modelName);
const std::string &Version() const;
uint64_t GetModelWorloadByName(const std::string &modelName);
std::vector<uint8_t> GetGoldenInputByModelName(const std::string &modelName);
std::vector<uint8_t> GetGoldenOutputByModelName(const std::string &modelName);
```
字段/生命周期规则:
- `GetAllModels()` 返回的 vector 由 Calbin 所有,禁止 delete,禁止在 Calbin 销毁后使用。
- `GetModelByName()` 返回借用指针;必须 exact match,null 是正常失败结果。
- `GetModelByType("prefill"/"decode"/"kv_update")` 只能用于发现,metadata 已知存在错误时不能取代 manifest。
- `GetLLMInfo()` 引用只在 Calbin 生命周期内有效;启动时复制必要的标量到 immutable generation。
- `GetGolden*()` 返回值由调用方拥有;空 vector 既可能是“无 golden”也可能是空数据,需结合产物 manifest 判定。
- `GetModelWorloadByName` 的拼写和单位需厂商确认,不能直接作为计费/容量口径。
启动校验至少断言:
```text
models != nullptr
expected exact model count and names
llm.max_batch_size == service expected batch
llm.max_seq_len == manifest max_seq
each required input/output/CSR exists
calbin Version is compatible with runtime/driver/firmware
golden hash agrees with signed manifest, when golden is required
```
## 5. Buffer 和 tensor 精确契约
### 5.1 创建
```cpp
std::unique_ptr<calrt::CalrtInputBuf> calrt::createInputBuf(const CalbinModel &model);
std::unique_ptr<calrt::CalrtOutputBuf> calrt::createOutputBuf(const CalbinModel &model);
```
创建后检查非空、`Name()``GetTensorNum()` 和 required tensor 集。对象不可复制;OutputBuf 也不可 move,所以 pool 应保存 `unique_ptr<BufferSlot>`,不把 OutputBuf 作为值移动。
### 5.2 tensor 获取与映射
```cpp
CalrtTensor *CalrtInputBuf::GetTensorByName(const std::string &);
CalrtTensor *CalrtOutputBuf::GetTensorByName(const std::string &);
CalrtError_e CalrtInputBuf::SliceTensorByName(void *src, const std::string &, uint64_t offset, uint64_t size);
CalrtError_e CalrtOutputBuf::SliceTensorByName(void *src, const std::string &, uint64_t offset, uint64_t size);
CalrtError_e ResetTensorByName(const std::string &);
void ResetAllTensors();
```
生产代码当前也直接使用 `CalrtTensor::MapBuf()``SliceTensor()`。安全包装必须保存:原始 ByteSize、当前 slice offset/size、host pointer、映射 generation。任何 slice 需满足:
```text
offset <= byte_size
size <= byte_size - offset
size > 0
host backing capacity >= size
offset and size satisfy Runtime alignment requirement (厂商确认)
```
归还 buffer pool 前调用 `ResetAllTensors()`;若使用 tensor 级 `SliceTensor()`,同时调用匹配的 `UndoSlice()`。两套 slice API 不混用,除非 runner 证明 reset 能完全恢复。
### 5.3 CSR
```cpp
uint32_t ModelHyperParameters_s::GetCsrValueByName(const std::string &csrName);
CalrtError_e ModelHyperParameters_s::SetCsrByName(const std::string &csrName,
uint32_t value);
```
当前图两个 CSR
| 名称 | 当前代码值 | 业务含义 | 检查 |
| --- | --- | --- | --- |
| `cur_seq_len[0]` | 当前 ubatch token 数 | 本次有效 token 数 | `1..max_seq` |
| `past_kv_cur_seq_len[0]` | `past_len + cur_seq_len` | 推理后逻辑总长度 | `<=max_seq` |
`GetCsrValueByName()` 用 0 表示错误,因此无法区分合法 0 和失败;只用于测试回读,不能作为错误判定主路径。setter 返回值才是主契约。
## 6. 推理和完成精确契约
真实接口:
```cpp
CalrtError_e calrt::infer(VirtualDevice *, CalbinModel *,
CalrtInputBuf *, CalrtOutputBuf *);
CalrtError_e calrt::infer_with_fixed_task_type(
VirtualDevice *, CalbinModel *, CalrtInputBuf *, CalrtOutputBuf *, TaskType_e);
CalrtError_e CalrtOutputBuf::Wait();
CalrtOutputBufStatus_e CalrtOutputBuf::GetStatus();
float CalrtInputBuf::GetInputTransferTime();
float CalrtOutputBuf::GetWaitTime();
float CalrtOutputBuf::GetOutputTransferTime();
```
状态枚举:`DEFAULT -> PENDING -> RUNNING -> DONE`,异常终态为 `DONE_CCU_EXCEPTION`。头文件只说明语义,未保证每次轮询都观察到中间状态;产品不能依赖一定出现 PENDING/RUNNING。
单次调用规则:
1. infer 前 OutputBuf 必须属于当前 job 且不是未完成的旧 job。
2. infer 返回非 success:不得 Wait 这个“新 job”,不得交付输出;是否可复用 buffer 由错误类别决定。
3. infer 返回 successbuffer、model、vdev、host memory 在 Wait 终止前全部保持有效。
4. Wait success 后仍检查 `GetStatus()!=DONE_CCU_EXCEPTION`
5. 只有完成 D2H、shape/size/数值检查后才交付结果和提交 KV。
6. `GetWaitTime()` 是 SDK 计时,不等同端到端 infer;单位虽注释/当前使用为 ms,仍应由 runner 校准。
## 7. PING/PONG 和 parallel mode
真实入口:
```cpp
void VirtualDevice::EnableParallelMode(bool switch_on);
void VirtualDevice::SubmitJob(CalbinModel *, CalrtInputBuf *, CalrtOutputBuf *,
TaskType_e forceEngineMode = UNDEFINED_TASK);
```
头文件注释只给出 `0=ping, 1=pong, -1=auto`。它没有给出:
- 最大 in-flight
- 是否线程安全;
- 同一 OutputBuf 能否连续提交;
- H2D/compute/D2H 的重叠范围;
- 两个 engine 的顺序、公平性和异常隔离;
- parallel mode 开关时对在途任务的影响。
因此现状为 C1。隔离 runner 必须用两套完全独立 buffer/backing storage,强制 Ping/Pong,覆盖四种序列:P-P、Q-Q、P-Q、Q-P(P/Q 代表不同输入/seq),验证输出 hash、KV、状态和队列计数。未通过前产品使用普通 `infer()` 且 in-flight=1。
## 8. VirtualDevice/Device 的可用面
| 接口 | 等级 | 产品用途 | 禁止事项 |
| --- | --- | --- | --- |
| `Status()` | C0 | 启动/健康检查 | 不能替代实际 golden |
| `GetDeviceInfo(idx)` | C1 | 净化后版本信息 | idx 不证明多物理设备 |
| `EnableTraceDevice(bool)` | C1 | 受控性能实验 | 不永久开启;限制磁盘 |
| `ReadMem/WriteMem(...,chipId)` | C4 特权 | 厂商/故障诊断 | 不给普通 API 任意地址 |
| `ResetCCU()` | C4 特权 | 明确 CCU 故障恢复 | 不在普通请求失败时调用 |
| `ResetConfiguration()` | C4 特权 | drain 后卸载 generation | 未知影响范围前不自动化 |
| `Reset()` | C4 特权 | 最后级设备恢复 | 全审计、限频、防 reset storm |
| `Release()/Shutdown()` | C1 | 有界退出 | 不在 in-flight 时直接释放 |
0.7.6 header 明确写着临时仅支持一个物理设备,成员也是单个 `shared_ptr<CalrtDevice>``GetDeviceInfo(size_t idx)` 不得作为多卡枚举 API 使用。
## 9. KV Manager 精确契约
```cpp
bool canAllocate(size_t seq_num);
void Allocate(KvSeqInfo &seq_info);
void Free(KvSeqId seq_id);
void RemoveTokensAtEnd(KvSeqId seq_id, size_t n_tokens);
bool isExist(KvSeqId seq_id);
bool Apply(KvSeqInfo &seq_info);
void DoShift(KvBatchMode_e mode, std::vector<CalrtSeqShift> &seqs);
KvPos GetSeqLen(KvSeqId seq_id) const;
void Clear();
KvPos seqPosMin(KvSeqId seq_id) const;
KvPos seqPosMax(KvSeqId seq_id) const;
```
已知限制:
- `Allocate/Free/Remove/DoShift/Clear` 返回 void,失败传播语义不清晰。
- `Apply` 只有 bool,不提供错误码。
- `KvPos/KvSeqId` 为 int32,服务入口需限制范围。
- batch>1 的 K/V 布局不同;header 的 layout 说明不能替代 batch calbin。
- `KvBatchMode::ALL` 注释指向 16 lanes 和阈值建议,但不等于任意 B4/B8/B16 已可用。
- S8 明确只在字段说明中提到 V-cache 支持,具体 K/V 压缩范围和误差仍需厂商确认。
适配层最小支持表:
| llama KV 语义 | 当前实现 | 对外策略 |
| --- | --- | --- |
| clear | 委托 `Clear()` | C0,补恢复测试 |
| remove tail (`p1=-1`) | `RemoveTokensAtEnd` | C0 部分能力,补边界测试 |
| arbitrary remove | 返回 false | 明确不支持 |
| copy sequence | 空实现 | 禁用 prompt/KV copy |
| keep only sequence | 空实现 | 禁用 |
| add/div positions | 空实现 | 禁用 context shift 相关路径 |
| state save/load | 空实现 | 禁止宣称 session state 支持 |
| min/max | 委托 Runtime | C1,覆盖不存在/空/满场景 |
## 10. C API 精确签名和使用场景
C API 提供 `create_calbin/create_device/configure_device/create_*_buffer`、tensor 查询/复制、`block_infer_model/non_block_infer_model/wait_infer_done` 和 release。关键差异是两个 infer 提交函数返回 `void`
因此:
- 新 C++ 产品路径继续优先使用 C++ API,以获得 submit 错误码。
- C API 只适合兼容/ABI runner,必须在每步检查可检查的返回值并在 infer 后检查 wait/status/health。
- `set_csr_by_name` 也是 void,无法替代 C++ CSR setter 的错误处理。
- `cal_copy_mem` 需要调用方保证 tensor 方向;优先使用按 tensor name 的安全复制接口。
- 所有 `release_*` 只接收 handle,没有幂等保证;包装层释放后立即清空自己的 handle。
## 11. 全部错误码与动作
| 值 | 名称 | 可重试 | 污染范围默认值 | 推荐动作 |
| --- | --- | --- | --- | --- |
| 0 | Success | - | 无 | 继续 |
| 1 | MemoryAllocation | 条件性 | job/generation | 降低 admission;持续则摘模型 |
| 2 | MemoryAlreadyRegistered | 否 | generation | 生命周期缺陷,drain |
| 3 | Assert | 否 | 未知 | 停止提交,保留证据 |
| 4 | InvalidValue | 否 | job | 拒绝输入/修代码 |
| 5 | InvalidConfiguration | 否 | generation | 回滚产物 |
| 6 | NoDevice | 条件性 | device | 503,等待设备/重启实例 |
| 7 | AlreadyConfigured | 否 | generation | 串行化 load/unload |
| 8 | IncompatibleDriver | 否 | instance | 升级 driver |
| 9 | RequireNewerRT | 否 | instance | 升级 Runtime |
| 10 | FileNotFound | 否 | artifact | 修复包/路径 |
| 11 | BrokenFile | 否 | artifact | hash/完整性失败 |
| 12 | InvalidELF | 否 | artifact | 编译器/兼容矩阵 |
| 13 | Timeout | 条件性 | submitted job/可能 generation | 停止 admission,按恢复流程 |
| 14 | OutOfRange | 否 | job | 本地边界应先拦截 |
| 15 | NotExpected | 否 | 未知 | 保守 drain |
| 16 | MissConfiguration | 否 | generation | 重新 configure/warmup |
| 17 | DeviceBusy | 是,有界 | job/queue | backoff,不立刻 reset |
| 18 | DeviceUnavailable | 条件性 | device | 摘流、恢复 |
| 19 | InvalidCalbin | 否 | artifact | 回滚旧 generation |
| 20 | Unknown | 否 | 未知 | 保守 drain,厂商分析 |
| 21 | InvalidDataType | 否 | artifact/job | manifest mismatch |
| 22 | InvalidDataShape | 否 | artifact/job | manifest mismatch |
| 23 | InvalidLibDep | 否 | instance/artifact | 修复依赖 |
| 24 | DeviceMapBroken | 否 | device | reset/restart |
| 25 | FailedSoftReset | 否 | device | 进程/设备级恢复 |
| 26 | FailedConnectServer | 条件性 | instance | 外部服务/EMU 路径诊断 |
| 27 | DeviceUnsupport | 否 | feature | 关闭特性 |
| 28 | DeviceCrash | 否 | device/generation | CCU fault 恢复 |
| 29 | WrongDirection | 否 | job/programming | 修正 copy 方向 |
“污染范围默认值”是我方保守策略,不是厂商保证。拿到官方 rollback/reset 语义后才可缩小。
## 12. 对外服务 API
### 12.1 普通推理面
```text
POST /v1/inference
request_id, model_alias, prompt/tokens, max_new_tokens,
sampling parameters, deadline_ms, stream
DELETE /v1/inference/{request_id}
只请求取消,不承诺立即停止硬件
GET /v1/models
alias, generation, context_limit, ready state
```
不得返回 calbin 路径、tensor 地址、寄存器、chip raw memory、内部子图名或未净化的 Runtime exception。
### 12.2 观测面
```text
GET /internal/health
GET /internal/runtime/version
GET /internal/models/{alias}/manifest
GET /metrics
POST /internal/trace/start (鉴权、时长和大小限制)
POST /internal/trace/stop
```
manifest 输出 hash、batch/context、dtype/shape、generation 和兼容版本,不输出绝对源路径/地址。
### 12.3 特权控制面
reset、configure、unload、原始内存/寄存器访问放在独立管理进程或 Unix socket;mTLS/RBAC、双人审批(生产环境)、完整审计、速率限制。默认不提供任意 `ReadMem/WriteMem` HTTP 代理。
## 13. API runner 测试矩阵
| 组 | 用例 | 通过条件 |
| --- | --- | --- |
| 生命周期 | 正常 load/configure/release 100 次 | 无泄漏、无 AlreadyConfigured |
| calbin | 缺文件、坏 ELF、错版本、错 hash | 在接流前失败,错误分类正确 |
| tensor | 缺名、错 dtype/rank/shape、零 size、越界 slice | 无设备提交、无越界 |
| CSR | 正常边界、未知名、总长溢出 | setter/本地校验可见失败 |
| infer | normal、submit busy/unavailable、Wait timeout/crash | job 状态和恢复符合表格 |
| status | 快速 DONE、PENDING->RUNNING->DONE、CCU exception | 不依赖必现中间态,异常不交付 |
| parallel | Auto/Ping/Pong 两任务排列 | 无串扰;未证明时不启用产品 |
| KV | allocate/apply/free/remove/clear、容量耗尽 | 无忙等,长度/资源可回收 |
| reset | CCU/config/device 分级恢复 | 厂商契约一致、warmup 后才 Ready |
| 安全 | 未授权 reset/read/write/trace | 403/拒绝且有审计 |
## 14. 厂商必须补齐的接口契约
1. 0.7.6 对应的正式 API 文档和完整 error-to-recovery 表。
2. `Wait()` timeout 行为、是否可取消、底层 DMA 完成判定。
3. OutputBuf status 原子性、状态转换和复用前置条件。
4. parallel/PING/PONG 时序、最大 in-flight、线程安全与关闭语义。
5. KV Apply 的 bool 失败原因和 submit/Wait 失败后的 KV 一致性。
6. 三种 reset 对在途任务、KV、参数、另一芯粒和 host mapping 的精确影响。
7. trace/counter 的 schema、单位、时钟域、开销和清零规则。
8. PDF-only API 的正式删除或版本归属,避免后续工程误用。