Skip to content

benchdnn: matmul: paneled (periodic) fast native reference (--mode-modifier=A) - #5969

Draft
karturov wants to merge 5 commits into
mainfrom
benchdnn-matmul-paneled-fill-poc
Draft

karturov wants to merge 5 commits into
mainfrom
benchdnn-matmul-paneled-fill-poc

Conversation

@karturov

@karturov karturov commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

1. Problem

benchdnn GPU matmul correctness runs can be dominated by the CPU "golden" reference. For configs with no fast CPU primitive (e.g. f8/f4/mx, some weight-decompression cases) benchdnn falls back to the scalar native reference, which is O(MB·M·N·K). On large layers this reference alone takes minutes per case, making broad sweeps impractical.

Currently, in oneDNN functional testing, we have to trim large shapes significantly (see #4355). While it works ok, two potential issues remain:

  1. There is a chance that trimmed shapes result in other strategies were selected, hence a testing gap
  2. We need to repeat this trimming every time we add a new set of models to testing

2. Solution

Fill the matmul inputs so the result is periodic along M and N (K kept full). Both the GPU primitive and the reference then produce a periodic output, so the native reference only needs to compute one representative panel_m × panel_n panel and broadcast it. Reference cost drops from O(MB·M·N·K) to ~O(panel_m·panel_n·K); the GPU primitive still runs the full shape. Panel width ≥128 and aligned to dst scale/zero-point groups so no block/group quantization parameter straddles a panel boundary.

Opt-in via --mode-modifier=A (env var DNNL_BENCHDNN_MATMUL_PANEL_FILL=1 kept as a deprecated alias). It does not disable --fast-ref: paneling engages only on the native-reference path. When a fast CPU primitive reference exists it runs the full shape vectorized and is generally faster, so paneling is skipped there to avoid regressions.

Periodic inputs can mask M/N address-arithmetic bugs, so this is opt-in and not a CI replacement for the default reference.

3. Implementation

  • utils/bench_mode.{hpp,cpp}, utils/parser.cpp: new mode_modifier_t::ref_periodic_fill (A).
  • matmul/matmul.cpp: apply_paneled_fill periodizes src/wei/bias, scales/zero-points, sum and binary/prelu post-op operands on the M/N panel grid; gated on the native path (!prim_ref).
  • matmul/ref_matmul.cpp: compute_ref_matmul_periodic computes the representative panel and broadcasts dst values and dynamic/derived dst scales (mx, dynamic_fp).
  • Supported: all dtypes, per-tensor/-channel/grouped scales & zero-points, dynamic dst scales, bias, sum, eltwise, binary/prelu post-ops, precomputed-reductions, runtime dims, batch. Falls back to the full reference for sparse/grouped, dropout, and stochastic rounding.
  • Docs: doc/driver_matmul.md, doc/knobs_common.md.

4. Validation

Validated on Intel Arc B580 (Battlemage), OpenCL GPU runtime.

  • Functional PASS across dtypes (f32/f64/bf16/f16/int8/f8/f4), quantization (per-tensor/-channel/grouped scales & zero-points, dynamic dynamic_fp dst scales with exact DST_SCALES comparison), bias, sum, eltwise, binary/prelu post-ops, precomputed-reductions, runtime dims, batched.
  • Coverage analysis of a full matmul nightly (15,269 cases): ~100% of large layers (≥1G MACs) are handled; the only uncovered large cases are sparse and dropout, which are non-periodic by construction and correctly fall back.
  • Fast-ref-aware gating verified: with a fast CPU reference available the modifier is a no-op (prim reference used); paneling engages only on the native path.

5. Performance (Intel Arc B580, layers from real LLM models, naturally native path, OFF vs ON)

These are matmul layers taken from real LLM models. Their configs (weight-decompression and mx/f4 scaling) have no fast CPU primitive reference, so benchdnn naturally uses the scalar native reference — exactly the slow case this feature targets. No flags force this path; --mode-modifier=A is the only difference between OFF and ON. All PASS.

Layer (M×N×K) dtype / attrs Wall OFF Wall ON Wall speedup compute_ref OFF → ON
4096×11008×4096 s8:u4:f16 woq (grouped wei scales+zp) + swish + binary_mul 187.9 s 5.6 s 33× 182.8 s → 0.29 s (~630×)
1009×14336×4096 f16:u4:f16 woq (grouped wei scales+zp, fpmath=f16) 48.2 s 5.1 s 9.5× 43.5 s → 0.12 s (~360×)
14558×3072×3072 f4_e2m1 + mx (e8m0) src/wei scales + bias 196.2 s 3.7 s 53× 192.9 s → 0.29 s (~665×)

The native reference itself speeds up ~360–665×. Since the data is periodic, the input fill was optimized to compute the reference once and broadcast it: fill_data's device reorder is skipped when paneling is active (the single post-periodize reorder supplies the device data), and periodization copies whole contiguous lines with memcpy in parallel. The remaining ON wall time is now the single unavoidable f32→device reorder (the GPU still runs the full shape).

@github-actions github-actions Bot added documentation A request to change/fix/improve the documentation. Codeowner: @oneapi-src/onednn-doc platform:gpu-intel Codeowner: @oneapi-src/onednn-gpu-intel component:tests Codeowner: @oneapi-src/onednn-arch labels Sep 6, 2026
karturov and others added 5 commits September 6, 2026 12:24
Add an experimental, env-gated fast path for the native CPU matmul
reference used in GPU accuracy validation. When
DNNL_BENCHDNN_MATMUL_PANEL_FILL=1 (and --fast-ref=false), inputs are
filled so the result is periodic along M and N (K kept full); the
reference is computed for a single representative panel_m x panel_n panel
and broadcast across the full output. This reduces the reference cost
from O(MB*M*N*K) to ~O(panel_m*panel_n*K) while the library still runs
the full shape.

Panels are at least 128 and aligned to dst scale/zero-point groups so no
block/group quantization parameter straddles a panel boundary. Supported:
common dtypes, int8/woq incl. K-grouped scales and zero-points, bias,
sum/eltwise/binary/prelu post-ops, batch tiling, and runtime dimensions.
Unsupported configurations (dynamic dst scales, dropout, stochastic
rounding, sparse/grouped, sub-128 shapes) fall back to the normal
reference and print the reason. Disabled by default.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ap 3)

The per-block dynamic destination scales are periodic when the panel width is a
multiple of the destination-scale group, so the native periodic reference computes
the representative panel's DST_SCALES and broadcasts them across the full
destination-scale tensor. Dynamic dst scales are a computed OUTPUT and are no longer
periodized as an input. Validated on Linux BMG (Intel Arc B580): dst:dynamic_fp
matmul PASSES with exact DST_SCALES comparison.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the DNNL_BENCHDNN_MATMUL_PANEL_FILL env variable (kept as a deprecated
alias) with a first-class benchdnn mode modifier, --mode-modifier=A, that opts
into the paneled (periodic) fast native reference for the matmul driver.

The modifier does not disable --fast-ref. Paneling engages only on the native
reference path (i.e. when no fast CPU primitive reference is available for the
problem, or when --fast-ref=false is passed). When a fast CPU primitive
reference exists it runs the full shape with vectorized kernels and is generally
faster than the paneled scalar native reference, so paneling is skipped to avoid
regressing those configs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The paneled ("periodic") fill previously did redundant full-tensor work:
fill_data filled the f32 reference and reordered it to the device, then
apply_paneled_fill periodized the reference and reordered to the device a
second time. The first device reorder was pure waste, and the periodization
itself used a per-element stride-decode loop over the whole tensor.

Two changes:
- Skip fill_data's device reorder when paneled fill is active; the single
  post-periodize reorder in apply_paneled_fill now supplies the device data.
- Add a fast path to periodize_ref: when the contiguous (stride-1) dimension
  is not periodized (the common src/wei case where K stays full), broadcast
  whole contiguous lines with memcpy in parallel instead of per-element copy.

On Intel Arc B580, real LLM layers: s8:u4:f16 woq 4096x11008x4096 fill
8.9s->4.8s (wall 10.9s->5.6s); f4_e2m1 mx 14558x3072x3072 wall 5.6s->3.7s;
f16:u4:f16 woq 1009x14336x4096 wall 10.8s->5.1s. Correctness unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Paneled fill (--mode-modifier=A) periodized scales and zero-points using a
positional role assignment on their physical dims. Quantization tensors,
however, only materialize the logical dims selected by their mask (see
md2dims()), so a per-batch wei scale (mask 3 -> dims [batch, K/group])
was mis-roled: the batch dim was treated as K and never collapsed. The
device then kept distinct per-batch scales while the reference broadcast
batch 0, producing mismatches on batches 1..MB-1.

Route scales/zero-points through a new mask-aware quant_period() that walks
the owner tensor's logical dims, keeps the masked ones, and assigns each the
role of its logical axis (leading=batch, last two=role_2nd/role_last), with
group sizes scaling the panel period. Data/bias/post-op operands keep the
positional full_rank_period() path. periodize_ref() now takes an explicit
period vector shared by both.

Fixes batched woq (e.g. --dt=f16:s4:f16 --attr-scales=wei:3:f16:128x1
4x300x4096:4x4096x512), previously failing with -A only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@karturov
karturov force-pushed the benchdnn-matmul-paneled-fill-poc branch from d863243 to da36d94 Compare September 6, 2026 19:26
@github-actions github-actions Bot removed the platform:gpu-intel Codeowner: @oneapi-src/onednn-gpu-intel label Sep 6, 2026
@@ -140,3 +118,54 @@ or

More examples with different driver options can be found at
inputs/matmul/test_\*.

@rjoursler rjoursler Sep 14, 2026

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.

There is a chance that trimmed shapes result in other strategies were selected, hence a testing gap

Rather than trying to optimize the current work, it may be simpler and faster to directly testing our strategies. For a given strategy, we really don't need to run a particularly large problem to validate all the code paths are correct (roughly 10-20 sizes smaller than 1Kx1Kx1K likely gets 100% functional validation). We could enable fairly direct strategy choice via something like #5461, by extending the configuration dispatcher to support something like entry=<index>. Exhaustive testing then just becomes running a small suite a problems against each entry until we hit the end of the supported entries.

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

Labels

component:tests Codeowner: @oneapi-src/onednn-arch documentation A request to change/fix/improve the documentation. Codeowner: @oneapi-src/onednn-doc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants