You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The CUDA LLM.int8 threshold path currently discovers outlier columns with an eager PyTorch pipeline before launching the native vector quantizer:
materialize A.abs() for the full activation tensor;
materialize the full >= threshold boolean mask;
reduce it with outliers.any() and convert that result to a Python boolean;
for nonempty inputs, reduce the mask again with outliers.any(dim=0) and call argwhere.
The native vector quantizer then rereads A to compute row statistics and quantized output. For batched/prefill inputs, outlier discovery therefore adds several full-tensor passes, two O(rows * K) temporary tensors, multiple launches, and a host-visible synchronization before quantization.
This issue proposes a measurement-gated experiment: replace only that eager discovery pipeline with a private tiled native detector that writes an O(K) int32 column mask directly from FP16 A, then retain the existing argwhere step and public return contract.
Why B300
The path runs for every positive-threshold Linear8bitLt layer. It matters for batch-1 decode, where launch and synchronization overheads are exposed, and for batched/prefill shapes, where the eager pipeline scans and materializes the full activation mask. The repository already benchmarks threshold-enabled M=1 LLM.int8, and upstream issue bitsandbytes-foundation#1867 reports substantial user-visible cost in the mixed LLM.int8 path.
B300/SM103 will be used to determine whether eliminating these full-tensor intermediates and reductions produces a meaningful public-path benefit. No speedup is assumed in advance.
Bounded implementation
Add one private FP16 CUDA detector in csrc/kernels.cu / csrc/kernels.cuh.
Use a 2-D launch where 256 threads own contiguous columns and each CTA scans a bounded row tile, initially 32 rows.
Each thread sets its column's int32 flag to 1 with an idempotent atomic only when an outlier is observed in its row tile.
Zero the O(K) mask once, invoke the detector, and use the existing argwhere behavior to produce sorted CUDA int64 column indices.
Add the smallest private launcher/C entry point needed by the CUDA backend; do not add a public torch.library schema.
If the initial row tile misses the performance gate, at most compare fixed row tiles 16, 32, and 64. Do not add architecture tables or shape-specific production dispatch.
Keep these paths unchanged:
threshold == 0 behavior;
the existing kInt8VectorQuant kernel and quantization math;
row statistics, output-column zeroing, mixed matmul, and public return metadata;
default/non-CUDA backend behavior;
op schemas and autograd;
all prior fork branches and unmerged work.
Current upstream accepts FP16 in this CUDA path. Do not copy or stack the unmerged BF16 changes from upstream PR bitsandbytes-foundation#1985. If that PR lands before this work is ready, rebase and reassess compatibility explicitly rather than silently broadening the experiment.
Correctness gates
Compare isolated baseline and candidate builds from the same upstream commit. Require exact equality for quantized rows, row statistics, and ordered outlier-column indices, including empty tensor dtype/device/rank.
Cover:
rows around the row tile and realistic prefill sizes: 1, 2, 15, 16, 17, 31, 32, 33, 63, 64, 65, 128, and 2048;
K around the column tile plus 3584, 4096, 8192, and 14336;
thresholds 0.1, 2.0, 3.0, and 6.0;
no, one, repeated-column, sparse, and dense outliers;
positive/negative values exactly at threshold and immediately below it;
first/last rows and columns, NaN, and Inf according to current PyTorch comparison semantics.
threshold == 0 must still return outlier_cols is None. Scalar threshold conversion must match the current A.abs() >= threshold behavior exactly. Run the focused vectorwise/COO tests, threshold-enabled Linear8bitLt tests, and applicable fullgraph/dynamic-output compile tests. Build the supported CUDA targets and preserve HIP/ROCm source compatibility; B300 provides the performance decision, not permission to break other builds.
B300 measurement plan
Use one B300 allocation and isolated baseline/candidate builds with identical software, target inputs, and inputs. Record the commit, loaded native-library paths, GPU/driver/toolkit/PyTorch identity, commands, raw samples, medians, and dispersion.
Benchmark the public CUDA quantizer with host wall time because dynamic output discovery is synchronization-sensitive. Interleave baseline and candidate over FP16 rows {1, 8, 64, 512, 2048}, K {3584, 4096, 8192, 14336}, and no/one/five/1%-dense outlier patterns. Include dense controls. Use at least 200 warmups and 30 interleaved samples with enough calls for stable samples.
Profile representative decode (1, 3584) and prefill (2048, 8192) cells to verify that the candidate actually removes the O(rows * K) abs/bool intermediates and Python-bool reduction, and to measure launches, synchronization, temporary allocation, bandwidth, and atomic behavior.
For required public-path evidence, benchmark threshold-enabled Linear8bitLt and a reproducible resident synthetic stack of such layers for both decode and prefill/batched shapes. Preallocate weights, activations, and states; exclude setup/weight quantization; record the exact stack definition. The repository's model-based int8-decomp benchmark is optional corroboration only when its artifacts are already available; this experiment must not depend on downloads.
Proceed to a draft PR only if all correctness/build gates pass and:
profiler evidence confirms the intended intermediate/synchronization removal;
isolated threshold quantization improves by at least 10% in at least four representative decode/prefill cells, with no primary cell regressing more than 3%;
threshold-enabled Linear8bitLt improves by at least 5% in at least one decode and one batched/prefill shape, with no primary cell regressing more than 2%;
the reproducible synthetic public-path stack improves by at least 3% in at least two workload regimes, with no regime regressing more than 2%.
Otherwise record a no-go rather than adding fusion, public API changes, architecture-specific dispatch, or a broader INT8 redesign.
Risks
argwhere remains dynamic and may still synchronize; this experiment removes preceding redundant work rather than claiming an asynchronous path.
No-outlier and small-row inputs may lose to mask initialization plus the detector, so they are veto cells.
Small row tiles can increase CTA/atomic overhead; large tiles can underutilize the GPU. Tuning is deliberately bounded.
Dense or repeated outliers may contend on the idempotent column flags and must be measured.
If the gates pass, the result is a contained native-system optimization with no public API or quantization-format change and direct relevance to a documented LLM.int8 user bottleneck.
Summary
The CUDA LLM.int8 threshold path currently discovers outlier columns with an eager PyTorch pipeline before launching the native vector quantizer:
A.abs()for the full activation tensor;>= thresholdboolean mask;outliers.any()and convert that result to a Python boolean;outliers.any(dim=0)and callargwhere.The native vector quantizer then rereads
Ato compute row statistics and quantized output. For batched/prefill inputs, outlier discovery therefore adds several full-tensor passes, two O(rows * K) temporary tensors, multiple launches, and a host-visible synchronization before quantization.This issue proposes a measurement-gated experiment: replace only that eager discovery pipeline with a private tiled native detector that writes an O(K)
int32column mask directly from FP16A, then retain the existingargwherestep and public return contract.Why B300
The path runs for every positive-threshold
Linear8bitLtlayer. It matters for batch-1 decode, where launch and synchronization overheads are exposed, and for batched/prefill shapes, where the eager pipeline scans and materializes the full activation mask. The repository already benchmarks threshold-enabled M=1 LLM.int8, and upstream issue bitsandbytes-foundation#1867 reports substantial user-visible cost in the mixed LLM.int8 path.B300/SM103 will be used to determine whether eliminating these full-tensor intermediates and reductions produces a meaningful public-path benefit. No speedup is assumed in advance.
Bounded implementation
csrc/kernels.cu/csrc/kernels.cuh.int32flag to 1 with an idempotent atomic only when an outlier is observed in its row tile.argwherebehavior to produce sorted CUDAint64column indices.torch.libraryschema.Keep these paths unchanged:
threshold == 0behavior;kInt8VectorQuantkernel and quantization math;Current upstream accepts FP16 in this CUDA path. Do not copy or stack the unmerged BF16 changes from upstream PR bitsandbytes-foundation#1985. If that PR lands before this work is ready, rebase and reassess compatibility explicitly rather than silently broadening the experiment.
Correctness gates
Compare isolated baseline and candidate builds from the same upstream commit. Require exact equality for quantized rows, row statistics, and ordered outlier-column indices, including empty tensor dtype/device/rank.
Cover:
threshold == 0must still returnoutlier_cols is None. Scalar threshold conversion must match the currentA.abs() >= thresholdbehavior exactly. Run the focused vectorwise/COO tests, threshold-enabledLinear8bitLttests, and applicable fullgraph/dynamic-output compile tests. Build the supported CUDA targets and preserve HIP/ROCm source compatibility; B300 provides the performance decision, not permission to break other builds.B300 measurement plan
Use one B300 allocation and isolated baseline/candidate builds with identical software, target inputs, and inputs. Record the commit, loaded native-library paths, GPU/driver/toolkit/PyTorch identity, commands, raw samples, medians, and dispersion.
Benchmark the public CUDA quantizer with host wall time because dynamic output discovery is synchronization-sensitive. Interleave baseline and candidate over FP16 rows
{1, 8, 64, 512, 2048}, K{3584, 4096, 8192, 14336}, and no/one/five/1%-dense outlier patterns. Include dense controls. Use at least 200 warmups and 30 interleaved samples with enough calls for stable samples.Profile representative decode
(1, 3584)and prefill(2048, 8192)cells to verify that the candidate actually removes the O(rows * K) abs/bool intermediates and Python-bool reduction, and to measure launches, synchronization, temporary allocation, bandwidth, and atomic behavior.For required public-path evidence, benchmark threshold-enabled
Linear8bitLtand a reproducible resident synthetic stack of such layers for both decode and prefill/batched shapes. Preallocate weights, activations, and states; exclude setup/weight quantization; record the exact stack definition. The repository's model-basedint8-decompbenchmark is optional corroboration only when its artifacts are already available; this experiment must not depend on downloads.Proceed to a draft PR only if all correctness/build gates pass and:
Linear8bitLtimproves by at least 5% in at least one decode and one batched/prefill shape, with no primary cell regressing more than 2%;Otherwise record a no-go rather than adding fusion, public API changes, architecture-specific dispatch, or a broader INT8 redesign.
Risks
argwhereremains dynamic and may still synchronize; this experiment removes preceding redundant work rather than claiming an asynchronous path.If the gates pass, the result is a contained native-system optimization with no public API or quantization-format change and direct relevance to a documented LLM.int8 user bottleneck.