CANN/ops-transformer Flash Attention梯度V3
aclnnFlashAttentionScoreGradV3【免费下载链接】ops-transformer本项目是CANN提供的transformer类大模型算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-transformer产品支持情况产品是否支持Ascend 950PR/Ascend 950DT×Atlas A3 训练系列产品/Atlas A3 推理系列产品√Atlas A2 训练系列产品/Atlas A2 推理系列产品√Atlas 200I/500 A2 推理产品×Atlas 推理系列产品×Atlas 训练系列产品×功能说明训练场景下计算注意力的反向输出即aclnnFlashAttentionScoreV3的反向计算。该接口相较于aclnnFlashAttentionScoreGradV2接口新增sinkInOptional参数和dsinkOut输出Ascend 950PR/Ascend 950DT 暂不支持sinkInOptional参数和dsinkOut输出。pseType1时与aclnnFlashAttentionScoreGrad实现相同。pseType其他取值时需要先mul再add。$$ YDropout(Softmax(Mask(\frac{QK^T}{\sqrt{d}}pse),atten_mask),keep_prob)V $$为方便表达以变量$S$和$P$表示计算公式$$ SMask(\frac{QK^T}{\sqrt{d}}pse),atten_mask $$$$ PDropout(Softmax(S),keep_prob) $$$$ YPV $$则注意力的反向计算公式为$$ VP^TdY $$$$ Q\frac{((dS)*K)}{\sqrt{d}} $$$$ K\frac{((dS)^T*Q)}{\sqrt{d}} $$其中增加sink之后计算逻辑见下主要修改相关softmax_max和softmax_sum逻辑计算部分$$ S Q K^{T} $$$$ m max(sink, max(S)) $$$$ Attention \frac{e^{S - m} V}{\sum e^{S-m} e^{sink - m}} $$$$ dSink reduce(-P * dP * SimpleSoftmax(sink, x_max, x_sum)) $$函数原型每个算子分为两段式接口 必须先调用“aclnnFlashAttentionScoreGradV3GetWorkspaceSize”接口获取计算所需workspace大小以及包含了算子计算流程的执行器再调用“aclnnFlashAttentionScoreGradV3”接口执行计算。aclnnStatus aclnnFlashAttentionScoreGradV3GetWorkspaceSize( const aclTensor *query, const aclTensor *keyIn, const aclTensor *value, const aclTensor *dy, const aclTensor *pseShiftOptional, const aclTensor *dropMaskOptional, const aclTensor *paddingMaskOptional, const aclTensor *attenMaskOptional, const aclTensor *softmaxMaxOptional, const aclTensor *softmaxSumOptional, const aclTensor *softmaxInOptional, const aclTensor *attentionInOptional, const aclTensor *sinkInOptional, const aclIntArray *prefixOptional, const aclIntArray *qStartIdxOptional, const aclIntArray *kvStartIdxOptional, double scaleValue, double keepProb, int64_t preTokens, int64_t nextTokens, int64_t headNum, char *inputLayout, int64_t innerPrecise, int64_t sparseMode, int64_t pseType, const aclTensor *dqOut, const aclTensor *dkOut, const aclTensor *dvOut, const aclTensor *dpseOut, const aclTensor *dsinkOut, uint64_t *workspaceSize, aclOpExecutor **executor)aclnnStatus aclnnFlashAttentionScoreGradV3( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, const aclrtStream stream)aclnnFlashAttentionScoreGradV3GetWorkspaceSize参数说明参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续Tensorquery输入公式中的Q。数据类型与keyIn/value一致。FLOAT16、BFLOAT16ND[BNSD]、[BSND]、[BSH]、[SBH]√keyIn输入公式中的K。数据类型与query/value一致。FLOAT16、BFLOAT16ND[BNSD]、[BSND]、[BSH]、[SBH]√value输入公式中的V。数据类型与query/keyIn一致。FLOAT16、BFLOAT16ND[BNSD]、[BSND]、[BSH]、[SBH]√dy输入公式中的dY。-FLOAT16、BFLOAT16ND[BNSD]、[BSND]、[BSH]、[SBH]√pseShiftOptional可选输入公式中的pse。数据类型与query的数据类型一致该参数需要与pseType配套使用。FLOAT16、BFLOAT16ND[B,N,Sq,Skv]、[B,N,1,Skv]、[1,N,Sq,Skv]、[B,N,1024,Skv]、[1,N,1024,Skv]、[B,N]、[N]√dropMaskOptional可选输入公式中的Dropout。-UINT8ND0、1√paddingMaskOptional可选输入预留参数暂未使用。-----qStartIdxOptional可选输入代表外切场景当前分块的query的sequence在全局中的起始索引。-INT64ND0、1-kvStartIdxOptional可选输入代表外切场景当前分块的key和value的sequence在全局中的起始索引。-INT64ND0、1-attenMaskOptional可选输入公式中的atten_mask。取值为1代表该位不参与计算为0代表该位参与计算。BOOL、UINT8ND[B,N,Sq,Skv]、[B,1,Sq,Skv]、[1,1,Sq,Skv]、[Sq,Skv]√softmaxMaxOptional可选输入注意力正向计算的中间输出。-FLOATND[B,N,Sq,8]√softmaxSumOptional可选输入注意力正向计算的中间输出。-FLOATND[B,N,Sq,8]√softmaxInOptional可选输入注意力正向计算的中间输出。预留参数暂未使用。----attentionInOptional可选输入注意力正向的最终输出。数据类型和shape与query一致。FLOAT16、BFLOAT16ND[BNSD]、[BSND]、[BSH]、[SBH]√sinkInOptional可选输入公式中的sink。长度是headNum。FLOAT32ND[headNum]√prefixOptional可选输入prefix稀疏场景每个Batch的N。-INT64ND0、1-scaleValue可选输入公式中的scale缩放系数。-DOUBLE---keepProb可选输入dropMask中1的比例。-DOUBLE---preTokens可选输入稀疏计算窗口左边界。-INT64---nextTokens可选输入稀疏计算窗口右边界。-INT64---headNum输入单卡head个数对应query的N轴。-INT64---inputLayout输入query/key/value的数据排布格式。支持BSH、SBH、BSND、BNSD。String---innerPrecise可选输入内部计算精度控制。保留参数暂未使用。INT64---sparseMode可选输入稀疏模式。支持配置值0~6。INT64---pseType可选输入pse类型。支持配置值0~3。INT64---dqOut输出公式中的dQquery的梯度。-FLOAT16、BFLOAT16ND[BNSD]、[BSND]、[BSH]、[SBH]√dkOut输出公式中的dKkeyIn的梯度。-FLOAT16、BFLOAT16ND[BNSD]、[BSND]、[BSH]、[SBH]√dvOut输出公式中的dVvalue的梯度。-FLOAT16、BFLOAT16ND[BNSD]、[BSND]、[BSH]、[SBH]√dpseOut输出d(pse)梯度。保留参数暂未使用。----dsinkOut输出公式中dSinkd(sinkInOptional)梯度。-FLOAT32ND[headNum]√workspaceSize输出返回Device侧需要申请的workspace大小。-----executor输出返回算子执行器包含计算流程。-----返回值返回aclnnStatus状态码具体参见aclnn返回码。第一段接口完成入参校验出现以下场景时报错返回值错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入参数是必选输入输出或者必选属性且是空指针。ACLNN_ERR_PARAM_INVALID161002query、keyIn、value、dy、pseShiftOptional、dropMaskOptional、paddingMaskOptional、attenMaskOptional、softmaxMaxOptional、softmaxSumOptional、softmaxInOptional、attentionInOptional、sinkInOptional、dqOut、dkOut、dvOut、dsinkOut的数据类型不在支持的范围内。query、keyIn、value、dy、pseShiftOptional、dropMaskOptional、paddingMaskOptional、attenMaskOptional、softmaxMaxOptional、softmaxSumOptional、softmaxInOptional、attentionInOptional、sinkInOptional、dqOut、dkOut、dvOut、dsinkOut的数据格式不在支持的范围内。aclnnFlashAttentionScoreGradV3参数说明参数名输入/输出描述workspace输入在Device侧申请的workspace内存地址。workspaceSize输入在Device侧申请的workspace大小由第一段接口aclnnFlashAttentionScoreGradV3GetWorkspaceSize获取。executor输入op执行器包含了算子计算流程。stream输入指定执行任务的Stream。返回值返回aclnnStatus状态码具体参见aclnn返回码。约束说明确定性计算aclnnFlashAttentionScoreGradV3默认非确定性实现支持通过aclrtCtxSetSysParamOpt开启确定性。该接口与PyTorch配合使用时需要保证CANN相关包与PyTorch相关包的版本匹配。输入query、key、value、dy的Bbatchsize必须相等。输入query、key、value的DHead-Dim必须满足(qD kD kD vD)。输入query、key、value、dy的inputLayout必须一致。输入key/value的shape除D外必须一致在query/key/value的D大小相同的情况下query/dy的shape必须一致。支持输入query/dy的N和key/value的N不相等但必须成比例关系即Nq/Nkv必须是非0整数Nq取值范围1~256。关于数据shape的约束以inputLayout的BSND、BNSD为例BSH、SBH下HN*D其中B取值范围为1~2M。带prefixOptional的时候B最大支持2K。N取值范围为1~256。S取值范围为1~1M。D取值范围为1~768。KeepProb: 取值范围为(0, 1].query、key、value数据排布格式支持从多种维度解读其中BBatch表示输入样本批量大小、SSeq-Length表示输入样本序列长度、HHead-Size表示隐藏层的大小、NHead-Num表示多头数、DHead-Dim表示隐藏层最小的单元尺寸且满足DH/N。pseShiftOptional如果Sq大于1024且每个batch的Sq与Skv等长且是sparseMode为0、2、3的下三角掩码场景可使能alibi位置编码压缩此时只需要输入原始PSE最后1024行实现内存优化即alibi_compress ori_pse[:, :, -1024:, :]具体如下参数每个batch不相同时shape为BNHSkv(H1024)。每个batch相同时shape为1NHSkv(H1024)。如果pseType为2或3的时候数据类型需为FLOAT32, 对应shape支持范围是[B,N]或[N]。如果不使能该参数pseShiftOptional需要传入nullptrpseType需要传入1。innerPrecise: 当前0、1为保留配置值2为使能无效行计算其功能是避免在计算过程中存在整行mask进而导致精度有损失但是该配置会导致性能下降。 如果算子可判断出存在无效行场景会自动使能无效行计算例如sparseMode为3Sq Skv场景。pseType 各个取值含义pseType含义备注0外部传入pse 先mul再add-1外部传入pse 先add再mul跟FlashAttentionScoreGrad实现一致。2内部生成pse 先mul再add-3内部生成pse 先mul再add再sqrt-sparseMode的约束如下:当所有的attenMaskOptional的shape小于2048且相同的时候建议使用default模式来减少内存使用量配置为1、2、3、5时用户配置的preTokens、nextTokens不会生效配置为0、4时须保证attenMaskOptional与preTokens、nextTokens的范围一致。用户不特意指定时建议传入0。sparse不同模式的详细说明请参见sparse模式说明。部分场景下如果计算量过大可能会导致算子执行超时aicore error类型报错errorStr为timeout or trap error 此时建议做轴切分处理注这里的计算量会受B、S、N、D等参数的影响值越大计算量越大。关于softmaxMax与softmaxSum参数的约束输入格式固定为[B, N, S, 8],TND的输入格式除外此时为[T, N, 8],注TB*S。headNum的取值必须和传入的Query中的N值保持一致。band场景preTokens和nextTokens之间必须要有交集。prefixOptional稀疏计算场景即sparseMode5或者sparseMode6当Sq Skv时prefix的N值取值范围[0, Skv]当Sq Skv时prefix的N值取值范围[Skv-Sq, Skv]。pseShiftOptional中的Sq在大于1024场景下且此时shape取值为BNHS或1NHS时需要满足Sq和Skv等长。sinkInOptional维度为1长度需要与query的headNum相同。调用示例调用示例代码如下仅供参考具体编译和执行过程请参考编译与运行样例。#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_flash_attention_score_grad.h #define CHECK_RET(cond, return_expr) \ do { \ if (!(cond)) { \ return_expr; \ } \ } while (0) #define LOG_PRINT(message, ...) \ do { \ printf(message, ##__VA_ARGS__); \ } while (0) int64_t GetShapeSize(const std::vectorint64_t shape) { int64_t shapeSize 1; for (auto i : shape) { shapeSize * i; } return shapeSize; } void PrintOutResult(std::vectorint64_t shape, void** deviceAddr) { auto size GetShapeSize(shape); std::vectorfloat resultData(size, 0); auto ret aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), *deviceAddr, size * sizeof(resultData[0]), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(copy result from device to host failed. ERROR: %d\n, ret); return); for (int64_t i 0; i size; i) { LOG_PRINT(mean result[%ld] is: %f\n, i, resultData[i]); } } int Init(int32_t deviceId, aclrtStream* stream) { // 固定写法资源初始化 auto ret aclInit(nullptr); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclInit failed. ERROR: %d\n, ret); return ret); ret aclrtSetDevice(deviceId); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtSetDevice failed. ERROR: %d\n, ret); return ret); ret aclrtCreateStream(stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtCreateStream failed. ERROR: %d\n, ret); return ret); return 0; } template typename T int CreateAclTensor(const std::vectorT hostData, const std::vectorint64_t shape, void** deviceAddr, aclDataType dataType, aclTensor** tensor) { auto size GetShapeSize(shape) * sizeof(T); // 调用aclrtMalloc申请device侧内存 auto ret aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtMalloc failed. ERROR: %d\n, ret); return ret); // 调用aclrtMemcpy将host侧数据拷贝到device侧内存上 ret aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtMemcpy failed. ERROR: %d\n, ret); return ret); // 计算连续tensor的strides std::vectorint64_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() { // 1. 固定写法device/stream初始化参考acl API手册 // 根据自己的实际device填写deviceId int32_t deviceId 0; aclrtStream stream; auto ret Init(deviceId, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(Init acl failed. ERROR: %d\n, ret); return ret); // 2. 构造输入与输出需要根据API的接口自定义构造 std::vectorint64_t qShape {256, 1, 128}; std::vectorint64_t kShape {256, 1, 128}; std::vectorint64_t vShape {256, 1, 128}; std::vectorint64_t dxShape {256, 1, 128}; std::vectorint64_t attenmaskShape {256, 256}; std::vectorint64_t softmaxMaxShape {1, 1, 256, 8}; std::vectorint64_t softmaxSumShape {1, 1, 256, 8}; std::vectorint64_t attentionInShape {256, 1, 128}; std::vectorint64_t sinkInOptionalShape {1}; std::vectorint64_t dqShape {256, 1, 128}; std::vectorint64_t dkShape {256, 1, 128}; std::vectorint64_t dvShape {256, 1, 128}; std::vectorint64_t dsinkShape {1}; void* qDeviceAddr nullptr; void* kDeviceAddr nullptr; void* vDeviceAddr nullptr; void* dxDeviceAddr nullptr; void* attenmaskDeviceAddr nullptr; void* softmaxMaxDeviceAddr nullptr; void* softmaxSumDeviceAddr nullptr; void* attentionInDeviceAddr nullptr; void* sinkInOptionalDeviceAddr nullptr; void* dqDeviceAddr nullptr; void* dkDeviceAddr nullptr; void* dvDeviceAddr nullptr; void* dsinkDeviceAddr nullptr; aclTensor* q nullptr; aclTensor* k nullptr; aclTensor* v nullptr; aclTensor* dx nullptr; aclTensor* pse nullptr; aclTensor* dropMask nullptr; aclTensor* padding nullptr; aclTensor* attenmask nullptr; aclTensor* softmaxMax nullptr; aclTensor* softmaxSum nullptr; aclTensor* softmaxIn nullptr; aclTensor* attentionIn nullptr; aclTensor* sinkInOptional nullptr; aclTensor* dq nullptr; aclTensor* dk nullptr; aclTensor* dv nullptr; aclTensor* dpse nullptr; aclTensor* dsink nullptr; std::vectorfloat qHostData(32768, 1); std::vectorfloat kHostData(32768, 1); std::vectorfloat vHostData(32768, 1); std::vectorfloat dxHostData(32768, 1); std::vectoruint8_t attenmaskHostData(65536, 0); std::vectorfloat softmaxMaxHostData(2048, 3.0); std::vectorfloat softmaxSumHostData(2048, 3.0); std::vectorfloat attentionInHostData(32768, 1); std::vectorfloat sinkInOptionalHostData(1, 0); std::vectorfloat dqHostData(32768, 0); std::vectorfloat dkHostData(32768, 0); std::vectorfloat dvHostData(32768, 0); std::vectorfloat dsinkHostData(1, 0); ret CreateAclTensor(qHostData, qShape, qDeviceAddr, aclDataType::ACL_FLOAT16, q); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(kHostData, kShape, kDeviceAddr, aclDataType::ACL_FLOAT16, k); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(vHostData, vShape, vDeviceAddr, aclDataType::ACL_FLOAT16, v); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(dxHostData, dxShape, dxDeviceAddr, aclDataType::ACL_FLOAT16, dx); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(attenmaskHostData, attenmaskShape, attenmaskDeviceAddr, aclDataType::ACL_UINT8, attenmask); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(softmaxMaxHostData, softmaxMaxShape, softmaxMaxDeviceAddr, aclDataType::ACL_FLOAT, softmaxMax); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(softmaxSumHostData, softmaxSumShape, softmaxSumDeviceAddr, aclDataType::ACL_FLOAT, softmaxSum); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(attentionInHostData, attentionInShape, attentionInDeviceAddr, aclDataType::ACL_FLOAT16, attentionIn); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(sinkInOptionalHostData, sinkInOptionalShape, sinkInOptionalDeviceAddr, aclDataType::ACL_FLOAT, sinkInOptional); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(dqHostData, dqShape, dqDeviceAddr, aclDataType::ACL_FLOAT16, dq); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(dkHostData, dkShape, dkDeviceAddr, aclDataType::ACL_FLOAT16, dk); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(dvHostData, dvShape, dvDeviceAddr, aclDataType::ACL_FLOAT16, dv); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(dsinkHostData, dsinkShape, dsinkDeviceAddr, aclDataType::ACL_FLOAT, dsink); CHECK_RET(ret ACL_SUCCESS, return ret); std::vectorint64_t prefixOp {0}; aclIntArray *prefix aclCreateIntArray(prefixOp.data(), 1); std::vectorint64_t qStartIdxOp {0}; std::vectorint64_t kvStartIdxOp {0}; aclIntArray *qStartIdx aclCreateIntArray(qStartIdxOp.data(), 1); aclIntArray *kvStartIdx aclCreateIntArray(kvStartIdxOp.data(), 1); double scaleValue 0.088388; double keepProb 1; int64_t preTokens 65536; int64_t nextTokens 65536; int64_t headNum 1; int64_t innerPrecise 0; int64_t sparseMode 0; int64_t pseType 1; char layOut[5] {S, B, H, 0}; // 3. 调用CANN算子库API需要修改为具体的Api名称 uint64_t workspaceSize 0; aclOpExecutor* executor; // 调用aclnnFlashAttentionScoreGradV3第一段接口 ret aclnnFlashAttentionScoreGradV3GetWorkspaceSize(q, k, v, dx, pse, dropMask, padding, attenmask, softmaxMax, softmaxSum, softmaxIn, attentionIn, sinkInOptional, prefix, qStartIdx, kvStartIdx, scaleValue, keepProb, preTokens, nextTokens, headNum, layOut, innerPrecise, sparseMode, pseType, dq, dk, dv, dpse, dsink, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnFlashAttentionScoreGradV3GetWorkspaceSize failed. ERROR: %d\n, ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 void* workspaceAddr nullptr; if (workspaceSize 0) { ret aclrtMalloc(workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(allocate workspace failed. ERROR: %d\n, ret); return ret); } // 调用aclnnFlashAttentionScoreGradV3第二段接口 ret aclnnFlashAttentionScoreGradV3(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnFlashAttentionScoreGradV3 failed. ERROR: %d\n, ret); return ret); // 4. 固定写法同步等待任务执行结束 ret aclrtSynchronizeStream(stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtSynchronizeStream failed. ERROR: %d\n, ret); return ret); // 5. 获取输出的值将device侧内存上的结果拷贝至host侧需要根据具体API的接口定义修改 PrintOutResult(dqShape, dqDeviceAddr); PrintOutResult(dkShape, dkDeviceAddr); PrintOutResult(dvShape, dvDeviceAddr); PrintOutResult(dsinkShape, dsinkDeviceAddr); // 6. 释放aclTensor和aclScalar需要根据具体API的接口定义修改 aclDestroyTensor(q); aclDestroyTensor(k); aclDestroyTensor(v); aclDestroyTensor(dx); aclDestroyTensor(attenmask); aclDestroyTensor(softmaxMax); aclDestroyTensor(softmaxSum); aclDestroyTensor(attentionIn); aclDestroyTensor(sinkInOptional); aclDestroyTensor(dq); aclDestroyTensor(dk); aclDestroyTensor(dv); aclDestroyTensor(dsink); // 7. 释放device资源 aclrtFree(qDeviceAddr); aclrtFree(kDeviceAddr); aclrtFree(vDeviceAddr); aclrtFree(dxDeviceAddr); aclrtFree(attenmaskDeviceAddr); aclrtFree(softmaxMaxDeviceAddr); aclrtFree(softmaxSumDeviceAddr); aclrtFree(attentionInDeviceAddr); aclrtFree(sinkInOptionalDeviceAddr); aclrtFree(dqDeviceAddr); aclrtFree(dkDeviceAddr); aclrtFree(dvDeviceAddr); aclrtFree(dsinkDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }【免费下载链接】ops-transformer本项目是CANN提供的transformer类大模型算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-transformer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考