当芯片越来越强,程序员为什么反而更难掌控它?

2026年3月,新一代昇腾950系列芯片逐渐浮出水面。

如果把它摊开来看,像不像一张密密麻麻的工业园区图?

[图片]

32个矩阵运算单元、64个向量处理核心、1.6TB/s的DDR带宽、1728 TFlops的FP4算力。数字很耀眼,硬件很凶猛。可问题也正出在这里:芯片越强,驾驭它的人却未必越轻松。

为什么?因为它不再是一座小作坊,而是一整座高速运转的工厂。多级存储层级、片上片外互联、CCU 集合通信加速引擎、海量调度体系……每一个部件都在吞吐,每一个环节都在争时延。

于是矛盾出现了:硬件强到离谱,软件却很容易在门口打转。

昇腾团队显然也看清了这一点。他们没有继续给复杂架构缝缝补补,而是换了个思路:给它设计一套可以直达底层、又足够好用的语言。

这就是今天的主角:PTO ISA

一、为什么要给芯片披一层“外衣”?

先把时间拨回去。

昇腾源自达芬奇架构,今已经演进了五六代。每一代物理指令集都在变化:矩阵单元从 FP16 走到 FP8/FP6,再走到昇腾950的 FP4;向量单元也不再满足于规规矩矩的 SIMD,引入了 SIMT 前端,去处理 Gather/Scatter、多线程和控制流这些“难啃的骨头”。

那问题来了:底层每一代都在变,程序员难道每一代都要从头学一遍?

当然能学,但代价高。传统做法是手写算子,用更底层的 Ascend C 去榨干性能。可这种方式往往意味着大量模板代码、繁重迁移成本,以及每次芯片迭代都要跟着“重打一遍地基”。

所以华为的选择很直接:在物理指令集之上,再封装一层虚拟指令集,并全面开源。

注意,它不是那种把硬件包得严严实实的“厚外衣”。PTO 更像是一件剪裁精准的外套——不掩盖肌肉,不模糊骨架,而是把底层能力按程序员能理解、能调用的方式重新组织起来。

说白了,底下还是那个强悍甚至有点“生猛”的达芬奇硬件,上面却终于能说人话了。

[图片]

更关键的是,这件“外衣”还有一个很重的承诺:跨代兼容。

里面的零件可以换,外面的接口不变。对开发者来说,这不是锦上添花,这是能不能长期写、敢不敢重投入的分水岭。

二、一套“外衣”,为什么想跑遍所有平台?

PTO-ISA 的设计目标非常清楚,优先级甚至有点“逆潮流”:

兼容性 > 易用性 > 性能

有人可能会问:性能不是第一位吗?

恰恰相反。因为现实很残酷:你连稳定可用、可迁移都做不到,谈性能,很多时候只是纸面上的豪言。

在硬件层面,PTO 想做的是:同一套指令集,服务器能跑,超节点能跑,手机能跑,甚至汽车也能跑。

这不是“一个接口适配万物”的空口号,而是试图在不同设备形态之间,维持一种足够稳定的编程抽象。

在编程语言层面,PTO 目前已支持两套框架:

  • PyPTO:MPMD 模式,多个核跑不同程序,底层直通 PTO-ISA

  • TileLang:SPMD 模式,多个核跑相同程序,同时支持 Ascend C 和 PTO-ISA 后端

一个更像多兵种联合作战,一个更像整齐编队协同推进。表面打法不同,底层吃的却是同一套 PTO 指令。

[图片]

这意味着什么?意味着程序员面对的,不再是每个平台一张脸、每代芯片一套脾气,而是终于有机会站在一个相对稳定的“中枢接口”上做事。

三、为什么理解 PTO,得先把芯片看成一座“物流城市”?

1. 冯·诺依曼架构,为什么说像从小农经济走向工业文明?

最早的处理器很像一个小村庄:计算核心和存储之间,只靠一条“乡间小路”连接。路窄、车少、节奏慢,也就凑合能过日子。

