Skip to content

cpu: x64: matmul: add MXFP4 (f4_e2m1 + e8m0) weights decompression for f32 matmul - #5645

Open
amakarev wants to merge 5 commits into
mainfrom
amakarev/matmul/f32_mxfp4
Open

cpu: x64: matmul: add MXFP4 (f4_e2m1 + e8m0) weights decompression for f32 matmul#5645
amakarev wants to merge 5 commits into
mainfrom
amakarev/matmul/f32_mxfp4

Conversation

@amakarev

Copy link
Copy Markdown
Contributor

Adds MXFP4 (f4_e2m1 weights + e8m0 group-32 scales) weights decompression for the f32 x64 brgemm matmul primitive on avx512_core.

Two decompression paths, selected by problem size:

  • Fused (M ≤ 4): inline f4 → f32 decode with e8m0 scale application inside the brgemm microkernel. Bypasses buffer_b entirely; one brgemm call covers exactly one K-group of 32.
  • Copy_b (M > 4): standard pre-decompression pass into buffer_b, then native f32 GEMM.

@amakarev
amakarev requested review from a team as code owners July 21, 2026 16:24
@github-actions github-actions Bot added platform:cpu-x64 Intel64/AMD64 processors. Codeowner: @oneapi-src/onednn-cpu-x64 component:tests Codeowner: @oneapi-src/onednn-arch component:common labels Jul 21, 2026
@amakarev
amakarev force-pushed the amakarev/matmul/f32_mxfp4 branch 2 times, most recently from cc0881c to 4d09e73 Compare July 21, 2026 16:41
@amakarev
amakarev requested a review from Copilot July 21, 2026 16:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds MXFP4 (f4_e2m1 weights + e8m0 group-32 scales) support to the x64 BRGEMM-based matmul path by introducing a fused in-kernel decode path for small M and extending the existing copy-B decompression pipeline for larger problems.

Changes:

  • Extend brgemm matmul configuration to recognize f32:f4_e2m1:f32 + e8m0 scales and to route between fused decode (no buffer_b) vs copy-B decompression.
  • Add fused FP4 decode + e8m0 scale preload support to the x64 JIT brgemm kernel and plumb an optional weights-scales pointer through the execute API.
  • Add reorder/test plumbing for f4 storage and add benchdnn harness inputs for MXFP4 decompression coverage.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/benchdnn/inputs/matmul/shapes_llm_mxfp4 Adds an LLM-oriented batch of MXFP4 matmul shapes for benchdnn.
tests/benchdnn/inputs/matmul/harness_matmul_decompression Adds a benchdnn option set to exercise MXFP4 weights decompression.
src/cpu/x64/matmul/brgemm_matmul.cpp Enables f32+f4 weights path and configures brgemm descriptors for fused FP4 decode (scale handling, packed offsets).
src/cpu/x64/matmul/brgemm_matmul_utils.hpp Adds config flags for f32+f4 weights and generic “packed elems per byte” handling.
src/cpu/x64/matmul/brgemm_matmul_utils.cpp Implements f32+f4 detection, ISA gating, buffer_b selection, and packed-element plumbing.
src/cpu/x64/matmul/brgemm_matmul_copy_utils.cpp Extends copy-B decompression to handle f4_e2m1 and e8m0 scales; refactors 4-bit masking helpers.
src/cpu/x64/brgemm/jit_brgemm_kernel.cpp Adds fused FP4 decode path in the microkernel (LUT/permd/masks + inline scaling).
src/cpu/x64/brgemm/brgemm.hpp Extends execute APIs with an optional ptr_wei_scales argument.
src/cpu/x64/brgemm/brgemm.cpp Plumbs ptr_wei_scales into brgemm_kernel_params_t on execution.
src/cpu/x64/brgemm/brgemm_utils.cpp Updates datatype support and blocking rules for fused FP4 decode.
src/cpu/x64/brgemm/brgemm_types.hpp Adds is_f4_fused_decompress flag and ensures post-op/store paths remain available.
src/cpu/reorder/simple_reorder.hpp Adds a reference any→any reorder for f4_e2m1→f4_e2m1 (byte copy / index remap).
src/cpu/reorder/cpu_reorder_regular_fp4.cpp Registers the new f4_e2m1→f4_e2m1 reorder implementation.
src/cpu/platform.cpp Exposes f4_e2m1 datatype availability on x64 AVX-512 Core.
src/cpu/matmul/ref_matmul.hpp Expands reference matmul datatype acceptance to include f4_e2m1.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/cpu/x64/brgemm/jit_brgemm_kernel.cpp Outdated
Comment thread src/cpu/x64/matmul/brgemm_matmul_utils.cpp Outdated
@amakarev
amakarev force-pushed the amakarev/matmul/f32_mxfp4 branch 2 times, most recently from 1882c04 to f383a96 Compare July 21, 2026 17:32
Comment thread src/cpu/x64/matmul/brgemm_matmul_utils.cpp
Comment thread src/cpu/x64/matmul/brgemm_matmul_utils.cpp
Comment thread tests/benchdnn/inputs/matmul/harness_matmul_decompression Outdated
Comment thread src/cpu/x64/matmul/brgemm_matmul.cpp Outdated
Comment thread src/cpu/x64/brgemm/brgemm.hpp Outdated
&& attr.fpmath_.apply_to_int_)
, f32_with_f4_wei_dt(everyone_is(f32, bgmmc.src_dt, bgmmc.dst_dt)
&& bgmmc.wei_dt == data_type::f4_e2m1)
, weights_decompression_support(f32_with_f4_wei_dt

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any restriction on fpmath for f32 with f4?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No restriction on fpmath is needed here. The MXFP4 pipeline decompresses f4_e2m1 * e8m0_scale → f32 and runs the FMA in f32; fpmath mode has no effect on either side of that.

&& IMPLICATION(bm_conf_utils.is_f16_fp8(),
one_of(isa, avx512_core_amx_fp16, avx10_2))
&& IMPLICATION(
bm_conf_utils.is_f32_with_f4_wei(), isa == avx512_core)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we use is_superset(isa, avx512_core)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_f4_fused_decompress is a zmm-non-AMX code path. Widening the check to is_superset(isa, avx512_core) would also match avx512_core_amx/avx512_core_fp16/avx10_2 etc., but the fused f4 nibble decode is only wired up in jit_brgemm_kernel under is_f4_fused_decompress_non_amx() (i.e. is_zmm && !is_tmm). On AMX-capable ISAs it wouldn't take effect.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @amakarev's conclusion but I believe the main reason for the check is that f32 is only supported by brgemm_matmul_t<avx512_core> (for machines with avx512 support). Everything else is the consequences of that.

Comment thread src/cpu/x64/matmul/brgemm_matmul_utils.cpp
@amakarev
amakarev force-pushed the amakarev/matmul/f32_mxfp4 branch from 737b8a9 to 4cbc656 Compare July 27, 2026 14:57
@@ -0,0 +1,16 @@
128x256x1536:128x1536x512_n"deepseek_r1_1*61"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this file unused?

&& IMPLICATION(bm_conf_utils.is_f16_fp8(),
one_of(isa, avx512_core_amx_fp16, avx10_2))
&& IMPLICATION(
bm_conf_utils.is_f32_with_f4_wei(), isa == avx512_core)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @amakarev's conclusion but I believe the main reason for the check is that f32 is only supported by brgemm_matmul_t<avx512_core> (for machines with avx512 support). Everything else is the consequences of that.

const int rd_unroll = 4;
const data_type_t rd_block_dt = get_mac_emu_data_type(
brg->dt_a, brg->isa_impl, brg->isa_impl != avx2_vnni_2);
const int rd_unroll = brg->is_f4_fused_decompress ? 32 : 4;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a comment documenting this choice (32) and relevant constrains (e.g. when and if it can be changed).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I understand fusion is a performance optimization. Did you see any performance benefits from this optimization?

|| bgmmc.wei_tag == adbc;
bgmmc.use_buffer_b = bm_conf_utils.use_buffer_b();

// Fused f4 decode applies scales inside the brgemm kernel, so don't fold

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The term decode is not really defined in oneDNN. We use decompression and quantization or a shorthand for them. I'd recommend to stick with the well known vocabulary.

dim_t jit_brgemm_kernel_t<Wmm>::ldb_B_offset(
dim_t ld_block2, bool is_tail) const noexcept {
if (brg.is_f4_fused_decompress_non_amx())
return (is_tail) ? brg.ldb_tail * brg.ld_step / 2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: since typesize_B is always defined as 1 for f4 data type so we could reuse the same formula by introducing b_elems_per_byte. Here and in other places.
const dim_t b_elems_per_byte = brg.is_f4_fused_decompress_non_amx() ? 2 : 1;

= bm_conf_utils.is_bf16_fp8() || bm_conf_utils.is_f16_fp8();
bgmmc.with_wei_decompression = bm_conf_utils.with_weights_decompression();
// The usage of this variable hardcodes the assumption
// there's only supported value > 1, which is 2. If this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Suggested change
// there's only supported value > 1, which is 2. If this
// there's only one supported value > 1, which is 2. If this

if (bgmmc.is_xf16_fp8) return true;
if (bgmmc.is_bf16_with_int_wei) return true;
if (bgmmc.is_f32_with_f4_wei) {
constexpr dim_t fused_M_threshold = 4;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add a justification for this threshold value?

const int reserved_regs_;
const bool is_wei_grouped_over_k_;

struct vmm_idx_t {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please elaborate on why you chose to create a structure instead of member functions we usually use for this purpose?

bool is_xf16_fp8 = false;
bool is_int4_weights = false;
bool is_f4_via_convert = false;
int wei_packed_elems_per_byte = 1;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the default value should be 0 as we expect this struct to be initialized before it's used.

if (bgmmc.is_f32_with_f4_wei) {
constexpr dim_t fused_M_threshold = 4;
const bool fused_eligible = !bgmmc.is_amx
&& is_superset(bgmmc.isa, avx512_core)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually need to check this here? I think if it's false then it doesn't matter as we'll return unimplemented anyway.

Same question about other conditions.

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

Labels

component:common component:tests Codeowner: @oneapi-src/onednn-arch platform:cpu-x64 Intel64/AMD64 processors. Codeowner: @oneapi-src/onednn-cpu-x64

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants