跳到主要内容
TileLang 系统教程

tilelang-tutorial.huandu.me

第 25 章编译期、后端与芯片26 分钟12 习题8 自测题

昇腾后端:代码生成与硬件特性

昇腾后端的四条路线与它们各自的落点:主干 ascend 方言、适配器的 Ascend C&PTO 与 AscendNPU IR,以及 T.alloc_l1/l0a/l0b/l0c、dual_copy 在代码生成里变成了什么。

本章进度状态 未读完习题 0 / 12自测 未作答

学习目标

读完本章,你应当能:

  1. 说清「昇腾后端」在锁定版本里其实是四条可执行的编译路线,并指出每一条的 入口、生成物、编译器分别是什么;
  2. 把 T.alloc_shared / T.alloc_l1 / T.alloc_l0a / T.alloc_l0b / T.alloc_l0c 逐条对应到 UB / L1 / L0A / L0B / L0C,并说出它们最终写进 CCE 源文件的 __ubuf__ / __cbuf__ / __ca__ / __cb__ / __cc__ 限定符;
  3. 看着一段 T.copy 就能说出它会被降到哪一条 DMA 通路(DMAPath)、哪一个硬件 流水线(MTE1/MTE2/MTE3/FIX)、以及 codegen 最终发出的 intrinsic 名;
  4. 说明 T.dual_copy、T.SimtVF / T.SimdVF、T.Pipelined 这三类昇腾专属原语 在代码生成阶段各自体现在哪里,并知道 T.tile.* 在 v0.1.15 里不存在;
  5. 写出 NV 与昇腾两条后端在生成物、工具链、指令层次、同步模型上的结构对照;
  6. 在没有 NPU 的机器上,用「读源码 + 打印生成物清单 + 静态审计」三步,对一份 昇腾生成代码做出可信的核查。

前置知识

  • 第 4 章(编译全流程)与第 20 章(pass 管线):你会用到 MaterializeKernelLaunch、 LowerTileOp、MakePackedAPI 这些 pass 的名字。
  • 第 6 章与第 19 章 §2:AIC/AIV、UB/L1/L0A/L0B/L0C、PipeBarrier、set/wait flag 的基本概念。第 19 章给了机器模型与手把手实践;本章只做代码生成,不重复 环境安装与调优流程。
  • 第 22、23 章:自动调度、多缓冲与同步插入的算法。本章只关心它们在 codegen 里 留下的痕迹。

本章的验证边界(务必先读)

写作环境是 macOS (Apple Silicon),没有 NVIDIA GPU,也没有昇腾 NPU,没有安装 CANN 与 bisheng。因此本章:

  • 所有「生成什么代码」的结论,都来自逐行阅读锁定版本的 codegen 与 lowering 源码,而不是运行结果;
  • 所有「谁编译、编译出什么」的结论,都来自编译器调用点的源码;
  • 不含任何本机实测的性能数字;引用到的厂商数字都注明出处与「厂商自述」;
  • code/05-emit-ascend-c/ 里有一个真实可编译的 kernel,但它的 --emit 模式 本机从未运行过,只提供了「在昇腾机器上怎么核对」的清单。

本章的引用行号由 code/04-cite-check/check_citations.py 逐个核对过(见 §7.4)。


1. 问题的由来:一次 target="ascend" 之后到底发生了什么

第 19 章告诉你:在昇腾上写 TileLang,要先决定用哪棵树。但「用哪棵树」只是开始。 真正决定你能不能用、好不好调、出问题找谁的是生成物这一层:

  • 编译器吐出来的是一份 CCE/Ascend C 源文件,还是一份 MLIR 文本?
  • 这份源文件里出现的是 asc_copy_gm2l1_nd2nz(...) 这样的内置函数调用, 还是 AscendC::TPipe / pipe.InitBuffer(...) 这样的 C++ 类库调用?
  • 最终把它变成二进制的是 bisheng 还是 bishengir-compile?
  • 用户写的 T.copy 到底变成哪一条硬件搬运指令,T.gemm 又变成哪一条矩阵指令?

这四个问题,就是本章的全部内容。答案在锁定版本里是明确的、可逐行核对的, 但它们分散在三个仓库、四种目标名、五套目录结构里——这一章把它们收拢到一张表上。

M1 主干路线:target="ascend" 的生成链M 树:_vendor/tilelang-v0.1.15(主干)降级发射链接@tilelang.jit(target="ascend")M 树方言 tilelang.ascend.languageAscendPassPipelineBodytilelang/ascend/pipeline.py:36CodeGenTileLangAscendsrc/ascend/codegen/codegen_ascend.ccCCE/Ascend C 源asc_* intrinsic + __ubuf__ / __cbuf__bisheng --npu-arch=dav-3510+ ld.lld 链接可执行 CCE ELF (aibin)
M 树从 @tilelang.jit(target="ascend") 到可执行 CCE ELF 的六跳,生成物是 asc_* 内置函数风格的 CCE/Ascend C 源。
  • 入口在 tilelang/ascend/,实现在 src/ascend/,两者通过 FFI 名 target.build.tilelang_ascend 连接。
  • 生成物里没有 AscendC:: 类库调用,只有 asc_* 内置函数与 __ubuf__ / __cbuf__ / __ca__ / __cb__ / __cc__ 限定符。
四条昇腾编译路线一览M1:主干 tilelang v0.1.15A1:适配器 ascendcA2:适配器 ptoL1:MLIR tilelang-mlir-ascend树 / 仓库_vendor/tilelang-v0.1.15(主干)_vendor/tilelang-ascend-v0.1.2.000同左(适配器)corpus 快照 tilelang-mlir-ascendtarget 名ascend(别名 asc)ascendcptoTVM target kind npuir中端AscendPassPipelineBodyengine/phase.py 两个阶段engine/phase.py 同一套 passMLIR pass:canonicalize / adapt-triton-kerneldevice codegenCodeGenTileLangAscend(src/ascend/codegen)CodeGenTileLangAscend(src/target)CodeGenTileLangAscendPtocodegen_npuir_apis / _dev生成物CCE/Ascend C 源:asc_* intrinsic + __ubuf__ / __cbuf__AscendC 类库 C++:TPipe / TBuf / GetWithOffsetPTO-DSL C++:pto::Tile / TLOAD / TMATMUL 模板AscendNPU IR(MLIR 文本,HFusion / HIVM / HACC)编译器bisheng --npu-arch=dav-3510 + ld.lldbisheng --npu-arch=dav-2201 -xasc --sharedbisheng --cce-aicore-arch=dav-c220|c310 -xccebishengir-compile --enable-hivm-compile=true产物可执行 CCE ELF(aibin).so.sokernel.o
锁定版本里「昇腾后端」是四条可执行路线:M1 进了主干,A1/A2 在适配器仓库,L1 是 MLIR 快照;四者的目标名、生成物与编译器都不通用。
  • 只有 M1 进了主干 v0.1.15;A1/A2 与 L1 分别在自己的仓库/分支里,锁定版本是 v0.1.2.000 与 corpus 快照。
  • PTO 在 v0.1.15 里搜不到(ascend_pto / "pto" target 无输出);把 PTO 带进主干的是 PR3310。
  • 四者的 API、target 名、编译器参数都不通用——同一段昇腾代码换一棵树就不可复现。

1.1 先固定「四条路线」这个词

锁定版本(见 版本锁定)里,与「昇腾代码生成」相关的 可执行路线一共四条:

代号 树 / 仓库 目标 生成物 编译器
M1 _vendor/tilelang-v0.1.15/(主干) target="ascend"(别名 "asc") CCE/Ascend C 源,asc_* 内置函数风格(不是 AscendC:: 类库风格) bisheng --npu-arch=dav-3510 + ld.lld → 可执行 CCE ELF
A1 _vendor/tilelang-ascend-v0.1.2.000/(适配器) target="ascendc" AscendC::TPipe / AscendC::TBuf 类库风格的 C++ bisheng --npu-arch=dav-2201 -xasc → .so
A2 同上 target="pto" PTO-DSL C++(pto::Tile / TLOAD / TMATMUL 模板) bisheng --cce-aicore-arch=dav-c220|dav-c310 -xcce → .so
L1 corpus 快照 tilelang-mlir-ascend TVM target kind npuir AscendNPU IR(MLIR 文本) bishengir-compile --enable-hivm-compile=true … → .o

前两条(A1/A2)就是适配器仓库自述里说的 「Ascend C & PTO」路线,L1 是 「AscendNPU IR」路线;两者在同一句自述里被并列为「两条技术路线」 (_vendor/tilelang-ascend-v0.1.2.000/README.md:12)。M1 则是 2026-09-30 随 PR3308 进入主干的那条,它自带一套自动调度与同步插入 ([Public Release 9/30] Introduce Ascend 950 backend)。

这四条的 API 不通用、target 名不通用、编译器参数也不通用。 后面每一节都会 明确写「这是哪一条」。

1.2 一个必须先纠正的直觉:T.tile.* 不在主干里

昇腾适配器的文档大量出现 T.tile.add(...)、T.tile.sort32(...) 这类写法。它来自 适配器仓库的 tilelang/language/ascend_tile.py(3,112 行),并在 tilelang/language/__init__.py:86 被注册成 T.tile 命名空间。

主干 v0.1.15 里没有这个命名空间:tilelang/ascend/language/ 下不存在 ascend_tile.py,tilelang/ascend/language/__init__.py:84-124 的导出清单里也没有 任何 tile 别名。code/03-api-presence-matrix/api_matrix.py 会替你验证这一点 (输出见 §6.1)。同理,T.alloc_ub、T.alloc_L1(大写)、T.gemm_v0、 T.dump_tensor 都只存在于适配器那一侧。


2. 概念与模型:昇腾的机器长什么样(只讲影响代码生成的部分)

第 19 章 §2 已给出完整机器模型,这里只留下会直接改变生成代码形状的四件事。

2.1 AI Core 是「一 Cube + 两 Vector」的物理组

昇腾 950PR 的规格是 32/28 个 Cube Core、64/56 个 Vector Core (hiascend.com · zh_hardware_processor 第 57–58 行), 也就是 1:2。这个比例不是抽象,它直接写在生成的函数签名前缀里:

// _vendor/tilelang-v0.1.15/src/ascend/codegen/codegen_ascend.cc:336-351
extern "C" __global__ __mix__(1, 2)   // kMix:物理 1:2 组
extern "C" __global__ __vector__      // kVector:纯 Vector
extern "C" __global__ __cube__        // kCube:纯 Cube

kernel_mode_ 由函数体预扫描得到(有没有 Cube、有没有 Vector);__mix__(1, 2) 的注释写得很直接:「dav-3510's direct AIC-to-AIV data paths require the physical 1:2 group」。所以**「一个 kernel 里混用 Cube 与 Vector」在代码生成层就是换一个 函数限定符**,剩下的由 AssignCore(把任务分到 kCoreCube / kCoreVector, src/ascend/transform/assign_core.cc:64-75、core_mask.h:26-37)与 MixedKernel(sids=…)(tilelang/ascend/language/kernel.py:176-228)负责。

2.2 五块片上存储,五个 scope,五个限定符

GM (HBM)
 └─ L1      __cbuf__   scope "shared.l1"
     ├─ L0A  __ca__     scope "shared.l0a"    Cube 左矩阵
     ├─ L0B  __cb__     scope "shared.l0b"    Cube 右矩阵
     └─ L0C  __cc__     scope "shared.l0c"    Cube 累加器
 └─ UB      __ubuf__   scope "shared"/"shared.dyn"   Vector 的工作内存

映射写在 CodeGenTileLangAscend::GetAscendScopeQualifier (src/ascend/codegen/codegen_ascend.cc:152-164)。用户侧的入口是 tilelang/ascend/language/allocate.py:alloc_shared 的默认 scope 是 shared.dyn(:15-23,注释明确说 Ascend 的 alloc_shared 就是 UB), alloc_l1 / alloc_l0a / alloc_l0b / alloc_l0c 分别是 shared.l1 / shared.l0a / shared.l0b / shared.l0c(:26-70)。

