已合并
【PR】: 编程指南增加了context管理和兼容性处理章节 #2262
tingwood创建于 5月20日
【PR】: 编程指南增加了context管理和兼容性处理章节 #2262
已合并
共 8 个文件变更+556-5
| @@ -15,8 +15,10 @@ | |||
| 15 | - ### [4.2 跨流捕获](04-02_跨流捕获.md) | 15 | - ### [4.2 跨流捕获](04-02_跨流捕获.md) |
| 16 | - ### [4.3 任务更新](04-03_任务更新.md) | 16 | - ### [4.3 任务更新](04-03_任务更新.md) |
| 17 | - ## [5. 多设备编程](05_多设备编程.md) | 17 | - ## [5. 多设备编程](05_多设备编程.md) |
| 18 | -- ## [6. 进程间通信](06_进程间通信.md) | 18 | +- ## [6. Context管理](06_Context管理.md) |
| 19 | -- ## [7. 运行时核资源控制](07_运行时核资源控制.md) | 19 | +- ## [7. 进程间通信](07_进程间通信.md) |
| 20 | -- ## [8. 配置AI Core栈空间大小](08_配置AI-Core栈空间大小.md) | 20 | +- ## [8. 运行时核资源控制](08_运行时核资源控制.md) |
| 21 | -- ## [9. 异常处理](09_异常处理.md) | 21 | +- ## [9. 配置AI Core栈空间大小](09_配置AI-Core栈空间大小.md) |
| 22 | +- ## [10. 异常处理](10_异常处理.md) | ||
| 23 | +- ## [11. 兼容性处理](11_兼容性处理.md) | ||
| 22 | 24 | ||
| @@ -97,7 +97,7 @@ myKernel<<<8, nullptr, s0>>>(); // 在Device 1上通过Stream s0下发算子 | |||
| 97 | 97 | ||
| 98 | ## 跨Device的数据交互 | 98 | ## 跨Device的数据交互 |
| 99 | 99 | ||
| 100 | -本节中的“跨Device的数据交互”是指一个进程内、根据硬件组网(例如处于PCIe或者HCCS互联的组网拓扑下)、Device之间能够访问彼此的内存。可以使用aclrtDeviceCanAccessPeer接口查询两个Device之间是否支持数据交互,若支持,再根据访问方向,分别调用aclrtDeviceEnablePeerAccess接口开启一个Device到另一个Device的数据交互功能,例如,调用一次aclrtDeviceEnablePeerAccess接口开启Device 0到Device 1的数据交互,再调用一次aclrtDeviceEnablePeerAccess接口开启Device 1到Device 0的数据交互。若需关闭Device之间的数据交互,可调用aclrtDeviceDisablePeerAccess接口。对于两个进程之间的通信请参见[进程间通信](06_进程间通信.md)。 | 100 | +本节中的“跨Device的数据交互”是指一个进程内、根据硬件组网(例如处于PCIe或者HCCS互联的组网拓扑下)、Device之间能够访问彼此的内存。可以使用aclrtDeviceCanAccessPeer接口查询两个Device之间是否支持数据交互,若支持,再根据访问方向,分别调用aclrtDeviceEnablePeerAccess接口开启一个Device到另一个Device的数据交互功能,例如,调用一次aclrtDeviceEnablePeerAccess接口开启Device 0到Device 1的数据交互,再调用一次aclrtDeviceEnablePeerAccess接口开启Device 1到Device 0的数据交互。若需关闭Device之间的数据交互,可调用aclrtDeviceDisablePeerAccess接口。对于两个进程之间的通信请参见[进程间通信](07_进程间通信.md)。 |
| 101 | 101 | ||
| 102 | 以下是跨Device内存复制的代码示例,不可以直接拷贝编译运行,仅供参考。完整样例代码请参见[Link](https://gitcode.com/cann/runtime/tree/master/example/1_basic_features/device/2_device_P2P)。 | 102 | 以下是跨Device内存复制的代码示例,不可以直接拷贝编译运行,仅供参考。完整样例代码请参见[Link](https://gitcode.com/cann/runtime/tree/master/example/1_basic_features/device/2_device_P2P)。 |
| 103 | 103 | ||
| @@ -0,0 +1,316 @@ | |||
| 1 | +# Context管理 | ||
| 2 | + | ||
| 3 | +## Context概念 | ||
| 4 | + | ||
| 5 | +Context是CANN Runtime中的核心抽象,代表一个Device上的执行上下文环境。它封装了Device上的计算资源、内存资源、Stream资源等运行时状态,是Runtime操作的基础载体。 | ||
| 6 | + | ||
| 7 | +每个Context与特定的Device绑定,包含该Device上的: | ||
| 8 | + | ||
| 9 | +- 计算资源:用于执行Kernel、系统任务等 | ||
| 10 | +- 内存资源:设备内存分配与管理 | ||
| 11 | +- Stream资源:任务队列及调度状态 | ||
| 12 | +- 运行时配置:影响任务执行的参数 | ||
| 13 | + | ||
| 14 | +Context与线程绑定,同一时刻一个线程只能使用一个Context。Runtime接口在执行时,会自动使用当前线程绑定的Context。 | ||
| 15 | + | ||
| 16 | +<br> | ||
| 17 | +<br> | ||
| 18 | + | ||
| 19 | +## 为什么要用Context | ||
| 20 | + | ||
| 21 | +### 使用Context的核心场景 | ||
| 22 | + | ||
| 23 | +1. **多线程并行计算**:多个线程并发使用同一Device时,各自创建独立Context,避免资源竞争和状态混乱。 | ||
| 24 | +2. **精细资源控制**:需要更细粒度地控制资源创建、使用、释放时机。 | ||
| 25 | +3. **多线程Context共享**:在同进程多线程场景下,显式创建的Context可在多线程间共享使用。 | ||
| 26 | +4. **模块化程序设计**:不同功能模块使用独立Context,便于资源管理和问题定位。 | ||
| 27 | + | ||
| 28 | +### 显式Context vs 默认Context | ||
| 29 | + | ||
| 30 | +调用`aclrtSetDevice`接口时,Runtime会自动为指定Device创建一个**默认Context**。对于简单应用,使用默认Context即可满足需求。但对于复杂应用,显式创建和管理Context具有以下优势: | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +| 场景 | 默认Context | 显式Context | | ||
| 34 | +| ------------ | ----------------------------------------------- | --------------------------------------- | | ||
| 35 | +| 多线程编程 | 线程间共享默认Context,任务执行顺序依赖线程调度 | 每个线程独立Context,便于隔离和调试 | | ||
| 36 | +| 资源隔离 | 同一Device上的不同模块共享资源 | 不同模块使用独立Context,资源隔离更清晰 | | ||
| 37 | +| 代码可维护性 | 线程切换Device时Context状态不明确 | 显式指定Context,代码意图清晰 | | ||
| 38 | +| 资源生命周期 | 随Device生命周期管理 | 独立控制Context创建和销毁时机 | | ||
| 39 | + | ||
| 40 | +<br> | ||
| 41 | + | ||
| 42 | +## Context与Device、Stream的关系 | ||
| 43 | + | ||
| 44 | +Context与Device、Stream的关系如下图所示: | ||
| 45 | + | ||
| 46 | +``` | ||
| 47 | +┌─────────────────────────────────────────────────────────────┐ | ||
| 48 | +│ Host进程 │ | ||
| 49 | +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ | ||
| 50 | +│ │ Thread 1 │ │ Thread 2 │ │ Thread 3 │ │ | ||
| 51 | +│ │ Context A │ │ Context B │ │ Context A │ │ | ||
| 52 | +│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ | ||
| 53 | +│ │ │ │ │ | ||
| 54 | +└─────────┼─────────────────┼─────────────────┼───────────────┘ | ||
| 55 | + │ │ │ | ||
| 56 | + ▼ ▼ ▼ | ||
| 57 | +┌────────────────────────────────────────────────────────────┐ | ||
| 58 | +│ Device 0 (NPU) │ | ||
| 59 | +│ ┌─────────────────────────────────────────────────────┐ │ | ||
| 60 | +│ │ Context A │ │ | ||
| 61 | +│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ | ||
| 62 | +│ │ │ Stream 0 │ │ Stream 1 │ │ Stream 2 │ ... │ │ | ||
| 63 | +│ │ │ (default)│ │ │ │ │ │ │ | ||
| 64 | +│ │ └──────────┘ └──────────┘ └──────────┘ │ │ | ||
| 65 | +│ └─────────────────────────────────────────────────────┘ │ | ||
| 66 | +│ ┌─────────────────────────────────────────────────────┐ │ | ||
| 67 | +│ │ Context B │ │ | ||
| 68 | +│ │ ┌──────────┐ ┌──────────┐ │ │ | ||
| 69 | +│ │ │ Stream 0 │ │ Stream 1 │ ... │ │ | ||
| 70 | +│ │ │ (default)│ │ │ │ │ | ||
| 71 | +│ │ └──────────┘ └──────────┘ │ │ | ||
| 72 | +│ └─────────────────────────────────────────────────────┘ │ | ||
| 73 | +└────────────────────────────────────────────────────────────┘ | ||
| 74 | +``` | ||
| 75 | + | ||
| 76 | +- **Device**:物理NPU设备,一个Device上可创建多个Context。 | ||
| 77 | +- **Context**:Device上的执行上下文,包含默认Stream和用户创建的Stream。 | ||
| 78 | +- **Stream**:任务队列,归属于特定Context。 | ||
| 79 | + <br> | ||
| 80 | + | ||
| 81 | +## 如何使用Context | ||
| 82 | + | ||
| 83 | +### 单线程基础使用模式(默认Context) | ||
| 84 | + | ||
| 85 | +对于简单应用,使用默认Context即可满足需求。调用`aclrtSetDevice`接口时,Runtime会自动创建默认Context,无需显式管理。 | ||
| 86 | + | ||
| 87 | +以下示例展示单线程使用默认Context的基础流程: | ||
| 88 | + | ||
| 89 | +```c | ||
| 90 | +// 1. 初始化Runtime | ||
| 91 | +aclInit(nullptr); | ||
| 92 | + | ||
| 93 | +// 2. 指定Device,自动创建默认Context和默认Stream | ||
| 94 | +int32_t deviceId = 0; | ||
| 95 | +aclrtSetDevice(deviceId); | ||
| 96 | + | ||
| 97 | +// 3. 创建显式Stream(可选,也可直接使用默认Stream传入nullptr) | ||
| 98 | +aclrtStream stream; | ||
| 99 | +aclrtCreateStream(&stream); | ||
| 100 | + | ||
| 101 | +// 4. 在Stream上下发任务 | ||
| 102 | +aclrtMemcpyAsync(devPtr, size, hostPtr, size, ACL_MEMCPY_HOST_TO_DEVICE, stream); | ||
| 103 | +myKernel<<<8, nullptr, stream>>>(devPtr, size); | ||
| 104 | +aclrtMemcpyAsync(hostPtr, size, devPtr, size, ACL_MEMCPY_DEVICE_TO_HOST, stream); | ||
| 105 | + | ||
| 106 | +// 5. 同步等待任务完成 | ||
| 107 | +aclrtSynchronizeStream(stream); | ||
| 108 | + | ||
| 109 | +// 6. 销毁显式Stream | ||
| 110 | +aclrtDestroyStream(stream); | ||
| 111 | + | ||
| 112 | +// 7. 复位Device,释放默认Context和默认Stream | ||
| 113 | +aclrtResetDeviceForce(deviceId); | ||
| 114 | + | ||
| 115 | +// 8. 去初始化 | ||
| 116 | +aclFinalize(); | ||
| 117 | +``` | ||
| 118 | + | ||
| 119 | +<br> | ||
| 120 | + | ||
| 121 | +### 多线程并行计算 | ||
| 122 | + | ||
| 123 | +多个线程使用同一Device时,推荐为每个线程创建独立Context: | ||
| 124 | + | ||
| 125 | +```c | ||
| 126 | +// 线程函数 | ||
| 127 | +void* threadFunc(void* arg) { | ||
| 128 | + int32_t deviceId = *(int32_t*)arg; | ||
| 129 | + | ||
| 130 | + // 每个线程创建自己的Context | ||
| 131 | + aclrtContext ctx; | ||
| 132 | + aclrtCreateContext(&ctx, deviceId); | ||
| 133 | + | ||
| 134 | + // 创建Stream | ||
| 135 | + aclrtStream stream; | ||
| 136 | + aclrtCreateStream(&stream); | ||
| 137 | + | ||
| 138 | + // 执行任务 | ||
| 139 | + // ... 业务逻辑 ... | ||
| 140 | + | ||
| 141 | + // 同步等待任务完成 | ||
| 142 | + aclrtSynchronizeStream(stream); | ||
| 143 | + | ||
| 144 | + // 销毁资源 | ||
| 145 | + aclrtDestroyStream(stream); | ||
| 146 | + aclrtDestroyContext(ctx); | ||
| 147 | + | ||
| 148 | + return nullptr; | ||
| 149 | +} | ||
| 150 | + | ||
| 151 | +// 主线程 | ||
| 152 | +int main() { | ||
| 153 | + aclInit(nullptr); | ||
| 154 | + | ||
| 155 | + int32_t deviceId = 0; | ||
| 156 | + aclrtSetDevice(deviceId); | ||
| 157 | + | ||
| 158 | + // 创建多个线程 | ||
| 159 | + pthread_t threads[4]; | ||
| 160 | + for (int i = 0; i < 4; i++) { | ||
| 161 | + pthread_create(&threads[i], nullptr, threadFunc, &deviceId); | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + // 等待线程完成 | ||
| 165 | + for (int i = 0; i < 4; i++) { | ||
| 166 | + pthread_join(threads[i], nullptr); | ||
| 167 | + } | ||
| 168 | + | ||
| 169 | + aclrtResetDeviceForce(deviceId); | ||
| 170 | + aclFinalize(); | ||
| 171 | + return 0; | ||
| 172 | +} | ||
| 173 | +``` | ||
| 174 | + | ||
| 175 | +<br> | ||
| 176 | + | ||
| 177 | +### 多线程Context共享 | ||
| 178 | + | ||
| 179 | +多个线程共享同一Context时,需自行保证Stream上任务的执行顺序: | ||
| 180 | + | ||
| 181 | +```c | ||
| 182 | +aclrtContext g_ctx; // 全局Context | ||
| 183 | + | ||
| 184 | +void* threadFunc(void* arg) { | ||
| 185 | + int threadId = *(int*)arg; | ||
| 186 | + | ||
| 187 | + // 切换到共享Context | ||
| 188 | + aclrtSetCurrentContext(g_ctx); | ||
| 189 | + | ||
| 190 | + // 创建线程专属Stream | ||
| 191 | + aclrtStream stream; | ||
| 192 | + aclrtCreateStream(&stream); | ||
| 193 | + | ||
| 194 | + // 在自己的Stream上执行任务 | ||
| 195 | + // ... 业务逻辑 ... | ||
| 196 | + | ||
| 197 | + aclrtSynchronizeStream(stream); | ||
| 198 | + aclrtDestroyStream(stream); | ||
| 199 | + | ||
| 200 | + return nullptr; | ||
| 201 | +} | ||
| 202 | + | ||
| 203 | +int main() { | ||
| 204 | + aclInit(nullptr); | ||
| 205 | + | ||
| 206 | + int32_t deviceId = 0; | ||
| 207 | + aclrtSetDevice(deviceId); | ||
| 208 | + | ||
| 209 | + // 创建一个Context供多线程共享 | ||
| 210 | + aclrtCreateContext(&g_ctx, deviceId); | ||
| 211 | + | ||
| 212 | + pthread_t threads[4]; | ||
| 213 | + int threadIds[4]; | ||
| 214 | + for (int i = 0; i < 4; i++) { | ||
| 215 | + threadIds[i] = i; | ||
| 216 | + pthread_create(&threads[i], nullptr, threadFunc, &threadIds[i]); | ||
| 217 | + } | ||
| 218 | + | ||
| 219 | + for (int i = 0; i < 4; i++) { | ||
| 220 | + pthread_join(threads[i], nullptr); | ||
| 221 | + } | ||
| 222 | + | ||
| 223 | + aclrtDestroyContext(g_ctx); | ||
| 224 | + aclrtResetDeviceForce(deviceId); | ||
| 225 | + aclFinalize(); | ||
| 226 | + return 0; | ||
| 227 | +} | ||
| 228 | +``` | ||
| 229 | + | ||
| 230 | +<br> | ||
| 231 | + | ||
| 232 | +### Context切换 | ||
| 233 | + | ||
| 234 | +使用`aclrtSetCurrentContext`切换当前线程的Context: | ||
| 235 | + | ||
| 236 | +```c | ||
| 237 | +// 在Device 0上创建两个Context | ||
| 238 | +aclrtSetDevice(0); | ||
| 239 | +aclrtContext ctx1, ctx2; | ||
| 240 | +aclrtCreateContext(&ctx1, 0); | ||
| 241 | +aclrtCreateContext(&ctx2, 0); | ||
| 242 | + | ||
| 243 | +// 切换到ctx1 | ||
| 244 | +aclrtSetCurrentContext(ctx1); | ||
| 245 | +aclrtStream stream1; | ||
| 246 | +aclrtCreateStream(&stream1); | ||
| 247 | +// ... 在ctx1上执行任务 ... | ||
| 248 | + | ||
| 249 | +// 切换到ctx2 | ||
| 250 | +aclrtSetCurrentContext(ctx2); | ||
| 251 | +aclrtStream stream2; | ||
| 252 | +aclrtCreateStream(&stream2); | ||
| 253 | +// ... 在ctx2上执行任务 ... | ||
| 254 | + | ||
| 255 | +// 切换Context时,如果新Context属于不同Device,Device也会随之切换 | ||
| 256 | +aclrtSetDevice(1); | ||
| 257 | +aclrtContext ctx3; | ||
| 258 | +aclrtCreateContext(&ctx3, 1); | ||
| 259 | + | ||
| 260 | +aclrtSetCurrentContext(ctx3); // 当前Context切换为ctx3,Device也切换为1 | ||
| 261 | +``` | ||
| 262 | + | ||
| 263 | +<br> | ||
| 264 | + | ||
| 265 | +### Context参数配置 | ||
| 266 | + | ||
| 267 | +使用`aclrtCtxSetSysParamOpt`设置Context级别的参数: | ||
| 268 | + | ||
| 269 | +```c | ||
| 270 | +aclrtContext ctx; | ||
| 271 | +aclrtCreateContext(&ctx, 0); | ||
| 272 | + | ||
| 273 | +// 设置Context参数(示例:设置内存配置) | ||
| 274 | +aclrtCtxSetSysParamOpt(ACL_SYS_PARAM_OPT_XXX, value); | ||
| 275 | + | ||
| 276 | +// 获取Context参数 | ||
| 277 | +int64_t paramValue; | ||
| 278 | +aclrtCtxGetSysParamOpt(ACL_SYS_PARAM_OPT_XXX, ¶mValue); | ||
| 279 | +``` | ||
| 280 | + | ||
| 281 | +<br> | ||
| 282 | + | ||
| 283 | +### 默认Stream获取 | ||
| 284 | + | ||
| 285 | +每个Context包含一个默认Stream,可通过接口获取: | ||
| 286 | + | ||
| 287 | +```c | ||
| 288 | +aclrtContext ctx; | ||
| 289 | +aclrtCreateContext(&ctx, 0); | ||
| 290 | + | ||
| 291 | +// 获取当前Context的默认Stream | ||
| 292 | +aclrtStream defaultStream; | ||
| 293 | +aclrtCtxGetCurrentDefaultStream(&defaultStream); | ||
| 294 | + | ||
| 295 | +// 使用默认Stream(也可直接传nullptr) | ||
| 296 | +aclrtMemcpyAsync(dst, size, src, size, ACL_MEMCPY_DEVICE_TO_DEVICE, nullptr); | ||
| 297 | +``` | ||
| 298 | + | ||
| 299 | +<br> | ||
| 300 | + | ||
| 301 | +## 如何用好Context | ||
| 302 | + | ||
| 303 | +### 推荐做法 | ||
| 304 | + | ||
| 305 | +1. **优先使用显式Context**:对于多线程或复杂应用,显式创建Context,代码意图更清晰。 | ||
| 306 | +2. **Context与线程绑定**:推荐在创建Context的线程中使用该Context,避免跨线程使用带来的复杂性。 | ||
| 307 | +3. **及时销毁资源**:Context不再使用时及时销毁,避免资源泄漏。 | ||
| 308 | +4. **使用aclrtResetDeviceForce**:Device使用完毕后,使用`aclrtResetDeviceForce`一次性释放Device上所有资源。 | ||
| 309 | +5. **明确Context切换时机**:在多Context场景下,显式调用`aclrtSetCurrentContext`切换,增加代码可读性。 | ||
| 310 | + | ||
| 311 | +### 避免的做法 | ||
| 312 | + | ||
| 313 | +1. **不要销毁默认Context**:默认Context由Runtime管理,不能调用`aclrtDestroyContext`销毁。 | ||
| 314 | +2. **避免Context状态不确定**:多线程共享Context时,避免依赖Context的隐式状态切换。 | ||
| 315 | +3. **不要跨线程随意操作同一Stream**:如果必须在多线程间共享Context,每个线程应使用独立的Stream。 | ||
| 316 | +4. **不要在已复位的Device上使用Context**:Device被复位后,相关Context不可用。 | ||
| @@ -0,0 +1,233 @@ | |||
| 1 | +# 兼容性处理 | ||
| 2 | + | ||
| 3 | +CANN Runtime 遵循语义化版本规范,提供版本查询机制和废弃接口处理策略,帮助开发者构建前后向兼容的应用程序。 | ||
| 4 | + | ||
| 5 | +<br> | ||
| 6 | +<br> | ||
| 7 | + | ||
| 8 | +## 1. 版本查询机制 | ||
| 9 | + | ||
| 10 | +Runtime提供多层次的版本查询接口,帮助应用在运行时获取环境信息,实现兼容性适配。 | ||
| 11 | + | ||
| 12 | +### 1.1 Runtime版本查询 | ||
| 13 | + | ||
| 14 | +使用`aclsysGetVersionNum`接口查询Runtime版本号,传入包名`"runtime"`: | ||
| 15 | + | ||
| 16 | +```c | ||
| 17 | +char pkgName[] = "runtime"; | ||
| 18 | +int32_t versionNum; | ||
| 19 | +aclsysGetVersionNum(pkgName, &versionNum); | ||
| 20 | + | ||
| 21 | +printf("Runtime Version Num: %d\n", versionNum); | ||
| 22 | +``` | ||
| 23 | + | ||
| 24 | +**版本号计算规则** | ||
| 25 | + | ||
| 26 | +返回的`versionNum`是一个整数值,遵循语义化版本编码规则,便于版本比较。 | ||
| 27 | + | ||
| 28 | +| 版本部分 | 权重 | 说明 | | ||
| 29 | +| ----------------- | -------- | ------------------ | | ||
| 30 | +| major(主版本号) | 10000000 | 不兼容的API变更 | | ||
| 31 | +| minor(次版本号) | 100000 | 向后兼容的功能新增 | | ||
| 32 | +| patch(修订号) | 1000 | 向后兼容的问题修复 | | ||
| 33 | + | ||
| 34 | +**正式版本计算公式:** | ||
| 35 | + | ||
| 36 | +``` | ||
| 37 | +versionNum = major × 10000000 + minor × 100000 + patch × 1000 | ||
| 38 | +``` | ||
| 39 | + | ||
| 40 | +**计算示例:** | ||
| 41 | + | ||
| 42 | +| 版本字符串 | 计算过程 | versionNum | | ||
| 43 | +| ---------- | --------------------------------- | ---------- | | ||
| 44 | +| 9.0.0 | 9×10000000 + 0×100000 + 0×1000 | 90000000 | | ||
| 45 | +| 8.5.1 | 8×10000000 + 5×100000 + 1×1000 | 80501000 | | ||
| 46 | + | ||
| 47 | +<br> | ||
| 48 | + | ||
| 49 | +### 1.2 Driver版本查询 | ||
| 50 | + | ||
| 51 | +Runtime依赖驱动能力,部分特性功能需要同时判断驱动版本号以做兼容性处理,传入"driver"包名可查询驱动版本号。 | ||
| 52 | + | ||
| 53 | +```c | ||
| 54 | +char pkgName[] = "driver"; // 查询驱动版本,使用"driver" | ||
| 55 | +int32_t versionNum; | ||
| 56 | +aclsysGetVersionNum(pkgName, &versionNum); | ||
| 57 | +printf("Package Version Num: %d\n", versionNum); | ||
| 58 | +``` | ||
| 59 | + | ||
| 60 | +<br> | ||
| 61 | + | ||
| 62 | +### 1.3 运行时特性查询 | ||
| 63 | + | ||
| 64 | +使用`aclrtGetDevFeature`查询设备支持的特性能力,实现功能特性的条件适配: | ||
| 65 | + | ||
| 66 | +```c | ||
| 67 | +int32_t deviceId = 0; | ||
| 68 | +aclrtSetDevice(deviceId); | ||
| 69 | + | ||
| 70 | +// 查询是否支持某特性 | ||
| 71 | +int32_t isSupported = 0; | ||
| 72 | +aclrtGetDevFeature(deviceId, ACL_FEATURE_XXX, &isSupported); | ||
| 73 | + | ||
| 74 | +if (isSupported) { | ||
| 75 | + // 使用新特性 | ||
| 76 | +} else { | ||
| 77 | + // 使用兼容方案 | ||
| 78 | +} | ||
| 79 | +``` | ||
| 80 | + | ||
| 81 | +<br> | ||
| 82 | +<br> | ||
| 83 | + | ||
| 84 | +## 2. 应用兼容性处理 | ||
| 85 | + | ||
| 86 | +应用开发时预留扩展能力便于前向兼容(适配新版本)或者新版本应用需在旧版本环境运行时做后向兼容(支持旧版本),可采用如下处理方式: | ||
| 87 | + | ||
| 88 | +* **运行时版本检测**:程序启动时获取版本信息,记录日志或进行适配判断。 | ||
| 89 | + | ||
| 90 | +```c | ||
| 91 | +// 版本条件执行 | ||
| 92 | +int32_t versionNum; | ||
| 93 | +aclsysGetVersionNum("runtime", &versionNum); | ||
| 94 | + | ||
| 95 | +int32_t major = 8; | ||
| 96 | +int32_t minor = 5; | ||
| 97 | + | ||
| 98 | +if (versionNum >= major * 10000000 + minor * 100000) { | ||
| 99 | + // 使用新版本特性接口 | ||
| 100 | + aclrtQueryEventStatus(event, &status); | ||
| 101 | +} else { | ||
| 102 | + // 使用旧版本兼容接口 | ||
| 103 | + aclrtQueryEvent(event, &status); | ||
| 104 | +} | ||
| 105 | +``` | ||
| 106 | + | ||
| 107 | +* **特性能力探测**:使用特性查询接口而非硬编码版本号判断。 | ||
| 108 | + | ||
| 109 | +```c | ||
| 110 | +// 查询特性支持情况 | ||
| 111 | +int32_t isSupported = 0; | ||
| 112 | +aclrtGetDevFeature(deviceId, ACL_FEATURE_TSCPU_TASK_UPDATE_SUPPORT_AIC_AIV, &isSupported); | ||
| 113 | + | ||
| 114 | +if (isSupported) { | ||
| 115 | + // 使用新特性实现高效路径 | ||
| 116 | + useNewFeaturePath(); | ||
| 117 | +} else { | ||
| 118 | + // 降级到兼容实现 | ||
| 119 | + useLegacyPath(); | ||
| 120 | +} | ||
| 121 | +``` | ||
| 122 | +<br> | ||
| 123 | +<br> | ||
| 124 | + | ||
| 125 | +## 3. CANN Runtime兼容性策略 | ||
| 126 | + | ||
| 127 | +### 3.1 兼容原则 | ||
| 128 | + | ||
| 129 | +CANN Runtime遵循以下兼容性原则: | ||
| 130 | + | ||
| 131 | +| 版本变更类型 | 兼容性保证 | 示例 | | ||
| 132 | +| ------------- | ------------ | ---------------------- | | ||
| 133 | +| Patch版本升级 | 完全向后兼容 | Bug修复、性能优化 | | ||
| 134 | +| Minor版本升级 | API向后兼容 | 新增接口、新增特性 | | ||
| 135 | +| Major版本升级 | 不保证兼容 | 删除废弃接口、架构调整 | | ||
| 136 | + | ||
| 137 | +### 3.2 废弃接口处理策略 | ||
| 138 | + | ||
| 139 | +Runtime使用`ACL_DEPRECATED_MESSAGE`宏标记废弃接口,在废弃周期内保持可用,同时提示替换接口(如有)。 | ||
| 140 | + | ||
| 141 | +#### 废弃接口标记方式 | ||
| 142 | + | ||
| 143 | +```c | ||
| 144 | +// 废弃接口声明示例 | ||
| 145 | +ACL_DEPRECATED_MESSAGE("aclrtQueryEvent is deprecated, use aclrtQueryEventStatus instead") | ||
| 146 | +ACL_FUNC_VISIBILITY aclError aclrtQueryEvent(aclrtEvent event, aclrtEventStatus *status); | ||
| 147 | +``` | ||
| 148 | + | ||
| 149 | +编译时将产生警告信息: | ||
| 150 | + | ||
| 151 | +``` | ||
| 152 | +warning: 'aclrtQueryEvent' is deprecated: aclrtQueryEvent is deprecated, use aclrtQueryEventStatus instead [-Wdeprecated-declarations] | ||
| 153 | +``` | ||
| 154 | + | ||
| 155 | +#### 废弃接口生命周期 | ||
| 156 | + | ||
| 157 | +| 阶段 | 状态 | 建议 | | ||
| 158 | +| ------------ | ------------------ | ------------------ | | ||
| 159 | +| 发布废弃通知 | 接口可用,编译警告 | 开始迁移到替代接口 | | ||
| 160 | +| 废弃过渡期 | 接口可用,持续警告 | 完成迁移 | | ||
| 161 | +| 正式移除 | 接口不可用 | 必须使用替代接口 | | ||
| 162 | + | ||
| 163 | +### 3.3 枚举/结构体成员废弃处理 | ||
| 164 | + | ||
| 165 | +枚举值和结构体成员也可能被废弃: | ||
| 166 | + | ||
| 167 | +```c | ||
| 168 | +typedef enum aclrtLaunchKernelAttrId { | ||
| 169 | + ACL_RT_LAUNCH_KERNEL_ATTR_LOCAL_MEMORY_SIZE | ||
| 170 | + ACL_DEPRECATED_MESSAGE("Use ACL_RT_LAUNCH_KERNEL_ATTR_DYN_UBUF_SIZE instead") = 2, | ||
| 171 | + ACL_RT_LAUNCH_KERNEL_ATTR_DYN_UBUF_SIZE = 2, // 替代值,同编号 | ||
| 172 | + ... | ||
| 173 | +} aclrtLaunchKernelAttrId; | ||
| 174 | + | ||
| 175 | +typedef union aclrtLaunchKernelAttrValue { | ||
| 176 | + ACL_DEPRECATED_MESSAGE("Use dynUbufSize instead") | ||
| 177 | + uint32_t localMemorySize; // 废弃成员 | ||
| 178 | + uint32_t dynUBufSize; // 替代成员 | ||
| 179 | + ... | ||
| 180 | +} aclrtLaunchKernelAttrValue; | ||
| 181 | +``` | ||
| 182 | + | ||
| 183 | +处理建议: | ||
| 184 | + | ||
| 185 | +- 使用替代枚举值/成员名。 | ||
| 186 | +- 注意替代值可能与废弃值编号相同,保持兼容。 | ||
| 187 | + | ||
| 188 | +<br> | ||
| 189 | +<br> | ||
| 190 | + | ||
| 191 | +## 4. 迁移废弃接口 | ||
| 192 | + | ||
| 193 | +### 4.1 迁移流程 | ||
| 194 | + | ||
| 195 | +1. **识别废弃接口**:编译时查看废弃警告,确认替代接口。 | ||
| 196 | +2. **评估迁移影响**:分析接口参数、返回值、语义差异。 | ||
| 197 | +3. **编写迁移代码**:替换废弃接口调用,适配新接口参数。 | ||
| 198 | +4. **测试验证**:确保迁移后功能正确、性能符合预期。 | ||
| 199 | + | ||
| 200 | +### 4.2 迁移示例 | ||
| 201 | + | ||
| 202 | +#### aclrtQueryEvent -> aclrtQueryEventStatus | ||
| 203 | + | ||
| 204 | +```c | ||
| 205 | +// 废弃接口(旧代码) | ||
| 206 | +aclrtEventStatus status; | ||
| 207 | +aclrtQueryEvent(event, &status); | ||
| 208 | +if (status == ACL_EVENT_STATUS_COMPLETE) { | ||
| 209 | + // ... | ||
| 210 | +} | ||
| 211 | + | ||
| 212 | +// 替代接口(新代码) | ||
| 213 | +aclrtEventRecordedStatus status; | ||
| 214 | +aclrtQueryEventStatus(event, &status); | ||
| 215 | +if (status == ACL_EVENT_RECORDED_STATUS_COMPLETE) { | ||
| 216 | + // ... | ||
| 217 | +} | ||
| 218 | +``` | ||
| 219 | + | ||
| 220 | +### 4.3 推荐做法 | ||
| 221 | + | ||
| 222 | +1. **定期检查编译警告**:及时处理废弃接口警告,避免积累。 | ||
| 223 | +2. **优先使用新接口**:新接口通常有更好的语义、性能或功能。 | ||
| 224 | +3. **版本信息记录**:应用启动时记录Runtime版本,便于问题定位。 | ||
| 225 | +4. **特性探测优于版本判断**:使用特性查询接口判断能力,而非硬编码版本号。 | ||
| 226 | +5. **封装版本适配逻辑**:将兼容性逻辑集中在适配层,便于维护。 | ||
| 227 | + | ||
| 228 | +### 4.4 避免的做法 | ||
| 229 | + | ||
| 230 | +1. **不要忽略废弃警告**:废弃接口可能在未来版本移除,导致编译失败。 | ||
| 231 | +2. **不要硬编码版本判断**:版本号判断缺乏灵活性,应使用特性探测。 | ||
| 232 | +3. **不要混用新旧接口**:同一功能模块统一使用新或旧接口,避免混乱。 | ||
| 233 | +4. **不要假设接口语义不变**:迁移时仔细阅读新接口文档,确认语义一致。 | ||