ACL API 的 stream 为空行为机制说明
1. 涉及的 15 个 ACL 接口
本文分析的 15 个 ACL 接口均包含 aclrtStream stream 入参。调用者可在语法上向该参数传入 NULL(C)或 nullptr(C++);在 ACL Runtime 通用语义中,NULL 表示使用当前 Context 的默认 Stream,但具体接口及产品型号是否支持仍以对应 API 约束为准。各接口的函数签名及声明所在的头文件如下表所示。
| 序号 | 接口名称 | 函数签名 | 对应的 .h 文件路径 |
|---|---|---|---|
| 1 | aclopExecute(已废弃) | aclError aclopExecute(const char *opType, int numInputs, const aclTensorDesc *const inputDesc[], const aclDataBuffer *const inputs[], int numOutputs, const aclTensorDesc *const outputDesc[], aclDataBuffer *const outputs[], const aclopAttr *attr, aclrtStream stream) |
ge/inc/external/acl/acl_op.h:265-268 |
| 2 | aclopExecuteV2 | aclError aclopExecuteV2(const char *opType, int numInputs, aclTensorDesc *inputDesc[], aclDataBuffer *inputs[], int numOutputs, aclTensorDesc *outputDesc[], aclDataBuffer *outputs[], aclopAttr *attr, aclrtStream stream) |
ge/inc/external/acl/acl_op.h:298-300 |
| 3 | aclopExecWithHandle | aclError aclopExecWithHandle(aclopHandle *handle, int numInputs, const aclDataBuffer *const inputs[], int numOutputs, aclDataBuffer *const outputs[], aclrtStream stream) |
ge/inc/external/acl/acl_op.h:352-354 |
| 4 | aclopCast | aclError aclopCast(const aclTensorDesc *srcDesc, const aclDataBuffer *srcBuffer, const aclTensorDesc *dstDesc, aclDataBuffer *dstBuffer, uint8_t truncate, aclrtStream stream) |
ge/inc/external/acl/acl_op.h:370-372 |
| 5 | aclopCompileAndExecute | aclError aclopCompileAndExecute(const char *opType, int numInputs, const aclTensorDesc *const inputDesc[], const aclDataBuffer *const inputs[], int numOutputs, const aclTensorDesc *const outputDesc[], aclDataBuffer *const outputs[], const aclopAttr *attr, aclopEngineType engineType, aclopCompileType compileFlag, const char *opPath, aclrtStream stream) |
ge/inc/external/acl/acl_op_compiler.h:91-94 |
| 6 | aclopCompileAndExecuteV2 | aclError aclopCompileAndExecuteV2(const char *opType, int numInputs, aclTensorDesc *inputDesc[], aclDataBuffer *inputs[], int numOutputs, aclTensorDesc *outputDesc[], aclDataBuffer *outputs[], aclopAttr *attr, aclopEngineType engineType, aclopCompileType compileFlag, const char *opPath, aclrtStream stream) |
ge/inc/external/acl/acl_op_compiler.h:117-122 |
| 7 | aclblasGemvEx | aclError aclblasGemvEx(aclTransType transA, int m, int n, const void *alpha, const void *a, int lda, aclDataType dataTypeA, const void *x, int incx, aclDataType dataTypeX, const void *beta, void *y, int incy, aclDataType dataTypeY, aclComputeType type, aclrtStream stream) |
ge/inc/external/acl/ops/acl_cblas.h:53-56 |
| 8 | aclblasHgemv | aclError aclblasHgemv(aclTransType transA, int m, int n, const aclFloat16 *alpha, const aclFloat16 *a, int lda, const aclFloat16 *x, int incx, const aclFloat16 *beta, aclFloat16 *y, int incy, aclComputeType type, aclrtStream stream) |
ge/inc/external/acl/ops/acl_cblas.h:100-103 |
| 9 | aclblasS8gemv | aclError aclblasS8gemv(aclTransType transA, int m, int n, const int32_t *alpha, const int8_t *a, int lda, const int8_t *x, int incx, const int32_t *beta, int32_t *y, int incy, aclComputeType type, aclrtStream stream) |
ge/inc/external/acl/ops/acl_cblas.h:144-146 |
| 10 | aclblasGemmEx | aclError aclblasGemmEx(aclTransType transA, aclTransType transB, aclTransType transC, int m, int n, int k, const void *alpha, const void *matrixA, int lda, aclDataType dataTypeA, const void *matrixB, int ldb, aclDataType dataTypeB, const void *beta, void *matrixC, int ldc, aclDataType dataTypeC, aclComputeType type, aclrtStream stream) |
ge/inc/external/acl/ops/acl_cblas.h:193-197 |
| 11 | aclblasHgemm | aclError aclblasHgemm(aclTransType transA, aclTransType transB, aclTransType transC, int m, int n, int k, const aclFloat16 *alpha, const aclFloat16 *matrixA, int lda, const aclFloat16 *matrixB, int ldb, const aclFloat16 *beta, aclFloat16 *matrixC, int ldc, aclComputeType type, aclrtStream stream) |
ge/inc/external/acl/ops/acl_cblas.h:250-253 |
| 12 | aclblasS8gemm | aclError aclblasS8gemm(aclTransType transA, aclTransType transB, aclTransType transC, int m, int n, int k, const int32_t *alpha, const int8_t *matrixA, int lda, const int8_t *matrixB, int ldb, const int32_t *beta, int32_t *matrixC, int ldc, aclComputeType type, aclrtStream stream) |
ge/inc/external/acl/ops/acl_cblas.h:301-304 |
| 13 | aclmdlExecuteV2 | aclError aclmdlExecuteV2(uint32_t modelId, const aclmdlDataset *input, aclmdlDataset *output, aclrtStream stream, const aclmdlExecConfigHandle *handle) |
ge/inc/external/acl/acl_mdl.h:850-851 |
| 14 | aclmdlExecuteAsync | aclError aclmdlExecuteAsync(uint32_t modelId, const aclmdlDataset *input, aclmdlDataset *output, aclrtStream stream) |
ge/inc/external/acl/acl_mdl.h:883-884 |
| 15 | aclmdlExecuteAsyncV2 | aclError aclmdlExecuteAsyncV2(uint32_t modelId, const aclmdlDataset *input, aclmdlDataset *output, aclrtStream stream, const aclmdlExecConfigHandle *handle) |
ge/inc/external/acl/acl_mdl.h:866-867 |
2. 各类接口的 stream 为 nullptr 行为机制分析
2.1 单算子执行类(aclopExecute / aclopExecuteV2 / aclopExecWithHandle / aclopCast)
全局前提:以下分析均假设当前线程已设置有效 Context。stream=nullptr 时,3 条分支中多处调用
aclrtCtxGetCurrentDefaultStream获取默认 Stream,若当前线程没有有效 Context,该调用失败并直接返回错误。此外,aclopExecute、aclopExecuteV2、aclopExecWithHandle官方均说明多线程场景不支持使用默认 Stream(即 stream=nullptr),否则可能任务执行异常。
本类 4 个接口的 stream 参数首先汇聚到 acl::OpExecutor::ExecuteAsync 的两个重载,而非同一个函数。其中 aclopExecute、aclopExecuteV2、aclopCast 三个接口汇聚到 AclOp 重载 [ge/api/acl/acl_op_executor/single_op/op_executor.cpp:410],aclopExecWithHandle 走 OpHandle 重载 [ge/api/acl/acl_op_executor/single_op/op_executor.cpp:515]。汇聚关系如下图所示:
flowchart TD
A1["aclopExecute(已废弃)<br/>[ge/api/acl/acl_op_executor/single_op/acl_op_executor.cpp:78]"]
A2["aclopExecuteV2<br/>[ge/api/acl/acl_op_executor/single_op/acl_op_executor.cpp:84]"]
A3["aclopExecWithHandle<br/>[ge/api/acl/acl_op_executor/single_op/acl_op_executor.cpp:99]"]
A4["aclopCast<br/>[ge/api/acl/acl_op_executor/single_op/acl_op_executor.cpp:104]"]
B1["aclopExecuteImpl<br/>[ge/api/acl/acl_op_executor/single_op/op.cpp:186]"]
B2["aclopExecuteV2Impl<br/>[ge/api/acl/acl_op_executor/single_op/op.cpp:218]"]
B3["aclopExecWithHandleImpl<br/>[ge/api/acl/acl_op_executor/single_op/op.cpp:150]"]
B4["aclopCastImpl<br/>[ge/api/acl/acl_op_executor/single_op/builtin/cast_op.cpp:25]"]
C1["OpExecutor::ExecuteAsync(AclOp)<br/>[ge/api/acl/acl_op_executor/single_op/op_executor.cpp:410]"]
C2["OpExecutor::ExecuteAsync(OpHandle)<br/>[ge/api/acl/acl_op_executor/single_op/op_executor.cpp:515]"]
A1 --> B1
A2 --> B2
A3 --> B3
A4 --> B4
B1 --> C1
B2 --> C1
B4 --> B2
B3 --> C2
2.1.1 ExecuteAsync(AclOp) 重载的 stream 为 nullptr 行为
该重载 [ge/api/acl/acl_op_executor/single_op/op_executor.cpp:410] 内部按条件分为 3 条分支,stream=nullptr 在每条分支中的处理方式不同。伪代码如下:
OpExecutor::ExecuteAsync(aclOp, inputs, outputs, stream=nullptr):
// 分支 A:动态内核选择器路径(尝试性,失败则 fall through)
if OpKernelSelector::HasSelectFunc(aclOp.opType):
streamExecutor = Executors::GetOrCreate(context, stream=nullptr)
ret = streamExecutor->ExecuteAsync(aclOp, inputs, outputs)
// 结论:默认 Stream 仅用作 cache key,rtKernelLaunch 最终仍收到 nullptr
if ret == ACL_SUCCESS: return ACL_SUCCESS
// 分支 B:RT2 路径(RuntimeV2 开启且为动态算子)
if IsRuntimeV2Enable() and isDynamic:
return DoExecuteAsync(opModel.executor, aclOp, stream=nullptr, isExactModel)
// 结论:stream=nullptr 没有被真正替换,arg.stream 保持 nullptr
// 分支 C:RT1 路径(默认或静态模型)
return DoExecuteRT1(aclOp, inputs, outputs, stream=nullptr, ...)
// 结论:stream=nullptr 没有被替换,rtKernelLaunchWithFlagV2 收到 nullptr
3 条分支对 stream=nullptr 的处理需要分别分析:
2.1.1.1 分支 A:动态内核选择器路径
当 OpKernelSelector::HasSelectFunc(aclOp.opType) 为 true 时进入此分支。stream=nullptr 的完整调用栈如下:
OpExecutor::ExecuteAsync(aclOp, inputs, outputs, stream=nullptr)
[ge/api/acl/acl_op_executor/single_op/op_executor.cpp:410]
│ stream=nullptr 作为入参传入
│
└─ Executors::GetOrCreate(context, stream=nullptr)
[ge/api/acl/acl_op_executor/single_op/executor/stream_executor.cpp:163]
│ stream==nullptr 时调用 aclrtCtxGetCurrentDefaultStream 获取默认 Stream
│ 默认 Stream 仅用作 cache key 查找/插入 [stream_executor.cpp:165-174, 177, 193]
│ new StreamExecutor(resMgr, stream) 传入的仍是 nullptr [stream_executor.cpp:187]
│ 即 streamExecutor->stream_ 保持 nullptr
│
└─ streamExecutor->ExecuteAsync(aclOp, inputs, outputs)
[ge/api/acl/acl_op_executor/single_op/executor/stream_executor.cpp:47]
│ 签名不含 stream 参数,内部使用 stream_(nullptr)
│
├─ OpKernelSelector::GetOpKernelDesc(aclOp, desc)
│ [ge/api/acl/acl_op_executor/single_op/executor/stream_executor.cpp:50]
│ 获取 kernel 描述,不涉及 stream
│
└─ ExecuteAsync(*desc, numInputs, inputs, numOutputs, outputs)
[ge/api/acl/acl_op_executor/single_op/executor/stream_executor.cpp:58]
→ [ge/api/acl/acl_op_executor/single_op/executor/stream_executor.cpp:62]
│
└─ TbeOpTask::ExecuteAsync(numInputs, inputs, numOutputs, outputs, stream_=nullptr)
[ge/api/acl/acl_op_executor/single_op/executor/stream_executor.cpp:69]
→ [ge/api/acl/acl_op_executor/single_op/executor/op_task.cpp:28]
│ stream_ 为 nullptr,透传给 rtKernelLaunch
│
└─ rtKernelLaunch(stubFunc, blockDim, args, argSize, nullptr, stream=nullptr)
[ge/api/acl/acl_op_executor/single_op/executor/op_task.cpp:45]
最终 Runtime API 收到的 stream 为 nullptr
结论:分支 A 中 stream=nullptr 没有被替换为默认 Stream。Executors::GetOrCreate 虽然调用 aclrtCtxGetCurrentDefaultStream 获取默认 Stream,但仅用于 cache key 查找/插入,StreamExecutor 内部成员 stream_ 保持 nullptr。最终 rtKernelLaunch 收到的 stream 仍是 nullptr,由 Runtime 侧按默认 Stream 语义解释。
2.1.1.2 分支 B:RT2 路径
当 IsRuntimeV2Enable() 为 true 且 isDynamic 为 true 时进入此分支。stream=nullptr 的完整调用栈如下:
OpExecutor::ExecuteAsync(aclOp, inputs, outputs, stream=nullptr)
[ge/api/acl/acl_op_executor/single_op/op_executor.cpp:410]
│ stream=nullptr 作为入参传入
│
└─ DoExecuteAsync(opModel.executor, aclOp, stream=nullptr, isExactModel)
[ge/api/acl/acl_op_executor/single_op/op_executor.cpp:444](调用点)→ [ge/api/acl/acl_op_executor/single_op/op_executor.cpp:335](定义点)
│
├─ UpdateExecuteArgs(arg, stream=nullptr)
│ [ge/api/acl/acl_op_executor/single_op/op_executor.cpp:350]
│ → [ge/api/acl/acl_op_executor/single_op/op_executor.cpp:29]
│ arg.stream = stream = nullptr
│ 即 arg.stream 保持 nullptr
│
├─ GetKeyByStreamOrDefaultStream(stream=nullptr)
│ [ge/api/acl/acl_op_executor/single_op/op_executor.cpp:351]
│ → [ge/api/acl/acl_op_executor/single_op/acl_op_resource_manager.cpp:63]
│ stream==nullptr 时调用 aclrtCtxGetCurrentDefaultStream 获取默认 Stream
│ 返回 currentStream = 默认 Stream
│ currentStream 仅用作下一步 CreateRT2Executor 的 cache key
│
├─ CreateRT2Executor(streamExecutor, currentStream=默认Stream, arg, executor)
│ [ge/api/acl/acl_op_executor/single_op/op_executor.cpp:354]
│ → [ge/api/acl/acl_op_executor/single_op/acl_op_resource_manager.cpp:594]
│ currentStream(默认 Stream)仅用于 cache key 查找/创建 executor
│
└─ executor->Execute(arg, ...)
[ge/api/acl/acl_op_executor/single_op/op_executor.cpp:357]
→ [ge/runtime/v2/core/model_v2_executor.cc:227]
│ arg.stream 仍为 nullptr
│
└─ SpecifyArgsInputs(arg, input_num, graph_executor)
[ge/runtime/v2/core/model_v2_executor.cc:238]
→ [ge/runtime/v2/core/model_v2_executor.cc:137]
│
└─ OccupyStreamResource(arg, ...)
[ge/runtime/v2/core/model_v2_executor.cc:142]
→ [ge/runtime/v2/core/model_v2_executor.cc:96]
│ 第 123 行:streams->MutableData()[0] = arg.stream
│ 即 streams[0] = nullptr,nullptr 被写入模型执行的第 0 号 stream
│
└─ graph_executor.SpecifyInput(streams, input_num)
[ge/runtime/v2/core/model_v2_executor.cc:143]
│ streams 数组(含 nullptr)传入 graph_executor
│
└─ graph_executor.Execute()
[ge/runtime/v2/core/model_v2_executor.cc:249]
→ [ge/runtime/v2/core/model_v2_executor.cc:312]
│ execute_func_(execution_data_)
│ execute_func_ 由 GraphExecutorBuilder 通过 SetExecuteFunc 设置
│ [ge/runtime/v2/core/builder/graph_executor_builder.cc:119]
│ 执行器类型由 SelectExecutor 选择,不固定为 SequentialExecute
│ 可选:Sequential / Topological / MultiThreadTopological / PriorityTopological
│
└─ execute_func_(execution_data)(以 SequentialExecute 为例)
[ge/runtime/v2/core/executor/sequential/executor/sequential_executor.c:17]
│ 遍历 execution_data->nodes,对每个 node 调用 node->func(&(node->context))
│ node->func 是通过 REGISTER_KERNEL 注册的 kernel 函数
│ stream 从 streams[0](nullptr)经 kernel context 传入各 kernel
│
├─ AICore/DSA/STARS kernel(如 AiCoreLaunchKernelWithHandle 等)
│ [ge/runtime/v2/engine/aicore/launch_kernel/ai_core_launch_kernel.cc:635/667/711/733/767/797]
│ [ge/runtime/v2/engine/dsacore/kernel/dsa_core_launch_kernel.cc:127]
│ [ge/runtime/v2/engine/dsacore/kernel/stars_launch_kernel.cc:49]
│ FE_ASSERT_NOTNULL(stream) 断言失败 → return ErrorResult()
│ 即 GE 层拦截报错,不会到达 Runtime API
│
└─ AICPU kernel(如 AicpuLaunchTfKernel / AicpuLaunchCCKernel 等)
[ge/runtime/v2/engine/aicpu/kernel/aicpu_launch_kernel.cc:264/332]
│ 无 stream 非空检查(aicpu_ext_info_handle.cc:455 注释 "// stream can be nullptr")
│ stream=nullptr 直接透传给 Runtime API:
│ rtAicpuKernelLaunchExWithArgs / aclrtLaunchKernelWithHostArgs(aicpu_launch_kernel.cc)
│ 是否报错取决于 Runtime 层处理
结论:分支 B 中 stream=nullptr 没有被真正替换为默认 Stream。GetKeyByStreamOrDefaultStream 获取的默认 Stream 仅用作 CreateRT2Executor 的 cache key;arg.stream 保持 nullptr,经 OccupyStreamResource 第 123 行写入 streams[0],透传给 graph_executor。graph_executor.Execute() 通过 execute_func_ 遍历执行各 node(执行器类型不固定),stream=nullptr 的最终行为取决于 kernel 类型:
- AICore/DSA/STARS kernel:GE 层
FE_ASSERT_NOTNULL(stream)断言失败,直接返回错误,不会到达 Runtime API。 - AICPU kernel:无 stream 非空检查,nullptr 直接透传给 Runtime API(
rtAicpuKernelLaunchExWithArgs/aclrtLaunchKernelWithHostArgs,aicpu_launch_kernel.cc),是否报错取决于 Runtime 层处理。
2.1.1.3 分支 C:RT1 路径
当不满足分支 B 条件(IsRuntimeV2Enable() 为 false 或 isDynamic 为 false)时进入此分支。stream=nullptr 的完整调用栈如下(以静态算子路径为例):
OpExecutor::ExecuteAsync(aclOp, inputs, outputs, stream=nullptr)
[ge/api/acl/acl_op_executor/single_op/op_executor.cpp:410]
│ stream=nullptr 作为入参传入
│
└─ DoExecuteRT1(aclOp, inputs, outputs, stream=nullptr, ...)
[ge/api/acl/acl_op_executor/single_op/op_executor.cpp:441/446](调用点)→ [op_executor.cpp:484](定义点)
│
├─ LoadSingleOp(modelInfo, stream=nullptr, &singleOp)
│ [ge/api/acl/acl_op_executor/single_op/op_executor.cpp:501]
│ → [ge/api/acl/acl_op_executor/single_op/op_executor.cpp:369]
│ │
│ ├─ GetAllocators(stream=nullptr)
│ │ [ge/api/acl/acl_op_executor/single_op/op_executor.cpp:371]
│ │ → [ge/api/acl/acl_model/model/acl_resource_manager.cpp:170]
│ │ stream==nullptr 时调用 GetKeyByStreamOrDefaultStream 获取默认 Stream
│ │ 默认 Stream 仅用作 cache key [acl_resource_manager.cpp:172-175]
│ │
│ └─ GeExecutor::LoadSingleOpV2(modelName, modelData, stream=nullptr, singleOp, modelId)
│ [ge/api/acl/acl_op_executor/single_op/op_executor.cpp:383]
│ → [ge/runtime/v1/executor/ge_executor.cc:1274]
│ │
│ └─ SingleOpManager::GetOpFromModel(modelName, modelData, stream=nullptr, singleOp, modelId)
│ [ge/runtime/v1/single_op/single_op_manager.cc:22]
│ │
│ ├─ GetResourceId(stream=nullptr, resource_id)
│ │ [ge/runtime/v1/single_op/single_op_manager.cc:32]
│ │ → [ge/runtime/v1/single_op/single_op_manager.cc:142]
│ │ stream==nullptr 时调用 aclrtCtxGetCurrentDefaultStream 获取默认 Stream
│ │ 默认 Stream 仅用作 resource_id(cache key)[single_op_manager.cc:144-149]
│ │
│ └─ GetResource(resource_id, stream=nullptr)
│ [ge/runtime/v1/single_op/single_op_manager.cc:33]
│ → [ge/runtime/v1/single_op/single_op_manager.cc:66]
│ 第 78 行:res->SetStream(stream=nullptr)
│ 注意:SetStream 接收的是原始 nullptr,非 GetResourceId 替换后的 curCtxDefaultStream
│ 即 StreamResource 内部 stream_ 保持 nullptr
│
└─ DoExecuteAsync(singleOp, aclOp, inputs, outputs, isExactModel)
[ge/api/acl/acl_op_executor/single_op/op_executor.cpp:509]
→ [ge/api/acl/acl_op_executor/single_op/op_executor.cpp:151]
│ 签名不含 stream 参数,内部使用 singleOp 绑定的 stream_(nullptr)
│
└─ GeExecutor::ExecuteAsync(singleOp, inputVec, outputVec)
[ge/api/acl/acl_op_executor/single_op/op_executor.cpp:188]
→ [ge/runtime/v1/executor/ge_executor.cc:1297]
│
└─ singleOp->ExecuteAsync(inputs, outputs)
[ge/runtime/v1/single_op/single_op.cc:224]
│
└─ impl_->ExecuteAsync(inputs, outputs)
[ge/runtime/v1/single_op/single_op.cc:226]
→ [ge/runtime/v1/single_op/single_op.cc:356]
│ SingleOpImpl 构造时 stream_(stream=nullptr) [single_op.cc:256]
│
└─ task->LaunchKernel(stream_=nullptr)
[ge/runtime/v1/single_op/single_op.cc:396]
→ [ge/runtime/v1/single_op/task/op_task.cc:383](TbeOpTask::LaunchKernel)
│
└─ DoLaunchKernel(stream=nullptr)
[ge/runtime/v1/single_op/task/op_task.cc:402]
→ [ge/runtime/v1/single_op/task/op_task.cc:730]
│
└─ DoLaunchKernelWithArgsEx(stream=nullptr)
[ge/runtime/v1/single_op/task/op_task.cc:732]
→ [ge/runtime/v1/single_op/task/op_task.cc:738]
│ 第 742 行:rtKernelLaunchWithFlagV2(stubFunc, blockDim, args, smDesc, stream=nullptr, 0, cfg)
│ 或第 745 行:rtKernelLaunchWithHandleV2(handle, tilingKey, blockDim, args, smDesc, stream=nullptr, cfg)
│ 最终 Runtime API 收到的 stream 为 nullptr
结论(静态 TBE 路径):分支 C 中 stream=nullptr 没有被真正替换为默认 Stream。GetResourceId 和 GetAllocators 获取的默认 Stream 仅用作 cache key;StreamResource 内部 stream_ 保持 nullptr,经 SingleOpImpl::ExecuteAsync 第 396 行透传给 TbeOpTask::LaunchKernel,最终 rtKernelLaunchWithFlagV2 / rtKernelLaunchWithHandleV2 收到的 stream 仍是 nullptr,由 Runtime 侧按默认 Stream 语义解释。
注意:以上调用栈仅验证了静态 TBE Task 路径。RT1 路径还包括 DynamicSingleOp、AiCpuTask、MemcpyAsyncTask、MixL2OpTask 等其他 Task 类型,各 Task 的
LaunchKernel实现不同,stream=nullptr 的行为需分别验证。
2.1.1.4 总体结论
3 条分支对 stream=nullptr 的处理方式对比如下:
| 分支 | 触发条件 | 默认 Stream 用途(cache key → value) | 最终到达 | stream 实际值 |
|---|---|---|---|---|
| A(动态内核选择器) | HasSelectFunc 为 true |
默认 Stream → StreamExecutor*(Executors::GetOrCreate) |
rtKernelLaunch(Runtime API) |
nullptr |
| B(RT2)- AICore/DSA/STARS | IsRuntimeV2Enable 且 isDynamic |
默认 Stream → gert::ModelV2Executor*(CreateRT2Executor) |
GE 层 FE_ASSERT_NOTNULL(stream) 报错 |
nullptr(写入 streams[0]) |
| B(RT2)- AICPU | 同上 | 同上 | rtAicpuKernelLaunchExWithArgs / aclrtLaunchKernelWithHostArgs(Runtime API) |
nullptr(透传) |
| C(RT1,静态 TBE) | 默认或静态模型 | 默认 Stream → StreamResource*(GetResource,作为 resource_id) |
rtKernelLaunchWithFlagV2 / rtKernelLaunchWithHandleV2(Runtime API) |
nullptr |
共同结论:
- 3 条分支均未在 GE 侧将 nullptr 替换为默认 Stream。所有分支中获取的默认 Stream 仅用作 cache key / resource_id,用于查找或创建 executor / StreamResource,不回写到实际执行路径的 stream 变量。
- stream=nullptr 的最终行为取决于分支和 kernel 类型:
- 分支 A:nullptr 透传到
rtKernelLaunch,由 Runtime 侧按默认 Stream 语义解释。 - 分支 B(AICore/DSA/STARS):GE 层
FE_ASSERT_NOTNULL(stream)拦截报错,不到达 Runtime。 - 分支 B(AICPU):nullptr 透传到 Runtime API,是否报错取决于 Runtime 层处理。
- 分支 C(静态 TBE):nullptr 透传到
rtKernelLaunchWithFlagV2/rtKernelLaunchWithHandleV2,由 Runtime 侧按默认 Stream 语义解释。
- 分支 A:nullptr 透传到
- 分支 A 是尝试性路径,只有
streamExecutor->ExecuteAsync返回失败才 fall through 到分支 B 或 C;Context 获取失败(aclrtGetCurrentContext)、executor 创建失败(GetOrCreate返回 nullptr)会直接返回错误。因此实际生效的路径取决于算子类型和 RuntimeV2 开关:动态算子走 B,静态算子或未开启 RuntimeV2 时走 C,注册了动态内核选择器的算子优先尝试 A。
2.1.2 ExecuteAsync(OpHandle) 重载的 stream 为 nullptr 行为
该重载 [ge/api/acl/acl_op_executor/single_op/op_executor.cpp:515] 内部代码含 3 条分支(含 isDynamic 动态分支 [op_executor.cpp:526]),但 aclopExecWithHandle API 不产生动态 shape 算子,故 isDynamic 分支不可达,实际仅 2 条分支生效。伪代码如下:
OpExecutor::ExecuteAsync(opHandle, inputs, outputs, stream=nullptr):
// 分支 A':动态内核选择器路径(kernelDesc != nullptr)
if opHandle.kernelDesc != nullptr:
streamExecutor = Executors::GetOrCreate(context, stream=nullptr)
return streamExecutor->ExecuteAsync(*kernelDesc, inputs, outputs)
// 结论:与 2.1.1.1 分支 A 调用栈相同,rtKernelLaunch 最终收到 nullptr
// 分支 C':RT1 路径(aclopExecWithHandle 不支持动态 shape 算子,仅静态路径)
// stream=nullptr 用作 cachedOperators 的 cache key
ret = LoadSingleOp(opHandle.opModel, stream=nullptr, &singleOp)
return DoExecuteAsync(singleOp, opHandle.aclOp, inputs, outputs)
// 结论:与 2.1.1.3 分支 C 调用栈相同,rtKernelLaunchWithFlagV2 最终收到 nullptr
2.1.2.1 分支 A':动态内核选择器路径
当 opHandle.kernelDesc != nullptr 时进入此分支。调用栈如下:
OpExecutor::ExecuteAsync(opHandle, inputs, outputs, stream=nullptr)
[ge/api/acl/acl_op_executor/single_op/op_executor.cpp:515]
│
└─ Executors::GetOrCreate(context, stream=nullptr)
[ge/api/acl/acl_op_executor/single_op/executor/stream_executor.cpp:163]
│ 与 2.1.1.1 分支 A 完全相同:默认 Stream 仅用作 cache key,stream_ 保持 nullptr
│
└─ streamExecutor->ExecuteAsync(*kernelDesc, numInputs, inputs, numOutputs, outputs)
[ge/api/acl/acl_op_executor/single_op/op_executor.cpp:523]
→ [ge/api/acl/acl_op_executor/single_op/executor/stream_executor.cpp:62]
│ 此处调用的是 OpKernelDesc 重载(2.1.1.1 分支 A 调用的是 AclOp 重载)
│ 但内部逻辑相同:第 69 行 task.ExecuteAsync(..., stream_=nullptr)
│
└─ TbeOpTask::ExecuteAsync(..., stream_=nullptr)
→ [ge/api/acl/acl_op_executor/single_op/executor/op_task.cpp:28]
│
└─ rtKernelLaunch(stubFunc, blockDim, args, argSize, nullptr, stream=nullptr)
[ge/api/acl/acl_op_executor/single_op/executor/op_task.cpp:45]
结论:与 2.1.1.1 分支 A 结论相同。GetOrCreate 中默认 Stream 仅用作 cache key,stream_ 保持 nullptr,最终 rtKernelLaunch 收到 nullptr,由 Runtime 侧按默认 Stream 语义解释。
2.1.2.2 分支 C':RT1 路径
当 opHandle.kernelDesc == nullptr 时进入此分支。由于 aclopExecWithHandle 接口不支持动态 shape 算子(参见官方文档),此分支仅走静态路径。
stream=nullptr 的处理与 2.1.1.3 分支 C 相同,差异在于 OpHandle 重载有自己的 cache 机制:第 563 行 cachedOperators.find(stream=nullptr) 用 nullptr 作 cache key 查找;未命中时调用 LoadSingleOp [op_executor.cpp:567],与 2.1.1.3 分支 C 的 LoadSingleOp [op_executor.cpp:369] 是同一个函数。
后续 DoExecuteAsync(singleOp, ...) 调用栈与 2.1.1.3 分支 C 完全相同(SingleOpImpl::ExecuteAsync → task->LaunchKernel(stream_=nullptr) → rtKernelLaunchWithFlagV2 / rtKernelLaunchWithHandleV2),此处不再重复。
结论(静态 TBE 子路径):与 2.1.1.3 分支 C 结论相同。GetResourceId 和 GetAllocators 获取的默认 Stream 仅用作 cache key;StreamResource 内部 stream_ 保持 nullptr,最终 rtKernelLaunchWithFlagV2 / rtKernelLaunchWithHandleV2 收到 nullptr,由 Runtime 侧按默认 Stream 语义解释。
注意:以上仅验证了静态 TBE 子路径。RT1 静态路径还包括
AiCpuTask、MemcpyAsyncTask、MixL2OpTask等其他 Task 类型,各 Task 的LaunchKernel实现不同,stream=nullptr 的行为需分别验证。
注意:OpHandle 重载的 cache 机制额外使用 stream 作 key(
cachedOperators),stream=nullptr 时以 nullptr 为 key 查找/插入。这与SingleOpManager::GetResource中用默认 Stream 作 resource_id 的机制是两套独立的缓存。
2.1.2.3 总体结论
| 分支 | 触发条件 | 与 AclOp 重载的关系 | 最终到达 | stream 实际值 |
|---|---|---|---|---|
| A'(动态内核选择器) | kernelDesc != nullptr |
调用栈同 2.1.1.1 分支 A(ExecuteAsync 重载不同但内部逻辑相同) | rtKernelLaunch(Runtime API) |
nullptr |
| C'(RT1,静态 TBE) | kernelDesc == nullptr |
调用栈同 2.1.1.3 分支 C | rtKernelLaunchWithFlagV2 / rtKernelLaunchWithHandleV2(Runtime API) |
nullptr |
与 AclOp 重载的关键差异:
- OpHandle 重载没有 RT2 路径(分支 B),因此不存在 AICore
FE_ASSERT_NOTNULL(stream)报错的情况。 - OpHandle 重载额外使用 stream 作 cache key(
cachedOperators),stream=nullptr 时以 nullptr 为 key。 - 两条分支的 stream=nullptr 最终都透传到 Runtime API,由 Runtime 侧按默认 Stream 语义解释。
2.2 编译并执行类(aclopCompileAndExecute / aclopCompileAndExecuteV2)
本类 2 个接口在编译完成后,执行阶段汇聚到 2.1.1 节已分析过的 OpExecutor::ExecuteAsync(AclOp) 重载 [ge/api/acl/acl_op_executor/single_op/op_executor.cpp:410]。汇聚关系如下图所示:
flowchart TD
A1["aclopCompileAndExecute(入口函数体内联,无独立 Impl)<br/>[ge/api/acl/acl_op_compiler/single_op/op_compiler.cpp:191]"]
A2["aclopCompileAndExecuteV2(入口函数体内联,无独立 Impl)<br/>[ge/api/acl/acl_op_compiler/single_op/op_compiler.cpp:224]"]
C["OpCompileProcessor::OpCompile(编译阶段,不涉及 stream)<br/>[ge/api/acl/acl_op_compiler/single_op/op_compiler.cpp:213/244]"]
D["OpExecutor::ExecuteAsync(AclOp)<br/>[ge/api/acl/acl_op_executor/single_op/op_executor.cpp:410]"]
A1 --> C
A2 --> C
C --> D
两个接口的调用链如下:
| 接口 | 定义 | 编译阶段 | 汇聚点(执行阶段) |
|---|---|---|---|
| aclopCompileAndExecute | op_compiler.cpp:191 | OpCompileProcessor::OpCompile [op_compiler.cpp:213] |
OpExecutor::ExecuteAsync(aclOp, inputs, outputs, stream) [op_compiler.cpp:221] → AclOp 重载 [op_executor.cpp:410] |
| aclopCompileAndExecuteV2 | op_compiler.cpp:224 | OpCompileProcessor::OpCompile [op_compiler.cpp:244] |
OpExecutor::ExecuteAsync(aclOp, inputs, outputs, stream) [op_compiler.cpp:252] → AclOp 重载 [op_executor.cpp:410] |
关键结论:
- 编译阶段不涉及 stream。
ConstructAclOp[op_compiler.cpp:75] 不接收 stream 参数,AclOp 结构体中也不存储 stream。stream 仅在编译完成后的ExecuteAsync阶段作为独立参数传入。 - 两个接口汇聚到 2.1.1 节的 AclOp 重载 [op_executor.cpp:410],stream 透传路径与 2.1.1 节完全相同。
- V1 与 V2 的唯一差异在于
exeucteType字段:V1 为ACL_OP_EXECUTE,V2 为ACL_OP_EXECUTE_REFRESH_OUTPUT_ORI_SHAPE。该差异影响PrepareRt2Execute中的trust_shape_on_out_tensor标志 [op_executor.cpp:465],但不影响 stream 的处理路径。
stream 为 nullptr 的行为:完全复用 2.1.1 节的结论。两个接口在编译完成后进入 OpExecutor::ExecuteAsync(AclOp) 重载,后续按 3 条分支(A/B/C)处理 stream=nullptr,行为与 2.1.1.1 / 2.1.1.2 / 2.1.1.3 完全一致,此处不再重复。
2.3 BLAS 类(aclblasGemvEx / aclblasHgemv / aclblasS8gemv / aclblasGemmEx / aclblasHgemm / aclblasS8gemm)
本类 6 个接口通过 MatMulTemplate::ExecuteAsync 统一转调 aclopExecuteV2,此后调用链与 2.1 节完全一致。汇聚关系如下图所示:
flowchart TD
A1["aclblasGemvEx<br/>[ge/api/acl/acl_cblas/single_op/blas/gemv_ops.cpp:15]"]
A2["aclblasHgemv<br/>[ge/api/acl/acl_cblas/single_op/blas/gemv_ops.cpp:32]"]
A3["aclblasS8gemv<br/>[ge/api/acl/acl_cblas/single_op/blas/gemv_ops.cpp:53]"]
A4["aclblasGemmEx<br/>[ge/api/acl/acl_cblas/single_op/blas/gemm_ops.cpp:137]"]
A5["aclblasHgemm<br/>[ge/api/acl/acl_cblas/single_op/blas/gemm_ops.cpp:252]"]
A6["aclblasS8gemm<br/>[ge/api/acl/acl_cblas/single_op/blas/gemm_ops.cpp:261]"]
B["aclblasGemmEx(汇聚点 1)<br/>[ge/api/acl/acl_cblas/single_op/blas/gemm_ops.cpp:137]"]
C["MatMulTemplate::ExecuteAsync<br/>[ge/api/acl/acl_cblas/single_op/blas/gemm_ops.cpp:91]"]
D["aclopExecuteV2(汇聚点 2,与 2.1 节汇合)<br/>[ge/api/acl/acl_op_executor/single_op/acl_op_executor.cpp:84]"]
A1 --> B
A2 --> A1
A3 --> A1
A4 --> B
A5 --> B
A6 --> B
B --> C
C --> D
6 个接口的调用链如下:
| 接口 | 定义 | 中间转调 | 汇聚点 |
|---|---|---|---|
| aclblasGemvEx | gemv_ops.cpp:15 | → aclblasGemmEx [gemv_ops.cpp:20] | aclopExecuteV2 [gemm_ops.cpp:111] |
| aclblasHgemv | gemv_ops.cpp:32 | → aclblasGemvEx [gemv_ops.cpp:37] → aclblasGemmEx | aclopExecuteV2 [gemm_ops.cpp:111] |
| aclblasS8gemv | gemv_ops.cpp:53 | → aclblasGemvEx [gemv_ops.cpp:58] → aclblasGemmEx | aclopExecuteV2 [gemm_ops.cpp:111] |
| aclblasGemmEx | gemm_ops.cpp:137 | → MatMulTemplate::ExecuteAsync [gemm_ops.cpp:194] | aclopExecuteV2 [gemm_ops.cpp:111] |
| aclblasHgemm | gemm_ops.cpp:252 | → aclblasGemmEx [gemm_ops.cpp:257] | aclopExecuteV2 [gemm_ops.cpp:111] |
| aclblasS8gemm | gemm_ops.cpp:261 | → aclblasGemmEx [gemm_ops.cpp:266] | aclopExecuteV2 [gemm_ops.cpp:111] |
关键结论:
- 6 个接口呈扇形收敛:3 个 gemv 接口先转调
aclblasGemvEx,再由aclblasGemvEx和 2 个 gemm 接口(aclblasHgemm/aclblasS8gemm)转调aclblasGemmEx,最终全部经MatMulTemplate::ExecuteAsync汇聚到aclopExecuteV2[gemm_ops.cpp:111]。 - stream 参数全程透传:从用户入口到
OpExecutor::ExecuteAsync,stream 在每一跳都原样传递,未被替换或丢弃。 - 与 2.1 节完全汇合:6 个 BLAS 接口在
aclopExecuteV2处与 2.1 节的aclopExecuteV2接口完全汇合,后续走同一条aclopExecuteV2Impl→OpExecutor::ExecuteAsync(AclOp)路径。
stream 为 nullptr 的行为:完全复用 2.1 节的结论。6 个 BLAS 接口在 aclopExecuteV2 处与 2.1 节汇合,后续按 3 条分支(A/B/C)处理 stream=nullptr,行为与 2.1.1.1 / 2.1.1.2 / 2.1.1.3 完全一致,此处不再重复。
2.4 模型执行类(aclmdlExecuteV2 / aclmdlExecuteAsync / aclmdlExecuteAsyncV2)
2.4.1 分流结构总览
本类 3 个接口分属两条完全独立的管线,无共同汇聚点。分流逻辑用伪代码表示如下:
// ─── 标准 C++ 管线(api/acl/)───
aclmdlExecuteV2(modelId, input, output, stream, handle) // 同步 [acl_model.cpp:329]
aclmdlExecuteAsync(modelId, input, output, stream) // 异步 [acl_model.cpp:340]
两者都先执行:
isOm2 = AclIsOm2ModelById(modelId) // [acl_model_router.cpp:33]
if isOm2: // OM2 路径
→ Om2ModelExecuteCommon(modelId, input, output, isAsync, stream) // [model_om2.cpp:354]
if isAsync: executor->RunAsync(stream, ...) // stream 透传
else: executor->Run(...) // stream 未传入
else if IsRuntimeV2Enable && GetExecutor(modelId) != null: // RuntimeV2 路径
→ RuntimeV2ModelExecute(modelId, input, output, isAsync, stream) // [model.cpp:562]
if isAsync: executor->Execute(arg.stream=stream, ...) // stream 参与执行
else: executor->ExecuteSync(...) // stream 仅用于 profiling
else: // RT1 路径
→ ModelExecute(modelId, input, output, isAsync, stream) // [model.cpp:710]
→ GeExecutor::ExecModel(modelId, stream, ...) // stream 透传
if !isAsync && stream != nullptr: // 同步路径额外做流同步
aclrtSynchronizeStreamWithTimeout(stream, ...) // [model.cpp:1715]
// ─── ACL_C 管线(api/acl_c/)───
aclmdlExecuteAsyncV2(modelId, input, output, stream, handle) // 异步 [model.c:173]
→ ModelExecute(modelId, input, output, sync=false, stream, handle) // [model.c:116]
├─ SetExecHandle(handle, stream, &execDesc) // [model.c:88]
│ workspace 优先取 handle->workPtr,其次 rtStreamGetWorkspace(stream)
│ 两者都无 → 返回 ACL_ERROR_INVALID_PARAM
│ execDesc.stream = stream
└─ ExecModel(modelId, &execDesc, ...) // [ge_executor.c:122]
→ rtStreamGetSqid(stream, &sqId) // stream 转为 sqId
→ rtNanoModelExecute(&rtMdlExec) // 最终 Runtime API,只收 sqId
2.4.2 标准 C++ 管线(aclmdlExecuteV2 / aclmdlExecuteAsync)
2.4.2.1 OM2 路径
当 AclIsOm2ModelById 返回 isOm2=true 时进入此路径。aclmdlExecuteV2 和 aclmdlExecuteAsync 都经 Om2ModelExecuteCommon [ge/api/acl/acl_model/model/model_om2.cpp:354] 汇聚,按 isAsync 参数分流到同步或异步执行。
同步路径(aclmdlExecuteV2)
aclmdlExecuteV2(modelId, input, output, stream, handle)
[ge/api/acl/acl_model/model/acl_model.cpp:329]
│
└─ aclmdlExecuteV2ImplOm2(modelId, input, output, stream, handle)
[ge/api/acl/acl_model/model/model_om2.cpp:1219]
│ 设置 streamSyncTimeout / eventSyncTimeout 到 Om2ThreadLocalContext
│ [model_om2.cpp:1224-1231]
│
└─ Om2ModelExecuteCommon(modelId, input, output, isAsync=false, stream)
[ge/api/acl/acl_model/model/model_om2.cpp:1233] → [model_om2.cpp:354]
│ 准备 input/output tensors(不涉及 stream)
│
└─ executor->Run(inputVec, outputVec)
[ge/api/acl/acl_model/model/model_om2.cpp:401]
→ [ge/runtime/om2/om2_model_executor.cc:864](Impl::Run)
│ Run() 签名不含 stream 参数
│
└─ run_func(&model_handle, inputs.size(), inputs.data(), outputs.size(), outputs.data(), timeout)
[ge/runtime/om2/om2_model_executor.cc:870]
│ run_func 是动态加载的 Om2ModelRun(来自编译产物 SO)
│ RunFunc 签名:(*)(Om2ModelHandle*, int, void**, int, void**, int32_t streamSyncTimeout)
│ 无 stream 参数,timeout 来自 Om2ThreadLocalContext
│ SO 内部使用自己管理的 stream 执行和同步
stream 处理:用户传入的 stream 在整条同步路径中未被使用。Om2ModelExecuteCommon 接收 stream 但 isAsync=false 时调用 executor->Run() 不传入 stream;Impl::Run 调用 run_func(Om2ModelRun)也不传入 stream,只传入 streamSyncTimeout。SO 内部使用自己管理的 stream 执行 kernel 并做同步,与用户传入的 stream 无关。
异步路径(aclmdlExecuteAsync)
aclmdlExecuteAsync(modelId, input, output, stream)
[ge/api/acl/acl_model/model/acl_model.cpp:340]
│
└─ aclmdlExecuteAsyncImplOm2(modelId, input, output, stream)
[ge/api/acl/acl_model/model/model_om2.cpp:1236]
│
└─ Om2ModelExecuteCommon(modelId, input, output, isAsync=true, stream)
[ge/api/acl/acl_model/model/model_om2.cpp:1241] → [model_om2.cpp:354]
│ 准备 input/output tensors(不涉及 stream)
│
└─ executor->RunAsync(stream, inputVec, outputVec)
[ge/api/acl/acl_model/model/model_om2.cpp:399]
→ [ge/runtime/om2/om2_model_executor.cc:876](Impl::RunAsync)
│ stream 透传给 run_async_func
│
└─ run_async_func(&model_handle, stream, inputs.size(), inputs.data(), outputs.size(), outputs.data())
[ge/runtime/om2/om2_model_executor.cc:880]
│ run_async_func 是动态加载的 Om2ModelRunAsync(来自编译产物 SO)
│ RunAsyncFunc 签名:(*)(Om2ModelHandle*, rtStream_t, int, void**, int, void**)
│ stream 直接传入 SO
stream 处理:用户传入的 stream 在异步路径中全程透传。Om2ModelExecuteCommon 在 isAsync=true 时调用 executor->RunAsync(stream, ...),Impl::RunAsync 将 stream 直接传给 run_async_func(Om2ModelRunAsync),由 SO 内部使用用户 stream 异步执行。
2.4.2.2 RuntimeV2 路径
当 isOm2=false 且 IsRuntimeV2Enable(true) 为 true 且 GetExecutor(modelId) != nullptr 时进入此路径。aclmdlExecuteV2 和 aclmdlExecuteAsync 都经 RuntimeV2ModelExecute [ge/api/acl/acl_model/model/model.cpp:562] 汇聚,按 isAsync 参数分流到同步或异步执行。
同步路径(aclmdlExecuteV2)
aclmdlExecuteV2(modelId, input, output, stream, handle)
[ge/api/acl/acl_model/model/acl_model.cpp:329]
│
└─ aclmdlExecuteV2Impl(modelId, input, output, stream, handle)
[ge/api/acl/acl_model/model/model.cpp:1694]
│ 设置 streamSyncTimeout / eventSyncTimeout 到 ge::GetContext()
│ [model.cpp:1701-1706]
│
└─ RuntimeV2ModelExecute(modelId, input, output, isAsync=false, stream)
[ge/api/acl/acl_model/model/model.cpp:1711] → [model.cpp:562]
│ 准备 input/output tensors(不涉及 stream)
│
├─ CANN_PROFILING_STEP_TRACE(modelId, index_id, kStartTag, stream)
│ [model.cpp:652] ← stream 用于 profiling 标记
│
├─ executor->ExecuteSync(inputVec, ..., outputVec, ...)
│ [model.cpp:659]
│ → [ge/runtime/v2/core/model_v2_executor.cc:252]
│ │ ExecuteSync() 签名不含 stream 参数
│ │
│ ├─ 若 default_stream_ == nullptr:
│ │ rtStreamCreateWithFlags(&default_stream_, ..., RT_STREAM_FAST_LAUNCH | RT_STREAM_FAST_SYNC)
│ │ [model_v2_executor.cc:253-256] ← 创建 executor 内部 stream
│ │
│ ├─ Execute({default_stream_}, inputs, ..., outputs, ...)
│ │ [model_v2_executor.cc:258]
│ │ → [model_v2_executor.cc:227]
│ │ │ arg.stream = default_stream_(非用户 stream)
│ │ │ → SpecifyArgsInputs → OccupyStreamResource
│ │ │ [model_v2_executor.cc:123] streams[0] = arg.stream = default_stream_
│ │ │ → graph_executor.Execute() → execute_func_ → REGISTER_KERNEL kernel
│ │ │ 模型在 default_stream_ 上执行
│ │
│ └─ DoRtStreamSyncWithTimeout(default_stream_)
│ [model_v2_executor.cc:262] ← 同步内部 default_stream_
│
└─ CANN_PROFILING_STEP_TRACE(modelId, index_id, kEndTag, stream)
[model.cpp:661] ← stream 用于 profiling 标记
stream 处理:用户传入的 stream 不参与模型执行和同步。RuntimeV2ModelExecute 在 isAsync=false 时调用 executor->ExecuteSync(),该函数签名不含 stream。ExecuteSync 内部使用 executor 成员变量 default_stream_(首次调用时通过 rtStreamCreateWithFlags 创建,带 RT_STREAM_FAST_LAUNCH | RT_STREAM_FAST_SYNC 标志)执行模型并同步。用户 stream 仅用于 CANN_PROFILING_STEP_TRACE 的 profiling 标记 [model.cpp:652/661]。
异步路径(aclmdlExecuteAsync)
aclmdlExecuteAsync(modelId, input, output, stream)
[ge/api/acl/acl_model/model/acl_model.cpp:340]
│
└─ aclmdlExecuteAsyncImpl(modelId, input, output, stream)
[ge/api/acl/acl_model/model/model.cpp:1730]
│
└─ RuntimeV2ModelExecute(modelId, input, output, isAsync=true, stream)
[ge/api/acl/acl_model/model/model.cpp:1738] → [model.cpp:562]
│ 准备 input/output tensors(不涉及 stream)
│
├─ CANN_PROFILING_STEP_TRACE(modelId, index_id, kStartTag, stream)
│ [model.cpp:652] ← stream 用于 profiling 标记
│
├─ arg.stream = stream
│ [model.cpp:655] ← 用户 stream 写入 arg
│
├─ arg.external_allocator = GetAllocators(arg.stream, false)
│ [model.cpp:656] ← stream 用于获取 allocator
│ arg.stream=nullptr 时 GetAllocators 内部用默认 Stream 作 cache key
│
├─ executor->Execute(arg, inputVec, ..., outputVec, ...)
│ [model.cpp:657]
│ → [ge/runtime/v2/core/model_v2_executor.cc:227]
│ │ arg.stream = 用户 stream(可能为 nullptr)
│ │ → SpecifyArgsInputs → OccupyStreamResource
│ │ [model_v2_executor.cc:123] streams[0] = arg.stream
│ │ → graph_executor.Execute() → execute_func_ → REGISTER_KERNEL kernel
│ │ 模型在 arg.stream(用户 stream)上执行
│ │ 若 arg.stream=nullptr,AICore kernel 的 FE_ASSERT_NOTNULL(stream) 会报错
│ │ (与 2.1.1.2 分支 B 结论一致)
│
└─ CANN_PROFILING_STEP_TRACE(modelId, index_id, kEndTag, stream)
[model.cpp:661] ← stream 用于 profiling 标记
stream 处理:用户传入的 stream 全程参与执行。RuntimeV2ModelExecute 在 isAsync=true 时将 stream 写入 arg.stream [model.cpp:655],并用于 GetAllocators 获取 allocator [model.cpp:656]。executor->Execute(arg, ...) 内部经 OccupyStreamResource 将 arg.stream 写入 streams[0] [model_v2_executor.cc:123],透传给 graph_executor,最终由 REGISTER_KERNEL 注册的 kernel 使用。stream=nullptr 时的行为与 2.1.1.2 分支 B 完全一致(AICore/DSA/STARS kernel 报错,AICPU kernel 透传)。
2.4.2.3 RT1 路径
当 isOm2=false 且不满足 RuntimeV2 条件(IsRuntimeV2Enable 为 false 或 GetExecutor(modelId) == nullptr)时进入此路径。aclmdlExecuteV2 和 aclmdlExecuteAsync 都经 ModelExecute [ge/api/acl/acl_model/model/model.cpp:710] 汇聚,按 basync 参数区分同步/异步。两条路径都调用 GeExecutor::ExecModel,stream 始终透传,最终到达 DavinciModel::NnExecute,由 InitModelStream 根据 stream 是否为 nullptr 决定使用用户 stream 还是创建内部 forbidden stream。
同步路径(aclmdlExecuteV2)
aclmdlExecuteV2(modelId, input, output, stream, handle)
[ge/api/acl/acl_model/model/acl_model.cpp:329]
│
└─ aclmdlExecuteV2Impl(modelId, input, output, stream, handle)
[ge/api/acl/acl_model/model/model.cpp:1694]
│
├─ ModelExecute(modelId, input, output, basync=false, stream)
│ [ge/api/acl/acl_model/model/model.cpp:1713] → [model.cpp:710]
│ │
│ └─ executor.ExecModel(modelId, stream, inputData, ..., basync=false)
│ [ge/api/acl/acl_model/model/model.cpp:807]
│ → [ge/runtime/v1/executor/ge_executor.cc:1170]
│ │
│ └─ GraphLoader::ExecuteModel(model_id, stream, async_mode=false, ...)
│ [ge/runtime/v1/executor/ge_executor.cc:1201]
│ → [ge/runtime/v1/graph/load/graph_loader.cc:181]
│ │
│ └─ ModelManager::ExecuteModel(model_id, stream, async_mode=false, ...)
│ [ge/runtime/v1/graph/load/model_manager/model_manager.cc:1761]
│ │
│ └─ davinci_model->NnExecute(stream, async_mode=false, ...)
│ [ge/runtime/v1/graph/load/model_manager/model_manager.cc:1775]
│ → [ge/runtime/v1/graph/load/model_manager/davinci_model.cc:7702]
│ │
│ ├─ InitModelStream(stream)
│ │ [davinci_model.cc:7711] → [davinci_model.cc:7625]
│ │ 同步模式 + stream==nullptr 时:
│ │ 创建内部 forbidden stream(RT_STREAM_FORBIDDEN_DEFAULT)
│ │ rt_model_stream_ = 内部 stream,is_forbidden_stream_=true,is_inner_model_stream_=true
│ │ [davinci_model.cc:7654-7664]
│ │ 同步模式 + stream!=nullptr 时:
│ │ rt_model_stream_ = stream(用户 stream),is_inner_model_stream_=false
│ │ [davinci_model.cc:7644-7651]
│ │
│ └─ if is_forbidden_stream_ && is_inner_model_stream_:
│ aclmdlRIExecute(rt_model_handle_, streamSyncTimeout)
│ [davinci_model.cc:7748] ← 同步接口,内部做流同步
│ else:
│ aclmdlRIExecuteAsync(rt_model_handle_, rt_model_stream_)
│ [davinci_model.cc:7750] ← 异步接口,但同步模式下 stream 非空
│
└─ if stream != nullptr:
aclrtSynchronizeStreamWithTimeout(stream, handle->streamSyncTimeout)
[ge/api/acl/acl_model/model/model.cpp:1714-1720]
│ 仅当用户 stream 非空时执行显式流同步
│ stream=nullptr 时跳过(此时模型在内部 forbidden stream 上执行,
│ aclmdlRIExecute 内部已完成同步)
stream 处理:同步路径中 stream=nullptr 时,InitModelStream 创建内部 forbidden stream(RT_STREAM_FORBIDDEN_DEFAULT)[davinci_model.cc:7654-7664],模型在该内部 stream 上执行,通过 aclmdlRIExecute(同步接口)完成执行和同步 [davinci_model.cc:7748]。stream 非空时,rt_model_stream_ 设为用户 stream,通过 aclmdlRIExecuteAsync(异步接口)执行 [davinci_model.cc:7750],返回后 aclmdlExecuteV2Impl 额外调用 aclrtSynchronizeStreamWithTimeout 同步用户 stream [model.cpp:1714-1720]。
异步路径(aclmdlExecuteAsync)
aclmdlExecuteAsync(modelId, input, output, stream)
[ge/api/acl/acl_model/model/acl_model.cpp:340]
│
└─ aclmdlExecuteAsyncImpl(modelId, input, output, stream)
[ge/api/acl/acl_model/model/model.cpp:1730]
│
└─ ModelExecute(modelId, input, output, basync=true, stream)
[ge/api/acl/acl_model/model/model.cpp:1740] → [model.cpp:710]
│
└─ executor.ExecModel(modelId, stream, inputData, ..., basync=true)
[ge/api/acl/acl_model/model/model.cpp:807]
→ [ge/runtime/v1/executor/ge_executor.cc:1170]
│
└─ GraphLoader::ExecuteModel(model_id, stream, async_mode=true, ...)
[ge/runtime/v1/executor/ge_executor.cc:1201]
→ [ge/runtime/v1/graph/load/graph_loader.cc:181]
│
└─ ModelManager::ExecuteModel(model_id, stream, async_mode=true, ...)
[ge/runtime/v1/graph/load/model_manager/model_manager.cc:1761]
│
└─ davinci_model->NnExecute(stream, async_mode=true, ...)
[ge/runtime/v1/graph/load/model_manager/model_manager.cc:1775]
→ [ge/runtime/v1/graph/load/model_manager/davinci_model.cc:7702]
│
├─ InitModelStream(stream)
│ [davinci_model.cc:7711] → [davinci_model.cc:7625]
│ 异步模式:
│ 销毁内部 stream(若存在),rt_model_stream_ = stream(用户 stream)
│ is_inner_model_stream_ = false
│ [davinci_model.cc:7632-7640]
│ stream=nullptr 时 rt_model_stream_ = nullptr
│
└─ aclmdlRIExecuteAsync(rt_model_handle_, rt_model_stream_)
[davinci_model.cc:7750]
│ rt_model_stream_ = 用户 stream(可能为 nullptr)
│ → aclmdlRIExecuteAsyncImpl [runtime/src/acl/aclrt_impl/model_ri.cpp:25]
│ → rtModelExecute(modelRI, stream, 0U) [model_ri.cpp:30]
│ → ApiImpl::ModelExecute → mdl->Execute(stm)
│ stream=nullptr 由 Runtime 侧按默认 Stream 语义解释
stream 处理:异步路径中 InitModelStream 直接将 rt_model_stream_ 设为用户 stream [davinci_model.cc:7638],stream=nullptr 时 rt_model_stream_ 为 nullptr。随后 aclmdlRIExecuteAsync(rt_model_handle_, rt_model_stream_) 将 stream 透传给 Runtime API rtModelExecute [model_ri.cpp:30],由 Runtime 侧按默认 Stream 语义解释。返回后不做额外流同步,调用者需自行同步。
2.4.3 ACL_C 管线(aclmdlExecuteAsyncV2)
aclmdlExecuteAsyncV2 走独立的 ACL_C 管线,与标准 C++ 管线无交汇。该接口在 SetExecHandle 阶段对 workspace 有强依赖,stream=nullptr 且 handle 未提供 workspace 时直接返回 ACL_ERROR_INVALID_PARAM。
2.4.3.1 完整调用栈
aclmdlExecuteAsyncV2(modelId, input, output, stream, handle)
[ge/api/acl_c/acl_model/model/model.c:173]
│ stream 作为参数传入
│
└─ ModelExecute(modelId, input, output, sync=false, stream, handle)
[ge/api/acl_c/acl_model/model/model.c:116]
│
├─ SetExecHandle(handle, stream, &execDesc)
│ [ge/api/acl_c/acl_model/model/model.c:121] → [model.c:88]
│ │ workspace 获取逻辑(优先级从高到低):
│ │ 1. handle != NULL && handle->workPtr != NULL → workPtr = handle->workPtr [model.c:91-95]
│ │ 2. workPtr == NULL && stream != NULL → rtStreamGetWorkspace(stream, &workPtr, &workSize) [model.c:97-98]
│ │ 3. workPtr == NULL → 返回 ACL_ERROR_INVALID_PARAM [model.c:100-103]
│ │
│ │ stream=nullptr 时:
│ │ 跳过步骤 2(stream==NULL 条件不满足)
│ │ 若 handle 未提供 workPtr → 步骤 3 报错,直接返回 ACL_ERROR_INVALID_PARAM
│ │ 若 handle 提供了 workPtr → 继续执行
│ │
│ │ execDesc.stream = stream [model.c:104] ← stream 直接赋值透传
│ │ execDesc.workPtr = workPtr [model.c:105]
│ │ execDesc.workSize = workSize [model.c:106]
│
└─ ExecModel(modelId, &execDesc, sync=false, input, output)
[ge/api/acl_c/acl_model/model/model.c:127]
→ [ge/runtime/c/executor/ge_executor.c:122]
│
└─ ModelExecuteInner(modelId, execDesc, sync, input, output)
[ge/runtime/c/executor/ge_executor.c:127]
→ [ge/runtime/c/executor/model_executor.c:116]
│ 获取 GeModelDesc
│
└─ ModelExecute(execDesc, sync, input, output, mdlDesc)
[ge/runtime/c/executor/model_executor.c:122](调用点)→ [model_executor.c:72](定义点)
│
├─ rtStreamGetSqid(execDesc->stream, &sqId)
│ [model_executor.c:75]
│ ★ 关键转换:stream 被转换为 sqId(uint32_t)
│ stream=nullptr 时:rtStreamGetSqid(nullptr, &sqId) 由 Runtime 侧处理
│ (Runtime 可能返回错误,或按默认 Stream 解释,取决于 Runtime 实现)
│
├─ 构造 rtMdlExecute_t:
│ [model_executor.c:93-106]
│ rtMdlExec.sqid = sqId ← 只有 sqId,没有 stream 指针
│ rtMdlExec.workPtr = execDesc->workPtr
│ rtMdlExec.sync = sync
│ ...(其他字段)
│
└─ rtNanoModelExecute(&rtMdlExec)
[model_executor.c:108]
│ 最终 Runtime API,只接收 rtMdlExecute_t(含 sqId,不含 stream 指针)
│ stream 已在上一跳被转换为 sqId
stream 处理:
-
workspace 强依赖:
SetExecHandle[model.c:88] 强制检查 workspace。stream=nullptr 时无法通过rtStreamGetWorkspace获取 workspace,若 handle 也未提供workPtr,直接返回ACL_ERROR_INVALID_PARAM[model.c:100-103]。这是 ACL_C 管线与标准 C++ 管线的关键差异——标准 C++ 管线无此检查。 -
stream 转换为 sqId:stream 在
model_executor.c:75处通过rtStreamGetSqid(execDesc->stream, &sqId)转换为 sqId,此后原始 stream 指针不再传递给底层 Runtime API。rtNanoModelExecute接收的rtMdlExecute_t中只有sqid字段,没有 stream 指针 [model_executor.c:94]。 -
stream=nullptr 的两种可能:
- 若 handle 提供了
workPtr:SetExecHandle通过,但rtStreamGetSqid(nullptr, &sqId)的行为取决于 Runtime 实现——可能返回错误,也可能按默认 Stream 解释。 - 若 handle 未提供
workPtr:SetExecHandle直接返回ACL_ERROR_INVALID_PARAM,不会到达rtStreamGetSqid。
- 若 handle 提供了
2.4.4 总体结论
3 个接口共 7 条路径,stream=nullptr 时的行为汇总如下:
| 接口 | 路径 | stream=nullptr 行为 | 结果 |
|---|---|---|---|
| aclmdlExecuteV2 | OM2 同步 | 用户 stream 未使用,SO 内部 stream 执行+同步 | 正常执行 |
| aclmdlExecuteV2 | RuntimeV2 同步 | 用户 stream 仅用于 profiling,内部 default_stream_ 执行+同步 |
正常执行 |
| aclmdlExecuteV2 | RT1 同步 | InitModelStream 创建内部 forbidden stream,aclmdlRIExecute 同步执行 |
正常执行 |
| aclmdlExecuteAsync | OM2 异步 | stream 透传给 SO 的 RunAsync |
待确定(取决于 SO 内部处理) |
| aclmdlExecuteAsync | RuntimeV2 异步 | stream 写入 streams[0],透传给 kernel |
取决于 kernel 类型:AICore/DSA/STARS 报错,AICPU 透传到 Runtime |
| aclmdlExecuteAsync | RT1 异步 | stream 透传给 aclmdlRIExecuteAsync → rtModelExecute |
待确定(取决于 Runtime 层处理) |
| aclmdlExecuteAsyncV2 | ACL_C | handle 未提供 workPtr:SetExecHandle 返回 ACL_ERROR_INVALID_PARAM;handle 提供 workPtr:通过 workspace 检查,stream=nullptr 透传给 rtStreamGetSqid,由 Runtime 侧处理 |
取决于 handle 是否提供 workPtr:未提供则报错;提供则待确定(取决于 Runtime 层处理) |
2.5 第二章总结
15 个接口按 4 类分组,stream=nullptr 的行为汇总如下:
| 类别 | 接口 | 汇聚点 | stream=nullptr 结果 |
|---|---|---|---|
| 单算子执行类 | aclopExecute / aclopExecuteV2 / aclopCast | OpExecutor::ExecuteAsync(AclOp) |
取决于分支和 kernel 类型(见下表) |
| 单算子执行类 | aclopExecWithHandle | OpExecutor::ExecuteAsync(OpHandle) |
取决于分支(见下表) |
| 编译并执行类 | aclopCompileAndExecute / aclopCompileAndExecuteV2 | OpExecutor::ExecuteAsync(AclOp) |
同单算子执行类 |
| BLAS 类 | aclblasGemvEx / aclblasHgemv / aclblasS8gemv / aclblasGemmEx / aclblasHgemm / aclblasS8gemm | aclopExecuteV2 → OpExecutor::ExecuteAsync(AclOp) |
同单算子执行类 |
| 模型执行类 | aclmdlExecuteV2 | OM2/RuntimeV2/RT1 同步路径 | 正常执行(3 条路径均使用内部 stream) |
| 模型执行类 | aclmdlExecuteAsync | OM2/RuntimeV2/RT1 异步路径 | 待确定(stream 透传给底层,取决于 SO/Runtime/kernel 处理) |
| 模型执行类 | aclmdlExecuteAsyncV2 | ACL_C 管线 | 取决于 handle:未提供 workPtr 则报错;提供则待确定 |
单算子类(含编译执行类、BLAS 类)的 3 条分支细化:
| 分支 | 触发条件 | stream=nullptr 结果 |
|---|---|---|
| A(动态内核选择器) | HasSelectFunc 为 true |
透传到 Runtime(rtKernelLaunch 收到 nullptr) |
| B(RT2)- AICore/DSA/STARS | IsRuntimeV2Enable 且 isDynamic |
报错(GE 层 FE_ASSERT_NOTNULL(stream)) |
| B(RT2)- AICPU | 同上 | 透传到 Runtime |
| C(RT1,静态 TBE) | 默认或静态模型 | 透传到 Runtime(rtKernelLaunchWithFlagV2 收到 nullptr) |
全局前提:以上分析均假设当前线程已设置有效 Context。stream=nullptr 时多处调用 aclrtCtxGetCurrentDefaultStream 获取默认 Stream,若无有效 Context 则直接报错。多线程场景不支持使用默认 Stream。