Description
VMI 的 vload(..., stride=1, dist_mode="brc", group=N) 会被降到 block-stride 形式(RV_VSLDB),而该指令要求有效地址 32 字节对齐;但编译器放行时用的对齐模型是 BRC_B8/B16/B32 的 contract(Size::Element → 1/2/4 字节)。两者矛盾时编译器不报错,而是生成一条非对齐的 RV_VSLDB,真机上表现为 ACL 507035 device fault。
根因链(逐条已核)
lib/PTO/IR/VPTOMemoryDist.cpp 的 contract 表:
{Load, LoadNorm, "NORM", 0, 0, 1, ..., Size::Block, Size::Vector, {}},
{Load, BrcB8, "BRC_B8", 8, 1, 1, ..., Size::Element, Size::Element, {}},
{Load, BrcB16, "BRC_B16", 16, 2, 1, ..., Size::Element, Size::Element, {}},
{Load, BrcB32, "BRC_B32", 32, 3, 1, ..., Size::Element, Size::Element, {}},
case Size::Element: return operandElementBits / 8; // BRC_B8→1, BRC_B16→2, BRC_B32→4 字节
case Size::Block: return 32; // NORM/NORM_B8/NORM_B16/NORM_B32 → 32 字节
lib/PTO/Transforms/VMIToVPTOConversionInternals.cpp:1528 的 isDirectMemoryDistAddressLegal() 就用这个 contract 算 requiredAlignment,对 BRC_B8 得到 1 字节 → 判定合法;
VMIToVPTOPatternInternals3.cpp 的 tryLowerDirectBRC / buildDirectBRCResult 于是发出 VldsOp{ dist = "BRC_B8" },偏移是原始字节偏移;
- 后端把它编成
RV_VSLDB(block-stride),该指令要求 32B 对齐 → 地址不满足 → trap。
Reproduction(最小)
canonical (1,32) 的 SF 惰性展开(模仿 ASC 的 lazy packed scale):sf_src_ub 为 uint8、行宽 32,读 sf_src_ub[row, half*4],即地址 ≡ 4 (mod 32):
raw = T.vmi.vload(sf_src_ub[group * bm, half * scales_per_chunk],
size=vector_lanes, stride=1, dist_mode='brc', group=vector_lanes // bk)
编译通过;真机运行报:
RuntimeError: npuSynchronizeDevice ... device error type 3, error code is 507035
Evidence:模拟器给出精确原因
npusim record -s Ascend950(用户态即可,无需设备):
[ERROR] rvec_dec.cc:1319 block_stride_proc ISA name RV_VSLDB, pc:0x9000d1247c
Address.Sn 0x00008004 is not aligned to 32 bytes
[ERROR] rvec_dec.cc:1319 block_stride_proc ISA name RV_VSLDB, pc:0x9000d1247c
Address.Sn 0x00008024 is not aligned to 32 bytes
... (同一条指令共 4096 次)
失败地址全部 ≡ 4 (mod 32)、步进 0x20(= 行距 32)。即 RV_VSLDB 的 32B 对齐要求与行内 4 字节偏移直接冲突。
对照:ASC 同样的语义没有这个问题
Nightly ASC(tile_kernels/quant/cast_back_asc.py,use_lazy_packed_scale 路径):
packed = S.vld(sf_ub[group, col // 64], dist='BRC_B16') # 注意:没有 stride 操作数
它在同一硬件上以 2 字节粒度的偏移(0,2,4,6,…)正常工作。差别在于它发的是纯广播形式,不是 block-stride。
去掉 stride 也不是可行规避
VMI 前端里 group 强制要求 stride(_validate_vmi_load_modes:vload(...) with group=... requires stride),所以只能 group/stride 一起去掉,退成 vload(ptr+off, size=N, dist_mode='brc');那样 PTOAS 会以另一个错误失败:
kernel.pto:274:24: error: VMI-LAYOUT-CONTRACT:
cannot materialize requested VMI operand layout #pto.vmi.layout<contiguous, lane_stride = 2>
即 grouped-brc 目前是该形状唯一能落下去的形态。
Expected behavior
二选一(任一即可让该形状可用):
stride == 1 的 BRC 不走 block-stride 形式,改用纯广播指令(与 ASC 等价);或
- 若必须用
RV_VSLDB,则让 isDirectMemoryDistAddressLegal 对这条路径按 32 字节校验,编译器据此拒绝并走回退,而不是发一条必然 trap 的指令。顺带:verifySupportedVMIFloatOp 里的针对性诊断(... supports contiguous 16-bit float-like or fp8-like physical source chunks ...)在这条路径上没有触发,最终只留下通用报错。
附:同一 repro 里另有一个独立的 lowering 缺口
(fp8 → f32 的 extf 需要它才能编过,已本地验证修法)
VMIToVPTOPatternInternals5.cpp 的 buildFactorPlan 只有两个分支:
if (sourceBits == 16 && resultPartCount == 2 * sourcePartCount) → {EVEN, ODD}, factor 2
if (sourceBits == 8 && resultPartCount == 4 * sourcePartCount) → {P0..P3}, factor 4
return notifyMatchFailure(op, "unsupported physical extf source/result width relation");
而 kLegalCastLayoutPatterns 里 4x widening 登记了 {bits<8>, bits<32>, ls(2), d(2)}。当消费者把 scale 按 EVEN/ODD 拆两半时,x 的 dequant 会被拉成 1 → 2 的 extf(sourceBits=8, sourcePartCount=1, resultPartCount=2),两个分支都不匹配 → op 残留 → VMI-RESIDUAL-OP: failed to convert all VMI ops/types to VPTO。
修法:packedE2M1LaneStride2 那个分支的判据从 isa<F4E2M1x2Type> 放宽到「8-bit 源 + lane_stride == 2 + resultTypes.size() == 2 * sourceParts.size()」(用 {P0, P2}),这与 packed f4 同构(8-bit + lane_stride=2 的有效字节同样落在偶数 lane)。本地改后该 .pto 可正常产出目标文件。
Environment
- PTOAS:本仓
main(本地 checkout codex/bump-vmi-version-0.1.7 @ 0c16503f1,以及 nightly pin 7ad34a47)均可复现
- CANN 9.2.0 / Ascend950DT /
npusim record -s Ascend950
- 复现用例来自 TileKernels-vmi
cast_back canonical (1,32),(32,1) 路径不受影响
Migrated from GitCode cann/pto-as#11. Original GitCode label: tech-debt.
Description
VMI 的
vload(..., stride=1, dist_mode="brc", group=N)会被降到 block-stride 形式(RV_VSLDB),而该指令要求有效地址 32 字节对齐;但编译器放行时用的对齐模型是BRC_B8/B16/B32的 contract(Size::Element→ 1/2/4 字节)。两者矛盾时编译器不报错,而是生成一条非对齐的RV_VSLDB,真机上表现为 ACL 507035 device fault。根因链(逐条已核)
lib/PTO/IR/VPTOMemoryDist.cpp的 contract 表:{Load, LoadNorm, "NORM", 0, 0, 1, ..., Size::Block, Size::Vector, {}}, {Load, BrcB8, "BRC_B8", 8, 1, 1, ..., Size::Element, Size::Element, {}}, {Load, BrcB16, "BRC_B16", 16, 2, 1, ..., Size::Element, Size::Element, {}}, {Load, BrcB32, "BRC_B32", 32, 3, 1, ..., Size::Element, Size::Element, {}},lib/PTO/Transforms/VMIToVPTOConversionInternals.cpp:1528的isDirectMemoryDistAddressLegal()就用这个 contract 算requiredAlignment,对BRC_B8得到 1 字节 → 判定合法;VMIToVPTOPatternInternals3.cpp的tryLowerDirectBRC/buildDirectBRCResult于是发出VldsOp{ dist = "BRC_B8" },偏移是原始字节偏移;RV_VSLDB(block-stride),该指令要求 32B 对齐 → 地址不满足 → trap。Reproduction(最小)
canonical
(1,32)的 SF 惰性展开(模仿 ASC 的 lazy packed scale):sf_src_ub为 uint8、行宽 32,读sf_src_ub[row, half*4],即地址 ≡ 4 (mod 32):编译通过;真机运行报:
Evidence:模拟器给出精确原因
npusim record -s Ascend950(用户态即可,无需设备):失败地址全部 ≡ 4 (mod 32)、步进 0x20(= 行距 32)。即
RV_VSLDB的 32B 对齐要求与行内 4 字节偏移直接冲突。对照:ASC 同样的语义没有这个问题
Nightly ASC(
tile_kernels/quant/cast_back_asc.py,use_lazy_packed_scale路径):它在同一硬件上以 2 字节粒度的偏移(0,2,4,6,…)正常工作。差别在于它发的是纯广播形式,不是 block-stride。
去掉 stride 也不是可行规避
VMI 前端里
group强制要求stride(_validate_vmi_load_modes:vload(...) with group=... requires stride),所以只能 group/stride 一起去掉,退成vload(ptr+off, size=N, dist_mode='brc');那样 PTOAS 会以另一个错误失败:即 grouped-brc 目前是该形状唯一能落下去的形态。
Expected behavior
二选一(任一即可让该形状可用):
stride == 1的 BRC 不走 block-stride 形式,改用纯广播指令(与 ASC 等价);或RV_VSLDB,则让isDirectMemoryDistAddressLegal对这条路径按 32 字节校验,编译器据此拒绝并走回退,而不是发一条必然 trap 的指令。顺带:verifySupportedVMIFloatOp里的针对性诊断(... supports contiguous 16-bit float-like or fp8-like physical source chunks ...)在这条路径上没有触发,最终只留下通用报错。附:同一 repro 里另有一个独立的 lowering 缺口
(fp8 → f32 的
extf需要它才能编过,已本地验证修法)VMIToVPTOPatternInternals5.cpp的buildFactorPlan只有两个分支:而
kLegalCastLayoutPatterns里 4x widening 登记了{bits<8>, bits<32>, ls(2), d(2)}。当消费者把 scale 按 EVEN/ODD 拆两半时,x 的 dequant 会被拉成 1 → 2 的extf(sourceBits=8, sourcePartCount=1, resultPartCount=2),两个分支都不匹配 → op 残留 →VMI-RESIDUAL-OP: failed to convert all VMI ops/types to VPTO。修法:
packedE2M1LaneStride2那个分支的判据从isa<F4E2M1x2Type>放宽到「8-bit 源 +lane_stride == 2+resultTypes.size() == 2 * sourceParts.size()」(用{P0, P2}),这与 packed f4 同构(8-bit + lane_stride=2 的有效字节同样落在偶数 lane)。本地改后该.pto可正常产出目标文件。Environment
main(本地 checkoutcodex/bump-vmi-version-0.1.7@0c16503f1,以及 nightly pin7ad34a47)均可复现npusim record -s Ascend950cast_backcanonical(1,32),(32,1)路径不受影响Migrated from GitCode cann/pto-as#11. Original GitCode label: tech-debt.