训练营简介
2025年昇腾CANN训练营第二季,基于CANN开源开放全场景,推出0基础入门系列、码力全开特辑、开发者案例等专题课程,助力不同阶段开发者快速提升算子开发技能。获得Ascend C算子中级认证,即可领取精美证书,完成社区任务更有机会赢取华为手机,平板、开发板等大奖。

报名链接:https://www.hiascend.com/developer/activities/cann20252#cann-camp-2502-intro

前言

“不懂历史的人,看不清未来。”

在昇腾算子开发的进化树上,有两个关键节点:

  1. TBE (Tensor Boost Engine) / TIK:基于 Python 的 DSL,强调灵活性,是“汇编级的 Python”。

  2. Ascend C:基于 C++ 的 DSL,强调工程化与高性能,是“结构化的 C++”。

为什么昇腾要从 Python 转向 C++?仅仅是为了性能吗? 其实,这背后是编程范式的根本转变:从显式指令流构建,转向面向对象的流水线编程

本期文章,我们将把同一个简单的 VectorAdd 算子,分别用 TIK 和 Ascend C 写一遍。对比之下,你将看到封装之下的真相。

一、 核心图解:语言的蜕变

TIK 像是在搭积木,你需要告诉系统“把积木A放到积木B上面”。 Ascend C 像是在画蓝图,你定义好“车间”和“传送带”,数据会自动流转。

二、 TIK 考古:用 Python 写汇编

TIK 的代码看起来是 Python,但执行逻辑完全不同。

2.1 TIK 代码实战 (Vector Add)

from tbe import tik

def tik_vector_add(shape, dtype):
    # 1. 创建 TIK 实例 (Builder)
    tik_instance = tik.Tik()

    # 2. 定义 Tensor (占位符)
    # 必须显式指定 scope (GM, UB)
    input_x_gm = tik_instance.Tensor(dtype, shape, name="x_gm", scope=tik.scope_gm)
    input_y_gm = tik_instance.Tensor(dtype, shape, name="y_gm", scope=tik.scope_gm)
    output_z_gm = tik_instance.Tensor(dtype, shape, name="z_gm", scope=tik.scope_gm)

    # 3. 申请 UB 空间
    input_x_ub = tik_instance.Tensor(dtype, shape, name="x_ub", scope=tik.scope_ubuf)
    input_y_ub = tik_instance.Tensor(dtype, shape, name="y_ub", scope=tik.scope_ubuf)
    output_z_ub = tik_instance.Tensor(dtype, shape, name="z_ub", scope=tik.scope_ubuf)

    # 4. 手动搬运 (Data Move)
    # 所有的参数 burst_len, stride 都需要手动计算,极易出错
    tik_instance.data_move(input_x_ub, input_x_gm, 0, 1, shape // 16, 0, 0)
    tik_instance.data_move(input_y_ub, input_y_gm, 0, 1, shape // 16, 0, 0)

    # 5. 计算 (Vector Add)
    # mask, repeat_times, dst, src0, src1... 也就是汇编指令的直接映射
    tik_instance.vec_add(128, output_z_ub, input_x_ub, input_y_ub, 1, 8, 8, 8)

    # 6. 搬回
    tik_instance.data_move(output_z_gm, output_z_ub, 0, 1, shape // 16, 0, 0)

    # 7. 编译构建
    tik_instance.BuildCCE(...)

TIK 的痛点

  • 无类型检查:Python 是动态类型的,很多错误(如地址偏移算错)要等到运行时才崩。

  • 手动调度:你需要自己计算 mask, repeat, stride,心智负担极重。

  • 显式同步:如果是双缓冲,你需要手动写 set_flag, wait_flag

三、 Ascend C 革命:C++ 的工程化胜利

Ascend C 引入了 类 (Class)模板 (Template) 的概念,把脏活累活封装了。

3.1 对比 Ascend C 实现

class KernelAdd {
    // ...
    __aicore__ inline void Process() {
        // Tiling 和 同步被封装在 Queue 和 Loop 中
        CopyIn();
        Compute();
        CopyOut();
    }
    
    __aicore__ inline void Compute() {
        // 不需要算 mask 和 stride,直接操作 Tensor 对象
        // Add(dst, src0, src1, length)
        Add(zLocal, xLocal, yLocal, tileLength);
    }
};

Ascend C 的优势

  1. 类型安全:C++ 编译器会检查 LocalTensor<T> 的类型匹配,杜绝了大部分低级错误。

  2. 流水线自动化:通过 TPipeTQue,硬件自动处理了大部分的数据依赖和同步。

  3. 调试友好:支持 CPU 孪生调试(printf),这在 TIK 时代是难以想象的。

四、 启示:从 TIK 中学到的底层逻辑

虽然我们现在写 Ascend C,但 TIK 揭示的底层原理依然有效,甚至能指导我们写出更高性能的 Ascend C 代码。

4.1 启示一:Mask 的本质

在 TIK 中,vec_add 的第一个参数是 mask。 Ascend C 的 Add 虽然隐藏了 mask,但在处理非对齐尾块时(第 46 期),我们通过 SetVectorMask 显式控制它。

  • TIK 思维:Mask 是指令的一部分。

  • Ascend C 思维:Mask 是一个状态上下文。

4.2 启示二:Stride 的威力

TIK 的 data_move 强迫你手动算 stride。这让你深刻理解了数据是非连续搬运的。 在 Ascend C 中做 Transpose(第 45 期)时,如果你理解 TIK 的 burst_lendst_stride,就能秒懂如何利用 DataCopy 做矩阵转置。

4.3 启示三:流水线的间隙

TIK 允许你在两条 data_move 之间插入任意指令。 Ascend C 的 Process 结构相对固定。 但在做极致优化时(指令级并行,第 43 期),我们有时会打破 Ascend C 的标准范式,像写 TIK 一样手动展开循环,手动控制指令发射顺序,以填充流水线气泡。

五、 总结

编程语言的演进,本质上是抽象层级的提升

  1. TIK 是“手工作坊”,让你看清每一块砖的纹理,适合探索硬件极限。

  2. Ascend C 是“现代化工厂”,让你关注架构设计和业务逻辑,适合大规模生产。

作为新时代的开发者,我们不仅要会用 Ascend C 盖楼,最好也要懂一点 TIK 的砌砖原理。这样,当大楼出现裂缝(性能瓶颈)时,你才能一眼看穿地基的问题。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