383 lines
18 KiB
Markdown
383 lines
18 KiB
Markdown
# 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 返回 success:buffer、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 的正式删除或版本归属,避免后续工程误用。
|