环境
仓库:github.com/LinxISA/Linx-TileOP-API @ a795b97
配套模型:SuperScalarModel a5dca25a + 31f7a8f(ElementBits(FP4)=4 已并入)
基线:ops-20260823
pr: Feat/dmxq rel0812 PTO-ISA/SuperNPUBench#49
复现算子:SuperNPUBench dynamic_mx_quant 的 TAIL_OCP_FP4 / NONTAIL_OCP_FP4
(data pass 末尾 TCVT(oq, xf),fp32 → 打包 fp4)
TL;DR
type_traits<__fp4_e2m1x2>::bits = 8(把 e2m1x2 当 8-bit 打包容器 )使 Cols=64 的
fp4 tile 算出 512B ,是「64 个逻辑 fp4 元素 = 256B」的 2 倍 。这个翻倍的 size 喂进模型
侧按 4-bit/元素 反推 row 的公式,行数翻倍(8→16),在 TCVT 运行期形状契约上崩。
修复须在工具链头把 fp4 tile 的 size 位宽从 8 改成按每逻辑元素 4-bit 计 (256B)。模型侧
ElementBits(FP4)=4 已就位(31f7a8f),无需再动。
现象(编译期已通过、运行期崩)
kernel(dynamic_mx_quant_tail_ocp_fp4.hpp:106)声明输出 tile:
using tile_o = Tile<Location::Vec, OutT /* =__fp4_e2m1x2*/ , TileM, PW ,
BLayout::RowMajor, TileM, BlockSize>; // Cols = PW = 64(逻辑元素列,与源 tile_f 同)
编译期 TileLogicalShapeMatch 已通过 (template_asm.hpp:115 static_assert 不再触发)。
tile_o 与源 tile_f(fp32, Cols=64)Rows/Cols 全等。实测 TAIL_OCP_FP4 编译 EXIT=0 。
运行期崩 SuperScalarModel/isa/Block.cpp:1155 ValidateOperandContract (TCVT 要求
src/dst 的 row/col/validRow/validCol/layout 全等),崩在 physical ROW :
src fp32 row=8,dst fp4 row=16。
根因(bit 口径错位)
模型侧 row 推导(Block.cpp:1558-1560):
dst.row = dst.size * 8 / (dstPhysicalCol * elementBits)
代入 tile_o(fp4, Rows=8, Cols=64):
量
值
来源
dst.size
512B
工具链头 pto_tile.hpp:643 kBytes = Rows*Cols*type_traits<DType>::bits/8 = 8*64*8/8
type_traits<__fp4_e2m1x2>::bits
8
工具链头 type.hpp:62(把 e2m1x2 当 8-bit 打包容器,storage=uint8_t)
dstPhysicalCol
64
tile 声明 Cols
elementBits
4
模型 DataType.h ElementBits(FP4)=4(每 fp4 元素 4-bit)
→ row = 512*8 / (64*4) = 16 ≠ src 8,崩。
矛盾点 :type.hpp 里 e2m1x2 用 8-bit,而 fp6 等窄类型用真实位宽(__fp6_e3m2 = 6-bit,
type.hpp:60)。fp4x2 的 8-bit 是「打包两个 4-bit 的容器位宽」,用于 storage 层没错;但当它被
kBytes = Rows*Cols*bits/8 直接乘进 tile size 时,把 Cols=64 当成 64 个 8-bit 打包单元
(=128 个 fp4,512B),而不是 64 个 fp4 逻辑元素(256B) ——size 翻倍。
模型侧 31f7a8f 已改用 pto-spec 的 TileElementBits(FP4)=4 反推 row,期望的自洽输入是
真实打包的 256B (256*8/(64*4)=8=src);但当前工具链头无法由任一 kernel 声明产出 256B
(size 恒 = Rows*Cols*8/8)。未对齐的那半在工具链头。
复现
# 工具链
export COMPILER_DIR=.../linx-toolchain-build/output/linx_blockisa_llvm_musl/bin
export LINX_SYSROOT=.../linx-toolchain-build/output/linx_blockisa_llvm_musl/sysroot/usr
cd SuperNPUBench/benchmark/one-level-arch/test/kernel/quant/dynamic_mx_quant
make TESTCASE=dynamic_mx_quant TYPE=TAIL_OCP_FP4 # 编译 EXIT=0
# gfrun 该 ELF → Block.cpp:1155 ValidateOperandContract 崩,src row=8 vs dst row=16
建议修复(双侧统一到 pto-spec:TileElementBits(FP4)=4)
工具链头(本 issue 主体,待修) :让 fp4(e2m1x2 / e1m2x2 / hif4x2) tile 的 size 位宽按
每逻辑元素 4-bit 计,使 Tile<fp4, Rows=8, Cols=64> → kBytes = 8*64*4/8 = 256B。两种落法:
在 pto_tile.hpp 的 kBytes 公式对 4-bit 打包类型特判(除 2);或
为 fp4x2 引入「tile-size 位宽」概念(4)与「storage 容器位宽」(8)分离,kBytes 用前者。
注意别破坏 storage 层(sizeof(__fp4_e2m1x2)=1B)与 32B 列对齐检查(pto_tile.hpp:722)。
模型侧 :ElementBits(FP4)=4 已就位(31f7a8f),无需再改。
对齐后:size=256B → row = 256*8/(64*4) = 8 = src → 契约通过。
不该采用的方案
kernel 侧把 Cols 写成 PW/2=32(打包字节数) :会违反 pto-spec「columns = 逻辑元素」语义,
并触发编译期 template_asm.hpp:115 static_assert(32 ≠ 源 64)。要「除 2」的是字节 size ,
属工具链头职责,kernel 声明层表达不了。
删 Block.cpp 的 physical row/col 校验只留 valid :与 pto-spec TileOperandsLegal_TCVT /
TCVT() 契约冲突,是掩盖而非对齐口径。
影响面
所有用 TCVT 把宽类型(fp32/bf16/half)转成打包 fp4(__fp4_e2m1x2 等)的 kernel data pass,
即 dynamic_mx_quant 的 tail/nontail OCP-FP4 全部路径。scale pass(e8m0)不受影响。
环境
github.com/LinxISA/Linx-TileOP-API@a795b97a5dca25a+31f7a8f(ElementBits(FP4)=4已并入)SuperNPUBenchdynamic_mx_quant的TAIL_OCP_FP4/NONTAIL_OCP_FP4(data pass 末尾
TCVT(oq, xf),fp32 → 打包 fp4)TL;DR
type_traits<__fp4_e2m1x2>::bits = 8(把 e2m1x2 当 8-bit 打包容器)使Cols=64的fp4 tile 算出 512B,是「64 个逻辑 fp4 元素 = 256B」的 2 倍。这个翻倍的 size 喂进模型
侧按 4-bit/元素 反推 row 的公式,行数翻倍(8→16),在 TCVT 运行期形状契约上崩。
修复须在工具链头把 fp4 tile 的 size 位宽从 8 改成按每逻辑元素 4-bit 计(256B)。模型侧
ElementBits(FP4)=4已就位(31f7a8f),无需再动。现象(编译期已通过、运行期崩)
kernel(
dynamic_mx_quant_tail_ocp_fp4.hpp:106)声明输出 tile:编译期 TileLogicalShapeMatch 已通过(
template_asm.hpp:115static_assert 不再触发)。tile_o与源tile_f(fp32, Cols=64)Rows/Cols 全等。实测TAIL_OCP_FP4编译 EXIT=0。运行期崩
SuperScalarModel/isa/Block.cpp:1155ValidateOperandContract(TCVT 要求src/dst 的 row/col/validRow/validCol/layout 全等),崩在 physical ROW:
src fp32 row=8,dst fp4 row=16。根因(bit 口径错位)
模型侧 row 推导(
Block.cpp:1558-1560):代入
tile_o(fp4, Rows=8, Cols=64):dst.sizepto_tile.hpp:643kBytes = Rows*Cols*type_traits<DType>::bits/8 = 8*64*8/8type_traits<__fp4_e2m1x2>::bitstype.hpp:62(把 e2m1x2 当 8-bit 打包容器,storage=uint8_t)dstPhysicalColelementBitsDataType.hElementBits(FP4)=4(每 fp4 元素 4-bit)→
row = 512*8 / (64*4) = 16 ≠ src 8,崩。矛盾点:
type.hpp里 e2m1x2 用 8-bit,而 fp6 等窄类型用真实位宽(__fp6_e3m2= 6-bit,type.hpp:60)。fp4x2 的 8-bit 是「打包两个 4-bit 的容器位宽」,用于 storage 层没错;但当它被kBytes = Rows*Cols*bits/8直接乘进 tile size 时,把Cols=64当成 64 个 8-bit 打包单元(=128 个 fp4,512B),而不是 64 个 fp4 逻辑元素(256B)——size 翻倍。
模型侧
31f7a8f已改用 pto-spec 的TileElementBits(FP4)=4反推 row,期望的自洽输入是真实打包的 256B(
256*8/(64*4)=8=src);但当前工具链头无法由任一 kernel 声明产出 256B(
size恒 =Rows*Cols*8/8)。未对齐的那半在工具链头。复现
建议修复(双侧统一到 pto-spec:TileElementBits(FP4)=4)
每逻辑元素 4-bit 计,使
Tile<fp4, Rows=8, Cols=64>→kBytes = 8*64*4/8 = 256B。两种落法:pto_tile.hpp的kBytes公式对 4-bit 打包类型特判(除 2);或kBytes用前者。sizeof(__fp4_e2m1x2)=1B)与 32B 列对齐检查(pto_tile.hpp:722)。ElementBits(FP4)=4已就位(31f7a8f),无需再改。size=256B → row = 256*8/(64*4) = 8 = src→ 契约通过。不该采用的方案
PW/2=32(打包字节数):会违反 pto-spec「columns = 逻辑元素」语义,并触发编译期
template_asm.hpp:115static_assert(32 ≠ 源 64)。要「除 2」的是字节 size,属工具链头职责,kernel 声明层表达不了。
Block.cpp的 physical row/col 校验只留 valid:与 pto-specTileOperandsLegal_TCVT/TCVT()契约冲突,是掩盖而非对齐口径。影响面
所有用 TCVT 把宽类型(fp32/bf16/half)转成打包 fp4(
__fp4_e2m1x2等)的 kernel data pass,即
dynamic_mx_quant的 tail/nontail OCP-FP4 全部路径。scale pass(e8m0)不受影响。