ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

CANN ops-nn 算子实战:aclnnMultilabelMarginLoss 多标签间隔损失计算详解

CANN ops-nn 算子实战:aclnnMultilabelMarginLoss 多标签间隔损失计算详解 CANN ops-nn 算子实战aclnnMultilabelMarginLoss 多标签间隔损失计算详解【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn本篇技术指南围绕 CANN 神经网络算子库 ops-nn 中loss/multilabel_margin_loss模块的 aclnn 接口aclnnMultilabelMarginLoss展开系统讲解其数学原理、产品支持范围、两段式接口调用方式、完整参数约束与 C 调用示例并结合仓库源码剖析其 shape 推导、tiling 切分与 Vector Core 内核实现。读完本文你将掌握如何在 Ascend NPU 上通过 aclnn 接口正确完成多标签分类场景的间隔损失multilabel margin loss计算并理解该算子在 CANN 分层架构中的完整落地链路。产品支持情况根据 aclnnMultilabelMarginLoss 接口文档 与 模块 README 的说明aclnnMultilabelMarginLoss在不同硬件产品上的支持情况如下产品是否支持Ascend 950PR / Ascend 950DT支持Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持Atlas 200I/500 A2 推理产品不支持Atlas 推理系列产品支持注接口文档标注该行不支持README 中标注√两者存在差异实际以对应版本发布清单为准Atlas 训练系列产品支持从源码看该算子的内核实现位于op_kernel/arch35/目录arch35 对应 Ascend 950 系列同时通过动态编译静态标志DynamicCompileStaticFlag(true)与DynamicShapeSupportFlag(true)支持多代产品的动态 shape 编译相关定义见 算子原型定义。功能说明与数学原理接口功能aclnnMultilabelMarginLoss用于计算多标签间隔损失multilabel margin loss。接口中self为输入预测分数shape 为(N, C)或(C)其中N表示 batch sizeC表示类别数target为真实标签shape 与输入一致每个元素取值范围为[-1, C - 1]用-1填充首个-1之前的标签代表该样本所属的真实标签集合yTrue。例如target [0, 3, -1, 1]则该样本真实标签yTrue [0, 3]输出isTarget为由target派生的 0/1 掩码istarget即target中有效标签对应位置为 1其余为 0。计算公式对每个样本先由target推导指示向量istarget$$ istarget[k]\begin{cases} 1, \text{k in yTrue}\ 0, \text{otherwise} \end{cases} $$每个样本的损失为所有真实标签对非真实标签的间隔罚项之和再除以类别数$$ l_n\sum^C_{j,istarget[j]1}\sum^C_{i,istarget[i]0} \frac{\max(0,1-x[j]x[i])}{C} $$当reduction为none时输出为逐样本损失向量$$ \ell(x, y) L {l_1,\dots,l_N}^\top $$当reduction不是none时$$ \ell(x, y) \begin{cases} \sum_{n1}^N \frac{1}{N} l_n, \text{if reduction} \text{mean;}\ \sum_{n1}^N l_n, \text{if reduction} \text{sum.} \end{cases} $$直观理解该损失鼓励真实标签对应的预测分数x[j]比非真实标签的预测分数x[i]至少高 1否则按差值累计罚项。与 PyTorch 语义对齐从 测试真值脚本 可以看到仓库将该算子的权威参照定为torch.ops.aten.multilabel_margin_loss_forwardaten forward即 golden 真值非 numpy 纯公式其中浮点输出y使用cross_check容差标准L1 级别is_target是承载掩码语义的整型/浮点输出采用binary_equal差 1 即为错不做 ±1 容忍空输入[0, C]且reductionsum/mean时aten会读取未初始化内存因此仓库按归约通用约定取值sum(空)0、mean(空)0/0nan与torch.empty(0).mean()语义一致见 golden.py 中_compute的注释说明。函数原型与两段式接口每个 CANN aclnn 算子都采用两段式接口设计必须先调用aclnnMultilabelMarginLossGetWorkspaceSize获取计算所需 workspace 大小以及包含算子计算流程的执行器executor再调用aclnnMultilabelMarginLoss执行计算。第一段接口获取 workspace 大小与执行器aclnnStatus aclnnMultilabelMarginLossGetWorkspaceSize( const aclTensor* self, const aclTensor* target, int64_t reduction, aclTensor* out, aclTensor* isTarget, uint64_t* workspaceSize, aclOpExecutor** executor)第二段接口执行计算aclnnStatus aclnnMultilabelMarginLoss( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)aclnnMultilabelMarginLossGetWorkspaceSize 参数详解参数说明参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorselfaclTensor*输入输入张量公式中的输入 xshape 为(N,C)或(C)N 表示 batch sizeC 表示类别数。当self中元素个数大于 15000×20000 时可能出现 507034 Vector Core 运行超时FLOAT、FLOAT16、BFLOAT16ND1、2√targetaclTensor*输入真实标签公式中的输入 yshape 为(N,C)或(C)元素取值范围[-1, C-1]用 -1 填充首个 -1 之前的标签代表样本所属真实标签INT32、INT64ND1、2√reductionint64_t输入指定要应用到输出的缩减公式中的参数 reduction支持 0(none) / 1(mean) / 2(sum)。none表示不应用缩减mean表示输出总和除以输出元素数sum表示输出被求和INT64--√outaclTensor*输出输出的 loss公式中的 ℓ(x,y)shape 为(N)或()与 self 保持一致ND0、1-isTargetaclTensor*输出公式中的输出 istargettarget 派生的 0/1 掩码shape 为(N,C)或(C)与 self 保持一致FLOAT、FLOAT16、BFLOAT16ND1、2√workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出返回 op 执行器包含算子计算流程-----关于reduction的取值源码 aclnn 实现 中的约定与此完全一致0none、1mean、2sum并通过GetReductionStr映射为字符串传给底层l0op::MultilabelMarginLoss。GE 图路径下reduction则是字符串属性none/mean/sum默认mean见算子原型定义与 tiling 属性解析。返回值与错误码第一段接口返回aclnnStatus状态码具体参见 aclnn 返回码说明。第一段接口完成入参校验出现以下场景时报错返回值错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 self、target、out、isTarget 为空指针ACLNN_ERR_PARAM_INVALID161002self 的数据类型不在支持范围之内仅支持 FLOAT/FLOAT16/BFLOAT16ACLNN_ERR_PARAM_INVALID161002out 或 isTarget 的数据类型与 self 不一致aclnn 路径校验 outself、isTargetself三者须同为同一浮点类型ACLNN_ERR_PARAM_INVALID161002target 的数据类型不在支持范围之内ACLNN_ERR_PARAM_INVALID161002self、target、isTarget 的 shape 不满足参数说明中的要求ACLNN_ERR_PARAM_INVALID161002out 的 shape 不满足参数说明中的要求从源码 CheckParams 的实现可以看到校验顺序先做空指针检查CheckNotNull再做数据类型检查CheckDtypeValid最后做 shape 一致性检查CheckShape。其中 dtype 校验按 SoC 分支区分910B 及后续代际支持 FLOAT/FLOAT16/BF16 三种浮点类型而 910 基线仅支持 FLOAT/FLOAT16见ASCEND910_DTYPE_SUPPORT_LIST与ASCEND910B_DTYPE_SUPPORT_LISTaclnn 实现target支持 INT32/INT64isTarget跟随self的 dtype对齐 torch 契约is_targetself。aclnnMultilabelMarginLoss 执行接口参数说明参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnMultilabelMarginLossGetWorkspaceSize 获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream返回值aclnnStatus返回状态码具体参见 aclnn 返回码说明。第二段接口的实现非常简洁——直接调用框架能力完成计算aclnnStatus aclnnMultilabelMarginLoss(void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream) { L2_DFX_PHASE_2(aclnnMultilabelMarginLoss); return CommonOpExecutorRun(workspace, workspaceSize, executor, stream); }参见 aclnn 实现。约束说明结合接口文档与源码实现aclnnMultilabelMarginLoss的约束如下确定性计算aclnnMultilabelMarginLoss默认是非确定性实现。跨核归并细节见下文内核实现部分不同核数下的执行顺序差异可能导致逐位结果略有不同。dtype 一致性isTarget数据类型跟随self0/1 掩码torch 契约is_targetselfself、out、isTarget三者须一致FLOAT/FLOAT16/BFLOAT16。这一点在 aclnn 实现 的CheckDtypeValid中有明确校验而 GE 图路径下is_target保持 IR 的 INT32与 A2 基线一致两种路径的 dtype 契约在 算子原型定义 中以独立重定义输入输出的方式区分。空 tensor 语义仅支持 Nbatch轴为空即[0,C]N0、C≥1类别维 C0[N,0]/[0,0]/1D[0]为非法输入返回 ACLNN_ERR_PARAM_INVALID(161002)。[0,C]时isTarget为空张量out取值遵循归约的通用约定reductionnone时 out 为空张量reductionsum时 out 为 0reductionmean时 out 为 nan0/0。shape 校验逻辑从源码CheckShapeaclnn 实现可以看出self为 2D 时要求target同为 2D 且两维相等self为 1D 时target维度数 ≤1 且元素数一致reductionnone且self为 2D 时要求outshape 为[N]其余情况out为单元素张量N0 时isTarget必须为空张量否则isTarget与targetshape 完全一致。性能提示当self中元素个数大于 15000×20000 时可能出现 507034 Vector Core 运行超时超大输入建议拆分成多个 batch 分别调用。完整调用示例以下示例代码摘自 test_aclnn_multilabel_margin_loss.cpp是aclnnMultilabelMarginLoss的标准调用流程示例输入为 shape[2,3]的 self 与 targetreduction0即 none 模式。具体编译与执行过程请参考 编译与运行样例。#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_multilabel_margin_loss.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; } 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 selfShape {2, 3}; std::vectorint64_t targetShape {2, 3}; std::vectorint64_t outShape {2}; std::vectorint64_t istargetShape {2, 3}; void* selfDeviceAddr nullptr; void* targetDeviceAddr nullptr; void* outDeviceAddr nullptr; void* istargetDeviceAddr nullptr; aclTensor* self nullptr; aclTensor* target nullptr; aclTensor* out nullptr; aclTensor* istarget nullptr; std::vectorfloat selfHostData {0, 1, 2, 3, 4, 5}; // target 每行真实标签后以 -1 填充第0行真实标签为{0,1}第1行为{2} std::vectorint32_t targetHostData {0, 1, -1, 2, -1, -1}; std::vectorfloat outHostData(2, 0); std::vectorfloat istargetHostData(6, 0); int64_t reduction 0; // 0none, 1mean, 2sum // 创建self aclTensor ret CreateAclTensor(selfHostData, selfShape, selfDeviceAddr, aclDataType::ACL_FLOAT, self); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建target aclTensor ret CreateAclTensor(targetHostData, targetShape, targetDeviceAddr, aclDataType::ACL_INT32, target); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建out aclTensor ret CreateAclTensor(outHostData, outShape, outDeviceAddr, aclDataType::ACL_FLOAT, out); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建istarget aclTensoraclnn 路径 dtype 跟随 self ret CreateAclTensor(istargetHostData, istargetShape, istargetDeviceAddr, aclDataType::ACL_FLOAT, istarget); CHECK_RET(ret ACL_SUCCESS, return ret); // 3. 调用CANN算子库API uint64_t workspaceSize 0; aclOpExecutor* executor; // 调用aclnnMultilabelMarginLoss第一段接口 ret aclnnMultilabelMarginLossGetWorkspaceSize(self, target, reduction, out, istarget, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnMultilabelMarginLossGetWorkspaceSize 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); } // 调用aclnnMultilabelMarginLoss第二段接口 ret aclnnMultilabelMarginLoss(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnMultilabelMarginLoss 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侧 auto outSize GetShapeSize(outShape); std::vectorfloat outData(outSize, 0); ret aclrtMemcpy(outData.data(), outData.size() * sizeof(outData[0]), outDeviceAddr, outSize * sizeof(outData[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 ret); for (int64_t i 0; i outSize; i) { LOG_PRINT(out[%ld] is: %f\n, i, outData[i]); } auto istargetSize GetShapeSize(istargetShape); std::vectorfloat istargetData(istargetSize, 0); ret aclrtMemcpy(istargetData.data(), istargetData.size() * sizeof(istargetData[0]), istargetDeviceAddr, istargetSize * sizeof(istargetData[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 ret); for (int64_t i 0; i istargetSize; i) { LOG_PRINT(istarget[%ld] is: %f\n, i, istargetData[i]); } // 6. 释放aclTensor和aclScalar aclDestroyTensor(self); aclDestroyTensor(target); aclDestroyTensor(out); aclDestroyTensor(istarget); // 7. 释放device资源 aclrtFree(selfDeviceAddr); aclrtFree(targetDeviceAddr); aclrtFree(outDeviceAddr); aclrtFree(istargetDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例要点解读两段式调用第一段接口完成参数校验与算子图编译返回workspaceSize与executor随后按workspaceSize申请 Device 侧内存并调用第二段接口执行最后通过aclrtSynchronizeStream同步等待结果并依次释放 tensor、workspace、stream 与 device 资源。非连续 Tensor 支持示例通过显式构造 strides 的方式创建aclTensor。self、target、isTarget均支持非连续输入——第一段接口内部会通过l0op::Contiguous将其转换为连续张量见 aclnn 实现因此用户无需手动做contiguous处理。空 tensor 快速路径当self为空张量[0,C]时第一段接口走空分支reductionnone直接返回out 为空sum/mean时通过FillEmptyReducedOut以l0op::FillViewCopy填充单元素 out 的标准归约值sum0、meannan避免调用方读到未初始化内存见 aclnn 实现。源码级实现剖析1. aclnn 层算子图组合与结果回写aclnn_multilabel_margin_loss.cpp 中aclnnMultilabelMarginLossGetWorkspaceSize的核心流程为参数校验空指针 → dtype → shapel0op::Contiguous归一化 self 与 target支持非连续输入target统一l0op::Cast为 INT32兼容 INT64 输入0 维输入通过l0op::UnsqueezeNd提升为 2 维以便统一处理调用l0op::MultilabelMarginLoss得到(outLoss, outIsTarget)二元组WriteBackOutputs将结果写回用户out/isTargetout经Cast对齐 self dtype 后ViewCopyisTarget在 A5DAV_3510 架构上由 kernel 直接产出 self dtype 直写非 A5 平台则经Cast(int32→self)后再回写以兼容 A2 基线内核产出 int32 掩码的行为。2. shape 推导infershapemultilabel_margin_loss_infershape.cpp 实现了 GE 图模式的 shape/类型推导InferShapeMultilabelMarginLossreductionnone且输入 2D 时输出y为 1D(N,)其余情况y为标量0 维。is_target的 shape 与输入x完全一致。动态 shape-1通过SetDim透传A5Ascend950下还额外处理了动态 rank-2的传播。InferDataType4MultilabelMarginLossy与x同类型is_target为 INT32与target同类型GE 图路径契约。对应的单测 test_multilabel_margin_loss_infershape.cpp 覆盖了 none/mean/sum × 2D/1D 以及动态 shape-1场景例如none_2d_y_eq_n_istarget_eq_x用例验证x{4,8}时y{4}、is_target{4,8}。3. tiling多核切分与 workspace 布局tiling 实现 为 arch35Ascend950/regbase独立 tiling关键点包括行方向batch 维分核按basePerCore pivot将 N 行均分到多核列方向C 维分块整行装不进 UB 时按cFactor切 C 维TILE_ALIGN16、TILE_MIN16、MAX_VEC_ELEMS8192等常量控制分块粒度使内核对 C 的支持面不再受单行必须整条驻留 UB限制workspace 两种布局reductionnone时每行一个 float 槽各核原子加写入mean/sum时每核独占一个 32B 槽WS_CORE_STRIDE8个 float核 0 按固定 blockIdx 顺序做 Kahan 补偿合并保证结果可复现且精度更高。4. Vector Core 内核精度与确定性kernel 实现 中的KernelMultilabelMarginLoss内核模板针对(T, IsTgtT)泛型化T 为 float/half/bf16其实现细节反映了对精度的细致考量FP32 累加无论输入为 fp16/bf16行内损失计算与跨核归并均在 FP32 上完成最后一次性Cast回输出 dtypeKahan 补偿求和mean/sum模式下本核行累加StageCorePartial与核 0 跨核合并FinalizeReduced都使用 Kahan 补偿算法并对inf/nan补偿项做自比清 NaN 处理避免单个非有限值污染后续所有车道归约除数单次除法ApplyReductionDivisor对mean优先一次除以C·NC·N ≤ 2^24时避免拆成两次除法带来的双重舍入当C·N超出 FP32 精确整数上限时才退回两步除法N0时mean保持0/0nan语义多核写入安全输出写回采用各核原子加写入 FLOAT 工作区 → 核 0 统一 cast 连续写出的策略规避跨核共享 32B 写块时的 RMW 竞争。5. 测试验证体系模块测试覆盖三层通路见 tests/assets/golden.py 与tests/st、tests/ut目录kernel/GEIR 通路MultilabelMarginLossKernelSpecgolden 以torch.ops.aten.multilabel_margin_loss_forward为权威真值aclnn 通路MultilabelMarginLossAclnnSpec参数名与aclnn_multilabel_margin_loss.h一致self/target、reduction为 int64并验证 A5 aclnn 契约下is_target跟随 self dtypee2e 通路MultilabelMarginLossTorchSpec对应torch.nn.functional.multilabel_margin_loss仅暴露 loss 输出。浮点输出采用cross_check容差标准与 GPU 侧同一 aten 接口的第三方标杆对比is_target采用binary_equal严格相等校验。相关文件索引如需深入研读可按以下路径定位仓库内相关资料接口文档aclnnMultilabelMarginLoss.md模块 READMEmultilabel_margin_loss/README.mdaclnn 接口实现aclnn_multilabel_margin_loss.cpp、aclnn_multilabel_margin_loss.h算子原型定义multilabel_margin_loss_def.cppshape 推导multilabel_margin_loss_infershape.cpptiling 实现multilabel_margin_loss_tiling_arch35.cppVector Core 内核multilabel_margin_loss.cpp、multilabel_margin_loss_impl.h调用示例test_aclnn_multilabel_margin_loss.cpp、test_geir_multilabel_margin_loss.cpp测试真值与用例golden.py、test_multilabel_margin_loss_infershape.cpp、test_aclnn_multilabel_margin_loss.cpp通用概念文档两段式接口、aclnn 返回码、编译与运行样例总结aclnnMultilabelMarginLoss是 CANN ops-nn 中实现多标签间隔损失的标准 aclnn 算子采用两段式接口设计支持 none/mean/sum 三种归约模式、FLOAT/FLOAT16/BFLOAT16 三种浮点输入与 1D/2D 两种 shape并在 Ascend 950 与 Atlas A2/A3 系列产品上提供完整支持。其实现贯穿 aclnn 算子组合、GE shape 推导、host 侧 tiling 切分与 Vector Core 内核四层通过 FP32 累加、Kahan 补偿求和与单次归约除法等设计保证了与 PyTorch aten 接口对齐的计算精度。掌握本文的两段式调用流程与参数约束即可在 NPU 上将多标签间隔损失无缝集成到自己的训练或推理流程中。【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表