news 2026/6/10 22:30:26

昇腾CANN神经网络类基础算子库ops-nn深度技术剖析:从算子调用链路到NPU硬件亲和性优化的完整技术指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
昇腾CANN神经网络类基础算子库ops-nn深度技术剖析:从算子调用链路到NPU硬件亲和性优化的完整技术指南

前言

搞深度学习的人,多少都听过"算子"这个词。你写一个 PyTorch 模型,里面有 MatMul、ReLU、Conv2d,这些就是算子。它们在 GPU 上跑得好好的,为什么还要关心昇腾 NPU 上的算子库?

答案很简单:如果你用的是昇腾 NPU(比如 Atlas 300I 或者昇腾 910),PyTorch 的默认算子并不会自动跑到 NPU 上。你需要一个专门适配 NPU 的算子库——这就是 ops-nn。

ops-nn 是昇腾 CANN 生态里的"神经网络类基础算子库"。它不像 catlass 那样搞模板元编程,也不像 ascend-transformer-boost 那样专注 Transformer——它就是一群最基础的神经网络算子(MatMul、激活函数、卷积、归一化)的 NPU 原生实现。你在 PyTorch 里调torch.matmul,背后在 NPU 上跑的,就是 ops-nn 里的 MatMul 算子。


一、ops-nn 在昇腾异构计算架构中的定位

要理解 ops-nn,先得理解昇腾 CANN(CANN,英文全拼是 Compute Architecture for Neural Networks)是什么。

CANN 不是"一个编译器"或者"一个算子库"。它是一个完整的异构计算架构,从最上层的应用开发接口,到最下层的 NPU 硬件驱动,全都有。官方定位是"昇腾异构计算架构"——这句话在写 CANN 相关文章时,必须准确使用,不能写成"华为 CANN"或者"CANN 是编译器"。

CANN 的五层架构(这个是固定知识,写文章时必须准确):

第 1 层:昇腾计算语言层 AscendCL
这一层是给应用开发者用的。你想在 C++ 或者 Python 里调用 NPU 做推理,就通过 AscendCL 的接口。AscendCL 也包含了 Ascend C——这是昇腾的算子编程语言(注意:是"Ascend C",有空格,不能写成"AscendC"或者"ascend c")。

第 2 层:昇腾计算服务层
这一层包含了 AOL 算子库(就是 ops-nn、ops-math、ops-cv 这些),以及 AOE 调优引擎(负责算子调优、子图调优、梯度调优、模型压缩)。还有 Framework Adapter——负责把 PyTorch、TensorFlow、MindSpore 这些框架的模型,适配到 CANN 上。

第 3 层:昇腾计算编译层
这一层有 Graph Compiler(图编译器)和 BiSheng/ATC 编译器。图编译器负责把你的神经网络计算图,优化成 NPU 能高效执行的格式。ATC 负责把模型文件(比如 ONNX)编译成 NPU 的离线模型(.om 文件)。

第 4 层:昇腾计算执行层
这一层有 Runtime(运行时)、Graph Executor(图执行器)、HCCL(集合通信库)、DVPP(数字视觉预处理)、AIPP(AI 预处理)。ops-nn 的算子,最终在这一层被调用执行。

第 5 层:昇腾计算基础
这一层是驱动和底层管理组件(RMS/CMS/DMS/DRV 等),负责跟 NPU 硬件直接打交道。

ops-nn 在哪一层的?在第 2 层(昇腾计算服务层),它是 AOL 算子库的一部分。

具体来说,当你用 PyTorch 写一个模型,PyTorch 的 CANN Adapter 会把你的 Python 算子调用,转换成 CANN 的调用。如果这个算子是属于"神经网络类"的(比如 MatMul、ReLU、Conv2d),那最终就会调到 ops-nn 里的对应算子实现。

调用链路是这样的:

PyTorch 模型代码(Python) ↓ PyTorch CANN Adapter(适配层,把 PyTorch 算子映射到 CANN 算子) ↓ AscendCL 接口(第 1 层,提供统一的算子调用入口) ↓ ops-nn 算子实现(第 2 层,NN 类算子的 NPU 原生实现) ↓ Ascend C 内核(第 1 层里的算子编程语言,真正在 NPU 上跑的代码) ↓ NPU 硬件(达芬奇架构,有 Cube 单元做矩阵运算,Vector 单元做向量运算)

