Skip to content

[WIP]solve fp4 -> bf16 layout - #1443

Open
Likai-19 wants to merge 1 commit into
hw-native-sys:mainfrom
Likai-19:fix_vcvt_fp4_layout
Open

[WIP]solve fp4 -> bf16 layout#1443
Likai-19 wants to merge 1 commit into
hw-native-sys:mainfrom
Likai-19:fix_vcvt_fp4_layout

Conversation

@Likai-19

@Likai-19 Likai-19 commented Sep 2, 2026

Copy link
Copy Markdown

1. 背景与问题

FP4 cast_back VMI kernel(512×2048, e2m1 packed UE8M0, out bf16)在 PTOAS lowering 后精度错误:bit_mismatch 约 47%,maxabs 约 3.0。

VPTO 中 FP4 数据装载为:

%result_0 = pto.vlds %17[%40] {dist = "UNPK_B8"}
    : !pto.ptr<!pto.f4E2M1x2, ub> -> !pto.vreg<256x!pto.f4E2M1x2>

UNPK_B8 的语义是 contiguous, lane_stride = 2:紧凑流元素 x[i] 落在物理寄存器 lane 2*i,即有效数据位于 P0 和 P2 两个 mod-4 lane 分区(P1/P3 是 padding)。

vmi-to-vptoOneToNVMIExtFOpPattern 对 sourceBits==8 的 packed4 转换按 P0, P1 顺序取 part:

%41 = pto.vcvt %result_0, %29 {part = "P0"}  // 有效数据
%42 = pto.vcvt %result_0, %29 {part = "P1"}  // 全是 padding

P1 是 padding,P2 的有效数据被漏掉,正好产生约一半错误。

2. 根因

两个独立的 layout 问题叠加:

  1. Preferred cast layout 表缺少 128 lane 的 8→32bit 行。默认推导让 FP4 vload 保留 ls(2) 的 packed4 布局,vlds lower 成单条 UNPK_B8,后续 vcvt 必须跨 P0/P2 取数;而 OneToNVMIExtFOpPattern 的 part 选择逻辑按连续 P0..P3 取,不感知 source 的 lane_stride,导致错取 P1。
  2. E2B group_broadcast_load 的 direct 路径选择过窄。当 scale 消费链强制 contiguous 且 direct E2B fact 为 deinterleaved、contiguous 形式跨多个物理 chunk 时,仍选了 direct E2B,后续落到 generic fallback vsldb + vselr;旧 SF 表布局下 vsldb base 只有 16B 对齐,触发 ACL_ERROR_RT_VECTOR_CORE_EXCEPTION (507035)

3. 改动内容

3.1 lib/PTO/Transforms/VMILayoutSupportTables.inc

kPreferredCastLayoutPatterns 中新增一行 preferred cast layout:

{bits<8>(), bits<32>(), 128, ls(4), c()},

效果:8bit source → 32bit result、128 lane 的 FP4 vload 优先以 lane_stride = 4 的 contiguous 布局 lower,生成两条 UNPK4,每个物理寄存器有效数据全部落在 P0,vcvt 只取 P0 即可。

3.2 lib/PTO/Transforms/VMILayoutAssignment.cpp

两处改动:

  1. getPreferredGroupBroadcastLoadLayout():当 E2B direct fact 的 result layout 是 deinterleaved,而 contiguous 形式物理 arity > 1 且存在 generic 支持时,返回 contiguous 优先(generic group_slots -> contiguous),避免后续 vldsx2/vintlv 式 deinterleave materialization。
  2. 新增 VMILoadOp 的自然 layout 约束:小 element count(小于 lanesPerPart 且整除)的 vload 设为 contiguous(lane_stride),补齐 SIMT 侧小粒度 load 的 layout 推导。

3.3 lib/PTO/Transforms/VMIToVPTO.cpp

两处改动:

  1. OneToNVMIGroupBroadcastLoadOpPattern:在 canUseDirectE2B 路径上增加保护——当 result layout 为 contiguous 且 chunks_per_part != 1 时,一个 E2B packet 实际填不满一个物理 part,无法复用单个 packet,此时 canUseDirectE2B = false,走 generic fallback,不再 hard fail 或生成错误指令。
  2. OneToNVMIExtFOpPattern:sourceBits==16 / sourceBits==8 两条 packed 转换路径,part factor 从硬编码 2 / 4 改为由 resultTypes.size() / sourceParts.size() 推导,并增加边界校验(factor 不合法时直接 notifyMatchFailure),避免在异常 result 拆分下静默生成错误 part 序列。

4. 生成的 VPTO 效果

4.1 FP4 值转换路径(核心修复)

修复前FP4_修复前_原始问题_UNPK_B8_P0P1_scratch_47pct.vpto):

%result_0 = pto.vlds %17[%40] {dist = "UNPK_B8"} : ... -> !pto.vreg<256x!pto.f4E2M1x2>
%41 = pto.vcvt %result_0, %29 {part = "P0"} : ... -> !pto.vreg<128xbf16>
%42 = pto.vcvt %result_0, %29 {part = "P1"} : ... -> !pto.vreg<128xbf16>
%low, %high = pto.vintlv %41, %42 : ...

P1 是 padding,P2 有效数据丢失。

修复后FP4_修复后_final_UNPK4_P0_SF32Bslot_half16_bit_exact.vpto):

%result_0 = pto.vlds %17[%47] {dist = "UNPK4"} : ... -> !pto.vreg<256x!pto.f4E2M1x2>
%result_1 = pto.vlds %17[%48] {dist = "UNPK4"} : ... -> !pto.vreg<256x!pto.f4E2M1x2>
%49 = pto.vcvt %result_0, %34 {part = "P0"} : ... -> !pto.vreg<128xbf16>
%51 = pto.vcvt %result_1, %34 {part = "P0"} : ... -> !pto.vreg<128xbf16>

两条 UNPK4,每个物理寄存器有效数据全在 P0,vcvt 只取 P0;数据位序与 CCE 参考实现一致。

4.2 scale 消费路径

修复前(直接 E2B + deinterleave 物化路径):

%result = pto.vlds %25[%38] {dist = "E2B_B16"} : ... -> !pto.vreg<128xbf16>
...
%low_5, %high_6 = pto.vdintlv %result_3, %result_4 : ...
%45 = pto.vmul %low_5, %result, %31 : ...
%46 = pto.vmul %high_6, %result, %31 : ...

修复后(generic contiguous 路径):

%43 = pto.addptr %25, %42 : <bf16, ub> -> <bf16, ub>
%result = pto.vsldb %43, %c0_i16, %c0_i16, %29 : ... -> !pto.vreg<128xbf16>
%44 = pto.vselr %result, %32 : ...
%45 = pto.vselr %44, %33 : ...
...
%53 = pto.vmul %result_2, %44, %30 : ...
%54 = pto.vmul %result_3, %45, %30 : ...

不再出现 vdintlv/vintlv 的 deinterleave 物化,scale 消费以 contiguous 形式完成。

说明:最终 VPTO 中 scale 加载地址为 row*32 + half*16(对应“每 8 个 scale + 8 个 pad = 32B slot”的 SF 表布局),这是 kernel 侧(TileKernels-vmi)为了保证 generic vsldb base 32B 对齐所做的配套布局调整。PTOAS 侧负责的是:当 contiguous 多 chunk 时选择并允许 generic 路径。

5. 验证

  • ptoas --pto-backend=vpto --emit-vpto:FP4 vcvt 段生成两条 UNPK4 + 两个 vcvt {part = "P0"},与预期一致。
  • NPU 实测(Ascend950 板):maxabs = 0.0bit_mismatch = 0/1048576,与参考实现 bit-exact。
  • E4M3 路径不做改变:E4M3 VPTO 结构与修复前基线一致,回归无影响。

@learning-chip

Copy link
Copy Markdown
Contributor

Should add unit test / lit test?

@learning-chip

Copy link
Copy Markdown
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants