Skip to content

Benchmark fused SM103 DP4A small-M INT8 scaled matmul #24

Description

@heiheiha798

Summary

This issue tracks a measurement-gated B300 experiment for the small-M CUDA bitsandbytes::int8_scaled_mm path used by Linear8bitLt.

Today the CUDA path materializes an INT32 output through cuBLASLt and then launches a second kernel to apply row/column scales, optional bias, and FP16 conversion. For token decode (M=1) and small batch/beam (M<=4), the extra launch, BLASLt setup, and 4*M*N-byte INT32 intermediate can be a disproportionate part of every 8-bit linear layer.

The hypothesis is that an SM103-only W8A8 DP4A GEMV kernel can compute the exact INT32 dot product and emit the scaled FP16 result in one launch, improving recurring LLM.int8 decode without changing public schemas, quantization state, or the exact INT32 matmul API.

This is an unmeasured hypothesis, not a performance claim.

Proposed bounded method

  • Add one CUDA kernel in a dedicated source file. One warp owns one output feature for one activation row; lanes traverse contiguous K in packed four-byte chunks using __dp4a, then perform an integer warp reduction.
  • Lane 0 applies the same CUDA epilogue as the existing kdequant_mm_int32_fp16 path: fmaf(dot * row_stat * col_stat, 6.200012e-05f, bias), followed by one FP16 conversion.
  • Use a fixed, measured CTA warp count sufficient to expose transformer-scale N across B300's 148 SMs. Do not add persistent weight repacking, workspace, shape-specific kernel families, or serialization state.
  • Register a CUDA implementation of bitsandbytes::int8_scaled_mm and select the fused path only when all of these hold:
    • exact runtime compute capability 10.3;
    • effective flattened M <= 4;
    • contiguous row-major INT8 A and B;
    • K % 4 == 0;
    • FP32 row/column statistics;
    • output dtype is FP16 (or omitted, preserving the existing FP16 default);
    • bias is absent or FP16.
  • Every architecture, dtype, layout, shape, validation/error, and bias case outside that contract must execute the current int8_linear_matmul plus int8_mm_dequant composition unchanged.
  • Preserve bitsandbytes::int8_linear_matmul, including its exact INT32 output/API, unchanged.

The initial B300 feasibility matrix is terminal. If DP4A does not win coherently across the fixed primary matrix, record a no-go rather than introducing per-shape rescue rules, weight transforms, tensor-core kernels, or a new dispatch framework.

Expected scope

  • CMakeLists.txt: add one CUDA-only source.
  • New csrc/gemm_int8_simt.cu and csrc/gemm_int8_simt.cuh: fused kernel and launcher.
  • csrc/pythonInterface.cpp or a narrowly exported symbol from the new translation unit: one private launcher entry point.
  • bitsandbytes/backends/cuda/ops.py: ctypes setup, CUDA registration, exact guard, and existing-composite fallback.
  • tests/test_ops.py: parity, dispatch/fallback, stream, and edge coverage.
  • One focused benchmarking/int8/ B300 harness if the existing drivers cannot isolate the operation.

No public API, schema, FakeTensor contract, quantization format, persistent state, or correction-GEMM change is expected.

Correctness oracle

For the selected path, require bitwise FP16 equality with the existing CUDA composition int8_mm_dequant(int8_linear_matmul(...)):

  • M in {1,2,3,4} and flattened 2-D/3-D activation shapes.
  • Representative and boundary K/N, including non-multiple N tails; K divisible by 4 on the selected path.
  • Full-range/random/zero/extreme INT8 operands.
  • Zero, finite, small, and large FP32 row/column scales.
  • Bias absent and FP16 bias present.
  • Repeated launches and a non-default current stream.

Independently require the integer dot to equal torch.matmul(A.int(), B.int().t()) for tested transformer dimensions, remaining within the non-overflow INT32 domain, and compare final output to the existing repository reference tolerance as a secondary oracle.

Fallback controls must prove existing behavior for M=5/8/16, K not divisible by 4, noncontiguous inputs, non-FP16 output, unsupported scale/bias dtype, non-SM103 devices/mocks, invalid/empty dimensions, and all existing error contracts. Preserve torch.compile and opcheck behavior because the public schema and fake registration are unchanged.

Run focused LLM.int8 tests, relevant test_ops/test_functional/Linear8bitLt coverage, official-compatible and native SM103 builds as available, full pre-commit, and compute-sanitizer memcheck/racecheck/synccheck on full/tail/extreme cases. All CUDA compilation and GPU execution must run through Slurm.

B300 feasibility and performance gates

Use isolated baseline/candidate builds from the same upstream commit in one B300 allocation, with resident precreated inputs/outputs, CUDA-event timing, at least 20 warmups, and at least seven interleaved timed batches. Report all batch samples plus median and p10/p90.

Phase 0: terminal raw gate

Compare fused versus the current two-operation composition for:

  • M in {1,2,4};
  • (K,N) in {(3584,3584), (4096,4096), (4096,11008), (4096,14336), (8192,28672)} subject to allocation memory;
  • bias absent/present;
  • M in {8,16} as verified fallback controls.

Proceed only if correctness is bitwise, the M=1..4 primary matrix has at least 1.10x geometric-mean speedup, no primary cell is below 0.98x, and every fallback control remains baseline-identical. A win confined to one shape or a result where B300 IMMA/cuBLASLt remains faster is a no-go.

Public-path gate

  • Benchmark steady-state Linear8bitLt and a synthetic decoder stack with representative attention/MLP projections at threshold 0 and threshold 6 with realistic outlier columns.
  • Exclude quantization/state construction and allocation from timing.
  • Require at least 1.08x for the pure-INT8 stack and 1.03x for the default thresholded stack, with no memory growth or primary regression.
  • Run the existing generation benchmark only if a model artifact is already locally available; model download is not required.

Profile one M=1 and one M=4 cell with the available profiler to prove that one fused launch replaces cuBLASLt plus dequantization, no hidden synchronization occurs, and bandwidth/occupancy support the claimed mechanism.

Risks and non-goals

  • B300's tuned IMMA/cuBLASLt path may beat DP4A despite removal of the second launch and INT32 intermediate.
  • Threshold-6 outlier discovery/correction can dilute base-path gains.
  • Packed signed loads, N tails, and INT32 overflow boundaries require strict validation.
  • Open upstream PRs touching adjacent INT8 dispatch and BLASLt handling may require a normal rebase, but this experiment must not copy or stack those branches.
  • Do not add BF16 semantics, weight repacking, tensor-core/WGMMA implementations, descriptor caching, persistent workspace, correction-path fusion, shape-by-shape dispatch, or changes to int8_linear_matmul.

No current upstream issue or PR was found that implements a native CUDA W8A8 DP4A GEMV or fused small-M int8_scaled_mm path. Existing upstream performance reports motivate the user path but do not propose this kernel.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions