Skip to content

feat(onnx-to-hip): lower MatMulBnb4, outer-product Einsum, and empty slices - #1255

Merged
asparkhi merged 3 commits into
mainfrom
feat/onnx-to-hip-bnb4-einsum-slice
Oct 21, 2026
Merged

asparkhi merged 3 commits into
mainfrom
feat/onnx-to-hip-bnb4-einsum-slice

Conversation

@pragyam32

@pragyam32 pragyam32 commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Lower com.microsoft.MatMulBnb4 to hip.dequant_bnb4 plus hip.matmul so the op bufferizes. FP4 and NF4 unpack high nibble first. Static shapes are absorbed into the rocMLIR matmul kernel; a dynamic activation keeps the dequant on a HIP kernel and the GEMM on hipBLASLt.
  • Lower a binary Einsum with no contracted index, such as bc,d->bcd, to a permute, unit-axis reshape, and hip.mul for f16 and f32. Rank stays at most 4. f64 stays onnx.Einsum.
  • Keep an empty onnx.Slice and a static zero-length onnx.Concat input inside the tensor.extract_slice / tensor.insert_slice verifier. A zero-length view uses offset 0, and a trailing empty concat input is skipped.

Related issue or design

None.

Why

MatMulBnb4 stayed as an ONNX op, so one-shot bufferization rejected the graph. The existing Einsum lowering only accepts a contraction with static M, N, and K, so an equation with no contracted index never bufferized. An empty shape-vector slice (tensor<3xi64> from index 3 through INT64_MAX) then failed verification as offset 0 is out-of-bounds: 3 >= 3: the leading 0 is the dimension index, and the offset value 3 is not strictly inside a length-3 dimension. Concatenating that empty tensor<0xi64> hits the same rule when the insert lands at offset == result dim.

What

  • hip.dequant_bnb4 writes static [N, K] f32. quant_type 0 is FP4 and 1 is NF4. Block sizes are 16, 32, 64, 128, and 256. transB must be 1 and training_mode must be 0. The op verifier requires ui8 packed weights, f32 absmax and output, a static [N, K] result, and packed and absmax lengths of N * K / 2 and N * K / block_size. The HIP kernel clears the last-error state before launch and returns hipGetLastError() afterward. The LUT matches the ONNX Runtime blockwise BNB4 tables. This path does not use tosa.matmul_t_block_scaled.
  • FuseROCMlir absorbs a single-use dequant into the matmul rock kernel when the activation shape is static. HipToTosa unpacks the high nibble, gathers the LUT, tiles the block absmax, and multiplies.
  • An outer-product Einsum permutes each operand into output-letter order, inserts size-1 axes for missing letters, and multiplies. The element type is f16 or f32. Contractions with a K dimension still go through transpose, collapse, and hip.matmul.
  • A size-0 extract_slice of a positive dimension uses offset 0. A source dimension of 0 becomes tensor.empty. The hip.slice zero-extent path is unchanged: a runtime empty slice still allocates full capacity so a loop accumulator can append. A concat input whose concat-axis extent is statically 0 is not inserted.

Test plan

  • hip-mlir-opt --hip-add-context-arg --convert-onnx-to-hip with FileCheck: test_matmul_bnb4.mlir, test_einsum.mlir (contraction and outer product, including f64 left as onnx.Einsum), test_slice.mlir (past-the-end and already-empty), test_concat.mlir (leading and trailing empty)
  • hip-mlir-opt --convert-hip-to-tosa: test_dequant_bnb4.mlir
  • hip-mlir-opt --verify-diagnostics: hip-dequant-bnb4-verifier.mlir for valid tensor and memref forms, and for an f16 output, a short packed buffer, and an unsupported block size
  • hip-mlir-opt --hipdnn-pipeline: test_matmul_bnb4_model.mlir and test_einsum_outer_model.mlir
  • test/numeric/tests/test_matmul_bnb4.py was added and not run on a GPU

Notes for reviewers

  • A dynamic MatMulBnb4 activation is not fused. training_mode != 0, transB != 1, and block sizes outside 16/32/64/128/256 stay unconverted.
  • Einsum with ellipsis, repeated letters, a dynamic extent, rank above 4, an f64 outer product, or an empty M or N on a real contraction stays onnx.Einsum.
  • Cursor Grok 4.7 drafted the conversions, the verifier, the tests, and this description.

Checklist

  • The change is focused, or links a design/series explaining its scope.
  • Relevant tests were added or updated and the results are documented.
  • User-facing or design documentation was updated when needed.
  • Substantial AI assistance is disclosed, and I reviewed and understand the result.

@github-actions

Copy link
Copy Markdown

Thanks for opening a PR!

This project follows LLVM's incremental-development and AI-tool-use
guidance. See CONTRIBUTING.md
for the project workflow.

Before requesting review, please check that:

  1. The change is focused. Substantial work links the relevant issue
    or design discussion.
  2. The PR documents relevant test results and updates affected
    documentation.
  3. If AI tools provided substantial assistance, the description
    explains what was assisted and how it was validated, and commit
    trailers identify the tool. The contributor has reviewed and
    understands the result.

Reviewers are assigned through
CODEOWNERS where ownership
is configured.

@github-actions github-actions Bot added the large-pr Soft size threshold (>500 LOC or >10 files). Reviewer signal, not a block. label Oct 18, 2026
@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

L2 Accuracy Results (EP vs CPU)

Model Combined L2 Total Elems Skipped NaN/Inf
conv_test_hybrid 1.12256E-07 64 0
GroupQueryAttention_seq256 59.2561 2621440 0
MatMulNBits_o_seq128 259.905 368640 0
QMoE_seq128 24.2434 368640 0

Threshold: 0.01 | Run: 7671 - Commit: 3dfd727

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Binary sizes

Baseline: run 39194929987 - ffeed9d (dll-sizes)

File This (MB) Main (MB) Delta (MB)
hipgpu.dll 58.83 58.79 +0.04
custom_kernels_gfx1170.dll 5.33 5.33 0.00
custom_kernels_gfx11-generic.dll 5.62 5.61 0.00

MorphiZen EP Performance Results

Model QPS Session (s) 1st Infer (ms) CPU% Mem (MB)
full_model_seq128 8.76 7.28 266 3 1156
GroupQueryAttention_seq128 11393.00 2.2983 1 5 227
matmul_down_seq128 510.53 3.08 124 3 272

EPContext Export Performance

Model QPS Session (s) 1st Infer (ms) CPU% Mem (MB)
full_model_seq128 8.81 48.55 245 3 15503

EPContext Import Performance

Model QPS Session (s) 1st Infer (ms) CPU% Mem (MB)
full_model_seq128 8.77 8.83 250 3 15636

OGA Benchmark Results

Model Warmup Reps Prompt Len Gen Tokens TTFT (ms) TPS Peak Mem (GB) GPU Mem (GB)
gpt-oss-20b-webgpu-int4-rtn-block-32 1 5 128 128 91.3 74.7 1.25 12.36
Llama-3.1-8B-awq-g128-int4-asym-fp16-onnx-dml 1 5 128 128 125.9 45.6 1.14 5.23

OGA Wheel Smoke (Python benchmark_e2e.py)

Model TTFT (ms) TPS
Llama-3.1-8B-awq-g128-int4-asym-fp16-onnx-dml 125 45.9

VLM Benchmark Results

Model TTFT (ms) TPS Output
Qwen3.8-27B-fp16-ve-fp16-int4-k_quant-gs128-text-dml-onnx 4800.5 13.6 The user wants a detailed description of the image. Let me carefully observe all elements before writing. Subject: A white do

Run: 7671 - Commit: 3dfd727

Copilot AI left a comment

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.

🟡 Changes recommended

Outer-product Einsum accepts f64 despite missing runtime multiplication support, which can leave outputs unwritten.

1 open issue
What changed in this PR

Extends hip-ep’s ONNX lowering support so more graphs can bufferize and execute on AMD GPUs.

Changes:

  • Adds FP4/NF4 MatMulBnb4 dequantization, runtime support, and static rocMLIR fusion.
  • Lowers static outer-product Einsum to broadcast multiplication.
  • Handles empty slices and concat inputs, with tests and documentation.
File Description
test/​numeric/​tests/​test_matmul_bnb4.py Adds FP4/NF4 numeric comparisons.
test/​lit/​e2e/​test_matmul_bnb4_model.mlir Checks runtime pipeline lowering.
test/​lit/​e2e/​test_einsum_outer_model.mlir Checks outer-product pipeline lowering.
test/​lit/​Conversion/​onnx-to-hip/​test_slice.mlir Covers empty slices.
test/​lit/​Conversion/​onnx-to-hip/​test_matmul_bnb4.mlir Tests conversion and training-mode rejection.
test/​lit/​Conversion/​onnx-to-hip/​test_einsum.mlir Covers outer products and rank limits.
test/​lit/​Conversion/​onnx-to-hip/​test_concat.mlir Covers leading and trailing empty inputs.
test/​lit/​Conversion/​hip-to-tosa/​test_dequant_bnb4.mlir Tests dequantization conversion.
lib/​Runtime/​real/​dequant_bnb4.cpp Adds runtime kernel dispatch.
lib/​Runtime/​mock/​mock_gpu.cpp Adds mock dequantization wrapper.
lib/​Runtime/​Kernels/​include/​hip_custom_kernels.h Declares the kernel entry point.
lib/​Runtime/​Kernels/​hip/​dequant_bnb4_kernel.hip Implements FP4/NF4 unpacking and scaling.
lib/​Runtime/​Kernels/​CMakeLists.txt Registers the new kernel source.
lib/​Runtime/​hipdnn_ep_runtime.h Declares the runtime wrapper.
lib/​Runtime/​CMakeLists.txt Includes dequantization runtime bitcode.
lib/​Dialect/​Transforms/​FuseROCMlir.cpp Absorbs single-use dequantization into kernels.
lib/​Dialect/​IR/​HipDialect.cpp Adds destination and memory-effect interfaces.
lib/​Conversion/​OnnxToHip/​SliceConversion.cpp Handles statically empty slice views.
lib/​Conversion/​OnnxToHip/​OnnxToHipUtils.h Declares MatMulBnb4 conversion registration.
lib/​Conversion/​OnnxToHip/​OnnxToHip.cpp Registers MatMulBnb4 conversion.
lib/​Conversion/​OnnxToHip/​MatMulBnb4Conversion.cpp Lowers MatMulBnb4 to dequantization and matmul.
lib/​Conversion/​OnnxToHip/​EinsumConversion.cpp Adds outer-product lowering.
lib/​Conversion/​OnnxToHip/​ConcatConversion.cpp Skips statically empty concat-axis inputs.
lib/​Conversion/​OnnxToHip/​CMakeLists.txt Builds MatMulBnb4 conversion.
lib/​Conversion/​HipToTosa/​HipToTosa.cpp Adds codebook-based TOSA dequantization.
lib/​Conversion/​HipToLLVM/​HipToLLVMUtils.h Declares dequantization lowering and symbol.
lib/​Conversion/​HipToLLVM/​HipToLLVM.cpp Registers dequantization lowering.
lib/​Conversion/​HipToLLVM/​DequantBnb4Lowering.cpp Emits the runtime wrapper call.
lib/​Conversion/​HipToLLVM/​CMakeLists.txt Builds dequantization lowering.
include/​hip/​Dialect/​IR/​HipOps.td Defines hip.dequant_bnb4.
include/​hip/​Dialect/​IR/​HipBufferize.h Registers dequantization bufferization.
docs/​supported-operations.md Documents expanded operation support.

Review effort: Balanced

Comment thread lib/Conversion/OnnxToHip/EinsumConversion.cpp
Pragya0302 and others added 2 commits October 19, 2026 14:11
…slices

MatMulBnb4 decomposes into a high-nibble dequant plus matmul so the op
bufferizes on the HIP and rocMLIR paths. An Einsum with no contracted
index lowers to a reshape and hip.mul. Empty Slice and Concat views no
longer place the offset at the dimension length.

Co-Authored-By: Cursor Grok 4.7 <cursoragent@cursor.com>
Made-with: Cursor
hip.mul's runtime multiply accepts f16 and f32. An f64 equation such as i,j->ij was rewritten anyway, and wrap_elementwise then returned failure without writing the output.

Co-Authored-By: Cursor Grok 4.7 <cursoragent@cursor.com>
Made-with: Cursor
@pragyam32
pragyam32 force-pushed the feat/onnx-to-hip-bnb4-einsum-slice branch from 41456d0 to cc4546f Compare October 19, 2026 08:41
@nvunnam57128
nvunnam57128 requested a balanced review from Copilot October 19, 2026 08:41

Copilot AI left a comment

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.

🟡 Changes recommended

Missing dequantization operand validation permits buffer overruns through direct HIP-to-LLVM compilation.

1 open issue
1 resolved since last review

Review effort: Balanced

Comment thread include/hip/Dialect/IR/HipOps.td
The HIP-to-LLVM lowering forwards the packed, absmax, and output pointers without checking them. An f16 output with the same element count is only half as large as the f32 buffer the kernel writes.

Co-Authored-By: Cursor Grok 4.7 <cursoragent@cursor.com>
Made-with: Cursor

Copilot AI left a comment

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.

🔵 Needs a closer look

The new rocMLIR fusion path lacks regression coverage, and GPU numerical validation remains unrun.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Add LIT coverage for dequantization fusion capture and remapping

lib/​Dialect/​Transforms/​FuseROCMlir.cpp:138

Please add LIT coverage for this dequantization capture/remapping, following test/lit/Dialect/hip-fuse-rocmlir.mlir. The new --hipdnn-pipeline test does not run this pass, and the HipToTosa test starts inside an already outlined function, so neither checks whether fusion produces a valid isolated function. Test static single-use fusion, verify that packed weights and absmax become kernel arguments, re-verify the outlined function, and run HipToTosa on it. Also check that dynamic activations bypass fusion and that a dequantization result with multiple uses is not absorbed.

1 resolved since last review

Review effort: Balanced

@asparkhi
asparkhi merged commit 579b6ec into main Oct 21, 2026
10 checks passed
@asparkhi
asparkhi deleted the feat/onnx-to-hip-bnb4-einsum-slice branch October 21, 2026 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

large-pr Soft size threshold (>500 LOC or >10 files). Reviewer signal, not a block.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants