[FlyDSL] gfx942 a16wi4: pack f32->bf16 with lshr-16 instead of scalar - #5017
Conversation
… truncf v_cvt_pk_bf16_f32 is gfx950-only. After #4646 the gfx942 int4 fallback used f32.to(bf16)/truncf, which is much more VALU than the old moe_gemm_2stage shift-pack. Same nibble order; gfx950 packed convert and MXFP4 are unchanged.
🏷️ CI GuideRuns automatically on every PR:
Extended tests (opt-in via labels):
PR title tags: |
There was a problem hiding this comment.
Pull request overview
Restores a faster gfx942-specific int4→bf16 upconvert path in the FlyDSL a16wi4 (bf16×int4) MOE 2-stage kernels by replacing the scalar f32→bf16 truncation fallback with a high-16-bit pack (lshr 16 + mask/OR). This targets the regression introduced after #4646 by avoiding a VALU-heavy conversion sequence on gfx942 (which lacks v_cvt_pk_bf16_f32).
Changes:
- Reworked
_int4_nibble_to_bf16x8(..., use_k16=True)to generate bf16 pairs by packing the high 16 bits of two f32 bitpatterns, instead of per-element scalar bf16 truncation. - Added clarifying comments about the gfx942 packing path and FlyDSL cache-key behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| aiter/ops/flydsl/kernels/moe_2stage_a16wmix/utils.py | Implements gfx942-only lshr-16 bf16 packing for int4 upconvert in _int4_nibble_to_bf16x8 and documents the cache-key implication. |
| aiter/ops/flydsl/kernels/moe_2stage_a16wmix/gemm1.py | Adds a note indicating gfx942 weight upconvert uses the lshr-16 pack path in utils. |
| aiter/ops/flydsl/kernels/moe_2stage_a16wmix/gemm2.py | Adds a note indicating gfx942 weight upconvert uses the lshr-16 pack path in utils. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Summary
On gfx942, FlyDSL a16wi4 (bf16 × int4) became slower after #4646.
gfx942 has no
v_cvt_pk_bf16_f32. The new pipeline fell back to scalarf32.to(bf16)/ truncf for the int4 → bf16 upconvert. That path is much more VALU than packing two f32 values into a bf16 pair withlshr 16.This PR restores that pack for a16wi4 on gfx942 only. Nibble order is unchanged. gfx950 still uses the packed convert. MXFP4 is not touched.
Why it is faster
truncf is ~5 VALU ops per element. The pack is:
lshr 16)For scaled int4 this is exact (no extra rounding vs the old truncf fallback).
What changed
_int4_nibble_to_bf16x8(..., use_k16=True)— gfx942 pack, not truncfPerf (convert only, same tile)
MI300X gfx942. a16wi4, SiLU,
h=3584,inter=512,E=896,topk=16,ksplit=1. Same gemm1 tile for both:16×64×128,k_wave=2. CUDA events, 10 warmup + 51 iters.origin/main(truncf) vs this branch (lshr).