第一次在算子层写代码
2026 年 8 月。参加国产 AI 芯片的算子题目:二维卷积、激活函数、量化 GEMM,以及一个"消除末维上限"的实现。在此之前,我写的都是"调用"算子的代码。
和模型层完全不同的三件事
地址空间是显式的。 在模型层写 x.to(device) 就完事了,在算子层要区分这段内存属于哪一类(全局、共享、寄存器),并决定数据在哪一级之间搬运。第一版代码编译不过,原因就是地址空间标注错了。
分块决定了性能。 同一个卷积,按不同方式切分输入与输出,性能可以差出好几倍。原因不复杂:芯片的片上缓存有限,块切得不好就要反复从外部存储读同一份数据。把"数据复用"这件事想清楚,比堆优化技巧更有效。
类型是逐个分支处理的。 一个"简单"的激活函数,要分别处理 FP16、FP32、BF16 三种数据类型,还要覆盖 0–8 维的不同形状;量化 GEMM 则要额外处理整数与浮点的混合计算。写的时候必须把每种组合都想清楚,不能靠"跑一遍看看"。
做过的几道题
| 题目 | 关键点 |
|---|---|
| 二维卷积(FP16 / FP32 / BF16) | 三种数据类型的分别实现与分块策略 |
| 逐元素激活(HardSwish,0–8 维) | 维度分支与形状对齐,避免小形状退化 |
| INT8 量化 GEMM(小 M/N 大 K) | 量化参数处理与累加精度,K 维分块 |
| 消除末维 65535 上限 + 转置组合 | 突破单次搬运的形状限制,处理转置后的访存模式 |
收获
最大的收获不是学会了某个具体写法,而是对"性能从哪来"有了具体的感知:不是靠更聪明的技巧,而是靠把数据搬运的次数降下来。这个视角反过头来影响了我做模型训练时的判断——例如在显存受限时如何设置批次与分块,本质上考虑的是同一件事。