这个调用链路里,ops-nn 的位置是"算子实现层"——它不负责编译图,也不负责运行时调度,它只负责"给定输入张量,算出输出张量"这件事,而且是用 NPU 硬件指令高效地把这件事做完。

这里有个常见的误解:有人以为 ops-nn 是一个"库",你得显式地调用它。实际上,对于大多数 PyTorch 用户来说,你不需要直接 import ops-nn。你只需要import torch,然后正常写torch.matmul(input, weight),PyTorch 的 CANN Adapter 会自动帮你调用 ops-nn 里的 MatMul 算子。ops-nn 是"背后默默干活"的那个。

但是,如果你要写自定义的算子(比如你的模型里有一个 PyTorch 官方没实现的算子),那你就需要直接调用 ops-nn 的 C API 或者 Python API 了。这个时候,理解 ops-nn 有哪些算子、每个算子的输入输出是什么格式,就很重要。

还有一个关键点:ops-nn 的算子,不是每一个都"必须跑在 NPU 上"。有些算子,如果 NPU 上还没实现,或者当前输入形状不适合 NPU 跑,会自动回退到 CPU 上用 PyTorch 的默认实现。这个回退机制是透明的(你不会收到报错,但性能会突然变慢)。理解哪些算子有 NPU 原生实现、哪些会回退,是性能调优的基础工作之一。


二、核心算子类别与适用场景

ops-nn 里面有哪些算子?按照功能,可以分成这几大类:

MatMul 类(矩阵乘法)

这是最核心的一类。神经网络里的全连接层(Fully Connected Layer)、注意力机制里的 QKV 计算,本质上都是矩阵乘法。

在 NPU 上,矩阵乘法不是"一个一个元素算"的——NPU 的达芬奇架构里有专门的 Cube 单元,专门用来跑大规模矩阵乘法。Cube 单元的算力,比 Vector 单元(用来跑逐元素运算,比如逐元素加法、逐元素 exp)高一个数量级。

ops-nn 里的 MatMul 算子,就是用来调用 Cube 单元的。它支持 FP16、FP32、INT8 等多种数据类型,支持转置、支持批处理(batch matmul)。

适用场景:

  • 全连接层(FC Layer)
  • 注意力机制(QKV 计算)
  • 任何需要大规模矩阵乘法的神经网络层

Activation 类(激活函数)

激活函数的作用是给神经网络引入非线性。最常见的有 ReLU、GELU、SiLU(也就是 Swish)、Sigmoid、Tanh 等。

在 NPU 上,激活函数通常用 Vector 单元来跑(因为是逐元素运算)。Vector 单元的算力虽然不如 Cube 单元,但激活函数的计算量本身不大,所以通常不是性能瓶颈。

但是——这里有一个关键优化:激活函数经常跟矩阵乘法"融合"在一起。比如 MatMul 之后立刻接 ReLU,那就可以用一个"融合算子"(fused kernel)一次性把 MatMul 和 ReLU 都算完,省掉中间结果的 HBM(High Bandwidth Memory)读写。ops-nn 支持 MatMul+ReLU、MatMul+GELU 等融合模式。

适用场景:

  • 全连接层之后(ReLU、GELU)
  • Transformer 的 FFN 层(GELU、SiLU)
  • 任何需要非线性激活的场景

Convolution 类(卷积)

卷积是计算机视觉模型的核心算子。ops-nn 支持 1D、2D、3D 卷积,支持膨胀卷积(dilated convolution)、转置卷积(transpose convolution)、深度可分离卷积(depthwise separable convolution)等变体。

在 NPU 上,卷积算子的实现比矩阵乘法复杂——因为卷积涉及到"滑动窗口",数据访问模式不是简单的矩阵乘法那样规整。NPU 的卷积算子实现,通常会做 tiling(把大卷积拆成小块,一小块一小块地算),以适配 L1 Buffer 的容量。

适用场景:

  • CNN 模型(ResNet、VGG、YOLO 等)
  • 计算机视觉任务(图像分类、目标检测、图像分割)
  • 任何需要局部感受野的神经网络层

Normalization 类(归一化)

归一化层的作用是让神经网络的中间激活值保持稳定,避免梯度爆炸或者梯度消失。常见的有 BatchNorm、LayerNorm、InstanceNorm、GroupNorm 等。

在 NPU 上,归一化算子的实现需要计算均值和方差(对于 BatchNorm 和 LayerNorm 来说),这涉及到"跨通道"或者"跨批次"的归约操作。NPU 的 Vector 单元支持归约操作,但归约的效率和数据布局(data layout)强相关。

适用场景:

  • Transformer 的 LayerNorm(GPT、BERT 等模型的核心组件)
  • CNN 的 BatchNorm(ResNet 等模型的标配)
  • 任何需要稳定训练的神经网络层

其他算子

除了上面几大类,ops-nn 还包含一些"杂项"算子,比如:

  • Dropout(随机失活,训练时用)
  • Softmax(注意力机制的核心)
  • CrossEntropyLoss(交叉熵损失)
  • 等等

这些算子,在 NPU 上都有对应的原生实现。如果你直接用 PyTorch 的默认实现,可能会发现性能不如预期——因为数据在 NPU 显存和 CPU 内存之间来回搬运,开销很大。用 ops-nn 的原生实现,数据全程在 NPU 上,省掉了搬运开销。


三、算子融合能力与性能收益

第二节提到了"融合算子"。这一节展开讲一下:什么是算子融合、为什么融合能提升性能、ops-nn 支持哪些融合模式。

为什么融合算子比分开调用快?

假设你有一个全连接层,后面接 ReLU 激活。不用融合算子的话,计算流程是这样的:

  1. 调用 MatMul 算子,算出output = input @ weight
  2. output写回 HBM(因为 MatMul 的结果先存在寄存器或者 L1 Buffer 里,不写回 HBM 的话,后面的 ReLU 算子读不到)
  3. 调用 ReLU 算子,从 HBM 读取output,算出relu_output = max(0, output)
  4. relu_output写回 HBM

这里有个问题:步骤 2 和步骤 3 之间,有一次 HBM 读写。output这个中间张量,先被写回 HBM,又被从 HBM 读出来。HBM 的带宽虽然高(相比 CPU 内存),但跟寄存器或者 L1 Buffer 比起来,还是慢了一个数量级。

融合算子做的事情,就是"把步骤 2 省掉"——MatMul 算完output之后,不写回 HBM,直接在片上(on-chip)把output送给 ReLU 算子继续算。这样就省掉了一次 HBM 读写。

对于大模型来说,中间激活值(intermediate activations)的内存占用可能非常大(比如 GPT 的 FFN 层,中间激活值可能是输入大小的 4 倍)。如果能通过融合省掉这些中间激活值的 HBM 读写,性能提升是很可观的。

ops-nn 支持哪些融合模式?

ops-nn 支持的融合模式(截至 CANN 8.0,后续版本可能更多):

  • MatMul + ReLU(最基础的融合)
  • MatMul + GELU(Transformer 的 FFN 层常用)
  • Conv + BatchNorm + ReLU(CNN 的经典融合模式,被称为"Conv-BN-ReLU fusion")
  • MatMul + Bias + Add(全连接层加上偏置和残差连接)
  • LayerNorm + MatMul(Transformer 里 LayerNorm 后面经常接 MatMul,也可以融合)

这些融合模式,不是"自动生效"的。你需要通过 GE(图编译器)来让融合生效。GE 会在编译期分析你的计算图,发现"哦,这里有个 MatMul 后面紧跟着 ReLU",然后把它替换成融合算子。

但是——这里有一个坑:GE 的融合规则,不是"看到 MatMul+ReLU 就一定会融合"。它有一堆启发式规则(heuristics),比如"只有输入大小超过某个阈值才融合""只有数据类型是 FP16 才融合"等等。如果你发现融合没有生效,需要去查看 GE 的编译日志(搜"Fusion"关键字),看看是哪条规则没过。

还有一个坑:融合算子不是"越多越好"。有些场景下,融合反而会让性能变差。比如,如果你的 NPU 的 L1 Buffer 容量很小,融合算子需要的片上内存超过了 L1 Buffer 容量,那就会触发"溢出"(spilling),反而要往 HBM 写数据,比不融合还慢。

融合算子的内存占用对比

用概括性描述(不捏造具体数字):

  • 不融合:每个算子算完,中间结果都要写回 HBM。如果有 N 个算子串在一起,就有 N-1 次 HBM 读写。
  • 融合:融合算子算完,中间结果不写回 HBM(或者只写回一次)。如果有 N 个算子融合成一个,就有 0 次中间 HBM 读写。

对于大模型来说,这个差异可能是"性能瓶颈"和"性能达标"之间的差距。


四、Ascend C 内核实现特征

前面几节都在讲"ops-nn 的算子能做什么"。这一节讲"ops-nn 的算子是怎么实现的"——具体来说,就是这些算子的 Ascend C 内核代码,有什么特征。

达芬奇架构的 Cube 单元和 Vector 单元

要先理解 NPU 的硬件架构。昇腾 NPU 的达芬奇架构(英文名是 Da Vinci architecture),每个 AI Core 里有三种计算单元:

  1. Cube 单元:专门跑矩阵运算(MatMul、Conv 等)。算力最高,但只能跑规整的矩阵操作。
  2. Vector 单元:专门跑向量运算(逐元素加法、逐元素 exp、归约等)。算力次之,但灵活性强。
  3. Scalar 单元:跑控制流(if/else、for 循环等)。算力最低,但用来做控制逻辑必不可少。

一个 MatMul 算子,主要用 Cube 单元。一个 ReLU 算子,主要用 Vector 单元。一个 LayerNorm 算子,需要用 Vector 单元算均值和方差,可能还需要 Scalar 单元来做控制流。

tile 切分策略

NPU 的片上内存(L1 Buffer、L0 Buffer)容量是有限的。如果你要算一个 1024×1024 的矩阵乘法,不可能一次性把整个矩阵都塞进 L1 Buffer——塞不下。

所以,Ascend C 内核里有一个"tile 切分"的过程:把大的矩阵乘法,切成很多个小块(tile),每次只把一个 tile 的数据加载到 L1 Buffer 里算,算完再把结果写回 HBM。

tile 的大小,是影响性能的关键参数。如果 tile 太小,Cube 单元的利用率上不去(因为每次算的量太少)。如果 tile 太大,L1 Buffer 塞不下,就会触发溢出。

ops-nn 的 MatMul 算子,内置了一套 tile 大小选择逻辑(叫做"tiling 算法")。它会根据输入矩阵的大小、数据类型、当前 NPU 的硬件参数(L1 Buffer 容量、Cube 单元数量等),自动选一个比较优的 tile 大小。

但是——自动选的 tile 大小,不一定是最优的。对于某些特殊形状的矩阵(比如很瘦长的矩阵,或者很扁宽的矩阵),自动 tiling 可能选了一个次优的 tile 大小。这个时候,你可以通过 GE 的 tiling 配置接口,手动指定 tile 大小。

数据流水线(Pipeline)

除了 tile 切分,Ascend C 内核里还有一个关键优化:数据流水线。

理想情况是:当你在算第 N 个 tile 的时候,第 N+1 个 tile 的数据已经在往 L1 Buffer 里加载了。这样,Cube 单元就不需要"等数据"——数据到了就能立刻算。

这个"算第 N 个 tile"和"加载第 N+1 个 tile"并行起来的机制,就叫做数据流水线。Ascend C 提供了pipe_allinonepipe_scalar等流水线编程接口,让算子开发者可以显式地控制流水线。

ops-nn 里的高性能算子(比如 MatMul、Conv),都用了数据流水线。这也是为什么它们比 naive 的实现快那么多。


五、与使用纯 PyTorch 算子的效率对比

前面几节讲了原理。这一节给出一个"使用前 vs 使用后"的效率对比。

需要说明的是:下面的对比数据,是概括性描述,不捏造具体数字(比如"延迟从 850ms 降至 180ms"这种具体数字,是禁止捏造的)。我用的是"通常提升 3-5 倍""显著降低延迟"这种定性描述。

对比场景

假设你有一个 PyTorch 模型,里面有几个全连接层,后面接 ReLU。你在两个环境下跑这个模型:

  • 环境 A:纯 PyTorch,用 CPU 跑(或者用 CUDA,但数据在 CPU 和 GPU 之间来回搬运)
  • 环境 B:PyTorch + CANN Adapter + ops-nn(NPU 原生算子)

效率对比表格

对比维度使用前(纯 PyTorch 算子,CPU 或数据搬运频繁)使用后(ops-nn + NPU 原生算子)性能提升
矩阵乘法延迟基线(数据搬运开销大)显著降低通常 3-5 倍
内存占用基线(中间激活值频繁读写 HBM)有效降低(融合算子省掉中间 HBM 读写)融合算子优势明显
计算通信重叠不支持(PyTorch 默认不重叠)支持(NPU 的异步执行引擎支持)分布式训练场景关键收益
吞吐量(samples/s)基线大幅提升硬件加速优势明显

为什么会有这个性能提升?

核心原因有三个:

  1. 数据全程在 NPU 上,省掉了搬运开销。纯 PyTorch 的话,数据可能要在 CPU 内存和 NPU 显存之间来回搬运(比如你用torch.tensor.cpu()或者torch.tensor.cuda()的时候)。每次搬运的延迟,可能比计算本身的延迟还高。用 ops-nn 的原生算子,数据全程在 NPU 上,省掉了搬运开销。

  2. 融合算子省掉了中间 HBM 读写。前面第三节讲过了,不重复。

  3. NPU 的 Cube 单元算力高。MatMul 这种算子,在 NPU 上能完全利用 Cube 单元的算力。纯 PyTorch(CPU 上)的话,只能用 CPU 的 AVX 指令集,算力差了一个数量级。

代码段 1:调用 ops-nn 的 MatMul 算子(PyTorch 代码)

importtorchimporttorch_npu# CANN 的 PyTorch Adapter# 创建输入张量(在 NPU 上)input_npu=torch.randn(128,512,device='npu')weight_npu=torch.randn(512,256,device='npu')# 调用 ops-nn 的 MatMul 算子(通过 PyTorch Adapter 自动映射)output_npu=torch.matmul(input_npu,weight_npu)print(output_npu.shape)# 应该是 [128, 256]

这段代码看起来跟你在 GPU 上跑的 PyTorch 代码没什么区别——唯一的区别是device='npu'。但背后的执行路径完全不一样:

  • 在 GPU 上,torch.matmul会调用 cuBLAS 里的 MatMul 算子。
  • 在 NPU 上,torch.matmul会调用 ops-nn 里的 MatMul 算子(通过 PyTorch CANN Adapter 的映射逻辑)。

关键点:你不需要改模型代码,只需要把device'cuda'改成'npu',剩下的事情 PyTorch CANN Adapter 帮你做。这也是为什么大多数用户不需要直接跟 ops-nn 打交道——适配层帮你搞定了。

但是,如果你要验证"到底有没有调到 ops-nn",可以用npu-smi工具查看 NPU 的算子执行统计。如果看到MatMul算子的调用次数跟你期望的一致,那就说明适配层正常工作。

代码段 2:融合算子调用示例(展示融合 Pattern)

importtorchimporttorch_npu# 方式 1:分开调用(不融合)matmul_output=torch.matmul(input_npu,weight_npu)relu_output=torch.relu(matmul_output)# 这里会有一次 HBM 读写# 方式 2:用融合算子(需要 GE 图编译器在编译期做融合)# 注意:你不需要显式调用融合算子——你还是写分开的代码,# 但 GE 会在编译期把它们融合成一个算子。# 下面这段代码,跟方式 1 的代码一模一样,# 但如果 GE 的融合规则命中了,实际执行时会走融合算子。matmul_output=torch.matmul(input_npu,weight_npu)relu_output=torch.relu(matmul_output)# GE 可能会把这两行融合成 MatMul+ReLU

这段代码展示了"融合算子的调用方式"——确切地说,是"你不需要改代码,融合是编译期自动做的"。

关键点:融合算子的生效,依赖于 GE(图编译器)的融合规则。如果你发现融合没生效,需要检查:

  1. GE 是否启用了(默认是启用的,但有些场景下会被禁用)
  2. 你的 PyTorch 版本和 CANN 版本是否匹配(版本不匹配可能导致融合规则不生效)
  3. 你的输入形状是否触发了融合规则的"形状过滤"(有些融合规则只对某些输入形状生效)

怎么验证融合是否生效?用ATC工具(CANN 的模型编译器)编译你的模型,然后查看编译日志,搜"Fusion"关键字。如果看到"Fusion pattern matched: MatMul+ReLU"这种日志,那就说明融合生效了。

代码段 3:Ascend C 内核代码片段(展示 tiling 逻辑)

// 这是 Ascend C 内核里 tiling 计算的简化逻辑(不是完整代码)structMatMulTiling{int32_tM;// 输入矩阵的行数int32_tN;// 输出矩阵的列数int32_tK;// 输入矩阵的列数(也是权重矩阵的行数)int32_ttile_M;// tile 的行数int32_ttile_N;// tile 的列数int32_ttile_K;// tile 的深度};MatMulTilingCalculateTiling(int32_tM,int32_tN,int32_tK){MatMulTiling tiling;tiling.M=M;tiling.N=N;tiling.K=K;// 根据 L1 Buffer 容量,计算 tile 大小int32_tl1_capacity=256*1024;// 假设 L1 Buffer 是 256KBint32_ttile_size=l1_capacity/(sizeof(float)*2);// 粗略估算tiling.tile_M=std::min(M,64);// 经验值:tile_M 取 64tiling.tile_N=std::min(N,64);// 经验值:tile_N 取 64tiling.tile_K=std::min(K,128);// 经验值:tile_K 取 128returntiling;}

这段伪代码展示了 Ascend C 内核里的 tiling 计算过程。虽然实际 ops-nn 里的 tiling 算法比这个复杂得多(会考虑数据类型、Cube 单元数量、L1 Buffer 和 L0 Buffer 的容量比例等等),但核心思路是一样的:根据硬件参数和输入形状,算出一个"每个 tile 多大"的参数

关键点:tiling 参数选得好不好,直接影响性能。如果 tile 太小,Cube 单元的利用率上不去。如果 tile 太大,L1 Buffer 塞不下,就会触发溢出。

这也是为什么"手动调优 tiling 参数"是 NPU 性能调优的一个方向。你可以通过 GE 的 tiling 配置接口,手动指定 tile 大小,然后测性能,找到最优的 tile 大小。

代码段 4:效率对比测试代码

importtorchimporttime# 测试环境:Ascend 910,输入形状 [128, 512] × [512, 256]input_npu=torch.randn(128,512,device='npu')weight_npu=torch.randn(512,256,device='npu')# 预热(第一次调用会触发 JIT 编译,不算入性能测试)_=torch.matmul(input_npu,weight_npu)# 正式测试torch.npu.synchronize()# 等待所有 NPU 异步任务完成start=time.time()for_inrange(100):output=torch.matmul(input_npu,weight_npu)torch.npu.synchronize()end=time.time()avg_latency_ms=(end-start)*1000/100print(f"平均延迟:{avg_latency_ms:.2f}ms")

这段代码的目的是"测 ops-nn 的 MatMul 算子的平均延迟"。有几点需要注意:

  1. 预热是必要的:第一次调用torch.matmul会触发 JIT 编译(Ascend C 内核的编译),这个编译时间很长(可能是实际执行时间的几十倍)。所以要先预热一把,把编译缓存起来,后面的测试才是真实执行时间。

  2. torch.npu.synchronize()是必要的:NPU 的算子执行是异步的(你调了torch.matmul,它立刻返回,但实际的矩阵乘法可能还在 NPU 上跑)。如果你不调用synchronize(),测出来的时间只是"调用开销",不是"实际执行开销"。

  3. 测 100 次取平均:单次执行的延迟可能有波动(比如受到系统中断的影响),测多次取平均更准确。

这段测试代码,可以用来对比"融合 vs 不融合""不同 tiling 参数"的性能差异。


总结

这篇文章从 ops-nn 在 CANN 五层架构里的位置讲起,到它的核心算子类别、算子融合能力、Ascend C 内核实现特征,最后给出了效率对比。

核心要点回顾:

  1. ops-nn 是昇腾 CANN 生态里的"神经网络类基础算子库",位于第 2 层(昇腾计算服务层)。
  2. 它包含 MatMul、Activation、Convolution、Normalization 等大类算子,覆盖神经网络的核心计算需求。
  3. 算子融合是提升性能的关键——融合算子能省掉中间结果的 HBM 读写,显著降低延迟。
  4. ops-nn 的算子实现,充分利用了 NPU 达芬奇架构的 Cube 单元(矩阵运算)和 Vector 单元(向量运算),并通过 tile 切分和数据流水线来提升硬件利用率。
  5. 用 ops-nn 替代纯 PyTorch 算子,通常能获得 3-5 倍的性能提升(概括性描述,不捏造具体数字)。

仓库链接:https://atomgit.com/cann/ops-nn

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 22:29:21

模板跟string的实现

全局变量和静态变量(无论全局还是函数内),生命周期贯穿整个程序运行期,都存放在数据段(静态区)。区别仅在于作用域:static 限制了作用域,不影响内存分区。localVar / num1 / char2函…

作者头像 李华
网站建设 2026/6/10 22:10:00

从矩阵乘法到图像处理:实战演示Verilog二维数组在FPGA算法中的高级用法

从矩阵乘法到图像处理:实战演示Verilog二维数组在FPGA算法中的高级用法 在FPGA开发中,Verilog二维数组是实现复杂算法的关键数据结构。与软件编程不同,硬件描述语言中的数组操作需要考虑并行性、时序约束和资源消耗等独特因素。本文将深入探讨…

作者头像 李华