Repository navigation
[CUDA] Add GatherPacked, MatMulPacked, and MoEPacked contrib ops for block-packed weights - #33953
Tianlei Wu (tianleiwu) wants to merge 10 commits into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical defects can cause initialization crashes, invalid memory reads, CUDA launch failures, and incorrect or undefined packed-operator outputs.
11 open issues
Reject empty vocabularies before gathering rows · New Reject top-k values greater than the expert count · New Handle shared-memory requests above CUDA's default limit · New Gate BF16 Tensor Core dispatch on hardware support · New Reject packed formats without a supported decoder · New Keep all warp lanes active in partial-tile MMA · New Regenerate and commit contrib operator documentation · New Stage device initializers into owned CPU tensors · New Decode external weights into native-endian float storage · New Disable CPU fallback in the CUDA decoder test · New Compare MoEPacked outputs against a numerical reference · New
What changed in this PR
Adds block-packed weight support to ONNX Runtime through three contrib operators, enabling embedding lookup, matrix multiplication, and mixture-of-experts execution without offline weight repacking.
Changes:
- Registers GatherPacked for CPU/CUDA and MatMulPacked and MoEPacked for CUDA.
- Adds CUDA dequantization, decode, prefill, and expert-routing kernels with Python tests.
- Extends external-initializer buffer borrowing and device reuse, with loading tests and documentation.
| File | Description |
|---|---|
onnxruntime/test/shared_lib/test_model_loading.cc |
Adds direct-buffer session-loading coverage. |
onnxruntime/test/python/transformers/test_gguf_packed_ops.py |
Tests packed formats and decode/prefill paths. |
onnxruntime/test/framework/inference_session_test.cc |
Tests initializer copying and pointer reuse. |
onnxruntime/core/session/inference_session.cc |
Passes the direct-buffer option to graph injection. |
onnxruntime/core/graph/graph.cc |
Extends external-initializer buffer retention. |
onnxruntime/core/graph/contrib_ops/ms_opset.h |
Adds the three operators to the Microsoft opset. |
onnxruntime/core/graph/contrib_ops/contrib_defs.cc |
Defines schemas and shape inference. |
onnxruntime/core/framework/tensorprotoutils.cc |
Extends in-memory serialization to small device tensors. |
onnxruntime/core/framework/session_state_utils.cc |
Reuses graph initializers on their planned device. |
onnxruntime/contrib_ops/cuda/quantization/matmul_packed.h |
Declares packed matrix-multiplication interfaces. |
onnxruntime/contrib_ops/cuda/quantization/matmul_packed.cu |
Implements decode kernels and cuBLAS prefill. |
onnxruntime/contrib_ops/cuda/quantization/matmul_packed.cc |
Validates inputs and dispatches CUDA computation. |
onnxruntime/contrib_ops/cuda/quantization/gguf_quant_cuda.cuh |
Adds shared CUDA decoders and BF16 Tensor Core helpers. |
onnxruntime/contrib_ops/cuda/quantization/gather_packed.h |
Declares CUDA packed-gather interfaces. |
onnxruntime/contrib_ops/cuda/quantization/gather_packed.cu |
Implements gathered-row dequantization. |
onnxruntime/contrib_ops/cuda/quantization/gather_packed.cc |
Registers and validates CUDA GatherPacked. |
onnxruntime/contrib_ops/cuda/moe/moe_packed.h |
Defines expert-routing parameters and interfaces. |
onnxruntime/contrib_ops/cuda/moe/moe_packed.cu |
Implements routing, expert computation, and output accumulation. |
onnxruntime/contrib_ops/cuda/moe/moe_packed.cc |
Validates expert tensors and chunks execution. |
onnxruntime/contrib_ops/cuda/cuda_contrib_kernels.cc |
Registers the three CUDA operators. |
onnxruntime/contrib_ops/cpu/quantization/gather_packed.cc |
Implements threaded CPU GatherPacked. |
onnxruntime/contrib_ops/cpu/cpu_contrib_kernels.cc |
Registers CPU GatherPacked variants. |
include/onnxruntime/core/session/onnxruntime_session_options_config_keys.h |
Documents expanded direct-buffer option behavior. |
include/onnxruntime/core/graph/graph.h |
Adds the graph-injection borrowing parameter. |
docs/Model_Loading_Performance.md |
Describes external-buffer borrowing and device reuse. |
Review effort: Balanced
993adf6 to
ad04a04
Compare
…block-packed weights Add com.microsoft::GatherPacked (CPU and CUDA), com.microsoft::MatMulPacked (CUDA), and com.microsoft::MoEPacked (CUDA) contrib operators that consume 2D/3D block-packed quantized weight blobs (such as GGUF IQ1/IQ2/IQ3/IQ4/Q2_0/Q4/Q5/Q6_K/Q8_0) directly without offline repacking or split scale tensors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Disallow output-type split-K reductions in MatMulPacked prefill so FP32 compute cannot silently round intermediate sums to BF16 or FP16. This addresses the Windows A10 packed-operator parity failures while retaining Tensor Core execution. Preserve the caller's cuBLAS math mode, propagate mode-update failures, and add FP16/BF16 cancellation coverage with a documented prefill numerical contract. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
320084a to
0051ca2
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical CUDA alignment, dimension-range, and BF16 dispatch defects can cause faults or silently incorrect results, while full-matrix dequantization can exhaust device memory.
16 open issues
Range-check MoE dimensions before narrowing casts · New Use scalar finalize for misaligned output pointers · New Validate K and grid limits before CUDA launches · New Make packed decoder reads safe for unaligned weights · New Handle BF16 dispatch when only older PTX was compiled · New Reject dimensions that overflow device index types · New Tile large prefill dequantization to limit scratch memory · New Handle misaligned FP16/BF16 activation loads · New Decode GGUF values explicitly as little-endian · New Add large-M numerical coverage for chunking and workspace reuse · New Preserve ggml copyright and MIT notices for copied tables · New Document negative-index behavior and format block requirements · New Gate BF16 tests on SM80 capability · New Add independent CPU known-answer decoder tests · New Cover FP16, prefill, and runtime-M GEMV paths · New Add MoE tests for lookup formats and mixed weight pairs · New
11 resolved since last review
Stage device initializers into owned CPU tensors Regenerate and commit contrib operator documentation Keep all warp lanes active in partial-tile MMA Reject packed formats without a supported decoder Gate BF16 Tensor Core dispatch on hardware support Handle shared-memory requests above CUDA's default limit Reject top-k values greater than the expert count Reject empty vocabularies before gathering rows Compare MoEPacked outputs against a numerical reference Disable CPU fallback in the CUDA decoder test Decode external weights into native-endian float storage
Review effort: Max
Validate dimensions before narrowing, support unaligned packed and activation buffers, and bound MatMulPacked prefill scratch. Check compiled BF16 capability, preserve endian-safe decoding and ggml notices, and extend CPU/CUDA regression coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Unchecked CPU row-size arithmetic can overflow, and CUDA FP16 prefill can produce infinite outputs even when the correct result is finite.
11 open issues
Check row-size multiplication for int64 overflow · New Avoid FP16 overflow when converting large prefill weights · New Avoid per-row heap allocations when gathering low-precision output · New Clarify GGUF loading claims or implement mapped-buffer access · New Probe CUDA function attributes on the configured device · New Validate complete quantization blocks during construction · New Fall back to SIMT when Tensor Core code is unavailable · New Validate output dtype before narrowing to 32 bits · New Test BF16 staging-buffer reuse with larger MoE dimensions · New Test large-N BF16 decode across quantized formats · New Add independent known-answer tests for quantized formats · New
16 resolved since last review
Handle misaligned FP16/BF16 activation loads Tile large prefill dequantization to limit scratch memory Reject dimensions that overflow device index types Handle BF16 dispatch when only older PTX was compiled Make packed decoder reads safe for unaligned weights Validate K and grid limits before CUDA launches Use scalar finalize for misaligned output pointers Range-check MoE dimensions before narrowing casts Add MoE tests for lookup formats and mixed weight pairs Cover FP16, prefill, and runtime-M GEMV paths Add independent CPU known-answer decoder tests Gate BF16 tests on SM80 capability Document negative-index behavior and format block requirements Preserve ggml copyright and MIT notices for copied tables Add large-M numerical coverage for chunking and workspace reuse Decode GGUF values explicitly as little-endian
Review effort: Max
Resolve the generated operator documentation conflicts by retaining both MoEGroupRouter and the packed operators. Validation: CUDA 13.0 rebuild; 22 packed-operator tests, 14 focused C++ tests, 19 documentation tests; merged registration lint. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reject oversized packed rows and invalid output types during initialization. Keep FP16 prefill finite, preserve BF16 SIMT fallback on forward-compatible builds, and avoid per-row CPU conversion allocations. Add independent known answers and multi-block GPU regressions, and clarify packed-weight device-memory requirements. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The JNI-only ORT AAR does not reference legacy support libraries. Avoid loading its large Debug native libraries into Jetifier, which failed NNAPI CI with Java heap space, while retaining Jetifier for other Android test dependencies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Baiju Meswani (baijumeswani)
left a comment
There was a problem hiding this comment.
What this PR does
This adds GatherPacked on CPU/CUDA and MatMulPacked and MoEPacked on CUDA. They read native block-packed weights without requiring a separate repacking step or separate scale tensors. That is useful for loading existing GGUF models without keeping an expanded copy of their weights. CUDA still needs device memory for the packed weights; host-buffer borrowing and hybrid CPU/GPU offload are separate parts of the wider work.
What looks good
The current version addresses several earlier concerns: dimension and byte-size checks, unaligned buffers, compiled BF16 capability checks and fallback, bounded MatMul prefill scratch, and FP16 prefill overflow. The tests now have independent known answers for all 15 accepted formats, and CUDA sessions disable CPU fallback. These are meaningful improvements, not just comparisons between two new implementations that could share the same mistake.
What remains before approval
Please fix the CPU GatherPacked global-type-reduction failure described inline and add a regression. The public format contract and the decode/prefill precision differences should also be documented clearly before models start depending on these schemas. The inline comments include two further improvement suggestions: reducing empty MoE launches and broadening the independent IQ fixtures.
For the stack as a whole, please restack and test the actual final candidate. Against this head, I reproduced 16 conflicting paths when combining #33962, including validation, decoders, kernels, schemas and tests. The upper branch contains an older copy of the packed implementation, so conflict resolution needs to preserve the newer fixes here as well as the precision/routing additions. #33991 is explicitly test-only and uses an older bottom snapshot; it does not establish that the current merge stack works together.
Before settling the public operator surface, I would also like a clear comparison with #33940's existing-operator approach. Native packed storage is a reasonable requirement; I am not asking for repacking merely to reuse an operator name, or assuming that the alternative covers every GGUF format here. The question is which new public contracts are necessary, while keeping decoding, routing and expert execution reusable.
Reviewed f5f7aee with three independent reviewers. I reproduced the registration filtering failure, the numerical example and tile-bound calculations, and checked the stack conflicts. I did not run a full ORT build, GPU runtime tests, sanitizers or latency benchmarks.
Preserve GatherPacked output/index pairs in reduced builds and bound MoE launches by the route count without changing workspace layout. Define stable public packed layouts and decode/prefill rounding behavior, with independent IQ known answers and dispatch-boundary regressions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed the five inline items in 9beea24119: pair-aware GatherPacked reduction, the MoE launch clamp, explicit rounding boundaries, a stable packed-format contract, and independent high-index IQ fixtures. Validation of this lower candidate
Comparison with #33940
These are not strict supersets: this lower PR does not implement NVFP4 or the Bolt-specific routing contract. Offset-UINT8 Q8 also must not be equated with native Q8_0's signed bytes and interleaved scale. I agree that native storage does not mathematically require new operator names or repacking merely to reuse existing names. The necessary public contracts are the byte codec/layout and dimensions, activation/output types and rounding, and MoE activation/routing semantics. Those need explicit, stable definitions under either operator surface. #33940 demonstrates the alternative of adding guarded fixed profiles to existing schemas; the three names here expose a uniform native-block interface. Private decoding, routing, and expert execution should remain reusable rather than being separated merely by the public names. Upper-stack qualification remains outstandingRe-running read-only Rui Ren (@rui-ren): #33962 needs replaying/restacking onto |
iOS device runs have stalled during cold simulator setup before app installation. Reset only the simulator selected by the Detox configuration, then wait for boot and app services without restarting the global CoreSimulator service. Keep app reinstallation, device assertions, and the existing Detox setup deadline unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
CI maintenance update at The preceding exact9beea iOS report contained zero testcase entries: cold simulator boot stalled, the unchanged240-second Detox setup deadline expired, and cleanup retained handles until the90-minute job deadline. The two-file ccea preparation repair passed all nine actual Packed YAML/Node-config regressions and only resets the uniquely selected simulator; it does not restart CoreSimulator globally or weaken the Detox deadline, reinstall, cleanup, or assertions. The subsequent exactccea React Native42622402833 successfully prepared and launched the device and actually executed three cases. MNIST and Basic Types passed; EPContext reload failed after a real native signal11, with The original39a worker has an independently reproduced post-dispatch lifetime bug: resolve/reject dispatch can release its last owner before the background lambda subsequently accesses raw Fresh validation on the actual Packed69a/f58 header:640 real pinned-Hermes/JSI ASan/LSan lifetime scenarios passed in two independent processes, covering resolve, reject, cancellation, Env invalidation and dispatch exceptions; compiler dependencies confirm the actual checkout header. All five focused EPContext component tests passed, the configured native policy suite passed1/1, and configured C++ formatting, repository TSX ESLint and repository-locked Prettier checks passed. This is local native lifecycle evidence, not a claim that the complete device or optional Packed matrix ran locally. Actual new-head React Native42634651129 and Windows CUDA42634651253 are IN_PROGRESS. Fresh exact69a Android/iOS three-case/full14 execution and complete current-head CI still need verification before full readiness. Normal human approval remains external. |
Run the native completion hook before dispatch can release the final worker reference. Preserve exactly-once completion when dispatch throws, and assert completion at promise settlement in the EPContext bridge regressions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>



Description
Adds three
com.microsoftcontrib operators—GatherPacked(CPU and CUDA),MatMulPacked(CUDA), andMoEPacked(CUDA)—that consume 2D and 3D block-packed quantized weight blobs (such as GGUFIQ2_XXS,IQ2_XS,IQ2_S,IQ3_XXS,IQ4_NL,IQ4_XS,Q2_0,Q4_0,Q5_0,Q5_K,Q6_K,Q8_0,F16,BF16,F32) directly in their native block layout without offline weight repacking or separate scale tensors. Direct external initializer buffers (#33877) avoid redundant host-side copies while loading.ggufshards. CUDA execution still copies CPU-backed weights to device memory and requires device storage for the packed weights; it does not execute zero-copy from host-backed shards.Summary of Changes
Contrib Operator Schemas & Registration
onnxruntime/core/graph/contrib_ops/ms_opset.hGatherPacked,MatMulPacked, andMoEPackedincom.microsoftopsetonnxruntime/core/graph/contrib_ops/contrib_defs.ccGatherPacked,MatMulPacked(A, B -> YwithK, N, packed_type), andMoEPackedonnxruntime/contrib_ops/cpu/cpu_contrib_kernels.ccGatherPackedkernelonnxruntime/contrib_ops/cuda/cuda_contrib_kernels.ccGatherPacked,MatMulPacked, andMoEPackedkernelsBlock-Packed Quantization & Kernel Implementations
onnxruntime/contrib_ops/cpu/quantization/gguf_quant_tables.hiq2xxs_grid,iq2xs_grid,iq2s_grid,iq3xxs_grid,iq3s_grid), block geometry helper (GgmlRowBytes), and CPU row dequantizeronnxruntime/contrib_ops/cpu/quantization/gather_packed.ccGatherPackedimplementationonnxruntime/contrib_ops/cuda/quantization/gguf_quant_cuda.cuhDecodeGgufSubBlock8Impl,DecodeGgufSubBlock8SmemLutTpl,DecodeGgufSubBlock16Impl), register-onlyIq2xsSignsDev/Iq4NlValueDevhelpers, and Tensor CoreMmaBf16wrapperonnxruntime/contrib_ops/cuda/quantization/gather_packed.{h,cc,cu}GatherPackedkernelonnxruntime/contrib_ops/cuda/quantization/matmul_packed.{h,cc,cu}MatMulPackedkernel with warp-K-split Tensor Core (mma.sync.aligned.m16n8k16) and warp GEMV paths for decode (M <= 8), bounded tiled dequantization + cuBLAS GEMM for prefill (M > 8), and FP32 decoded weights for FP16 prefill when the packed format is not F16onnxruntime/contrib_ops/cuda/moe/moe_packed.{h,cc,cu}MoEPackedkernel with fused top-k softmax routing, parallel warp-scan tile bucketing (8routes/tile in decode,32routes/tile in prefill), warp-K-split Tensor Core FC1+SwiGLU in decode, 4-subtile Tensor Core FC1/FC2 in prefill, and 128-bit vectorized finalizeonnxruntime/test/python/transformers/test_gguf_packed_ops.pyGatherPacked(CPU vs CUDA bit-exactness across 15 formats),MatMulPacked(M = 1, 7, 8, 32), andMoEPacked(M = 4, 48)Testing
lintrunnerchecks pass.Qwen3.8-Flash-Next-GGUF(UD-IQ1_S,UD-IQ2_XS,UD-IQ3_XXS) on NVIDIA H200:engine_behaviours.py: 27 / 27 PASS across all three quantization variants.DFlash2 S=8): 20.0–20.9 ms/cycle (183–269 tok/s median, up to 359 tok/s peak).max_new=8192): 87.50% (UD-IQ1_S), 85.50% (UD-IQ2_XS), 86.00% (UD-IQ3_XXS).Checklist