可一旦算力上去,这条路立刻堵死。怎么办?

答案不是喊口号,而是修仓库、修干线、修中转站。于是,多级存储层级出现了。

把它想象成一座工业城市,就容易多了:

[图片]

这里有个显然易见的原则:近距离用电动三轮车、中距离用大卡车、远距离用轮船+集装箱。

到计算卡上,近距离挪一挪,32bit 标量就行;跨卡搬运,你还拿着小盒子一点点运?那不是算力不够,是人在拖后腿。跨卡这种场景,必须上兆级别的大块数据,才能把通信时延摊薄。

总结一下:距离越远,搬的数据块就必须越大。

这是什么?这就是 Tiling 的本质。

不是玄学,不是术语堆砌,而是一次很朴素的工程判断:路远,就得装满车再出发。

2. 达芬奇架构的“天才设计”,妙在哪里?

达芬奇架构下,最关键的一点洞察:矩阵运算的数据复用潜力太大了,大到值得专门为它造一套机制。

怎么理解?把矩阵乘法想成一个魔方。

左矩阵的一块数据,放在 L0A 里稳稳不动;右矩阵的数据从 L0B 一股股灌进来;结果则不断累加到 L0C。这样一来,原本需要反复搬运的一边数据,被固定住了,带宽压力立刻减轻,效率自然上去。

当这一边的数据用完了怎么办?再换另一边固定。

这不就是流水作业的极致版本吗?不是“每次都重新搬全套”,而是尽可能让贵的数据多待一会儿、多复用几次。

[图片]

这套思路,说到底不是花哨,而是狠:把带宽当黄金,把复用当纪律。

也正因为如此,达芬奇架构后续每一代演进,本质上都没有脱离这条主线。

3. 昇腾950,为什么像32座城市组成的超级国家?

到了昇腾950,规模彻底上去了。

  • 32个矩阵单元:每个都像一座独立运转的工业小城

  • 64个向量单元:承担更通用、更灵活的向量计算

  • CCU 集合通信加速引擎:像高速公路网和港口系统,负责更远距离的数据协同

  • STARS 启明星调度系统:像中央调度塔台,安排任务和流量

  • 统一调度、全局一致性:书同文、车同轨,大家遵守同样的 Cache Line 规格

[图片]

复杂吗?当然复杂。

但复杂不是原罪。真正麻烦的是:如果没有合适的抽象,复杂就会直接砸到程序员头上。

PTO 存在的意义,恰恰就是把这种复杂度重新编排,让人能下场、能调优、能持续写。

四、PTO-ISA,到底像什么?像一套集装箱调度系统

[图片]

1. 多种粒度的指令,为什么重要?

PTO 指令集本质上是一套分层的集装箱调度语言

pto.load      # 标量指令,32bit,最细粒度
pto.vload     # 向量指令,256B,处理一维数据
pto.tload     # 块/Tile 指令,64KB,处理二维矩阵块
pto.tget      # GM → 更远层级,512KB,大块搬运

[图片]

看起来像几条指令,实则是在回答一个核心问题:

同样是“搬数据”,你到底是在搬一粒沙,还是在调一整个集装箱?

编程的本质,也因此被重新显形:你要设计的不只是算子逻辑,而是集装箱的大小、形状、内容物,以及它在不同港口之间的转运顺序。

2. 数据流为什么是五步走?

以矩阵乘法为例,数据在芯片里的旅程大致如下:

GM (全局内存)
  ↓ tload(64KB Tile 块加载)
Mat Tile(L1 Buffer)
  ↓ textract(抽取成更小块)
L0A + L0B(一级仓库,喂给矩阵单元)
  ↓ matmul(矩阵乘,累加到 L0C)
L0C
  ↓ tstore(结果写回) 
GM

这条路径看上去清楚,真正的难点却藏在背后:每一层仓库之间,接受的数据块大小不一样,吞吐节奏也不一样。

所以程序员真的要管这么多吗?

是,也不是。

是,因为你必须理解层级结构,否则性能优化无从谈起。

不是,因为 PTO 的目的,正是让你直击硬件逻辑,而不是陷进每个同步细节的泥潭里。

3. SIMD + SIMT,为什么能放进同一个框架?

昇腾950的向量单元支持两种并行范式:

  • SIMD:规整向量计算,适合连续 Tile 数据,写法接近普通 for 循环

  • SIMT:处理不规则并行,如 Gather/Scatter、控制流,通过 parallel for 组织

以往很多架构里,这两套东西像两种方言,彼此隔着一道墙。

PTO 的做法是:别分家,直接让它们在同一个程序里交织。

也就是说,规整计算和不规则控制,不再非要二选一。

这就是华为所谓的新同构架构——不是把一切揉成一团,而是在同一框架里,让不同并行模式各安其位、各尽其职。

4. 三层调度,为什么说“你能控到哪一层,全看你敢不敢下潜”?

PTO 暴露了三个层级的调度接口:

层级

接口

调度力度

最上层

任务调度接口

任务级

中间层

块间 Pipeline

块级,自动插 set/wait

最底层

向量微指令

指令级,软向量排流水

 

这套设计有意思的地方在于,它没有假装“人人都只需要高层抽象”。

相反,它承认现实:有人要快上手,有人要抠极限。

所以你可以怎么选?

想先跑起来,就站在高层,让系统替你自动排流水。

想把性能榨到最后一滴,就往下潜,自己排、自己控、自己对每一拍时序负责。

这不是单纯的易用性设计,而是一种很有锋芒的工程态度:简单可以有,但别拿简单阉割掉专业能力。

五、实战:手搓 Matmul 与 FlashAttention,PTO 到底能打到什么程度?

最有说服力的,从来不是概念,而是结果。

  • Step 1:为什么说 30 多行代码就能“开箱见山”?

用 PyPTO 写一个矩阵乘,三十多行 Python 代码,编译后就能直接在昇腾950上运行:

a_l1 = pto.alloc_tile(tile_buf_a_l1)
b_l1 = pto.alloc_tile(tile_buf_b_l1)
a_l0 = pto.alloc_tile(tile_buf_a_l0)
b_l0 = pto.alloc_tile(tile_buf_b_l0)
c_l0 = pto.alloc_tile(tile_buf_c)

for li in pto.range(bid, core_loop, num_blocks):
    m_idx = li // n_loop
    n_idx = li % n_loop
    m_offset = m_idx * c128
    n_offset = n_idx * c256
    pto.load(sv_a0, a_l1)

    for k_idx in pto.range(c0, k_dtile_num, c1):
        k_offset = k_idx * c_kd
        is_first_k_tile = k_idx == c0

        for phase in range(8):
            if phase % 4 == 0:
                b_half = phase // 4
                h_off = const(b_half * K_TILE)

            pto.load(sv_b, b_l1)
            pto.extract(a_l1, c0, a_col, a_l0)
            pto.extract(b_l1, b_row, c0, b_l0)

            if phase == 0:
                pto.matmul(c_l0, a_l0, b_l0, c_l0)
            else:
                pto.matmul_acc(c_l0, a_l0, b_l0, c_l0)

        if k_idx + c1 < k_dtile_num:
            pto.load(sv_a_next, a_l1)

    pto.store(c_l0, sv_c)

性能呢?

开箱约 40%,对比对象是专家手写 Ascend C 的极致算子。

40% 算高吗?如果拿“极限性能”做标尺,它当然不是终点。

但如果你想到这只是几十行 Python、而且是直接打到昇腾950底层,那它已经不仅仅是“能跑”,而是说明这条抽象路径是成立的。

  • Step 2:矩阵单元为什么会空转?因为你让它“饿着肚子干活”

泳道图一看就明白:矩阵单元大约有 50% 的时间在空转。

它不是不想算,而是在等数据。