两个细节值得记:

  • alloc_l0c 会顺手打上 L0C 布局(make_ascend_l0c_layout,:65-69);
  • alloc_l0a / alloc_l0b 不打布局,注释说「Layout is deferred to the consuming gemm operation」(:37-52)——布局由消费它的 GEMM 按 transpose 标志推断。这就是为什么你没有「给 L0A 指定 K-major 还是 MN-major」的 API。

2.3 四条搬运/收尾流水线,七个搬运代号

搬运不是「一句 memcpy」,而是按源/目的 scope 组合选出的固定通路。枚举在 src/ascend/op/oob_padding.h:32-44,选择逻辑在 src/ascend/op/oob_padding.cc:46-68:

GM  → UB   kGMToUB      GM  → L1   kGMToL1
UB  → GM   kUBToGM      UB  → L1   kUBToL1
L1  → L0A  kL1ToL0A     L1  → L0B  kL1ToL0B
L0C → UB   kL0CToUB     L0C → GM   kL0CToGM
L1  → L0A.sf / L0B.sf   (MX 尺度因子的槽位加载)

每一条通路归属于一条硬件流水线,定义在 src/ascend/transform/ascend_pipe.h:52-63:

位 名字 负责的搬运
kMTE1 MTE1 L1 → L0A / L1 → L0B
kMTE2 MTE2 GM → UB / GM → L1
kMTE3 MTE3 UB → GM / UB → L1
kCube M / MAD 矩阵乘
kVector V SimtVF / SimdVF / 向量运算
kFixpipe FIX L0C → UB / L0C → GM
kScalar S 标量与配置操作

这张表是整章的骨架:后面 §4.3 说 T.copy 怎么落到这七条通路之一,§4.5 说 T.dual_copy 为什么走 FIX,§4.6 说 Vector 区域为什么用 V。

2.4 同步是「pipe 到 pipe 的事件」,不是线程屏障

因为执行单元是分离的(MTE1/2/3 搬运、M 计算、FIX 收尾、V 向量),依赖必须表达成 流水线之间的一次通知。codegen 把这种事件拆成三样东西:

  1. hard_event 字符串,形如 MTE2_FIX;GetPipePair 只接受 {MTE1, MTE2, MTE3, M, V, S, FIX} 组成的对,否则直接 LOG(FATAL) (src/ascend/codegen/codegen_ascend.cc:262-272);
  2. 事件号 event_id(8 槽命名空间,由 RewriteFlagToBuf 归一化, tilelang/ascend/pipeline.py:176-179);
  3. 最终文本:asc_sync_notify(PIPE_src, PIPE_dst, event_t(id)) 与 asc_sync_wait(...)(codegen_ascend.cc:1349-1364)。

跨核(Cube↔Vector)另有一套:asc_sync_inter/subblock/block/intra_arrive|_wait (codegen_ascend.cc:274-292,1366-1381)。用户不需要写它们—— InsertSync 在 LowerScheduledTIR 之前插好(tilelang/ascend/pipeline.py:113-115)。

2.5 四条路线的一张总图

见 图:01-two-routes。要点是:M1 只进主干, A1/A2 与 L1 各自在自己的仓库/分支里;四者的「生成物」形态完全不同。


3. 用法:从两段真实 kernel 看生成物

3.1 M1:target="ascend" 的 GEMM + ReLU

这段改写自锁定版本的官方示例(_vendor/tilelang-v0.1.15/tilelang/ascend/README.md:66-108, 另见 examples/ascend/example_gemm.py):

import tilelang
import tilelang.ascend.language as T          # ← M 树方言,不是 tilelang.language

@tilelang.jit(target="ascend")
def matmul_relu(A, B, block_M: int = 256, block_N: int = 224, block_K: int = 128):
    M, N, K = T.const("M, N, K")
    A: T.Tensor((M, K), T.bfloat16)
    B: T.Tensor((N, K), T.bfloat16)
    C = T.empty((M, N), T.float32)
    num_blocks, n_tiles = 32, N // block_N

    with T.Kernel(num_blocks) as bx:                 # 一维核网格,没有 threads=
        A_l1  = T.alloc_l1((block_M, block_K), T.bfloat16)   # → __cbuf__
        B_l1  = T.alloc_l1((block_N, block_K), T.bfloat16)
        C_l0c = T.alloc_l0c((block_M, block_N), T.float32)   # → __cc__ + L0C 布局
        C_ub  = T.alloc_shared((block_M // 2, block_N), T.float32)  # → __ubuf__

        for tile in T.Persistent([M // block_M * n_tiles], num_blocks, bx):
            m, n = tile // n_tiles * block_M, tile % n_tiles * block_N
            for k in T.Pipelined(K // block_K, num_stages=2):
                T.copy(A[m, k * block_K], A_l1)                       # GM→L1, MTE2
                T.copy(B[n, k * block_K], B_l1, l2_cache_ctrl="NOTALLOC_KEEP")
                T.gemm(A_l1, B_l1, C_l0c, transpose_B=True, clear_accum=(k == 0))
            T.dual_copy(C_l0c, C_ub)                                   # L0C→UB, FIX
            with T.SimtVF(threads=128):                                # 线程域
                for i, j in T.Parallel(block_M // 2, block_N):
                    C_ub[i, j] = T.max(C_ub[i, j], 0)                  # ReLU 在 Vector
            T.dual_copy(C_ub, C[m:m+block_M, n:n+block_N], l2_cache_ctrl="NOTALLOC_PW")
    return C

这里有四个「读代码就能看见」的事实:

  1. 没有 threads=:T.Kernel 在昇腾方言里只接受一个 grid extent, 传多维或 threads= 会被 Python 层直接拒绝 (tilelang/ascend/language/kernel.py:139-171)。
  2. 线程域只在 T.SimtVF 里出现:pipeline 调 MaterializeKernelLaunch(lower_thread_binding=False, launch_dim_tags=["cthread"]) (tilelang/ascend/pipeline.py:45-51),注释写明 kernel scope 没有 threadIdx。
  3. 两处 T.dual_copy 走的是两条不同机制:L0C→UB 是硬件双目的拷贝, UB→GM 是按 cthread 的逐 AIV 软件拷贝(copy_op.py:76-79)。
  4. C_ub 的形状是 block_M // 2:因为两个 AIV 各处理一半,这正是 dual_copy 的形状契约(copy_op.py:83-84)。

3.2 A1 / A2:同一件事在适配器里的两种写法

适配器侧的等价 kernel 长相完全不同——T.Kernel(..., is_npu=True) 会额外绑定一个 子核号(官方示例里写成 vid;这里只写 Cube 侧,所以用 _ 忽略):

import tilelang
import tilelang.language as T                     # ← A 树方言

@tilelang.jit(out_idx=[-1])
def matmul(M, N, K, block_M, block_N, K_L1, dtype="float16", accum_dtype="float"):
    m_num, n_num = M // block_M, N // block_N

    @T.prim_func
    def main(A: T.Tensor((M, K), dtype), B: T.Tensor((K, N), dtype),
             C: T.Tensor((M, N), dtype)):
        with T.Kernel(m_num * n_num, is_npu=True) as (cid, _):
            bx, by = cid // n_num, cid % n_num
            A_L1  = T.alloc_L1((block_M, K_L1), dtype)      # 大写
            B_L1  = T.alloc_L1((K_L1, block_N), dtype)
            C_L0  = T.alloc_L0C((block_M, block_N), accum_dtype)
            with T.Scope("C"):                              # 显式声明这是 Cube 侧
                for k in T.serial(T.ceildiv(K, K_L1)):
                    T.copy(A[bx * block_M, k * K_L1], A_L1)
                    T.copy(B[k * K_L1, by * block_N], B_L1)
                    T.barrier_all()                         # 手写同步
                    T.gemm_v0(A_L1, B_L1, C_L0, init=(k == 0))
                    T.barrier_all()
                T.copy(C_L0, C[bx * block_M, by * block_N])
    return main

出处:examples/gemm/example_gemm.py、examples/elementwise/elementwise_add.py, 以及 tilelang/language/allocate.py:140-156(大写分配)、 tilelang/language/ascend.py:418-533(gemm_v0)、:275-286(barrier_all)。

同一段前端代码,target="ascendc" 与 target="pto" 会走出两个 codegen; 目标选择在 tilelang/utils/target.py:99-100 与 tilelang/engine/lower.py:158-170。

3.3 拿到生成代码的三条路

路线 怎么拿到 出处
M1 kernel.get_kernel_source(),或在 @tilelang.jit 编译后打印 资料库索引;code/05-emit-ascend-c/
A1 / A2 同上;适配器编程指南 §5.2 给了完整片段 tilelang-ascend/docs/TileLang-Ascend Programming Guide.md 第 2465–2490 行
L1 环境变量 TILELANG_DUMP_IR=1 会打印 ====== TVM IR ====== 与 ====== npuir ====== tilelang-mlir-ascend/tilelang/engine/lower.py 第 269–294 行

本章的 code/05-emit-ascend-c/print_ascend_c.py 把第一条做成了可复现脚本:--audit 离线核对 API 名,--emit 在有卡的机器上打印并逐条对照 §4 的检查清单。


4. 实现机制

A 树的两条链:ascendc 与 ptoA1:target="ascendc"(Ascend C 类库路线)A2:target="pto"(PTO-DSL 路线)发射发射@tilelang.jit(target="ascendc")engine/phase.py 两个阶段CodeGenTileLangAscendsrc/target/codegen_ascend.ccAscendC 类库 C++AscendC::TPipe / TBuf / GetWithOffsetbisheng --npu-arch=dav-2201 -xasc --shared.so@tilelang.jit(target="pto")engine/phase.py 同一套 passCodeGenTileLangAscendPtosrc/target/codegen_ascend_pto.ccPTO-DSL C++pto::Tile / TLOAD / TMATMUL 模板bisheng --cce-aicore-arch=dav-c220|c310 -xcce.so
适配器仓库里同一个前端可以走出两条 codegen:ascendc 发 AscendC 类库 C++,pto 发 PTO-DSL 模板 C++,但都编成 .so。
  • A 树的中端是传统 TileLang 管线:PipelinePlanning + InjectSoftwarePipeline,同步用 AscendSyncInsert / AscendSyncInsertVS。
  • 分配代码在 ascendc 上是 AscendC::TPipe + pipe.InitBuffer,在 pto 上是 pto::Tile<...> 别名实例。
存储层级、DMA 通路与硬件流水线MTE2 kGMToL1MTE2 kGMToUBMTE1 kL1ToL0AMTE1 kL1ToL0BMADFIX kL0CToUB(双目的)FIX kL0CToGMMTE3 kUBToGMMTE3 kUBToL1SIMT / SIMD归属GM (HBM)Global MemoryL1 (CBuf)__cbuf__ · scope shared.l1L0A (CA)__ca__ · Cube 左矩阵L0B (CB)__cb__ · Cube 右矩阵L0C (CC)__cc__ · Cube 累加器UB__ubuf__ · scope shared.dynAIC / CubePIPE_M(MAD)AIV / VectorPIPE_V(SIMT / SIMD)FixpipePIPE_FIX(收尾)
昇腾把片上存储拆成 L1/L0A/L0B/L0C/UB 五块,T.copy 按 src/dst 的 scope 选出一条 DMAPath,再归到 MTE1/MTE2/MTE3/MAD/FIX/V 之一。
  • T.alloc_shared 在 M 树方言里就是 UB(scope="shared.dyn" → __ubuf__);T.alloc_l1/l0a/l0b/l0c 分别是 __cbuf__/__ca__/__cb__/__cc__。
  • GM→L1 默认走 nd2nz(顺带做 ND→NZ),transpose=True 时改走 dn2nz;UB→L1 的 ND→NZ 先被 InsertNd2Nz 改写成 scatter + asc_copy_ub2l1。
  • kL1ToL0A / kL1ToL0B 还有 _transpose 变体;kL0CToUB 带 asc_dual_dst_mode。
从方言 op 到 CCE intrinsic 的链路编译期(TIR pass + codegen)重拼按 scope 选通路按名分派发射用户代码T.copy(A[m, k*block_K], A_l1) · T.gemm(A_l1, B_l1, C_l0c)Python 方言层tilelang/ascend/language/* — copy 重拼成 tl.tileop.ascend_copyTIR + SBlocktl.tileop.ascend_copy / tl.tileop.gemmAscendPassPipelineBodyAutoSchedule → AssignCore → InsertSyncAscendLowerTileOp按 src/dst scope 选 DMAPathascend_copy_* 调用 + ascend_mad7 条搬运 + MX 两条CodeGenTileLangAscendEmitGmToUbufCopy_ / EmitL1ToL0Copy_ / EmitMad_CCE 源文本asc_copy_gm2ub_align(...) / asc_mmad(...)bisheng dav-3510 + ld.lld
T.copy 先在 Python 方言层被重拼成 tl.tileop.ascend_copy,随后 AscendLowerTileOp 按 scope 选 DMAPath,codegen 再把每个 op 换成 asc_* 内置函数。
  • 同步不是用户写的,而且在 IR 里先于 intrinsic 产生:InsertSync 在 LowerScheduledTIR 之前运行,插入的是 hard_event(MTE2_FIX 这类 pipe 对)。
  • RewriteFlagToBuf 把 hard_event 落到 8 槽 flag 命名空间,超出的按背包问题溢出到共享 get_buf/rls_buf 互斥池。
SimtVF / SimdVF 两个区域与 2048-bit 约束T.Kernel(N) as bx一维核网格,无 kernel 级线程域T.Cube()AIC 上的工作T.Vector()AIV 上的工作with T.SimtVF(threads=128)真正的 threadIdx.x 域with T.SimdVF()没有线程,只有寄存器T.Parallel over tile迭代映射到线程跨线程竞争 → asc_syncthreads()T.simd.pset / vld / vadd / vsts一次 2048-bit2048-bit = 64×f32 或 128×f16predicate 必须是 boolx256,否则 LOG(FATAL)
核内没有 kernel 级线程域:只有 T.SimtVF 内部真的开一个 threadIdx.x 域,T.SimdVF 则完全没有线程,一条指令吃一整个 2048-bit 寄存器。
  • 两个区域在 IR 里都是不透明 SBlock(SIMT_VF / SIMD_VF),一直活到 codegen。
  • 两个区域都接受 latency= 作为调度模型输入;不填时由编译器估算,它不是实测周期。

4.1 入口是三层的

「后端」不是单一函数,而是三个注册点:

  1. target 归一化:normalize_ascend_target 把字符串 "ascend" 变成 Target({"kind": "ascend"}),normalize_asc_target 接受缩写 "asc" (tilelang/ascend/target.py:40-58)。
  2. backend manifest:register_backend(BackendModule(name="ascend", …)) 把 pipeline、device codegen、execution backend、host codegen 和 tilelang_callback_ascend_compile 捆在一起 (tilelang/ascend/backend.py:52-62)。
  3. device codegen 函数名:tilelang/ascend/codegen.py:5-9 把它绑到 FFI 函数 target.build.tilelang_ascend / ..._without_compile。

所以「昇腾后端在哪里」的准确答案是:入口在 tilelang/ascend/,实现在 src/ascend/,两者通过 target.build.tilelang_ascend 这一个 FFI 名连接。

4.2 pass 管线:昇腾不是把 CUDA 的管线换掉几个 pass

对比两条 pipeline 的骨架(NV 见 tilelang/cuda/pipeline.py:69-275):

NV:   MaterializeKernelLaunch(lower_thread_binding=True)
      → PipelinePlanning → InjectSoftwarePipeline → LayoutInference
      → LowerTileOp → … → ThreadSync("shared") → MakePackedAPI

昇腾: MaterializeKernelLaunch(lower_thread_binding=False, launch_dim_tags=["cthread"])
      → AscendLayoutInference → NormalizeAscendFractalStorage → InsertNd2Nz
      → [AutoSchedule → AssignCore → PrepareMultiBuffer → ResolveCore
         → InsertSync → MaterializeMultiBuffer → LowerScheduledTIR]
      → AscendSimdVFLowerParallel → AscendLowerTileOp
      → MergeUBAllocations → RewriteFlagToBuf → AscendThreadSync → MakePackedAPI

(昇腾侧的完整调用见 tilelang/ascend/pipeline.py:36-190,rg -c 'mod = ' 数到 80 行;同一条命令在 NV 侧 tilelang/cuda/pipeline.py 数到 74 行,其中 1 行是 mod = CUDAPassPipelineBodyPrologue(mod, target) 这条 pipeline 主体调用而非 pass,真正的 pass 调用是 73 次,与第 4、20 章的表一致;方括号里的 九步只在 TL_ENABLE_AUTO_SCHEDULE 打开时执行,默认打开, pipeline.py:22-33,103-116。)

三条差异会在第 22、23 章展开,这里只记结论:

  • 流水与多缓冲由求解器负责:AutoSchedule 读 T.Pipelined(..., num_stages=k) 的 num_stages 注解与 T.annotate_buffer_versions 的映射, 经 Z3 求出一个版本数,再由 PrepareMultiBuffer / MaterializeMultiBuffer 落成物理版本(src/ascend/transform/auto_schedule.cc:705-740、 tilelang/ascend/language/annotations.py:19-104)。
  • 同步由 InsertSync 生成、RewriteFlagToBuf 收口:前者产出 hard_event, 后者把它们压进 8 槽 flag 命名空间,超出的按背包问题溢出到共享 get_buf/rls_buf 互斥池(tilelang/ascend/pipeline.py:176-179)。
  • UB 分配最后合并:MergeUBAllocations(align_bytes=32),对齐常量 _MERGE_UB_ALIGNMENT = 32(tilelang/ascend/pipeline.py:19,171-174)。

4.3 T.copy 的降级:从 scope 到 intrinsic

这是全章最「可操作」的一张表。tilelang/ascend/language/copy_op.py:230-372 先做 Ascend 侧的参数归一化(l2_cache_ctrl 字符串→整数、pad_value/data_select、 transpose),然后把 tl.tileop.copy 重拼成 tl.tileop.ascend_copy (:362-371)——这一步很关键,它是「方言 op 与通用 op 分家」的分界。

接着 src/ascend/op/copy.cc 按 GetDMAPath 选出的通路组装实参, src/ascend/codegen/codegen_ascend.cc 把它们变成文本:

通路(源码枚举) pipe 生成代码
kGMToUB MTE2 asc_copy_gm2ub_align(...)(codegen_ascend.cc:1383-1418)
kUBToGM MTE3 asc_copy_ub2gm_align(...)(:1420-1433)
kGMToL1 MTE2 asc_copy_gm2l1_nd2nz / asc_copy_gm2l1_dn2nz(:1435-1467)
kL1ToL0A / kL1ToL0B MTE1 asc_copy_l12l0a / asc_copy_l12l0b(_transpose 变体,:1557-1577)
kL0CToUB FIX asc_copy_l0c2ub(...),前置 asc_set_l0c_copy_nz_para(1,0,0);第 8 个实参是 asc_dual_dst_mode(:1469-1492)
kL0CToGM FIX asc_copy_l0c2gm(...)(:1493-1515)
kUBToL1 MTE3 asc_copy_ub2l1(...)(:1597-1609)

两个工程细节,读生成代码时会直接看到:

  • ND→NZ:GM→L1 的拷贝默认走 nd2nz(顺带做格式转换);transpose=True 改走 dn2nz。UB→L1 的 ND→NZ 走另一条路:InsertNd2Nz 先把它改写成 scatter + asc_copy_ub2l1(tilelang/ascend/pipeline.py:88-91、 codegen_ascend.cc:1610-1651)。
  • OOB/尾部:AscendInsertOOBPadding 在调度之前把越界的 DMA 范围夹住、并把 GM→L1 的填充写成语义 T.fill,这样 AutoSchedule 能看到每次填充的确切 写区间(tilelang/ascend/pipeline.py:97-101)。

code/02-scope-dma-pipes/scope_dma_pipes.py 会把上面三张表(scope、通路、pipe) 从源码里抽出来并做四条一致性检查。

4.3.1 两张「实参长什么样」的原文

与其记住 intrinsic 的名字,不如记住它要几个实参、每个实参是什么。下面两段是 锁定版本里 EmitUbufToGmCopy_ 与 EmitL1ToL0Copy_ 的原文( src/ascend/codegen/codegen_ascend.cc:1420-1433 与 :1557-1577):

void CodeGenTileLangAscend::EmitUbufToGmCopy_(const CallNode *op) {
  ICHECK_EQ(op->args.size(), 8)
      << "tl.ascend_copy_ubuf_to_gm expects exactly 8 arguments";
  // IR arg 2 is sid, which asc_copy_ub2gm_align does not take.
  EmitCApiCall_("asc_copy_ub2gm_align",
                {{op->args[0], "(__gm__ uint8_t*)(", ")"},
                 {op->args[1], "(__ubuf__ uint8_t*)(", ")"},
                 {op->args[3]}, {op->args[4]},
                 {op->args[5], "static_cast<asc_store_l2_cache_mode>(", ")"},
                 {op->args[6]}, {op->args[7]}});
}

void CodeGenTileLangAscend::EmitL1ToL0Copy_(const CallNode *op, bool is_l0a) {
  const bool is_transpose = GetIntImmArg(op, 8, intrinsic, "transpose") == 1;
  const char *function_name =
      is_transpose
          ? (is_l0a ? "asc_copy_l12l0a_transpose" : "asc_copy_l12l0b_transpose")
          : (is_l0a ? "asc_copy_l12l0a" : "asc_copy_l12l0b");
  EmitCApiCall_(function_name, { /* 8 个实参 */ });
}

三个可迁移的读法:

  • 实参里出现 "(__gm__ uint8_t*)(" / "(__ubuf__ uint8_t*)(",说明这个 intrinsic 是指针风格的,参数顺序就是「目的、源」;
  • static_cast<asc_store_l2_cache_mode>(...) 说明 l2_cache_ctrl 最终变成了 C++ 枚举转换,而不是一个裸整数(对应的 Python 侧归一化在 copy_op.py:166-227);
  • is_transpose 只影响函数名的选择,不影响实参个数——所以你在生成代码里看到 _transpose 后缀,就知道上游某个 T.copy 带了 transpose=True。

4.4 T.gemm 的降级:只有一条路,就是 L0 MAD

T.gemm 在昇腾上的指令选择函数不做选择:

// _vendor/tilelang-v0.1.15/src/ascend/op/gemm.cc:23-48
static String SelectInst(const GemmNode &op, int block_size, Target target) {
  (void)op; (void)block_size; (void)target;
  return kAscendMAD;                    // 常量 kAscendMAD = "ascend.mad"
}

ComputeWarpPartition 也固定返回 {1, 1}——因为昇腾没有 warp 概念。 所以 M 树的 GEMM 只有一种形态:A 在 L0A、B 在 L0B、结果累加到 L0C 的 MAD (asc_mmad,codegen_ascend.cc:1652-1667)。区别只在参数:

  • clear_accum:对应 unit_flag_ctrl(copy_op.py:97-98 与 gemm_op.py 的注释说明 unit_flag_ctrl 0=手工同步、3=与 MAD 流水);
  • 精度模式:T.set_hf32_mode(...) 把 fp32 输入截成 HF32 (tilelang/ascend/language/mode.py:24-53);
  • 量化写法:gemm_blockscaled + alloc_l0a_sf / alloc_l0b_sf 的 MX 尺度 槽位(allocate.py:73-128),最终走 asc_mmad_mx(codegen_ascend.cc:1652-1667)。

没有 mma/wgmma/tcgen05 的对应物,也没有 m×n×k 指令族:昇腾侧只有一个 MAD 原语,M/N/K 是它的循环维度(源码里 node_M/node_N/node_K 必须是静态 IntImm,否则 T.gemm 直接抛错,见 gemm_op.py:55-60)。

4.5 T.dual_copy:一次拷贝,两种实现

T.dual_copy 的 docstring(tilelang/ascend/language/copy_op.py:53-106)把契约 写得很清楚:

  • 它必须在混合 kernel 里用:你还要在同一个 kernel 里提供 Cube 侧的工作; dual_copy 不会把纯 Vector kernel 变成两 AIV 启动。
  • 形状推断:rank ≥ 2 时恰有一个尾部维度是 2:1: [M, N] → [M/2, N] 是 M-split(dual_dst_ctl=0b01), [M, N] → [M, N/2] 是 N-split(0b10);静态 extent 必须是偶数。
  • 两条实现:
    • L0C→UB 是硬件双目的拷贝——它不是软件劈成两条,而是在 asc_copy_l0c2ub 的实参里传 asc_dual_dst_mode(codegen_ascend.cc:1469-1492);
    • GM→UB、UB→GM、UB→L1 是逐 AIV 的软件拷贝,用 cthread (子核 ID)把区域切片,RewriteDualCopy 在 layout/OOB/调度之前完成这次改写 (tilelang/ascend/pipeline.py:63-68、src/ascend/transform/rewrite_dual_copy.cc)。

还有一条会静默出错的限制:dav-950 不支持在 cc→ub 双目的拷贝里做类型转换。 dual_copy 在 Python 层主动拦截——只要 src 的 scope 是 shared.l0c 且 dtype 不同,就直接 raise ValueError,并建议你改用单独拷贝或在 VF 区域里 cast (copy_op.py:140-151)。这是本章最值得记住的一个「编译期保护」。

4.6 T.SimtVF / T.SimdVF:两个区域,两套约束

SIMT_VF 与 SIMD_VF 在 IR 里是不透明 SBlock,一直活到 codegen (src/ascend/transform/vf_regions.h:19-24)。它们的区别是:

T.SimtVF(threads=n) T.SimdVF()
线程域 有:块体第一个 AttrStmt(thread_extent) 绑定 threadIdx.x(vf_regions.h:38-60) 无
数据宽度 按线程数切 必须恰好 2048 bit
写法 T.Parallel 映射到线程 显式 T.simd.* 指令
典型生成物 asc_syncthreads()(codegen_ascend.cc:3554-3557)+ 线程映射后的向量代码 vector_f32 / vector_bool 寄存器操作(PrintType,:451-537)

SimdVF 的硬约束写在 ValidateSimdVectorType(:408-434):向量类型 element_bits × lanes 必须等于 2048,否则 LOG(FATAL);predicate 必须是 boolx256。这解释了为什么官方示例里 SIMD 循环的步长是 tile // 64 (float32 时 2048/32 = 64,见 examples/ascend/example_vecadd.py:31-38)。

VFChecker(tilelang/ascend/analysis/vf_checker.py)在管线中途就检查区域内的 分配与访问合法性,位置在 InsertNd2Nz 之后 (tilelang/ascend/pipeline.py:93-95)——因为有的 UB→L1 拷贝要先被改写成 SIMD scatter 才合法。

4.7 T.Pipelined 与多缓冲:注解 → 求解 → 物理版本

用户只写 T.Pipelined(iters, num_stages=k);真正决定生成代码里出现几个 buffer slot 的是三件事:

  1. AutoSchedule 读循环上的 num_stages 注解(auto_schedule.cc:705-740), 再结合 T.annotate_buffer_versions({buf: …}) 的映射 (annotations.py:19-104,取值语义见第 19 章 §7.5 的表);
  2. 求解器给出每个 buffer 的版本数与索引模式(iteration / counter / auto);
  3. MaterializeMultiBuffer 把逻辑版本落成物理槽位,RewriteAscendBufferVersionLayout 再改写布局(tilelang/ascend/pipeline.py:114,135)。

如果用户自己做了 ping-pong(buf[l % 2]),要用 T.annotate_manual_multi_buffer(buf, {flat: 3}) 告诉编译器「别再给我加一维」, 它只负责分析依赖并补 set_flag/wait_flag(annotations.py:107-162)。 两种多缓冲不要叠加——这正是第 19 章 §7.5 末尾的警告。

4.8 A 树的两条 codegen

适配器的两条路线在 src/target/ 下各有一个 codegen:

target="ascendc" target="pto"
目标归一化 lower() 里是 Target({"kind": "llvm", "model": "ascendc"})(tilelang/engine/lower.py:224);determine_target 另把 "ascendc" 映射成 llvm --keys=ascend(tilelang/utils/target.py:99-100) 同上,model 为 "pto"
选 codegen target.build.tilelang_ascend(tilelang/engine/lower.py:160-163) target.build.tilelang_ascend_pto(:164-165)
头文件 tl_templates/ascend/common.h、acl/acl.h、runtime/rt_ffts.h(src/target/codegen_ascend.cc:105-109) tl_templates/pto/common.h、<pto/pto-inst.hpp>(src/target/codegen_ascend_pto.cc:503-508)
函数前缀 extern "C" __global__ __aicore__(codegen_ascend.cc:100-101) extern "C" __global__ AICORE(codegen_ascend_pto.cc:4249-4250)
分配代码 AscendC::TPipe pipe; + AscendC::TBuf<AscendC::TPosition::A1> ascend_l1; pipe.InitBuffer(...),随后 auto v = ascend_l1.GetWithOffset<T>(size, offset)(codegen_ascend.cc:986,1028-1032,838-856) pto::Tile<...> 别名实例(src/tl_templates/pto/common.h)
编译器 bisheng --npu-arch=dav-2201 -std=c++17 -xasc … --shared(libgen.py:150-183) bisheng --cce-aicore-arch=dav-c220|dav-c310 -std=gnu++17 -xcce -I3rdparty/pto-isa/include …(libgen.py:185-230)

一个容易忽略的差别:A 树的中端用的是传统 TileLang 管线 ——PipelinePlanning + InjectSoftwarePipeline(tilelang/engine/phase.py:103-104), 同步用 AscendSyncInsert / AscendSyncInsertVS(:121-122);而 M 树把这两件 事换成了 AutoSchedule + InsertSync。这解释了为什么两棵树的 pass_configs 完全不同(TL_ASCEND_* 只在 A 树,tilelang/transform/pass_config.py:38-62)。

4.9 L1:AscendNPU IR(MLIR)路线

MLIR 路线的中端不是 TIR pass,而是 MLIR 方言:

  • codegen_npuir_apis / codegen_npuir_dev 把 TIR 变成 npuir 文本 (tilelang/engine/lower.py:167-181;TILELANG_ASCEND_MODE 选 expert/dev 两套);
  • 然后跑 MLIR pass:canonicalize、adapt-triton-kernel (tilelang/tladapter/transforms/,见 tilelang/engine/lower.py:286-290);
  • 最后由 bishengir-compile 编成 .o,关键开关是 --enable-hivm-compile=true、--enable-auto-multi-buffer=true、 --enable-triton-kernel-compile=true(jit_npu.py:1862-1916)。

AscendNPU IR 自己的分层方言是 HFusion(Linalg 扩展)/ HIVM(Tile 级计算、搬运、 同步)/ HACC(Host-Device 与 launch 语义)/ Annotation、Scope (TileLang AscendNPU IR 从入门到专家算子开发知识点 第 136–146 行)。 其中 HIVM 负责 CV 核映射(Mix Kernel 融合、核间同步、CVPipeline、AutoSubTiling), 以及核内片上内存映射与多级流水。

和 §4.2 的 M 树对照,两边的职责分配是镜像的:

M 树(TIR 内部) L1(MLIR 内部)
CV 融合 AssignCore + __mix__(1,2) HIVM 的 CV 核映射
多缓冲 PrepareMultiBuffer / MaterializeMultiBuffer --enable-auto-multi-buffer
同步 InsertSync → flag HIVM 的 AutoSync / CVPipeline
内存规划 MergeUBAllocations PlanMemory

4.10 PTO 路线:tile 粒度的指令集

PTO 的定位是一套以 tile 为粒度的指令集:pto.tload / pto.tstore 做搬运, pto.tadd / pto.trowsum 等做整 tile 计算,全部围绕 !pto.tile_buf<...> 这一个存储对象展开(PTO-Gym/docs/PTO-tile-Instruction-SPEC.md 第 18–60 行)。 它的几个概念和 TileLang 几乎一一对应,值得对照读:

PTO 概念 含义 TileLang 对应
loc(UB / L0A / L0B / L0C / bias / scaling) tile 的片上位置 T.alloc_shared/l1/l0a/l0b/l0c
物理形状 (rows, cols) vs 有效区 (v_row, v_col) 分配多少 vs 真正有数据 对齐后的 allocation vs OOB/tail mask
pto.tload / pto.tstore GM↔片上搬运 T.copy 的 kGMToUB / kUBToGM
pipeline 归属:PIPE_MTE2 / PIPE_MTE3 / PIPE_V 每条 tileop 挂在哪个硬件队列 §2.3 的同一张表
pto.set_flag/wait_flag 或 pto.get_buf/rls_buf 跨 pipeline 排序 asc_sync_notify / asc_sync_wait 与 buffer 版本

TileLang 生成 PTO 的方式不是发 MLIR,而是发 PTO-DSL 的 C++ 模板调用: codegen 头里 #include <pto/pto-inst.hpp>,模板层在 src/tl_templates/pto/common.h 里把 pto::Tile<...> 落成 L1 / L0A / L0B / L0C / UB 各自的 TileMatL1 / TileMatL0A / TileUbDataND 别名 (pto-dsl/README.md 也确认了 「tilelang-ascend 用 PTO-ISA 作为 codegen 后端」这一点)。

状态提示:把 PTO 后端带进主干的是 PR3310。corpus 里有两份互相矛盾的快照: PR 页面快照(2026-09-30,[Ascend] Add PTO backend for Ascend950) 把它标为 draft,但同一 corpus 的 main 源码快照(commit 994b44e)就是该 PR 的 合并提交。事实陈述以代码为准:v0.1.15 里搜不到 pto 目标(见 §6.2),main 快照里有 (详见第 30 章 §4.1.2)。

还有两层容易混淆的粒度,值得在名词上分开:

  • PTO micro instruction:流水线同步、pto.vlds / pto.vsts / pto.vadd 这类微操作。它是「一个向量循环」级别的东西;
  • PTO tile instruction:pto.tload / pto.tadd / pto.trowsum 这类 整 tile 的操作,内部展开成一串 micro instruction 加同步 flag (PTO-Gym/docs/PTO-tile-Instruction-SPEC.md 第 18–46 行)。

PTO 生态里的 ptoas 是这套指令的汇编器:PTO-Gym 把「一个可运行用例」拆成 kernel.pto(设备侧 PTO 源)、launch.cpp、main.cpp、golden.py、compare.py 五个文件,用 ptoas + bisheng 编成可执行文件再比对 golden (PTO-Gym/README.md 第 1–40 行)。 TileLang 不写 .pto 汇编,而是发 pto:: 模板的 C++——两者最终都会经过 PTO-ISA 这一层,但中间形态不同。

如果你要判断「某个 PTO tile instruction 在 spec 里到底怎么定义」,权威来源是 pto-spec(ASL 形式化定义,工作树自述为架构 0.58.7: 466 条 scalar instruction form、96 条 bundle/command、118 个 direct Tile operation、 905 个 ASL 单元,pto-spec/README.md 第 1–40 行), 而不是某一篇博客。


5. 与 NV 后端的结构对照

第 24 章会展开 NV 侧;这里只给结构对照表,供你定位「同一个抽象在两边落在哪」。 NV 侧来源:tilelang/cuda/backend.py:69-120、 tilelang/cuda/pipeline.py:69-275;昇腾侧来源见 §4.2。

维度 NV / CUDA(v0.1.15) 昇腾 M1(v0.1.15)
执行单元 SM(SIMT,warp=32 lane) AIC(Cube)+ AIV(Vector),物理 1:2 组
线程模型 thread/block/warp 贯穿始终 核内无线程;只有 T.SimtVF 内部有 threadIdx.x
片上存储 shared memory + 寄存器 L1 / L0A / L0B / L0C / UB
矩阵指令 mma → wgmma → tcgen05(按 arch 选择) 单一 asc_mmad(MAD),M/N/K 是循环维度
搬运 cp.async、TMA asc_copy_gm2l1_* / _ub2gm_align / _l12l0*
收尾 epilogue 在寄存器里做 Fixpipe:asc_copy_l0c2ub / _l0c2gm
流水/多缓冲 PipelinePlanning + InjectSoftwarePipeline AutoSchedule(Z3) + MaterializeMultiBuffer
同步 ThreadSync("shared"):warp barrier / mbarrier InsertSync → hard_event → 8 槽 flag
生成物 CUDA C++ CCE/Ascend C(asc_* 内置函数风格)
编译器 nvcc / NVRTC bisheng + ld.lld
产物 cubin / fatbin 链接好的 CCE ELF(aibin)

一句话:NV 是「一个线程块一组线程 + shared/register」;昇腾是「Cube 核 + 两个 Vector 子核 + 五块片上 buffer + 四条搬运/收尾流水线」。这是两侧 codegen、 同步 pass、多缓冲模型完全不同的根因。见 图:05-nv-vs-ascend。

为什么这条差异会一路影响到代码生成?因为同步的语义对象不同:

  • NV 侧同步的是线程:同一个 block 里的 128/256 个线程要看到同一份 shared memory,所以 ThreadSync 插的是 __syncthreads() / mbarrier 这类 「等到所有线程到齐」的屏障;
  • 昇腾侧同步的是流水线:MTE2 把数据放进 UB 之后,V 才能读;M 把结果写进 L0C 之后,FIX 才能搬。这里没有「所有线程到齐」的概念,只有「MTE2 → V 这一对事件 完成了吗」。所以 InsertSync 产出的是 hard_event(一对 pipe 名), codegen 发出的是 asc_sync_notify/wait。

把这两件事当成同一种东西,是理解昇腾自动同步时最常见的错误:昇腾的 num_stages 之所以能自动决定 buffer 版本数,正是因为每条依赖都能写成 「生产 pipe → 消费 pipe + 距离」,而这恰好是一个可求解的约束问题(第 23 章)。


NV 与昇腾后端的结构对照NV / CUDA(v0.1.15)昇腾 M1(v0.1.15)执行单元SM(SIMT,warp=32 lane)AIC(Cube)+ AIV(Vector),物理 1:2 group线程模型thread / block / warp 贯穿始终核内无线程;只有 T.SimtVF 内部有 threadIdx片上存储shared memory / registersL1 / L0A / L0B / L0C / UB矩阵指令mma → wgmma → tcgen05(按 arch 选择)单一 asc_mmad(MAD),M/N/K 是循环维度搬运cp.async / TMAasc_copy_gm2l1_* / _ub2gm / _l12l0*收尾epilogue 在 register 里做Fixpipe:asc_copy_l0c2ub / _l0c2gm流水 / 多缓冲PipelinePlanning + InjectSoftwarePipelineAutoSchedule(Z3) + MaterializeMultiBuffer同步ThreadSync(warp barrier / mbarrier)InsertSync → hard_event → 8 槽 flag生成物CUDA C++CCE/Ascend C(asc_* 内置函数风格)编译器nvcc / NVRTCbisheng(+ ld.lld 链接)产物cubin / fatbinlinked CCE ELF(aibin)
只列结构性事实(谁生成什么、谁编译、同步插在哪个 pass),不比较性能;差异的根因是执行单元、线程模型与同步语义整条链都不同。
  • 一句话:NV 是「一个线程块一组线程 + shared/register」,昇腾被拆成「Cube 核 + 两个 Vector 子核 + 五块片上 buffer + 四条搬运/收尾流水线」。
  • 同步的语义对象不同:NV 同步线程(要看到同一份 shared),昇腾同步流水线(MTE2 → V 这一对事件完成了吗)。
  • 把这两件事当成同一种东西,是理解昇腾自动同步时最常见的错误。

6. 进阶与陷阱

6.1 两棵树的 API 不通用(已逐条核对)

code/03-api-presence-matrix/api_matrix.py 在锁定版本上给出下面的结果:

API M 树(v0.1.15) A 树(v0.1.2.000)
target "ascend" / "asc" "ascendc" / "pto"
前端导入 tilelang.ascend.language tilelang.language
UB 分配 T.alloc_shared(scope shared.dyn) T.alloc_ub(scope shared.ub)
L1/L0 分配 T.alloc_l1/l0a/l0b/l0c(小写) T.alloc_L1/L0A/L0B/L0C(大写)
双目的拷贝 T.dual_copy 无
SIMT/SIMD 区域 T.SimtVF / T.SimdVF 无(用 T.Scope("V") + T.Parallel)
矩阵乘 T.gemm T.gemm_v0
T.tile.* 不存在 T.tile = ascend_tile(:86)
设备打印 T.print(无 dump_tensor) T.printf / T.dump_tensor
pass_configs 前缀 TL_ENABLE_AUTO_SCHEDULE TL_ASCEND_*(8 个键)

把 A 树的 T.alloc_L1 写进 M 树的 kernel,会得到 AttributeError;把 M 树的 T.dual_copy 写进 A 树,同样是 AttributeError。同一段昇腾代码在另一棵树 上不可复现,这是排查昇腾问题时第一个要确认的事。

6.2 v0.1.15 里没有 PTO 目标

这条结论可以直接验证:

rg -n 'tilelang_pto|ascend_pto|"pto"' \
   _vendor/tilelang-v0.1.15/tilelang/ _vendor/tilelang-v0.1.15/src/   # 无输出
ls _vendor/tilelang-v0.1.15/src/ascend/                                # 无 pto codegen

PTO codegen(codegen_ascend_pto.cc、rt_mod_ascend_pto.cc、 tl_templates/pto/)只存在于适配器仓库。任何看到「在 v0.1.15 里用 target="pto"」的说法,都要先确认他指的是哪棵树。

6.3 issue 3317:两条路线怎么选,没有维护者结论

corpus 收了这个问题:「It seems there are two Ascend 950 backends, which is suggested to use」([Question] It seems there are two Ascend 950 backen…)。 该 issue 已被关闭,但抓取到的页面里只有提问正文、标签与元信息,没有维护者回答 (帖子里的 Activity 区没有可见评论)。因此:

  • 不要引用它作为「官方推荐用某一条」的依据;
  • 它唯一能支撑的结论是「社区已经意识到存在两条昇腾 950 路线,并且这是一个 需要被回答的问题」。

需要选路线时,唯一可靠的依据还是各自仓库的适用范围: PR3308 明确写「target hardware is Ascend 950 (dav-3510),A2/A3 请用社区维护的 TileLang-Ascend」;PR3310 的评审快照是 draft,但 main 快照已合入它(见 §4.10) (PR3308 正文见 [Public Release 9/30] Introduce Ascend 950 backend)。

6.4 三个具体的「会咬人」的点

  1. arch 字符串不通用。M 树默认 dav-3510(tilelang/contrib/bisheng.py:91-96), A 树 AscendC 用 --npu-arch=dav-2201,A 树 PTO 用 --cce-aicore-arch=dav-c220(A2/A3)或 dav-c310(A5) (tilelang/jit/adapter/libgen.py:150-190)。写死一个值换树就会失败。
  2. dual_copy 不做类型转换。L0C→UB 的 src/dst dtype 必须一致,否则 Python 层直接报错(copy_op.py:140-151)。
  3. SimdVF 的宽度必须整。2048-bit 不是「建议」,是 ValidateSimdVectorType 的 LOG(FATAL) 条件(codegen_ascend.cc:408-434)。

7. 调试与验证:没有 NPU 时怎么核查生成代码

7.1 官方给的三类调试手段

适配器编程指南 §5–§7 给了完整清单 (tilelang-ascend/docs/TileLang-Ascend Programming Guide.md 第 2342–2526 行):

  • 设备端打印:T.printf(fmt, *args) 与 T.dump_tensor(tensor, desc, dump_size, shape_info); 需要编译时开调试(compile_flags=["-D_DEBUG", "--cce-enable-print"], 或 PTO 的 TL_PTO_DEBUG=1)。注意指南明确写了代价:每个核上的 dump 总空间上限 1 MB,且会明显影响性能。
  • 看生成的 Ascend C:func.get_kernel_source()(指南 §5.2)。
  • 性能剖析:msprof op(上板)与 msprof op simulator(仿真)。仿真能看 指令流水图/热点图/内存通路波形,但仿真数字不等于上板数字,也不要跨设备比较。

7.2 看生成代码时按这个顺序读

拿到一份 M1 的生成源,建议按下面的顺序核对(对应 code/05-emit-ascend-c/ 的清单):

  1. 函数前缀:__mix__(1,2) / __vector__ / __cube__,确认它是不是混合核;
  2. buffer 声明:__ubuf__ / __cbuf__ / __ca__ / __cb__ / __cc__ 各自出现几次,和你的 T.alloc_* 对不对得上;
  3. 搬运:asc_copy_gm2l1_nd2nz(含 ND→NZ)、asc_copy_l12l0a/b、 asc_copy_l0c2ub、asc_copy_ub2gm_align;
  4. 矩阵:asc_mmad 的实参(M/N/K、transpose、unit flag);
  5. 同步:asc_sync_notify / asc_sync_wait 的 pipe 对是否与 §2.3 一致; 跨核时看 asc_sync_inter_arrive/wait;
  6. 多缓冲:同一个逻辑 buffer 是否出现多个 slot 偏移(版本数 ≥ num_stages)。

把上面六条并起来,一份 §3.1 那种 GEMM+ReLU kernel 的生成源形态大致是下面 这样。注意:这不是一段实测输出,而是从 codegen_ascend.cc 的发射语句 (:336-351、:1383-1690)与 src/tl_templates/ascend/common.h:1-13 反推的骨架; 真实函数名、实参个数与 buffer 偏移只有上机才能确认。

// 由 extern "C" __global__ __mix__(1, 2) 决定:这是一个混合核
#include <tl_templates/ascend/common.h>      // c_api/asc_simd.h、simt_api/* ...(见 src/tl_templates/ascend/common.h:1-13)
#include <tl_templates/ascend/debug.h>

extern "C" __global__ __mix__(1, 2) void main_kernel(__gm__ bfloat16* A,
                                                    __gm__ bfloat16* B,
                                                    __gm__ float* C) {
  __cbuf__ bfloat16* A_l1;   // T.alloc_l1  -> scope shared.l1 -> __cbuf__
  __cbuf__ bfloat16* B_l1;
  __cc__   float*    C_l0c;  // T.alloc_l0c -> scope shared.l0c -> __cc__
  __ubuf__ float*    C_ub;   // T.alloc_shared -> scope shared.dyn -> __ubuf__

  for (/* persistent tiles */) {
    for (/* k, num_stages=2 */) {
      asc_copy_gm2l1_nd2nz(A_l1, A, /* ... */);              // MTE2
      asc_copy_gm2l1_nd2nz(B_l1, B, /* ..., l2 策略 ... */);  // MTE2
      asc_sync_notify(PIPE_MTE2, PIPE_MTE1, static_cast<event_t>(...));
      asc_sync_wait (PIPE_MTE2, PIPE_MTE1, static_cast<event_t>(...));
      asc_copy_l12l0a(A_l0a, A_l1, /* ... */);               // MTE1
      asc_copy_l12l0b(B_l0b, B_l1, /* ... */);               // MTE1
      asc_mmad(C_l0c, A_l0a, B_l0b, /* M,N,K,transpose,... */);  // PIPE_M
    }
    asc_copy_l0c2ub(C_ub, C_l0c, /* ..., asc_dual_dst_mode, ... */);  // FIX
    // T.SimtVF(threads=128) 区域:ReLU + asc_syncthreads()
    asc_copy_ub2gm_align(C, C_ub, /* ..., l2 策略 ... */);           // MTE3
  }
}

这段骨架的价值不在于「它长得对」,而在于它把要核对的位置都标出来了: 如果你在真机上打印出来的代码里,某条搬运的名字、pipe 对或 buffer 限定符与这里 不符,就说明你对 §4 的某一步理解错了——或者你用的不是这一条路线。

7.3 没有卡时的「三级可信度」

任何关于昇腾运行结果的结论,只可能属于下面三级之一,写文档时必须标清楚:

级别 你能说什么 不能说什么
仅静态核对 源码里确实这么写;API 名存在;生成物会长什么样 「跑通了」「性能是 X」
CPU/仿真验证 camodel 之类的仿真里逻辑正确 上板性能
真实硬件运行 有输入的实测结果 无条件的跨设备推广

本章属于第一级。code/05-emit-ascend-c/ 的 --emit 也只是第一级(它打印生成物, 不运行 kernel),本机从未跑过;只有在真实 NPU 上跑出结果、并做数值比对,才进入第三级。

7.4 引用自查

python3 chapters/25-ascend-backend-codegen/code/04-cite-check/check_citations.py

它会把 chapter.md / 本章习题 / 参考答案 / 本章引用 里每个 path:line 逐个解析,拒绝「文件不存在」与「行号越界」。


8. 本章小结

  • 「昇腾后端」= 四条路线:M1(主干 target="ascend",CCE/Ascend C + bisheng dav-3510)、A1(适配器 ascendc,AscendC 类库 + bisheng dav-2201)、 A2(适配器 pto,PTO-DSL + bisheng -xcce)、L1(MLIR 仓库 npuir, AscendNPU IR + bishengir-compile)。
  • 存储映射:T.alloc_shared→UB/__ubuf__、alloc_l1→__cbuf__、 alloc_l0a→__ca__、alloc_l0b→__cb__、alloc_l0c→__cc__; alloc_l0c 自动带 L0C 布局,alloc_l0a/l0b 的布局由消费它的 GEMM 推断。
  • 搬运映射:T.copy 先按 scope 选 DMAPath(7 条 + MX 两条),再落到 MTE1/MTE2/MTE3/FIX 之一,最后变成 asc_copy_* 的一个具体名字。
  • 专属原语:T.dual_copy(L0C→UB 硬件双目的;其它路径逐 AIV)、 T.SimtVF/T.SimdVF(区域划分 / 2048-bit 寄存器)、T.Pipelined + T.annotate_buffer_versions(Z3 决定版本数)。T.tile.* 与 T.alloc_ub 只在适配器里。
  • 版本差异:v0.1.15 只有一个昇腾目标 ascend,没有 PTO;main 源码快照已合入 PTO(PR3310,commit 994b44e);issue 3317 没有维护者结论。
  • 与 NV 的差别不是「换几个 pass」,而是执行单元、线程模型、片上存储、 矩阵指令、同步模型整条链都不同。

延伸阅读

条目 id 与 本章引用 一一对应。

习题

12 题 · 答案在提交后展开

先在纸上或输入框里写下你的答案,再展开参考答案对照。客观判断请自己打勾:这三颗按钮 只影响本机保存的学习进度。

E1概念难度 ★无需硬件概念题

锁定版本里与昇腾代码生成相关的四条可执行路线分别是哪四条?对每一条写出:

  1. 目标名(target= 或 target kind);
  2. 生成物的形状(CCE/Ascend C?AscendC C++?PTO-DSL C++?MLIR?);
  3. 最终编译器与它收到的关键参数。

需要硬件:否


E2概念难度 ★★无需硬件概念题

CodeGenTileLangAscend::PrintFuncPrefix(_vendor/tilelang-v0.1.15/src/ascend/codegen/codegen_ascend.cc:336-351) 有三种前缀。请回答:

  1. 三种前缀分别对应什么 kernel_mode_?它们各自意味着这个 kernel 里有 Cube 还是 Vector 的工作?
  2. __mix__(1, 2) 为什么是「1, 2」而不是「1, 1」或「2, 2」?请给出两处依据: 一处源码注释,一处 corpus 里的硬件规格。
  3. 什么决定一个函数用哪种前缀?

需要硬件:否


E3代码阅读难度 ★★无需硬件代码阅读题

下面是从 _vendor/tilelang-v0.1.15/src/ascend/codegen/codegen_ascend.cc:152-164 抽出的片段(略去行号):

if (scope == "shared" || scope == "shared.dyn")  return "__ubuf__";
if (scope == "shared.l0a" || scope == "shared.l0a.dyn") return "__ca__";
if (scope == "shared.l0b" || scope == "shared.l0b.dyn") return "__cb__";
if (scope == "shared.l0c" || scope == "shared.l0c.dyn") return "__cc__";
if (scope == "shared.l1"  || scope == "shared.l1.dyn")  return "__cbuf__";
return "";

请回答:

  1. 把这些 scope 一一对到硬件存储(UB / L1 / L0A / L0B / L0C)。
  2. _vendor/tilelang-v0.1.15/tilelang/ascend/language/allocate.py 里 alloc_shared / alloc_l1 / alloc_l0a / alloc_l0b / alloc_l0c 的默认 scope 各是什么?是否都被上面这段覆盖?
  3. 为什么这段函数没有处理 "local" 或 "local.fragment"?提示:看看 alloc_l0c 在分配时顺手做了什么(allocate.py:55-70)。

需要硬件:否


E4代码阅读难度 ★★无需硬件代码阅读题

给定 kernel 里的四条拷贝(都来自 M 树):

T.copy(A[m, k*block_K], A_l1)          # (a)
T.copy(A_l1, A_l0a)                     # (b)
T.copy(C_l0c, C_ub)                     # (c)
T.copy(C_ub, C[m:m+block_M, n:n+block_N])  # (d)

对每一条写出:

  1. GetDMAPath 会返回哪个枚举值;
  2. 它归属哪条硬件流水线(MTE1 / MTE2 / MTE3 / FIX);
  3. codegen_ascend.cc 会发出哪个 intrinsic 名字(有多个变体时说明差别)。

依据:src/ascend/op/oob_padding.cc:46-68、src/ascend/codegen/codegen_ascend.cc:1383-1609。

需要硬件:否


E5概念难度 ★★无需硬件概念题

_vendor/tilelang-v0.1.15/src/ascend/op/gemm.cc:23-63 里,SelectInst 与 ComputeWarpPartition 都几乎不做事。请回答:

  1. SelectInst 永远返回什么?这个常量的字面值是什么?
  2. ComputeWarpPartition 把 policy.m_warp / policy.n_warp 设成多少? 为什么昇腾侧不需要 warp 划分?
  3. 那么 T.gemm 在昇腾上「不一样」的地方体现在哪些参数上?至少写出三个 (提示:累加、精度模式、块缩放)。

需要硬件:否


E6代码阅读难度 ★★无需硬件代码阅读题

阅读 _vendor/tilelang-v0.1.15/tilelang/ascend/language/copy_op.py:53-163 (dual_copy),回答:

  1. 对 rank ≥ 2 的区域,形状推断规则是什么?[M, N] → [M/2, N] 与 [M, N] → [M, N/2] 分别对应哪个 dual_dst_ctl?
  2. 哪些源/目的组合走硬件双目的,哪些走逐 AIV 的软件拷贝?
  3. 为什么在 src 的 scope 是 shared.l0c 且 dtype 不同时代码会直接 raise? 如果这里不拦,会发生什么(注释怎么说的)?

需要硬件:否


E7概念难度 ★★无需硬件概念题

T.SimtVF(threads=N) 与 T.SimdVF() 是两种 Vector 编程模型。请回答:

  1. 哪一个有真正的线程域?它在 IR 里的标记是什么(提示: src/ascend/transform/vf_regions.h:38-60)?
  2. SimdVF 里的向量类型必须满足什么位宽约束?predicate 必须是什么类型? 出错时代码是报错还是悄悄降级?
  3. 为什么官方 example_vecadd.py 的 SIMD 分支里步长是 tile // 64?

需要硬件:否


E8动手难度 ★★无需硬件动手题

在书根运行:

python3 chapters/25-ascend-backend-codegen/code/02-scope-dma-pipes/scope_dma_pipes.py

然后:

  1. 贴出它打印的「DMAPath → owning pipe」表;
  2. 在脚本里新增一条检查:kL0CToUB 与 kL0CToGM 都必须映射到 kFixpipe, 否则报错;
  3. 说明为什么 kGMToUB 与 kGMToL1 可以共用 MTE2,而 kL0CToUB 必须用 FIX 而不是 MTE3。

需要硬件:否


E9动手难度 ★★无需硬件动手题

运行 code/03-api-presence-matrix/api_matrix.py,然后回答:

  1. 哪一列里出现了 alloc_ub?另一列是什么?
  2. T.tile 命名空间在哪一列?给出它的注册行。
  3. 如果一段教程代码同时用了 T.alloc_l1 和 T.alloc_L1,你能立刻判断它写错了 吗?为什么?

需要硬件:否


E10概念难度 ★★★无需硬件概念题

比较 M 树(TIR 内做自动调度)与 L1(AscendNPU IR/MLIR)两条路线在下面四件事上的 责任划分:CV 融合、多缓冲、同步插入、内存规划。

要求每一格都给出源码或 corpus 依据(M 树用 tilelang/ascend/pipeline.py 与 src/ascend/transform/,L1 用 corpus 的 tilelang-mlir-ascend 与华为技术文章)。

需要硬件:否


E11代码阅读难度 ★★★无需硬件代码阅读题

下面三行分别来自三份源码,请指出各属于哪条路线,并解释每个参数/开关的作用:

bisheng --npu-arch=dav-2201 -std=c++17 -xasc ... --shared
bisheng --cce-aicore-arch=dav-c220 -D MEMORY_BASE -std=gnu++17 -xcce -I .../3rdparty/pto-isa/include
bishengir-compile kernel.npuir --enable-hivm-compile=true --enable-triton-kernel-compile=true -o kernel.o

另外回答:为什么第一条里没有 --cce-aicore-arch,第三条里没有 -xasc?

需要硬件:否


E12动手难度 ★★★无需硬件动手题

你被要求给一个昇腾团队做技术选型建议。已知:

  • 他们的现有算子库全部是手写 Ascend C,跑在 A2;
  • 他们想先在没有 NPU 的开发机上把新算子写出来、验证功能,再上板;
  • 他们不接受「生成物不可读」的方案。

请基于本章的四条路线,给出你的推荐与理由,并明确指出:

  1. 哪条路线适合/不适合,依据是什么;
  2. 你会先用哪几个现成脚本来验证环境与生成物;
  3. 你的建议里有哪些是必须上板才能确认的(也就是本章不能替你保证的部分)。

需要硬件:否(建议本身);若要坚持某一结论,需要硬件

章节自测

8 题 · 提交后本地评分

单选题。提交后立即给出分数与逐题解析;重做会清空选择,但会保留上一次的成绩记录。

  1. 1. 锁定版本里,「昇腾后端」其实是四条可执行的编译路线。哪一条进了主干 v0.1.15,并且生成物是 CCE/Ascend C?

  2. 2. 在 M 树方言里,T.alloc_shared 最终写进 CCE 源文件的哪个限定符、对应哪块存储?

  3. 3. 一段 T.gemm 在昇腾 M 树上会被降级成什么?

  4. 4. T.dual_copy 的哪一条限制会在 Python 层主动报错,属于「编译期保护」?

  5. 5. 关于 T.SimdVF,下列哪一项是硬约束?

  6. 6. 为什么昇腾能用 num_stages 自动决定 buffer 版本数,而 NV 侧要靠 PipelinePlanning + InjectSoftwarePipeline?

  7. 7. 在 M 树里,一句 T.copy 变成具体 intrinsic 的正确顺序是?

  8. 8. 关于两棵昇腾树的 API 差异,下列哪一项正确?

本章代码

11 个文件

正文里提到的每个示例都能在这里直接阅读(带语法高亮),也可以整章浏览。

浏览本章全部代码 →

本章引用

源码链接指向锁定 tag

本章写到的每个源码位置与外部资料都在下面列出:源码链接指向锁定 tag 的 GitHub 文件, 资料链接指向原文。查看全部资料索引 →

展开引用清单

第 25 章 参考资料

本章所有结论都基于下表中的本地资料或锁定版本源码。版本以 版本锁定 为准:TileLang v0.1.15 (commit a35f8ddf45eba16c21211ec8822d56ce5363036f,VERSION = 0.1.15)与 TileLang-Ascend TileLang-ascend-v0.1.2.000-release (commit 89a9364e47a341fffdd65ed94786b1d4f9b6a78c,仓库内 VERSION 写 0.1.4)。

写作环境:macOS (Apple Silicon)、Python 3.11;没有 NVIDIA GPU,没有昇腾 NPU, 没有安装 CANN / bisheng,也没有安装 tilelang/tvm。因此本章代码示例的最高验证 级别是「仅静态核对」;code/ 下的五个脚本是「CPU 逻辑验证」(见各目录 README)。 本章不包含任何自测性能数字。

核验日期均为 2026-10-01。核验方式:在锁定版本的工作树上用 rg -n / sed -n 定位、 阅读完整签名与注释;正文每个 path:line 都由 code/04-cite-check/check_citations.py 重新解析校验;两棵树的 API 差异由 code/03-api-presence-matrix/api_matrix.py 现场生成。这不是运行验证。


一、版本与总则

id 引用位置 用途 核验状态
V1 版本锁定 版本锁定表:两个 commit、日期、路径 已核验(2026-10-01)
V2 _vendor/tilelang-v0.1.15/VERSION 主干版本 = 0.1.15 已核验(2026-10-01)
V3 _vendor/tilelang-ascend-v0.1.2.000/VERSION 适配器仓库内版本号 = 0.1.4(以 tag TileLang-ascend-v0.1.2.000-release 为准) 已核验(2026-10-01)
V4 _vendor/tilelang-v0.1.14/(对照) 只用于回答「某能力是不是 v0.1.15 新增」;本章未做 v0.1.14 对照,正文不含相关结论 未使用(本章不需要)

二、M 树(主干 _vendor/tilelang-v0.1.15/)

id 引用位置 用途 核验状态
R1 tilelang/ascend/target.py:40-58 normalize_ascend_target / normalize_asc_target;"ascend" 与 "asc" 归一化 已核验(2026-10-01):逐行读
R2 tilelang/ascend/backend.py:15-62 tilelang_callback_ascend_compile(bisheng 编译 + aibin 缓存)与 BackendModule 注册 已核验(2026-10-01):逐行读
R3 tilelang/ascend/codegen.py:5-9 FFI 名 target.build.tilelang_ascend / ..._without_compile 已核验(2026-10-01)
R4 tilelang/ascend/pipeline.py:19-190 昇腾 pass 管线全文:MaterializeKernelLaunch 参数、RewriteDualCopy、AutoSchedule 九步、RewriteFlagToBuf、MergeUBAllocations(align_bytes=32) 已核验(2026-10-01):全文阅读
R5 tilelang/ascend/pipeline.py:22-33,103-116 allow_autoschedule 与 TL_ENABLE_AUTO_SCHEDULE(默认 True) 已核验(2026-10-01)
R6 资料库索引 默认 dav-3510;GEMM+ReLU 快速开始;「You write / Ascend C you can skip」分工表 已核验(2026-10-01):全文阅读
R7 tilelang/ascend/language/__init__.py:8-124 方言导出清单;确认没有 tile 别名 已核验(2026-10-01):全文阅读
R8 tilelang/ascend/language/allocate.py:15-70 alloc_shared(UB)/alloc_l1/alloc_l0a/alloc_l0b/alloc_l0c(含 L0C 布局) 已核验(2026-10-01):逐行读
R9 tilelang/ascend/language/allocate.py:73-128 MX 尺度因子槽位 alloc_l0a_sf / alloc_l0b_sf 已核验(2026-10-01)
R10 tilelang/ascend/language/copy_op.py:53-163 dual_copy 契约:混合内核约束、M/N split、dual_dst_ctl、L0C dtype 拦截 已核验(2026-10-01):逐行读 docstring 与校验
R11 tilelang/ascend/language/copy_op.py:230-372 copy 的 Ascend 参数归一化与 tl.tileop.ascend_copy 重拼 已核验(2026-10-01)
R12 tilelang/ascend/language/gemm_op.py:18-100,168 L0 MAD 组装、静态 M/N/K 断言、T.gemm 签名 已核验(2026-10-01)
R13 tilelang/ascend/language/frame.py:29-31,50-63,81-196 Cube / Vector / SimtVF / SimdVF 的区域定义与 latency 参数 已核验(2026-10-01):逐行读
R14 tilelang/ascend/language/kernel.py:150-228 T.Kernel(N) 一维核网格、T.MixedKernel(N, sids=2) as (bx, sid) 已核验(2026-10-01)
R15 tilelang/ascend/language/annotations.py:19-188 annotate_buffer_versions / annotate_manual_multi_buffer / annotate_unlimit_memory 已核验(2026-10-01):逐行读 docstring
R16 tilelang/ascend/language/mode.py:8-105 set_mmad_direction / set_hf32_mode / set_atomic 已核验(2026-10-01)
R17 tilelang/ascend/analysis/vf_checker.py:1-60 VFChecker 与 SIMD_VF/SIMT_VF 的分配归属 已核验(2026-10-01)
R18 tilelang/ascend/debug.py:35 device_assert(M 树有、A 树无) 已核验(2026-10-01):rg -n "def device_assert"
R19 tilelang/transform/pass_config.py:76 TL_ENABLE_AUTO_SCHEDULE = "tl.enable_auto_schedule" 已核验(2026-10-01)
R20 src/ascend/codegen/codegen_ascend.cc:152-164 GetAscendScopeQualifier:scope → __ubuf__/__ca__/__cb__/__cc__/__cbuf__ 已核验(2026-10-01):逐行读
R21 src/ascend/codegen/codegen_ascend.cc:262-292 GetPipePair(合法 pipe 集合)与 GetCrossCoreSyncScope 已核验(2026-10-01)
R22 src/ascend/codegen/codegen_ascend.cc:327-351 头文件包含与 PrintFuncPrefix(__mix__(1, 2) / __vector__ / __cube__) 已核验(2026-10-01):逐行读
R23 src/ascend/codegen/codegen_ascend.cc:408-434 ValidateSimdVectorType:2048-bit 寄存器与 boolx256 predicate 已核验(2026-10-01)
R24 src/ascend/codegen/codegen_ascend.cc:1349-1381 EmitHardEventSync_(asc_sync_notify/wait)与 EmitCrossCoreSync_ 已核验(2026-10-01)
R25 src/ascend/codegen/codegen_ascend.cc:1383-1609 七个 Emit*Copy_:asc_copy_gm2ub_align / _ub2gm_align / `_gm2l1_nd2nz dn2nz/_l0c2ub/_l0c2gm/_l12l0a
R26 src/ascend/codegen/codegen_ascend.cc:1652-1690 EmitMad_(asc_mmad / asc_mmad_mx) 已核验(2026-10-01)
R27 src/ascend/codegen/codegen_ascend.cc:3554-3557 PrintStorageSync → asc_syncthreads() 已核验(2026-10-01)
R28 src/ascend/op/oob_padding.h:32-44 enum class DMAPath(7 条搬运 + 两条 MX SF) 已核验(2026-10-01)
R29 src/ascend/op/oob_padding.cc:46-68 GetDMAPath:scope 组合 → 通路 已核验(2026-10-01):逐行读
R30 src/ascend/op/utils.h:30-69 IsL1Buffer / IsL0ABuffer / IsL0BBuffer / IsL0CBuffer 等 scope 谓词 已核验(2026-10-01)
R31 src/ascend/op/gemm.cc:23-63 SelectInst 恒返回 ascend.mad;ComputeWarpPartition 恒 {1,1} 已核验(2026-10-01):逐行读
R32 src/ascend/transform/ascend_pipe.h:52-117 ResourcePipe 位掩码与注释(MTE1/MTE2/MTE3/M/V/FIX/S) 已核验(2026-10-01):逐行读
R33 src/ascend/transform/vf_regions.h:19-60 SIMT_VF / SIMD_VF 为不透明 SBlock;SIMT_VF 自绑 threadIdx.x 已核验(2026-10-01)
R34 src/ascend/transform/assign_core.cc:64-75、src/ascend/transform/core_mask.h:26-37 CoreMask 与跨核 pipe 的核归属 已核验(2026-10-01)
R35 src/ascend/transform/rewrite_dual_copy.cc(文件头 :1-20) RewriteDualCopy 的职责与执行时机 已核验(2026-10-01)
R36 src/ascend/transform/auto_schedule.cc:705-740 读循环 num_stages 注解决定多缓冲 已核验(2026-10-01):rg -n "num_stages"
R37 src/ascend/op/ascend_mte_plan.h:1-8,198-275,296-409 MTE 二维拷贝描述符的规划(MTECopy2D、PlanMTECopy) 已核验(2026-10-01):全文阅读
R38 src/tl_templates/ascend/common.h:1-13 生成源包含的 c_api/asc_simd.h、simt_api/* 已核验(2026-10-01)
R39 tilelang/contrib/bisheng.py:88-228 dav-3510 默认值、编译选项、aibin 两段式(bisheng + ld.lld) 已核验(2026-10-01):逐行读
R40 src/ascend/runtime/ascend_module.cc:400-430,480-500 aclrtBinaryLoadFromData / aclrtBinaryGetFunction / aclrtLaunchKernelWithHostArgs 已核验(2026-10-01):rg -n "aclrt"
R41 examples/ascend/example_vecadd.py:14-42 SIMT / SIMD 两种写法的官方对照(tile // 64) 已核验(2026-10-01):逐行读
R42 资料库索引 官方示例清单与「每个 example 一个 correctness test」的约定 已核验(2026-10-01)
R43 资料库索引 昇腾测试分层(analysis / transform / auto_schedule / layout / runtime / target / language)与无卡检查命令 已核验(2026-10-01):全文阅读

三、A 树(适配器 _vendor/tilelang-ascend-v0.1.2.000/)

id 引用位置 用途 核验状态
A1 资料库索引 自述里并列的「两条技术路线:Ascend C & PTO / AscendNPU IR」 已核验(2026-10-01)
A2 VERSION 仓库内版本号 0.1.4(tag 为准,见 V3) 已核验(2026-10-01)
A3 tilelang/utils/target.py:99-100,111-152 target in ["ascendc", "pto"] → llvm --keys=ascend;平台 A2/A3/A5 判定 已核验(2026-10-01):逐行读
A4 tilelang/engine/lower.py:156-170 device_codegen 按 target.model 分派 tilelang_ascend / tilelang_ascend_pto 已核验(2026-10-01)
A5 tilelang/engine/phase.py:49-124 两条阶段:LowerAndLegalize 与 OptimizeForTarget(含 PipelinePlanning+InjectSoftwarePipeline、AscendSyncInsert) 已核验(2026-10-01):全文阅读
A6 tilelang/jit/adapter/libgen.py:60-290 resolve_compile_flags 与两条 bisheng 命令行(-xasc 与 -xcce)、camodel 分支 已核验(2026-10-01):逐行读
A7 tilelang/transform/pass_config.py:38-62 TL_ASCEND_AUTO_SYNC / TL_ASCEND_MEMORY_PLANNING / TL_ASCEND_AUTO_CV_SYNC 等 8 个键 已核验(2026-10-01)
A8 tilelang/language/allocate.py:130-156 alloc_L1 / alloc_L0A / alloc_L0B / alloc_L0C / alloc_ub 与 scope 对应表 已核验(2026-10-01):逐行读
A9 tilelang/language/__init__.py:83-86 from .ascend import *、from . import ascend_tile as tile(T.tile 的来源) 已核验(2026-10-01)
A10 tilelang/language/ascend.py:191-317 set_cross_flag / wait_cross_flag / set_flag / wait_flag / barrier_all / pipe_barrier / sync_all 已核验(2026-10-01):逐行读
A11 tilelang/language/ascend.py:418-533 gemm_v0(A, B, C, transpose_A, transpose_B, init, kL0Size, n_actual) 已核验(2026-10-01)
A12 tilelang/language/ascend.py:534-616 printf 与 dump_tensor(M 树没有 dump_tensor) 已核验(2026-10-01)
A13 tilelang/language/ascend_tile.py(3,112 行;示例锚点 :1572) T.tile.* 的实现体(sort32、row_expand_mul 等) 已核验(2026-10-01):文件存在 + 抽样读取;未逐行审阅全部 3,112 行
A14 tilelang/language/warpgroup.py:74 T.Scope(name)(A 树用 T.Scope("C")/T.Scope("V") 指定执行域) 已核验(2026-10-01)
A15 tilelang/language/pipeline.py:11、tilelang/language/persistent.py:11 A 树的 T.Pipelined / T.Persistent 已核验(2026-10-01)
A16 src/target/codegen_ascend.cc:100-109,808-860,986,1028-1032 __global__ __aicore__ 前缀、scope→ascend_l1/l0a/l0b/l0c/ub 的 GetWithOffset、AscendC::TPipe / TBuf 已核验(2026-10-01):逐行读
A17 src/target/codegen_ascend_pto.cc:495-512,4249-4250 PTO codegen 的包含(<pto/pto-inst.hpp>)与 __global__ AICORE 前缀 已核验(2026-10-01)
A18 src/tl_templates/pto/common.h:1-60 pto::Tile<...> 系列别名(L1 / L0A / L0B / UB)与 PTO_PLATFORM_A5 已核验(2026-10-01):全文阅读
A19 3rdparty/pto-isa/ PTO-ISA 子模块目录(.gitmodules 指向 gitcode.com/cann/pto-isa) 未核验内容:子模块目录为空,未取得头文件
A20 examples/gemm/example_gemm.py、examples/elementwise/elementwise_add.py 正文 §3.2 的 A 树示例原型 已核验(2026-10-01):逐行读
A21 docs/TileLang-Ascend Programming Guide.md(corpus 副本) 调试(§5)、生成代码查看(§5.2)、msProf(§6)、aclgraph(§7)、camodel(§2.4) 已核验(2026-10-01,见 R44 同样路径)

四、corpus 资料

id 引用位置 用途 核验状态
R44 tilelang-ascend/docs/TileLang-Ascend Programming Guide.md 第 2342–2560 行 T.printf/T.dump_tensor、get_kernel_source()、msProf、aclgraph 已核验(2026-10-01):逐行读该区间
R45 tilelang-mlir-ascend/README.md 第 11 行 「两条技术路线」的原文 已核验(2026-10-01)
R46 .../tilelang-mlir-ascend/tilelang/engine/lower.py:167-181,269-294 target kind == "npuir" 的分派与 TILELANG_DUMP_IR 输出 已核验(2026-10-01):逐行读
R47 .../tilelang-mlir-ascend/tilelang/jit/jit_npu.py:1862-1916 bishengir-compile 调用与 A5/非 A5 两套开关 已核验(2026-10-01):逐行读
R48 .../tilelang-mlir-ascend/tilelang/utils/npu_utils.py:355-380 bishengir-compile / bishengir-opt 的查找 已核验(2026-10-01)
R49 TileLang AscendNPU IR 从入门到专家算子开发知识点 第 54–146 行 AscendNPU IR 定义、MLIR 方言(HFusion/HIVM/HACC/Annotation、Scope)与关键编译特性 已核验(2026-10-01):逐行读
R50 同上 :402-455 JIT 编译流程与 bishengir-compile 开关 已核验(2026-10-01)
R51 PTO-Gym/docs/PTO-tile-Instruction-SPEC.md 第 1–140 行 PTO tile 模型(tile buffer、物理形状 vs 有效区、pipeline 归属、pto.tload/tstore) 已核验(2026-10-01):逐行读前 140 行
R52 pto-spec/docs/tile/matrix-and-matrix-vector/matrix-matrix/TMATMUL.md 第 1–40 行 TMATMUL 的语义与执行引擎(CUBE) 已核验(2026-10-01)
R53 pto-spec/README.md 第 1–40 行 PTO ISA 的规模(466 scalar forms、96 bundle/command、118 direct Tile ops、905 ASL units) 已核验(2026-10-01)
R54 pto-dsl/README.md 第 1–52 行 PTO-DSL 与 tilelang-ascend 的关系(「tilelang 用 PTO-ISA 作为 codegen 后端」)、与 triton-ascend/CATLASS/PyPTO 的定位对照 已核验(2026-10-01):逐行读
R55 PTO-Gym/README.md 第 1–40 行 ptoas 与 PTO 微指令/tile 指令的验证工作流 已核验(2026-10-01)
R56 hiascend.com · zh_cann_bisheng 第 1–60 行 毕昇编译器的异构编译、微架构优化、亲和调度描述 已核验(2026-10-01)
R57 hiascend.com · zh_cann_ascend-c 第 1–40 行 Ascend C 的定位(语言扩展层 + C++ 类库,.asc 后缀,异构编译) 已核验(2026-10-01)
R58 hiascend.com · zh_hardware_processor 第 57–58 行 Ascend 950PR 的 Cube Core 32/28、Vector Core 64/56(1:2 比例的依据) 已核验(2026-10-01)
R59 [Question] It seems there are two Ascend 950 backen… 「两个昇腾 950 后端选哪个」的提问;无维护者结论 已核验(2026-10-01):全文读;页面 Activity 区无可见评论
R60 [Public Release 9/30] Introduce Ascend 950 backend 主干昇腾后端 PR 的正文:目标硬件 dav-3510、A2/A3 走社区仓库、功能清单 已核验(2026-10-01):逐行读
R61 [Ascend] Add PTO backend for Ascend950 PTO 后端 PR:注册 pto target、用 ptoas/bisheng 编译 PTODSL。corpus 的 main 源码快照(commit 994b44e)即其合并提交 已核验(2026-10-01):全文读;PR 页面快照被标为 draft(marked this pull request as draft),但 main 源码快照里有 src/ascend/codegen/codegen_pto.cc 与 tilelang/jit/adapter/pto/,以代码为准
R62 相邻编译器 · 资料清单 MLIR 官方文档 未核验(内容不足):抓取到的是导航页,正文未落盘;正文未引用其具体内容

五、NV 侧对照(仅用于 §5 的结构对照表)

id 引用位置 用途 核验状态
N1 _vendor/tilelang-v0.1.15/tilelang/cuda/backend.py:69-120 tilelang_callback_cuda_compile(nvcc、gencode、cubin/fatbin) 已核验(2026-10-01):逐行读
N2 _vendor/tilelang-v0.1.15/tilelang/cuda/pipeline.py:69-275 CUDA pipeline:ThreadSync("shared")、mbarrier、warp specialization 等 已核验(2026-10-01):rg -n "^ mod = "
N3 _vendor/tilelang-v0.1.15/src/cuda/codegen/codegen_cuda.cc(6,631 行) CUDA codegen 的规模对照 已核验(2026-10-01):文件存在与行数;未逐行审阅(第 24 章的职责)

六、上游链接

id 链接 用途 核验状态
U1 https://github.com/tile-ai/tilelang/pull/3308 主干昇腾 950 后端 PR 未核验(本机未联网;corpus 快照 08/raw/github-pr-3308.md 已核验)
U2 https://github.com/tile-ai/tilelang/pull/3310 PTO 后端 PR(draft) 未核验(同上,见 08/raw/github-pr-3310.md)
U3 https://github.com/tile-ai/tilelang/issues/3317 两条路线选型提问 未核验(同上,见 08/raw/github-issue-3317.md)
U4 https://github.com/tile-ai/tilelang-ascend 适配器仓库 未核验(本机未联网;锁定快照见 _vendor/tilelang-ascend-v0.1.2.000/)
U5 https://github.com/tile-ai/tilelang-ascend/tree/npuir AscendNPU IR 分支 未核验(本机未联网;corpus 快照 07/raw/tilelang-mlir-ascend/)
U6 https://github.com/tile-ai/tilelang-ascend/tree/ascendc_pto Ascend C & PTO 分支 未核验(同上)
U7 https://gitcode.com/cann/pto-isa PTO-ISA(适配器 3rdparty/pto-isa 的上游) 未核验(本机未联网;corpus 里 PTO-Isa 相关文档来自 PTO-Gym,见 R51/R55)
U8 https://www.hiascend.com/cann/bisheng 毕昇编译器官方页 未核验(本机未联网;镜像见 R56)

七、本章代码

目录 作用 验证级别
code/01-route-map/ 抽取四条路线的入口/生成物/编译器并做锚点断言(24 个锚点) CPU 逻辑验证
code/02-scope-dma-pipes/ 抽取 scope→限定符、DMAPath、ResourcePipe 三张表并做 4 条一致性检查 CPU 逻辑验证
code/03-api-presence-matrix/ 两棵树的 API 存在性矩阵(38 个 API + 3 条路线检查),含 4 条断言 CPU 逻辑验证
code/04-cite-check/ 校验本章所有 path:line 引用可解析、不越界 CPU 逻辑验证
code/05-emit-ascend-c/ 真实 kernel + 生成物检查清单;--audit 离线,--emit 需要昇腾环境 --audit = CPU 逻辑验证;--emit = 仅静态核对(未运行)

八、待补资料

# 缺什么 为什么需要 建议来源
G1 AscendNPU IR 的方言定义(.td 文件、tilelangir 的 op/type 定义) 正文 §4.9 只能引用二手技术文章描述 HFusion/HIVM/HACC;若拿到 .td,可把「TIR → npuir」的映射逐条落到 op 级别 tilelang-mlir-ascend/3rdparty/AscendNPU-IR 与 AscendNPU-IR-Dev 子模块(corpus 快照中目录为空);需在上游 clone 后补进 corpus
G2 3rdparty/pto-isa 的头文件(pto/pto-inst.hpp 与 pto::Tile 的定义) 正文 §4.10 与 A18 只能看到别名用法,看不到 Tile 模板的完整语义(padding、valid region 如何落成指令) gitcode.com/cann/pto-isa;或 PTO-Gym 的 PTO-tile-Instruction-SPEC.md 已覆盖一部分(R51)
G3 昇腾 A2/A3 上 T.dual_copy 的等价写法 正文 §4.5 只覆盖 M 树(950)的 dual_copy;A 树没有该原语,读者若在 A2 上需要双 AIV 分裂只能手写 A 树 examples/(如 gemv、flash_attention)里的 vid 手写切分示例;需要一次专门的例子梳理
G4 T.tile.* 的语义清单 正文只指出它存在于 A 树,没有逐个讲清 sort32/brcb/row_expand_mul 等 op 的语义 A19/A13 已定位文件;需要按 docs/language_ref/ 逐条整理(本任务范围外)
G5 mlir-docs 的可读正文 R62 只抓到导航页,导致 §4.9 无法直接引用 MLIR 官方对 dialect/progressive lowering 的定义 在可联网环境重新抓 mlir.llvm.org/docs/(或改用 Huawei 技术文章的等价描述,正文当前采用后者)

本章共 12 个二级小节;右上角目录在桌面端会跟随滚动高亮。