image.png


前言:当OpenAI的“新宠”遇上国产算力

Triton最近两年在AI圈子里火得一塌糊涂。作为OpenAI力推的编程语言,它让咱们不用写那该死的CUDA C++,直接用Python就能写出高性能Kernel。

但是,Triton能在国产NPU(昇腾)上跑吗?
答案是肯定的。
在参加CANN训练营之前,我对此半信半疑。但上完图3那节**“Triton算子开发与迁移”**的课,并亲手把一个 Softmax Triton Kernel 搬到昇腾上后,我发现这条路不仅通,而且门槛比我想象中低。

今天就来聊聊从GPU到NPU,Triton算子迁移的那些“坑”与“路”。


一、 为什么要用Triton做迁移?

通常迁移算子有两种路:

  1. 硬核路:把CUDA代码重写成Ascend C(C++)。性能最强,但工作量巨大,相当于重写。
  2. 偷懒路:直接用Triton。
    昇腾官方现在的策略是:拥抱生态
    只要你的Triton代码符合规范,昇腾的编译器后端(Backend)就能把它翻译成NPU能懂的指令。这对于手里囤了一堆Triton算子的算法工程师来说,简直是福音。

二、 基础架构:Triton在昇腾上是怎么跑的?

Triton在昇腾上的运行逻辑其实是一个**“翻译”**过程。

  • 前端(Frontend):你写的Python Triton代码。
  • 中端(Optimizer):MLIR中间表示(这部分是通用的)。
  • 后端(Backend):这是关键!
    • NVIDIA GPU对应的是 NVPTX Backend。
    • 昇腾 NPU 对应的是 Ascend Backend

知识点:
我们在昇腾上跑Triton,其实是调用了CANN软件包里的专门插件。这个插件负责把Triton的 load, store, dot 等指令,映射成Ascend C底层的 DataCopy, Matmul 等指令。
image.png


三、 迁移实战:那些必须要踩的“坑”

虽然说“自动翻译”,但毕竟硬件架构不同,完全平替是不可能的。以下是我在迁移过程中遇到的三个真实大坑。

坑1:Block Size的限制

现象:
在GPU上,我把 BLOCK_SIZE 设成1024,跑得飞起。
搬到昇腾上,报错或者性能极差。

原因:
UB内存大小不同。昇腾的AI Core也是由Unified Buffer(UB)大小限制的。如果你的Block太大,翻译过来的中间变量把UB撑爆了,程序就挂了。
填坑方案:
迁移时,建议先减小Block Size(比如从1024降到256或128),跑通了再说。昇腾对小Block的流水线优化其实做得很好,不一定非要大Block。

坑2:原子操作(Atomic Add)的差异

现象:
我在写一个带有 tl.atomic_add 的Kernel时,结果总是不对。

原因:
Triton的原子操作在不同硬件上的实现机制不同。昇腾目前对某些特定数据类型或场景的原子操作支持可能还在完善中(具体看CANN版本)。
填坑方案:
尽量避免过度依赖 atomic_add,或者尝试改写算法逻辑,用 tl.reduce 等更通用的指令替代。

坑3:非连续内存访问(Mask处理)

现象:
处理非对齐Shape时,GPU上Triton的Mask机制很鲁棒。但在NPU上,某些复杂的Mask写法可能会导致生成的搬运指令效率低下。

原因:
Ascend C底层喜欢连续、对齐的数据。
填坑方案:
在写Triton代码时,尽量保证 tl.loadtl.store 的地址是连续的。如果有复杂的Mask逻辑,尽量在逻辑层面把它简化,或者Padding成对齐的Shape再处理。

Uploading file...nc6yc


四、 代码对比:从CUDA到Triton on Ascend

为了让大家更有体感,看一段简单的 Vector Add 代码。你会发现,这在昇腾上是可以直接跑的!

import torch
import triton
import triton.language as tl

# 这段代码在NVIDIA GPU和昇腾NPU上都能运行!
# 只要环境配好了CANN和Ascend PyTorch插件

@triton.jit
def add_kernel(
    x_ptr,  # *Pointer* to first input vector
    y_ptr,  # *Pointer* to second input vector
    output_ptr, # *Pointer* to output vector
    n_elements, # Size of the vector
    BLOCK_SIZE: tl.constexpr, # Number of elements each program should process
):
    # 1. 获取当前程序的pid
    pid = tl.program_id(axis=0)
    
    # 2. 计算当前Block处理的数据范围
    block_start = pid * BLOCK_SIZE
    offsets = block_start + tl.arange(0, BLOCK_SIZE)
    
    # 3. 创建Mask防止越界(这一步在NPU上同样生效)
    mask = offsets < n_elements

    # 4. 加载数据(NPU Backend会自动把它翻译成DataCopy指令)
    x = tl.load(x_ptr + offsets, mask=mask)
    y = tl.load(y_ptr + offsets, mask=mask)
    
    # 5. 计算
    output = x + y
    
    # 6. 写回
    tl.store(output_ptr + offsets, output, mask=mask)

总结: 只要不触碰底层硬件特性的差异(如特殊的Inline ASM),纯Triton逻辑代码的可移植性是非常高的。


五、 结语:一条值得探索的新路

虽然现在Ascend上的Triton支持还在快速迭代中,可能还有些小bug,但这绝对是未来的趋势。
对于我们开发者来说,掌握Triton意味着掌握了**“一次编写,到处运行”**的主动权。

如果你受够了手写C++算子的繁琐,或者想把现有的Triton资产变现到国产算力上,CANN训练营的这门课绝对是最好的入门指南。


🔥 2025昇腾CANN训练营·第二季 报名开启!
别让你的AI模型只跑在黑盒子里,来这里,亲手拆解它!

👇 扫码/点击链接,硬核玩家速来集合:
https://www.hiascend.com/developer/activities/cann20252

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