[图片]

问题出在哪?

答案很朴素:计算和搬运是串行的。

你在算的时候,没有顺手把下一块数据提前运过来。于是算完就停,停完再等,等完再算。硬件再猛,也经不起这么“喂一口、停一下”。

解决办法是什么?Double Buffer

在 PTO 里,直接声明两个 Tile,形成双缓冲:一个在算,一个在装。更关键的是,PTOAS 编译器会自动帮你排好流水线。 程序员不需要手动去插那些令人头皮发麻的同步指令。

只要把:

a_l1 = pto.alloc_tile(tile_buf_a_l1)
b_l1 = pto.alloc_tile(tile_buf_b_l1)
a_l0 = pto.alloc_tile(tile_buf_a_l0)
b_l0 = pto.alloc_tile(tile_buf_b_l0)
c_l0 = pto.alloc_tile(tile_buf_c)

改成:

a_l1 = [pto.alloc_tile(tile_buf_a_l1), pto.alloc_tile(tile_buf_a_l1)]
b_l1 = [pto.alloc_tile(tile_buf_b_l1), pto.alloc_tile(tile_buf_b_l1)]
a_l0 = [pto.alloc_tile(tile_buf_a_l0), pto.alloc_tile(tile_buf_a_l0)]
b_l0 = [pto.alloc_tile(tile_buf_b_l0), pto.alloc_tile(tile_buf_b_l0)]
c_l0 = pto.alloc_tile(tile_buf_c)

在 PyPTO 里写乒乓 buffer,本质上不是让程序员去手工拉时序图,而是只需要把意图说清楚,剩下的流水由编译器去排。

效果如何?

小矩阵性能直接抬到 80% 左右。

[图片]

这一步很说明问题:很多时候性能差,不是因为算子不聪明,而是因为数据没跟上。

算力像一头猛兽,你却拿细水管喂它,怪谁?

不过,乒乓 buffer 解决了小矩阵,大矩阵还没彻底过关。

为什么?因为 16384×16384 的 float16 矩阵,数据规模大约 256 MiB,远超 L2 缓存。局部流水线优化到这里,就开始撞墙了。

  • Step 3:Swizzle 优化,为什么叫“扭秧歌的达芬奇”?

这时候就要上更有画面感的招数:Swizzle 优化。

[图片]

名字听着像玩笑,原理却很硬:

矩阵遍历不再“一条道走到黑”,而是走到 L2 容量边界就折返,让数据块尽量在 L2 内部就地算完、就地释放,减少缓存冲突。

换句话说,它不再让数据块在缓存里横冲直撞,而是让它们沿着更聪明的路径转身、回摆、再推进。展开来看,轨迹像麻花,像扭动,也难怪大家管它叫“扭秧歌”。

[图片]

用 PyPTO 实现,代码并不夸张:

def swizzle_nz(li, m_loop, n_loop, c_swizzle, c_swizzle_m1, c1, c2):
    tile_block_loop = (n_loop + c_swizzle_m1) // c_swizzle
    tile_block_span = c_swizzle * m_loop
    tile_block_idx = li // tile_block_span
    in_tile_block_idx = li % tile_block_span
    is_last_block = tile_block_idx == (tile_block_loop - c1)
    n_col_tail = n_loop - c_swizzle * tile_block_idx
    n_col = s.select(is_last_block, n_col_tail, c_swizzle)
    m_idx = in_tile_block_idx // n_col
    n_idx = tile_block_idx * c_swizzle + (in_tile_block_idx % n_col)
    odd_block = (tile_block_idx % c2) == c1
    flipped_m_idx = m_loop - m_idx - c1
    m_idx = s.select(odd_block, flipped_m_idx, m_idx)
    return m_idx, n_idx

加上 Swizzle 之后,结果很直接:

大矩阵性能也基本追平了手写极致算子。

[图片]

这里真正值得注意的,不只是“性能追平”四个字,而是它说明了一件事:

PTO 不是只能拿来写原型,它也能承载真正深入的性能优化。

  • 进阶挑战:Flash Attention,为什么更像一场立体会战?

Flash Attention 的难点在于什么?

不在某一个算子本身,而在于 Cube 矩阵计算Vector 操作(比如 Softmax) 高频交替。

如果把它们老老实实串行写出来,会发生什么?

很简单:Cube 等 Vector,Vector 等 Cube,彼此轮流站岗。硬件不是没活干,而是总在排队。

优化思路也因此很明确:把内层循环做成细粒度流水,让数据块持续注入,让 Cube 和 Vector 尽量并行工作。

[图片]

PTO 的编译器为什么能在这里发挥作用?

因为它拥有强约束:数据块对齐、没有指针乱飞、没有非法操作。

这些约束不是“束手束脚”,而是给编译器腾出了大胆优化的空间。某种意义上,它可以模拟乱序 CPU 的思路,对不同数据块做时间评估,然后自动排出更优流水。

这点很重要。因为真实高性能 Flash Attention 的依赖关系并不优雅,甚至可以说相当缠绕。继续手工一层层抠,成本极高,心智负担也极重。

而 PTO 的价值正在于:把优化问题从“全靠人工死磕”推进到“编译器与 AI Agent 可以共同介入”。

篇幅有限,感兴趣大家可关注: https://gitcode.com/cann/pto-isa/tree/master/demos/baseline/flash_atten

六、One More Thing:AI Agent 为什么能在半小时里写出一套算子?

这部分,可能才是最有冲击力的。

Claude Code 直接使用 PTO 指令集写算子,再与专家手写的 Ascend C 算子对比,结果如下:

怎么看这些数字?

先说结论:Cube 和 Vector 类算子离极手写限性能还有距离,但是已经接近一半的性能。计算通讯融合类算子更是已经做到 90%+

总体来看,这已经不是“能试试”,而是足以支撑算法研发先导阶段的理论验证和快速迭代。

更关键的不是百分比本身,而是时间——整个过程只用了 30 分钟,代码同时跑在昇腾910和950上,功能和精度全部正确。

这意味着什么?

意味着昇腾算子的开发,正在从“先攒一堆底层专家,再花很久把第一版做出来”,转向另一种范式:

先让 AI Agent 在 PTO 这套可控抽象上快速生成可用代码,再由人类专家把关键路径继续压到极致。

这是替代专家吗?不是。

这是把专家从“重复搭脚手架”的劳动里解放出来,让他们把精力投向真正值钱的地方:调度策略、内存布局、性能边界和体系结构级优化。

说得更锋利一点:

如果一套底层抽象不能被 AI Agent 理解和操作,它在未来的软件生产方式里,很可能就不够先进。

而 PTO,显然已经在往这个方向走。

结语

回到最开始那个问题:当芯片复杂到程序员难以驾驭,我们到底该怎么办?

答案不是一味降低门槛,更不是把硬件能力藏起来假装简单。

真正有效的做法,是用更好的抽象去驯服复杂度,同时把通往极限性能的路保留下来。

PTO-ISA 做的,正是这件事。

它像给昇腾芯片穿上了一套合身的西装:骨架没变,力量没减,动作却更容易被理解、被编排、被复用。

在直播展示的 Matmul 场景里,100 多行 Python 代码就能一步步把性能逼近手写实现。这不是单纯“开发更省事”,而是说明一种新的开发范式正在成形——技术门槛没有被粗暴抹平,生产效率却被显著抬高。

而当 AI Agent 真正进入主战场,这套范式的价值,恐怕才刚刚露出刀锋。

资源链接

  • GitCode PTO-ISA: https://gitcode.com/cann/pto-isa

  • GitHub PTO-ISA: https://github.com/PTO-ISA/pto-isa

  • GitHub PTOAS:https://github.com/PTO-ISA/PTOAS

  • 飞书社区群

  • 微信社群

Logo

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

更多推荐