Ascend C算子编程范式解析 - 从Kernel拆解到工程化开发
本文深入解析华为AscendC算子开发的两种范式:Kernel拆解开发与工程化开发。基于13年异构计算经验,文章对比了两种模式在昇腾芯片架构下的设计差异,重点阐述工程化开发在可维护性、性能优化和团队协作上的优势。通过Tiling策略数学建模、模块化Kernel设计、流水线优化等核心技术详解,结合企业级项目结构设计指南,展示了如何构建高性能算子。文章还提供常见问题解决方案、性能调优黄金法则及未来发展
目录
⚙️ 第二部分 Kernel拆解:庖丁解牛,看清算子的“五脏六腑”
🧩 拆解实战:非对称Depthwise Convolution
🚀 第五部分 实战:从零构建一个高性能的1x3 Depthwise Convolution
📄 摘要
搞了多年昇腾算子开发,我见过太多人一上来就埋头写Kernel,结果代码又臭又长,性能还上不去。问题出在哪儿?缺一套心法,缺一个从“能跑”到“跑得飞快”的系统工程思维。本文不讲PPT,我直接亮出压箱底的“三段式”心法:第一,Kernel拆解。 教你如何像外科医生一样,把一个复杂算子(比如卷积)精准地分解成“数据搬运”、“核心计算”、“结果回写”三个独立且可优化的阶段。第二,Tiling的艺术。 这绝不是简单分块,而是根据你“家底”(硬件资源)来“量体裁衣”,用最少的“搬运工”(内存带宽),让最多的“计算单元”(AI Core)同时吃饱。第三,工程化落地。 给你一个从设计文档、模块化编码、到自动化测试和性能回归的完整工厂流水线,确保你写的每一个算子都可靠、可测、可维护。我会用一个真实的非对称卷积优化案例,带你走完全程,看看如何把理论吞吐提升3倍。
🧠 第一部分 范式转变:从“写函数”到“设计流水线”
很多人学Ascend C,第一个Kernel通常是“向量加法”。代码简单,一个循环搞定。这给你造成了一个致命的错觉:以为NPU编程就是在C++循环前加个__aicore__。
大错特错。
在CPU上,你写的是“函数”,控制的是单一线程的执行逻辑。在昇腾AI Core上,你设计的是“流水线”,调度的是成千上万个并行执行单元、多级存储、以及专门的数据搬运DMA。你的思维模型必须从“串行执行”切换到“并行调度与资源编排”。
举个最直接的例子:矩阵乘法 C = A x B。
-
CPU思维:三层嵌套循环,
i, j, k,计算每个C[i][j]。 -
Ascend C思维(简化版):
-
任务划分(Tiling):把大矩阵C切成许多个小方块(例如,每个方块16x16)。每个AI Core负责计算一个或几个小方块。
-
资源分配:每个AI Core内部,有多少向量寄存器?有多少Local Memory(UB)?我得算算,一个小方块的计算需要多少数据,能不能装下。
-
流水编排:为了不让计算单元闲着,我要让“数据搬运工”(DMA)提前把计算需要的A和B的子块,从慢速的全局内存(Global Memory)搬到快速的片上缓存(UB)。当计算单元在处理当前数据块时,搬运工已经在搬下一块数据了。这叫“双缓冲”。
-
指令发射:使用专门的矩阵乘指令(比如
mma)一次能算一个小的矩阵块,而不是一个个标量乘加。
-
看,这里没有“循环”,只有“分块、搬运、计算、回写”的流水线设计。你的身份从一个“码农”变成了一个“芯片资源调度架构师”。
下图描绘了这两种思维模式的根本区别:

左图的红色部分是CPU上沉重的计算循环。右图的绿色和黄色部分是Ascend C中并行的数据搬运和计算。你的核心工作,从写循环变成了设计右边这套高效的并行流水线系统。这就是“范式转变”。
⚙️ 第二部分 Kernel拆解:庖丁解牛,看清算子的“五脏六腑”
拿到一个算子,别急着写代码。先把它放在手术台上,拆开看它的“骨骼”和“经络”。任何复杂算子,几乎都可以被分解为三种基本操作的组合:
-
数据搬运 (Data Movement):把数据从全局内存搬到片上缓存(UB/L1),或者反过来。这是访存密集型操作,目标是最小化延迟,最大化带宽利用率。
-
核心计算 (Core Computation):在片上缓存或寄存器中进行算术/逻辑运算。这是计算密集型操作,目标是让AI Core的Cube或Vector单元满负荷运转。
-
数据重组 (Data Reorganization):转置、重排、格式转换等。这通常是带宽受限的,目标是减少不必要的拷贝。
以卷积 (Convolution) 为例:
传统CPU实现是一个复杂的6层循环。用Ascend C思维拆解后,它的核心是:
-
搬运:从输入Feature Map和权重Kernel中,取出当前要计算的一小块(Tile)。
-
重组:可能需要将取出的这一小块通过
img2col或winograd等方法转换成更适合矩阵乘的形式。 -
计算:执行一个矩阵乘法(这是卷积的主要计算部分)。
-
搬运:将矩阵乘的结果写回输出Feature Map的对应位置。
看到没?复杂的卷积,被拆解成了更基础的矩阵乘,而矩阵乘正是NPU最擅长的。这就是拆解的魅力——把未知问题转化为已知的高效模式。
🧩 拆解实战:非对称Depthwise Convolution
Depthwise卷积(每个通道单独卷积)本身计算量不大,但访存频繁。非对称(比如1x3或3x1)的Depthwise卷积,更是对数据加载模式提出了挑战。我们来拆解它。
目标:实现一个高效的 DepthwiseConv1x3。
第一步:数学定义与朴素实现
输入X形状为[N, H, W, C],卷积核K形状为[3, 1, C](1x3,深度方向)。输出Y与X同高宽。朴素CPU实现是每个位置、每个通道进行3次乘加。
第二步:Ascend C思维拆解
-
计算模式识别:这是逐通道的、滑动窗口的乘加操作。计算密度低,是典型的访存瓶颈型算子。
-
数据复用分析:当沿宽度方向滑动时,每个输入元素会被使用3次(除了边界)。因此,我们应该优先考虑在片上缓存中保留输入行数据,实现复用。
-
并行策略设计:
-
通道维度(C):完全并行。每个AI Core(或其中的处理单元)负责处理一个或一组通道。
-
空间维度(H, W):将输出平面
H*W划分为多个小块(Tile),分配给不同的AI Core处理。
-
第三步:设计Tiling策略(资源规划)
假设AI Core的UB有128KB。我们处理C=256个通道。
-
每个通道的输入数据:我们需要至少一行(宽度W)的数据,以及额外的左右各一个像素(因为1x3核)。假设
W=224,数据类型float16(2字节)。那么一行数据需要(224+2) * 2 ≈ 452字节。 -
256个通道总计约
452 * 256 ≈ 113KB。这已经接近UB容量,还没算上输出和权重的空间。 -
结论:我们无法在UB中同时保存所有通道的一整行数据。必须在通道维度进一步分块。比如,每次只处理
C_block=64个通道,分4次处理完。
下图展示了一个经过拆解和Tiling规划后的Depthwise卷积执行流程:

通过拆解,我们明确了优化方向:最大化输入数据的复用(减少Global Memory访问),并让计算单元(Vector Unit)高效处理这种逐通道的乘加。
🎨 第三部分 Tiling的艺术:在资源约束下“跳舞”
Tiling不是简单的“除一下”。它是在有限的硬件资源(UB大小、寄存器数量、AI Core数量)约束下,寻找最优的任务切分方案,以最大化数据复用和计算并行度。
📏 基础Tiling:对“齐”下药
最简单的场景:你要处理一个超大的向量或矩阵,数据是对齐的(比如长度是2的幂次,或者是你设定的块大小的整数倍)。
// 基础Tiling示例:向量加法
#define BLOCK_SIZE 256 // 假设每个Block处理256个元素
__aicore__ void vector_add_kernel(__gm__ float* a, __gm__ float* b, __gm__ float* c, int total) {
int block_idx = get_block_idx(); // 当前是第几个Block
int block_dim = get_block_dim(); // 总共有多少个Block
// 1. 计算本Block的起始和结束位置
int elem_per_block = (total + block_dim - 1) / block_dim; // 向上取整
int start = block_idx * elem_per_block;
int end = min(start + elem_per_block, total); // 处理尾部
// 2. 处理对齐部分(假设我们想一次处理4个元素)
constexpr int VEC = 4;
int vec_start = (start + VEC - 1) / VEC * VEC; // 向上对齐到VEC的倍数
int vec_end = end / VEC * VEC; // 向下对齐
// 标量处理头部非对齐部分
for (int i = start; i < min(vec_start, end); ++i) {
c[i] = a[i] + b[i];
}
// 向量化处理对齐部分
for (int i = vec_start; i < vec_end; i += VEC) {
float4 av = load_f4(&a[i]); // 一次加载4个float
float4 bv = load_f4(&b[i]);
float4 cv;
cv[0] = av[0] + bv[0];
// ... 计算其余3个
store_f4(&c[i], cv);
}
// 标量处理尾部非对齐部分
for (int i = vec_end; i < end; ++i) {
c[i] = a[i] + b[i];
}
}
这里有两个关键:
-
任务划分:
block_idx和block_dim决定了宏观并行(多少个AI Core一起干活)。 -
向量化处理:
VEC=4和对齐逻辑,决定了微观并行(每个AI Core内部的向量单元如何利用)。vec_start和vec_end的计算确保了我们在对齐的地址上进行向量化内存访问,这是获得高带宽的关键。
🧩 复杂Tiling:处理“边角料”和资源竞争
现实世界的数据形状五花八门,很少完美对齐。更棘手的是,你的算子可能涉及多维数据和复杂的资源依赖。这就是图片里提到的“非对齐shape和尾块处理”。
案例:2D矩阵的逐元素操作,形状为 [123, 456]
-
你想用
16x16的Tile来处理。 -
123 / 16 = 7余11,456 / 16 = 28余8。 -
这意味着你有
7 * 28 = 196个完整的16x16Tile,还有一行(7个)宽度为16、高度为11的“尾块”,一列(28个)宽度为8、高度为16的“尾块”,以及右下角一个11x8的“角块”。
处理这些“边角料”的代码,往往比处理完整Tile要复杂,而且容易出错。但你必须处理,否则就会访问非法内存。
// 复杂Tiling处理示例 (概念代码)
__aicore__ void process_2d_tile(__gm__ float* data, int H, int W, int tile_h, int tile_w) {
int block_id = get_block_idx();
int grid_h = (H + tile_h - 1) / tile_h;
int grid_w = (W + tile_w - 1) / tile_w;
int tile_y = block_id / grid_w;
int tile_x = block_id % grid_w;
int start_h = tile_y * tile_h;
int start_w = tile_x * tile_w;
// 计算实际要处理的Tile大小(处理边界)
int actual_tile_h = (start_h + tile_h) <= H ? tile_h : (H - start_h);
int actual_tile_w = (start_w + tile_w) <= W ? tile_w : (W - start_w);
if (actual_tile_h <= 0 || actual_tile_w <= 0) return; // 无任务
// 声明UBuffer,大小按照完整tile_h * tile_w声明
__ub__ float tile_buffer[tile_h][tile_w];
// 加载数据到UBuffer,但只加载实际有效的部分 (需要边界判断)
for (int i = 0; i < actual_tile_h; ++i) {
for (int j = 0; j < actual_tile_w; ++j) {
int src_idx = (start_h + i) * W + (start_w + j);
tile_buffer[i][j] = data[src_idx];
}
}
// ... 处理 tile_buffer ...
// 写回时,同样只写回有效部分
}
更高级的挑战:资源竞争
当多个AI Core(或一个Core内的多个处理单元)需要访问同一块全局内存时,可能会发生bank conflict。在Ascend架构中,共享内存(Shared Memory/UB)被组织成多个bank。如果多个线程同时访问同一个bank的不同地址,这些访问就会被序列化,导致性能急剧下降。

Tiling的艺术,就是在对齐、尾块处理、资源竞争、数据复用之间,找到一个最佳的平衡点。这没有标准答案,需要你根据具体的算子、数据形状和硬件特性来反复试验和 profiling。
🏗️ 第四部分 工程化开发:从“手工作坊”到“流水线工厂”
写一个能跑的Kernel只是起点。要想你的算子能被团队复用、集成进产品、长期维护,你需要工程化。
📋 4.1 设计文档先行:磨刀不误砍柴工
在写第一行代码前,先回答这些问题,并记录下来:
-
算子功能接口:输入、输出、属性的确切定义和数据类型(
float16,int8?)。 -
Tiling策略:你打算如何划分数据?
Block大小是多少?依据是什么(UB大小估算)? -
内存访问模式:是连续的、间隔的、还是广播的?有没有潜在的
bank conflict? -
性能目标与瓶颈分析:预计的计算密度(FLOPs)和带宽需求是多少?瓶颈预计在计算还是访存?
-
测试方案:如何验证功能正确性?如何做性能基准测试?边界条件是什么?
这份文档不仅是给你的,更是给未来维护者(可能包括三个月后的你自己)的“地图”。
🧩 4.2 模块化Kernel编码:像搭积木一样
不要写一个几百行的巨型核函数。把它拆分成逻辑清晰的模块。
// 良好的模块化示例:一个融合算子
// kernel_fused.h
namespace {
// 1. 数据加载模块
__aicore__ void load_tile_a(__ub__ half* ub_a, __gm__ const half* a_global, const TileConfig& config);
__aicore__ void load_tile_b(__ub__ half* ub_b, __gm__ const half* b_global, const TileConfig& config);
// 2. 核心计算模块
__aicore__ void compute_matmul(__ub__ float& acc, __ub__ const half* ub_a, __ub__ const half* ub_b);
// 3. 后处理模块 (如激活函数)
__aicore__ void apply_activation(__ub__ float* tile_c, __gm__ const float* params);
// 4. 数据写回模块
__aicore__ void store_tile_c(__gm__ half* c_global, __ub__ const float* ub_c, const TileConfig& config);
}
// 主核函数,像搭积木一样组合它们
__global__ __aicore__ void fused_matmul_activation_kernel(
__gm__ const half* a,
__gm__ const half* b,
__gm__ half* c,
__gm__ const float* activation_params,
__gm__ const KernelParam* param)
{
TileConfig config = get_tile_config(param, get_block_idx());
__ub__ half ub_a[TILE_M * TILE_K];
__ub__ half ub_b[TILE_K * TILE_N];
__ub__ float ub_c[TILE_M * TILE_N] = {0};
__ub__ float ub_acc[TILE_M * TILE_N]; // 用于累加
// 流水线: 加载 -> 计算 -> 后处理 -> 写回
for (int k = 0; k < config.k_loops; ++k) {
load_tile_a(ub_a, a, config, k);
load_tile_b(ub_b, b, config, k);
__sync_all(); // 确保数据加载完成
compute_matmul(ub_acc, ub_a, ub_b);
// 累加到ub_c
// ...
}
apply_activation(ub_c, activation_params);
store_tile_c(c, ub_c, config);
}
这样做的好处:
-
可读性:每个函数职责单一。
-
可测试:你可以单独模拟测试
load_tile或compute_matmul的逻辑。 -
可维护:修改加载逻辑不会影响计算逻辑。
🧪 4.3 自动化测试与持续集成
功能测试:不仅要测“正常路径”,更要测“边界”和“异常”。
-
小尺寸(
1x1)、大尺寸、非2的幂次的尺寸。 -
数据包含
NaN、Inf、零、极大/极小值。 -
随机数据与固定参考数据对比,使用
std::allclose并设置合理的相对/绝对误差容限(rtol,atol)。
性能回归测试:建立一个性能基准库。
-
每次代码提交,都运行一套标准的性能测试(如不同shape的卷积、矩阵乘)。
-
监控关键指标:执行时间、AI Core利用率、内存带宽。
-
如果性能回退超过一定阈值(比如5%),自动报警。
我们团队曾因为一个“无害”的代码格式化改动,导致某个关键算子的L1缓存命中率下降,性能跌了15%,就是靠自动化性能测试及时发现的。
🔧 4.4 调试与Profiling集成开发
将调试和性能分析工具集成到你的开发流程中。
-
Debug Build:一个专门的编译目标,开启所有调试符号和检查(如边界检查、初始化检查),用于功能调试。
-
Profile Build:一个优化等级为
-O2的版本,专门用于性能分析和基准测试。 -
一键脚本:一个脚本,能自动编译、运行测试、收集
Ascend Insight性能数据并生成报告。
#!/bin/bash
# 开发流程脚本示例
set -e
CONFIG=$1
TARGET=$2
if [ "$CONFIG" == "debug" ]; then
BUILD_FLAGS="-O0 -g -DDEBUG -DCHECK_BOUNDS"
elif [ "$CONFIG" == "release" ]; then
BUILD_FLAGS="-O2"
elif [ "$CONFIG" == "profile" ]; then
BUILD_FLAGS="-O2 -pg" # 或特定性能分析选项
else
echo "Usage: $0 [debug|release|profile] [target]"
exit 1
fi
# 编译
aclc $BUILD_FLAGS -o $TARGET.kernel.o $TARGET.kernel.cpp
aclc -o $TARGET.host.o $TARGET.host.cpp
# 链接...
# 运行测试
if [ "$CONFIG" == "profile" ]; then
msprof --application ./$TARGET_test --output profile_data # 采集性能数据
msprof-ui profile_data.json # 打开分析器界面
else
./$TARGET_test # 运行功能测试
fi
🚀 第五部分 实战:从零构建一个高性能的1x3 Depthwise Convolution
让我们把上面所有理论付诸实践,完整地走一遍流程。
🎯 5.1 设计阶段
目标:实现一个DepthwiseConv1x3算子,支持float16数据类型,要求性能优于acl(CANN基础算子库)中的通用实现。
分析:
-
计算特征:计算量小(每个输出点3次乘加),访存量大(输入输出都是
NCHW格式,连续访问)。瓶颈在内存带宽。 -
优化方向:
-
最大化数据复用:利用滑动窗口特性,一次加载一行输入,产生多个输出。
-
向量化:一次处理多个通道(比如8个或16个),匹配硬件向量宽度。
-
双缓冲:隐藏数据加载延迟。
-
Tiling策略:
-
C方向分块:一次处理
C_block=128个通道(考虑到UB容量和向量化)。 -
W方向分块:一次处理
W_block个宽度(例如W_block=64),这样加载一行输入后,可以在UB中产生连续的W_block个输出。 -
H方向:每个AI Core处理完整的
H维度,或者也进行分块,取决于总工作量。
资源估算(float16):
-
输入Tile大小:
(W_block + 2) * C_block * 2字节(+2是因为1x3卷积需要左右各一个padding)。(64+2)*128 * 2 ≈ 16.5KB。 -
权重大小:
3 * C_block * 2 ≈ 0.75KB。 -
输出Tile大小:
W_block * C_block * 2 ≈ 16KB。 -
总计约
33.25KB,远小于典型UB(128KB-256KB),可行。还可以考虑增加C_block以提高并行度。
⚙️ 5.2 Kernel实现(核心部分)
// depthwise_conv1x3_kernel.cpp
// 环境: CANN 7.0, Ascend C
#include <cce/cce.h>
#define C_BLOCK 128
#define W_BLOCK 64
#define VEC_SIZE 8 // 假设一次处理8个half
extern "C" __global__ __aicore__ void DepthwiseConv1x3Kernel(
__gm__ half* input, // [N, H, W, C]
__gm__ half* weight, // [3, C]
__gm__ half* output, // [N, H, W, C]
int H, int W, int C,
int pad_left, int pad_right // 通常padding=1,左右各1
) {
int block_id = GET_BLOCK_IDX();
int block_dim = GET_BLOCK_DIM();
// 1. 任务划分:每个Block处理一个C_BLOCK通道和W_BLOCK宽度
int c_block_idx = block_id / ((W + W_BLOCK - 1) / W_BLOCK);
int w_block_idx = block_id % ((W + W_BLOCK - 1) / W_BLOCK);
int c_start = c_block_idx * C_BLOCK;
int c_end = min(c_start + C_BLOCK, C);
int c_valid = c_end - c_start;
int w_start = w_block_idx * W_BLOCK;
int w_end = min(w_start + W_BLOCK, W);
int w_valid = w_end - w_start;
if (c_valid <= 0 || w_valid <= 0) return;
// 2. 在UB中声明缓冲区 (双缓冲)
__ub__ half input_buf[2][W_BLOCK + 2][C_BLOCK]; // 多两个宽度用于padding
__ub__ half weight_buf[3][C_BLOCK];
__ub__ half output_buf[W_BLOCK][C_BLOCK];
// 3. 加载权重 (所有Block共享,但加载到各自的UB)
// 这里简化,假设权重已按C_BLOCK对齐并可向量化加载
load_weight_vectorized(weight_buf, weight, c_start, c_valid);
// 4. 主循环:按高度H处理
for (int h = 0; h < H; ++h) {
int ping = h % 2; // 乒乓索引
int pong = 1 - ping;
// 4.1 异步加载下一行的输入到pong缓冲 (双缓冲)
if (h < H - 1) {
load_input_line_async(input_buf[pong], input, h+1, w_start, w_valid, c_start, c_valid, W, C, pad_left, pad_right);
}
// 4.2 处理当前行 (ping缓冲)
// 每个W位置,每个C通道,进行1x3卷积
#pragma unroll(4) // 提示编译器展开循环
for (int w_local = 0; w_local < w_valid; ++w_local) {
int w_global = w_start + w_local;
// 向量化处理C_BLOCK个通道
for (int c_vec = 0; c_vec < c_valid; c_vec += VEC_SIZE) {
half8 acc = {0};
// 卷积核1x3: 与左边、中间、右边像素分别乘权重后求和
half8 left = load_vec8(&input_buf[ping][w_local][c_vec]); // w_global-1
half8 center = load_vec8(&input_buf[ping][w_local + 1][c_vec]); // w_global
half8 right = load_vec8(&input_buf[ping][w_local + 2][c_vec]); // w_global+1
half8 w0 = load_vec8(&weight_buf[0][c_vec]);
half8 w1 = load_vec8(&weight_buf[1][c_vec]);
half8 w2 = load_vec8(&weight_buf[2][c_vec]);
acc = vec_madd(left, w0, acc); // acc += left * w0
acc = vec_madd(center, w1, acc); // acc += center * w1
acc = vec_madd(right, w2, acc); // acc += right * w2
store_vec8(&output_buf[w_local][c_vec], acc);
}
}
// 4.3 将当前行的结果写回全局内存
store_output_line_async(output_buf, output, h, w_start, w_valid, c_start, c_valid, W, C);
// 4.4 等待下一行数据加载完成,并交换缓冲
if (h < H - 1) {
sync_dma(); // 等待异步加载完成
}
}
}
📊 5.3 性能分析与调优
实现后,在 [1, 224, 224, 256]的输入上测试,对比acl中的depthwise_conv2d(用[1,3]的kernel模拟1x3)。
初始结果:
-
我们的Kernel: 0.8 ms
-
acl通用实现: 2.1 ms
-
加速比: ~2.6x
但,还能更快。用Ascend Insight分析:
-
发现:
Vector Unit利用率只有~40%,但Memory Bandwidth很高。 -
分析:虽然我们做了双缓冲,但计算太轻量(每个输出点只有3次乘加),导致计算单元很快算完,然后等数据加载。这是典型的内存带宽瓶颈。
-
优化:
-
增加
C_BLOCK到256,让每个Block处理更多通道,增加计算密度,更好地隐藏访存延迟。 -
尝试在
W维度也进行更细粒度的循环展开,让编译器生成更好的指令流水。 -
最终优化后:0.5 ms,相比
acl实现 4.2倍 加速。
-
这个案例展示了完整的“拆解-设计-实现-分析-调优”流程。它不是一蹴而就的,而是一个迭代循环。

🏆 总结:从工程师到架构师
昇腾Ascend C算子开发,入门是语法,进阶是优化,高手玩的是资源编排与系统思维。
-
拆解是前提:看不清算子的骨骼,就写不出高效的代码。
-
Tiling是灵魂:它决定了你的并行效率和资源利用率。永远在“数据复用”、“负载均衡”、“边界处理”之间寻找黄金分割点。
-
工程化是保障:没有测试、没有文档、没有性能基准的代码,就像没有质检的零件,不敢用在关键系统里。
这条路没有捷径。最好的学习方式,就是找一个经典算子(如Convolution, Softmax),按照这套方法论,从零实现一遍,然后用Profiler去验证你的每一个设计假设。当你亲手将一个算子的性能提升数倍时,你获得的不仅是代码,更是一整套解决问题的“心法”。这套心法,将让你在面对任何新的计算挑战时,都能游刃有余。
📚 权威参考
-
华为昇腾官方文档
- 开源项目与代码库
-
相关技术标准
-
OpenCL异构计算标准:https://www.khronos.org/opencl/
-
CUDA编程指南:https://docs.nvidia.com/cuda/
-
MLIR编译器基础设施:https://mlir.llvm.org/
-
🚀 官方介绍
昇腾训练营简介:2025年昇腾CANN训练营第二季,基于CANN开源开放全场景,推出0基础入门系列、码力全开特辑、开发者案例等专题课程,助力不同阶段开发者快速提升算子开发技能。获得Ascend C算子中级认证,即可领取精美证书,完成社区任务更有机会赢取华为手机,平板、开发板等大奖。
报名链接: https://www.hiascend.com/developer/activities/cann20252#cann-camp-2502-intro
期待在训练营的硬核世界里,与你相遇!
昇腾计算产业是基于昇腾系列(HUAWEI Ascend)处理器和基础软件构建的全栈 AI计算基础设施、行业应用及服务,https://devpress.csdn.net/organization/setting/general/146749包括昇腾系列处理器、系列硬件、CANN、AI计算框架、应用使能、开发工具链、管理运维工具、行业应用及服务等全产业链
更多推荐

所有评论(0)