程序序(Program Order, PO)¶
1. 什么是程序序¶
灵犀处理器按收到的指令与外部事件的先后顺序推进内部状态:你给它什么指令、以什么顺序给,它就按这个顺序“理解并生效”。 为了把这种“应该按什么顺序理解执行”的语义说清楚,我们定义了程序序(Program Order, PO):
- PO = 从代码角度看,硬件对外表现应当等价的执行顺序。
- 硬件内部可能乱序或并行,但对外部可见的效果(寄存器、内存、Tile 的最终值)必须与 PO 一致。
2. 从“块头顺序”展开成“程序序”¶
灵犀的块指令由两部分组成:
- 块头:配置“这块要干什么”(数据类型、维度、输入/输出 Tile 等)。
- 块身:真正执行的步骤(搬运/乘累加/转换/回写等)。
编译时,块头会被排成一个线性顺序(可以理解为“调用点”的顺序)。之后,把每个块头展开成它的块身步骤,得到最终的程序序 PO。
两种展开方式¶
-
随机替换(不强制内部顺序) 块身里的几个动作可以并行或调度顺序不固定,只要整块在其他块之前/之后的大顺序不变即可。
-
顺序替换(内部顺序固定) 块身里的动作有明确先后,必须逐步执行——例如“先搬数据,再乘累加,最后回写”。
多数矩阵/向量计算块的块身都是顺序替换,因此最终 PO 通常是一个清晰的全序。
3. 用三个真实代码片段看 PO¶
下面给出三个常见块的“块头 → 展开 → PO”对照,完全不需要抽象记号。
例 1:纯 GEMM(16×16×16)并把结果写回 Tile¶
块头(出现顺序,就是 L1-IPO):
BSTART.PAR TMATMUL, FP16 ; 配置:做 FP16 的 A×B
B.DIM rM, 128, ->M ; M=128
B.DIM rN, 128, ->N ; N=128
B.DIM rK, 256, ->K ; K=256
B.IOT [TA, TB], group=0, ->ACC<64KB> ; 绑定 A/B 到 ACC
B.ARG CD2RD ; 结果默认行主序,无额外变换
展开后的块身步骤(顺序替换):
- 读取/预取 A、B 的分片到内部缓冲;
- CUBE Core 以 16×16×16 的粒度做乘累加,结果累加到 ACC;
- 把 ACC 写回目标 Tile(行主序)。
最终 PO(语义顺序):
例 2:GEMM + TCVT 随路转换(ACC→Tile 之前做量化/重排)¶
块头:
BSTART.PAR TMATMUL, FP16
B.DIM zero, 64, ->M
B.DIM zero, 64, ->N
B.DIM zero, 256, ->K
B.IOT [TA, TB], group=0, ->ACC<64KB>
B.ARG ZZ2RD ; 指定 TCVT 的布局变换(例)
展开后的块身(顺序替换):
- 读取/预取;
- CUBE 乘累加到 ACC;
- TCVT FixPipe:在“ACC→Tile 写回”的道路上,边搬边做格式转换(如反量化、激活、布局变换等);
- 将转换后的数据写入 Tile Register。
最终 PO:
这里的关键是:TCVT 在“ACC→Tile 的路径上”执行,省掉了“先落地再另起一段转换”的往返。
例 3:TMATMULMX(微缩放)+ 可选 C Tile 累加¶
块头:
BSTART.PAR TMATMULMX, INT8
B.DIM zero, 128, ->M
B.DIM zero, 128, ->N
B.DIM zero, 256, ->K
B.IOT [TA, TSA], group=0, ->ACC<64KB> ; A 与 ScaleA
B.IOT [TB, TSB], group=1 ; B 与 ScaleB
; 可选:B.IOT [TC], group=2 ; 若做 +C
B.ARG CD2RD
块身(顺序替换):
- 装入 A、ScaleA 以及 B、ScaleB;
- 在乘法前,对 A、B 按 Tile 广播缩放:
A' = A * ScaleA,B' = B * ScaleB; - 对缩放后的 A'、B' 做乘累加到 ACC;
- 如果存在 C/ACC 累加,执行相应加法;
- 将 ACC 写回 Tile,或继续由 TCVT 转换后写回。
最终 PO:
4. PO 与“真实执行”的关系(再强调)¶
- PO 是“应该表现成的顺序”:编译器、验证和上层框架都以此为准去理解程序语义。
- 硬件内部可乱序/并行,但对外可见效果必须与 PO 等价。
- 后续诸如寄存器距离计算、内存一致性/顺序、屏障等约束,均以 PO 为基础。
5. 微观到宏观:如何落地到你的代码¶
- 把块头理解为“在时间线上占一个位置”的调用点;
- 顺序替换:把这个调用点用“具体步骤”替换掉;
- 随机替换:你只强调“这是一组动作”,但这组内部顺序不重要(或由调度器决定);
- 所有块按块头出现顺序排好,再逐个展开,你看到的就是整个程序的程序序。
6. 总结¶
程序序(PO)= 代码层面,块头按出现顺序 → 用块身动作(顺序/随机)展开后的整体顺序。 硬件再怎么优化,外部观察到的行为都必须与这条“顺序线”一致。
这样写完,你在读任何一段灵犀代码时,都能清楚回答三件事:
- 先后关系:谁在前、谁在后;
- 展开内容:一个块头到底代表哪些具体动作;
- 可见语义:不管底层怎么并行,外部看到的结果与这条顺序一致。