任务更新
当Stream上的任务已经被捕获,并暂存到模型中之后,若要更新任务(包含任务本身以及任务的参数信息),当前支持两种方式:
- 方式一:以需要更新的任务为分界点,在aclmdlRICaptureBegin和aclmdlRICaptureEnd接口之间分别捕获该任务前后的任务、并暂存到不同的模型中,分开执行。
该方式适用于大量任务需要更新的场景(例如一个模型有两种不同Shape的input输入场景),接口调用逻辑比较简单,但导致暂存捕获任务的模型数量增多,若模型数量超出硬件资源限制,则会触发报错。
该方式的基本使用流程如下图所示:

- 方式二:在aclmdlRICaptureBegin、aclmdlRICaptureEnd接口之间下发主流上需捕获的任务,通过aclmdlRICaptureTaskGrpBegin、aclmdlRICaptureTaskGrpEnd接口将待更新的任务标记为在一个任务组中,并返回任务组的handle,在aclmdlRICaptureTaskUpdateBegin、aclmdlRICaptureTaskUpdateEnd接口之间更新任务。
该方式适用于少量单算子调用任务需要更新的场景,支持先更新任务再依次执行模型实例中的任务,也支持更新任务与模型实例中其他任务的并发执行。但更新任务比单独下发任务更耗时,另外,还存在一些使用限制:aclmdlRICaptureTaskGrpBegin、aclmdlRICaptureTaskGrpEnd接口之间的任务数量、任务类型,要与aclmdlRICaptureTaskUpdateBegin、aclmdlRICaptureTaskUpdateEnd接口之间的任务数量、任务类型一致;跨Stream捕获任务的场景下,在aclmdlRICaptureTaskGrpBegin、aclmdlRICaptureTaskGrpEnd接口之间,其它捕获状态的Stream上不允许同时下发任务;任务组类似一个临界资源,不支持多线程多Stream并发更新,否则会导致更新结果非预期。
- 对于“先更新任务,再依次执行aclmdlRI实例中的任务”的场景,使用流程如下图所示:

- 对于“更新任务与其他任务的并发执行”的场景,使用流程如下图所示:
若模型的运行实例中存在大量任务,为了提升性能,可使用external类型的Event实现更新任务与其他任务的并发执行,并且需再单独创建一个用于更新任务的Stream(下文称之为UpdateStream)。这里的external类型的Event,是指调用aclrtCreateEventWithFlag接口并设置flag为ACL_EVENT_EXTERNAL的Event,该类型的Event规格有限,且无法实现跨Stream的任务捕获,需要考虑合理复用。创建external类型的Event之后,在UpdateStream上下发更新任务,接着调用aclrtRecordEvent接口下发一个Event Record任务。然后,在主流中,在待更新的任务之前,调用aclrtStreamWaitEvent接口下发一个Event Wait任务,用于等待UpdateStream中的任务更新完成。最后,在主流中,调用aclrtStreamWaitEvent接口之后,再调用aclrtResetEvent接口重置external类型的Event。

以任务并发执行的场景为例,以下是更新aclnnAdd算子输入参数的关键代码示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
#include <stdio.h> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_add.h" #define ACL_LOG(fmt, args...) fprintf(stdout, "[INFO] " fmt "\n", ##args) int64_t GetShapeSize(const std::vector<int64_t> &shape) { int64_t shape_size = 1; for (auto i : shape) { shape_size *= i; } return shape_size; } int CreateAclTensor(const std::vector<int64_t> &shape, void **deviceAddr, aclDataType dataType, aclTensor **tensor) { auto size = GetShapeSize(shape) * sizeof(float); // 申请Device侧内存 auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); // 计算连续tensor的stride std::vector<int64_t> strides(shape.size(), 1); for (int64_t i = shape.size() - 2; i >= 0; i--) { strides[i] = shape[i + 1] * strides[i + 1]; } // 调用aclCreateTensor接口创建aclTensor *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); return 0; } int main() { int devID = 0; void *self_d = nullptr; void *other_d = nullptr; void *out_d = nullptr; void *outtmp_d = nullptr; aclTensor *self = nullptr; aclTensor *other = nullptr; aclScalar *alpha = nullptr; aclScalar *updatealpha = nullptr; aclTensor *out = nullptr; aclTensor *outtmp = nullptr; /* aclnnAdd: self = self + other * alpha */ float *self_h = nullptr; float *other_h = nullptr; std::vector<int64_t> shape = {4, 2}; float *out_h = nullptr; float alphaValue = 1.1f; float updatealphaValue = 5.5f; uint64_t workspaceSize = 0; uint64_t workspaceSize1 = 0; uint64_t workspaceSize2 = 0; aclOpExecutor *executor2; aclOpExecutor *executor; aclOpExecutor *executor1; auto size = GetShapeSize(shape); // 初始化 aclInit(NULL); // 指定计算设备 aclrtSetDevice(devID); // 准备aclnnAdd算子的输入、输出参数 CreateAclTensor(shape, &self_d, aclDataType::ACL_FLOAT, &self); CreateAclTensor(shape, &other_d, aclDataType::ACL_FLOAT, &other); alpha = aclCreateScalar(&alphaValue, aclDataType::ACL_FLOAT); updatealpha = aclCreateScalar(&updatealphaValue, aclDataType::ACL_FLOAT); CreateAclTensor(shape, &out_d, aclDataType::ACL_FLOAT, &out); CreateAclTensor(shape, &outtmp_d, aclDataType::ACL_FLOAT, &outtmp); // 调用aclnnAdd算子的第一段接口,获取算子计算所需的workspace大小以及包含了算子计算流程的执行器 // 后续涉及多次调用aclnnAdd算子,此处需调用多次第一段接口,获取不同的aclOpExecutor // outtmp = self + alpha * other // 更新前:out = outtmp + alpha * other 更新后:out = outtmp + updatealpha * other aclnnAddGetWorkspaceSize(self, other, alpha, outtmp, &workspaceSize, &executor); void *workspaceAddr = nullptr; if (workspaceSize > 0) { aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); } // 更新前:out = outtmp + alpha * other aclnnAddGetWorkspaceSize(outtmp, other, alpha, out, &workspaceSize1, &executor1); void *workspaceAddr1 = nullptr; if (workspaceSize1 > 0) { aclrtMalloc(&workspaceAddr1, workspaceSize1, ACL_MEM_MALLOC_HUGE_FIRST); } // 更新后:out = outtmp + updatealpha * other aclnnAddGetWorkspaceSize(outtmp, other, updatealpha, out, &workspaceSize2, &executor2); void *workspaceAddr2 = nullptr; if (workspaceSize2 > 0) { aclrtMalloc(&workspaceAddr2, workspaceSize2, ACL_MEM_MALLOC_HUGE_FIRST); } // 使用aclrtMallocHost申请锁页内存 aclrtMallocHost((void **)&self_h, size * sizeof(float)); aclrtMallocHost((void **)&other_h, size * sizeof(float)); aclrtMallocHost((void **)&out_h, size * sizeof(float)); for (int i = 0; i < 8; i++) { self_h[i] = static_cast<float>(0); other_h[i] = static_cast<float>(1); out_h[i] = static_cast<float>(0); } aclmdlRI modelRI; aclrtStream stream1; aclrtCreateStream(&stream1); aclrtEvent event; // 创建external类型的event aclrtCreateEventWithFlag(&event, ACL_EVENT_EXTERNAL); // ========开始捕获任务======== aclmdlRICaptureBegin(stream1, ACL_MODEL_RI_CAPTURE_MODE_GLOBAL); // 异步拷贝,将aclnnAdd算子self输入的数据从Host侧传到Device侧 aclrtMemcpyAsync(self_d, size * sizeof(float), self_h, size * sizeof(float), ACL_MEMCPY_HOST_TO_DEVICE, stream1); // 异步拷贝,将aclnnAdd算子other输入的数据从Host侧传到Device侧 aclrtMemcpyAsync(other_d, size * sizeof(float), other_h, size * sizeof(float), ACL_MEMCPY_HOST_TO_DEVICE, stream1); // 执行aclnnAdd算子 aclnnAdd(workspaceAddr, workspaceSize, executor, stream1); // 在主流stream1上,下发一个Event Wait任务,等待更新任务完成 aclrtStreamWaitEvent(stream1, event); aclrtResetEvent(event, stream1); aclrtTaskGrp handle; // 标记要更新的任务 aclmdlRICaptureTaskGrpBegin(stream1); aclnnAdd(workspaceAddr1, workspaceSize1, executor1, stream1); aclmdlRICaptureTaskGrpEnd(stream1, &handle); // 异步拷贝,将算子输出数据从Device侧传回Host侧 aclrtMemcpyAsync(out_h, size * sizeof(float), out_d, size * sizeof(float), ACL_MEMCPY_DEVICE_TO_HOST, stream1); // ========结束捕获任务======== aclmdlRICaptureEnd(stream1, &modelRI); aclrtStream updateStream; aclrtCreateStream(&updateStream); for (int i = 0; i < 2; i++) { ACL_LOG("execute model, loop: %d", i); aclmdlRIExecuteAsync(modelRI, stream1); // 开始更新任务,将aclnnAdd算子的alpha参数更新为updatealpha aclmdlRICaptureTaskUpdateBegin(updateStream, handle); if (i == 1) { aclnnAdd(workspaceAddr2, workspaceSize2, executor2, updateStream); ACL_LOG("update alpha value of aclnnAdd"); } aclmdlRICaptureTaskUpdateEnd(updateStream); // 更新任务之后,在updateStream上,下发Event Record任务,用于通知主流stream1继续执行Event Wait之后的任务 aclrtRecordEvent(event, updateStream); aclrtSynchronizeStream(updateStream); aclrtSynchronizeStream(stream1); ACL_LOG("%f %f %f %f %f %f %f %f\n", out_h[0], out_h[1], out_h[2], out_h[3], out_h[4], out_h[5], out_h[6], out_h[7]); } // 释放资源 aclmdlRIDestroy(modelRI); aclDestroyTensor(self); aclDestroyTensor(other); aclDestroyTensor(out); aclDestroyTensor(outtmp); aclDestroyScalar(alpha); aclDestroyScalar(updatealpha); aclrtFree(self_d); aclrtFree(other_d); aclrtFree(out_d); aclrtFree(outtmp_d); aclrtDestroyStream(stream1); aclrtDestroyStream(updateStream); aclrtDestroyEvent(event); if (workspaceAddr != nullptr) { aclrtFree(workspaceAddr); } if (workspaceAddr1 != nullptr) { aclrtFree(workspaceAddr1); } if (workspaceAddr2 != nullptr) { aclrtFree(workspaceAddr2); } // 释放计算设备的资源 aclrtResetDevice(devID); // 去初始化 aclFinalize(); }
- 对于“先更新任务,再依次执行aclmdlRI实例中的任务”的场景,使用流程如下图所示: