Skip to content

[cuda] Nestest Superscale Quantization for CUDA Weights (int4 / int6)#20571

Merged
Gasoonjia merged 12 commits into
mainfrom
cuda-int4-int6-metadata-opt
Jul 9, 2026
Merged

[cuda] Nestest Superscale Quantization for CUDA Weights (int4 / int6)#20571
Gasoonjia merged 12 commits into
mainfrom
cuda-int4-int6-metadata-opt

Conversation

@Gasoonjia

@Gasoonjia Gasoonjia commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Quant schema update

Weights are loaded from GGUF and kept at 4-bit (Q4_K) / 6-bit (Q6_K); we further-quantize the per-group metadata (the affine scale/zero) instead of storing it in bf16. Dequant stays affine: w = (q - zero) * scale. The grouping mirrors GGUF's Q4_K/Q6_K layout: group_size = 32 (int4) / 16 (int6), and a 256-weight super-block that shares one fp16 metadata step.

  • int4 (Q4_K): group_size=32, super-block=256 (8 groups). scale = per-group uint8 code x per-256 fp16 step; zero = per-group uint8 code x per-256 fp16 step. Both fp16 steps are packed into one 32-bit word and broadcast to the 8 lanes of a super-block with a single warp shuffle in the decode kernel. 4.625 bpw.
  • int6 (Q6_K): symmetric (no zero, exactly like Q6_K), group_size=16, super-block=256 (16 groups). scale = per-group int8 code x per-256 fp16 step; the per-group int8 codes are hoisted (loaded once per uint4) in the decode kernel. 6.5625 bpw.

Why this design

(a) Follow Q4_K / Q6_K. Weights come from GGUF, so we mirror llama.cpp's 256-weight super-block granularity (one fp16 step per 256 weights) and its symmetric-Q6_K / asymmetric-Q4_K split, keeping our representation close to the source format.

(b) int6 keeps its int8 scale code. int6 is symmetric, so scale is its only metadata; dropping it to a 6-bit code collapses accuracy (-9.5 dB) with no zero to compensate, so int6 keeps int8 scale + per-256 fp16 step. A dedicated int6 6-bit-planar-metadata path could revisit this in the future.

Ablation study

int4 / Q4_K whole-weight SNR vs the exact GGUF Q4_K dequant (same 8-tensor sample throughout; anchors reproduce exactly).

scale code scale step zero code zero step SNR (dB) bpw
bf16 (no code quant) - bf16 - 48.04 -
uint8 per-256 fp16 uint8 per-256 fp16 (latest / z_pack) 45.89 4.625
uint8 per-256 fp16 uint8 per-256 bf16 45.67 4.625
uint8 per-256 fp16 uint8 per-row fp16 45.17 4.5625
uint8 per-256 fp16 uint8 per-row bf16 45.15 4.5625
uint8 per-256 bf16 uint8 per-row bf16 45.10 4.5625
uint8 per-row bf16 uint8 per-row bf16 44.37 4.500
uint8 per-row fp16 uint8 per-row bf16 44.36 4.500
6-bit per-256 fp16 8-bit per-256 fp16 46.19* 4.5625
6-bit per-256 fp16 6-bit per-256 fp16 38.5 4.500
6-bit per-row 8-bit per-row 37.85 4.441

*6-bit-scale variant: higher raw SNR but rejected on perf (6-bit planar scale reconstruct = +5.2% decode; see below).

Analysis. The accuracy lever is step granularity, not step dtype or code width: moving the scale step from per-row to per-256 buys +0.78 dB (44.37 -> 45.15), and moving the zero step to per-256 as well buys another +0.74 dB (-> 45.89); step dtype (fp16 vs bf16) is worth <=0.05 dB for a uint8 code. We therefore keep both codes at uint8 (a 6-bit code loses 6-10 dB, and its planar reconstruct also costs decode perf) and spend the accuracy budget on per-256 fp16 steps for both scale and zero — which is exactly GGUF Q4_K's per-super-block-fp16-d granularity. This keeps the weight codes byte-aligned and identical to the source 4-bit/6-bit values, holds bpw in a tight band (4.625, +0.125 over the 4.5 baseline), and lands at 45.89 dB (+1.52 dB over the prior 44.37 baseline). The one higher-raw-SNR alternative (6-bit scale + 8-bit zero, 46.19 dB) was rejected because the 6-bit planar scale reconstruct regresses decode +5.2% for +0.3 dB — not worth it.

e2e results

gemma4-31b Q4_K_M @131072, greedy, cuda_graph, 3-rep median.

A100, turboquant OFF / bf16 KV

Comparing ET latest (this PR) vs ET prior (pre-metadata-opt baseline) vs llama.cpp.

Prefill (tok/s, higher better)

prompt len ET latest ET prior llama.cpp
512 1729.7 1701.0 1266.2
2048 2438.1 2494.5 1513.7
8192 2273.7 2275.6 1514.9
32768 1674.6 1710.0 1269.1
130048 851.6 855.9 768.8

Decode (tok/s, higher better)

prompt len ET latest ET prior llama.cpp
512 49.93 50.5 43.63
2048 47.93 48.4 42.73
8192 47.42 48.1 42.12
32768 46.79 47.0 39.74
130048 36.98 37.2 33.04

Peak VRAM (GB, lower better)

prompt len ET latest ET prior llama.cpp
512 31.56 31.38 19.38
2048 31.85 31.66 19.70
8192 31.88 31.69 20.17
32768 31.88 31.69 22.04
130048 31.88 31.69 29.46

A100 takeaways. vs ET prior: decode neutral (-0.45% to -1.41%, mean ~-0.9%), prefill neutral (+-2.3%), VRAM +~0.19 GB (the expected +0.06 bpw of the per-256 zero step) — i.e. +1.52 dB SNR at negligible runtime cost. vs llama.cpp: ET decode is +12-18% faster and ET prefill is faster at every length; llama.cpp uses ~12 GB less VRAM at short/mid lengths (packed Q4_K_M weights vs ET's bf16 runtime weights), a gap that narrows to ~2.4 GB at 130048 as the KV cache dominates.

RTX 5090, turboquant ON / tq4 KV cache

Comparing ET latest (New, this PR) vs ET prior (Orig, pre-metadata-opt baseline), decode d=512.

Decode (tok/s, higher better) & Peak VRAM (GiB, lower better)

prompt len Orig decode New decode Decode improvement Orig VRAM New VRAM VRAM improvement
512 57.16 60.78 +6.33% 25.06 23.29 -7.06%
2048 56.39 59.79 +6.03% 25.06 23.29 -7.06%
8192 55.60 58.88 +5.90% 25.06 23.29 -7.06%
32768 55.29 58.60 +5.99% 25.06 23.29 -7.06%

5090 takeaways. With turboquant tq4 KV, the metadata-opt is a clear win on both axes: decode +5.9-6.3% faster and peak VRAM -7.06% (-1.77 GiB) at every measured length. (The VRAM drop here is a turboquant-path effect, not the +0.06 bpw weight-metadata cost seen on the A100 bf16-KV config.)

…ow super-scale)

Decode for gemma4-31B on CUDA is weight-bandwidth-bound: the int4/int6
weight-only matvecs are ~72% of per-token decode time and already run at
~89% of the RTX 5090 HBM roofline using the same dp4a algorithm as
llama.cpp. The only lever left is fewer bytes/token.

ET previously stored per-group (group_size=32) scale AND zero as bf16:
  int4 = 5.0 bits/weight (20% metadata overhead), int6 = 7.0 bits/weight.
llama.cpp's Q4_K/Q6_K store far less metadata (4.5 / 6.5625 bpw).

This change re-encodes the quant metadata to match llama.cpp's byte
density WITHOUT touching the dp4a inner loop or the 4/6-bit weight codes:
  - per-group scale/zero: bf16 -> uint8 codes
  - per-row bf16 "super-scale" (step = row_max/255) restores dynamic range
    (plain fp8/int8 alone fails: Q4_K scales span ~1e-4..8e-2 -> subnormal
     blowup, 15-18 dB SNR; the two-level encoding keeps 46.7-48.1 dB == bf16)
  - int6 is symmetric (no zero), int8 scale codes + per-row step (mirrors Q6_K)

Result -> 4.77 bpw (llama.cpp parity). Touches the packer
(coalesced_int4_tensor / dp4a_planar_int6_tensor), the decode shims
(int4/int6_plain_mm .cu/.cuh/.h, +steps arg), the dispatch ops, the AOTI
ABI sig in cuda_backend, and the gemma4_31b concat source transform.

e2e (gemma4-31B, 128k+TurboQuant export, CUDA-graph decode, 3-rep median):
  ctx     baseline  ->  this
  512      57.16    ->  60.78   (+6.3%)
  2048     56.39    ->  59.79   (+6.0%)
  8192     55.60    ->  58.88   (+5.9%)
  32768    55.29    ->  58.60   (+6.0%)
VRAM peak 25.06 -> 23.29 GiB; .ptd 26.18 -> 24.28 GB. Accuracy: Paris
coherent, dequant SNR ~baseline. Win holds at long context under CUDA
graph (verified, not a microbenchmark estimate).
@pytorch-bot

pytorch-bot Bot commented Jun 28, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/20571

Note: Links to docs will display an error until the docs builds have been completed.

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 28, 2026
@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

@Gasoonjia Gasoonjia marked this pull request as ready for review June 30, 2026 22:26
The compact-metadata change (bf16 -> uint8/int8 codes + per-row bf16
super-scale) migrated the int4/int6 plain_mm kernels and Python dispatch
but left the tests on the old signatures, breaking the CUDA shim CMake
build and the dispatch unit tests.

- test_aoti_torch_cuda_int4_plain_mm.cpp: regenerate vectors as uint8
  scale/zero codes + [N,2] bf16 steps; update to the 7-arg signature.
- test_aoti_torch_cuda_int6_plain_mm.cpp: regenerate vectors as int8
  scale codes + [N,1] bf16 steps; update to the 6-arg signature.
- test_int4_dispatch.py / test_int6_dispatch.py: add the steps arg to
  the custom-op recorders, build tensors via the new constructors, and
  compare against the decoded (code * step) scale instead of raw codes.

Vectors are generated from the production pack path (CudaCoalescedInt4Tensor
/ pack_int6 + _encode_int8_per_row) with expected[] from the export-path
_dequant_matmul references.

Test Plan:
- Built the CUDA shim gtests and ran on GPU: int4 7/7, int6 3/3 pass.
- python -m pytest backends/cuda/tests/test_int4_dispatch.py
  backends/cuda/tests/test_int6_dispatch.py -> 33 passed.
- lintrunner init && lintrunner -a: no lint issues.
Gasoonjia added 3 commits July 1, 2026 21:18
Migrate the CUDA int4 (Q4_K) path from a per-row bf16 scale-step to a
per-256-super-block fp16 scale-step + uint8 scale code, mirroring GGUF Q4_K's
per-super-block scale granularity. Improves whole-weight dequant SNR to
45.15 dB (vs 44.37 dB), with NEW >= OLD generation quality.

The naive per-group load of the separate scale_step tensor cost ~8% decode
(latency-bound W4A8 matvec). Fixed with a llama.cpp-style super-block-cooperative
kernel: 8-lane warp subgroups each cover one super-block per iteration; only the
subgroup leader loads+converts the fp16 scale_step and __shfl-broadcasts it to
its 7 followers (8x fewer loads, fp16 convert off the dp4a path), register-only
(no smem, no occupancy cliff). dp4a inner product + zero decode unchanged.

Same-box decode vs the prior encoding: +1.7%..+2.9% (512/8K, bf16+tq4), VRAM
neutral. Op-level: packer SNR 45.15 dB, int4 gtest 5/5, dispatch 20/20, int6 3/3.

Also adds aoti_torch_dtype_float16 (ScalarType::Half) to the AOTI shims, required
by the fp16 scale_step tensor.

Files: kernel (int4_plain_mm.{cu,cuh,h}), packer (coalesced_int4_tensor.py),
dispatch (int4_dispatch.py), ABI (cuda_backend.py), fp16 shim
(common_shims{,_slim}.{cpp,h}), tests (int4 gtest + test_int4_dispatch.py),
gate/up fusion consumer (cuda_source_transformations.py).
…code

Migrate the CUDA int6 (Q6_K) path from a per-row bf16 scale-step to a
per-256-super-block fp16 scale-step + int8 scale code, mirroring GGUF Q6_K's
per-super-block scale granularity. Improves whole-weight dequant SNR to
~49.2 dB (vs ~45.7-46.6 dB for the per-row step), measured against the exact
GGUF Q6_K decode reference (extension/llm/export/gguf.py) across 3 shapes.
NEW >= OLD everywhere (+3.3 dB). Weight layout (planar ql/qh), int8 code width,
and Q6_K symmetry (no zero) are UNCHANGED.

Kernel: keep decode perf-neutral by amortizing metadata per uint4 rather than
the T3 warp-shuffle used for int4. The int4 shuffle only pays off because
int4's OLD baseline had a distant per-group step load to amortize; int6's OLD
step was already a per-row loop-invariant, so a shuffle recovers nothing (an
int6 T3 variant measured ~2-7% SLOWER, tied by a plain per-super-block __ldg).
The actual lever is the SAME code-load hoist int4's commit landed: load the
per-group int8 codes once per group per uint4 (gs=16 => 2 codes/uint4, held in
registers across the 4 dp4a words) instead of 4x/uint4 per word, plus a single
per-uint4 __ldg of the fp16 step (8 lanes share each tiny L1-resident address).
SASS confirms LDG.E.S8 drops 4 -> 2 per uint4. dp4a inner product + planar
weight decode unchanged; register-only (no smem, no occupancy cliff).

Op-level decode vs the prior per-row encoding (A100, M=1, same-box, 3 reps):
avg -3.0% (faster in 4/5 of {4096,8192,11008,14336}x{4096,8192,11008}, worst
+1.0%) -- neutral-to-better. The hoist's extra ILP raises the matvec from
REG:40 to REG:48 (theoretical occupancy 75% -> 62% on sm_80) with STACK:0 (zero
spills); measured wall-clock is faster regardless (ILP > occupancy on this
latency-bound matvec), and __launch_bounds__(256,6) to reclaim REG:40 caused
spills, so REG:48 is kept intentionally.

Gates: packer round-trip SNR +3.3 dB vs exact GGUF Q6_K; int6 gtest 4/4
(single/multi super-block step indexing + wide-N warp reduction + null args);
python int6 dispatch 20/20-style green (13 tests). ABI: steps arg is now the
per-256 fp16 tensor [N, K/256] (was per-row bf16 [N, 1]); reuses the float16
AOTI shim added for int4.

Files: kernel (int6_plain_mm.{cuh,h}), packer (dp4a_planar_int6_tensor.py),
dispatch (int6_dispatch.py), tests (int6 gtest + test_int6_dispatch.py).
…+0.74dB SNR (45.89 vs 45.15), op-level decode neutral (-0.44%), +0.06 bpw (4.625)

Promote the int4 zero step from per-row bf16 to per-256-super-block fp16 (the
SNR-max zero256 encoding, B2), decoded via z_pack: the 8-lane subgroup leader
packs BOTH fp16 steps (scale low16, zero high16) into the ONE existing 32-bit
warp-shuffle word and broadcasts it once — no extra shuffle vs the committed
scale-only baseline (the naive second __shfl / z_2shuf regressed +7.4%).

- packer (coalesced_int4_tensor.py): zero_step now [N,K/256] fp16 via
  _encode_uint8_per_super (same per-256 grid as scale); dequantize + from_int4
  updated. bpw 4.5625 -> 4.625.
- decode (int4_plain_mm.cuh): per-256 fp16 zero_step packed with scale_step into
  one shuffle word (z_pack); dp4a inner loop unchanged. cuobjdump: REG:32
  STACK:0 SHARED:0 == committed (no occupancy change).
- ABI (int4_plain_mm.cu/.h): zero_step dtype Half, shape [N,K/256]. C-shim
  arg count unchanged (AtenTensorHandle), so cuda_backend.py needs no change.
- dispatch (int4_dispatch.py): prefill _dequant_matmul zero uses per-256 step.
- tests: int4 gtest regenerated for fp16 [N,K/256] zero_step + new
  PackedShuffleMultiSuper case (K=1024/n_super=4, packed-shuffle-word
  correctness); test_int4_dispatch.py per-256 zero decode.

Gates (same A100 box): whole-weight SNR 45.89 dB (calibration: committed 45.15);
int4 gtest 6/6 + python dispatch 20/20 + int6 gtest 4/4 (untouched) green;
op-level decode z_pack -0.44% time-weighted (3 solo reps, within ~1-2% bar).
int6 path untouched. e2e export intentionally deferred (running separately).
@Gasoonjia Gasoonjia changed the title [cuda] Compact int4/int6 weight quant metadata (bf16 -> uint8 + per-row super-scale) [cuda] Nestest Superscale Quantization for CUDA Weights (int4 / int6) Jul 6, 2026
@digantdesai

Copy link
Copy Markdown
Contributor

@claude review this PR

@claude

claude Bot commented Jul 8, 2026

Copy link
Copy Markdown

Claude finished @digantdesai's task in 7m 15s —— View job


PR Review: Superscale Quantization for CUDA Weights (int4 / int6)

Reviewed the full diff against origin/main. This is a clean, well-documented change: the quant metadata (scale/zero) moves from per-row bf16 to per-group uint8/int8 codes + a per-256-super-block fp16 step, mirroring GGUF Q4_K/Q6_K. The op signatures, meta kernels, C shims, .cu/.cuh, dispatch, and tests are all updated consistently, and the ablation study justifies the design well. Overall it looks correct and mergeable. A few notes below.

Correctness

  1. _encode_uint8_per_super assumes zero_point >= 0 (safe today, but undocumented invariant). backends/cuda/coalesced_int4_tensor.py:147-150 uses block_max = xb.amax(...) (max, not absmax) and clamp_(0, 255), so any negative value silently clamps to 0. I confirmed torchao's Int4Tensor always produces non-negative zero_point (unsigned [0,15], and the symmetric path pins it to 8.0), so this is correct for all real inputs. But the old code stored t.zero_point.t() as bf16 and would have preserved a negative sign if one ever appeared. Consider a one-line assert or docstring note in _encode_uint8_per_super that the input must be non-negative, so a future non-torchao packer fails loudly instead of silently degrading. The scale encoder has the same max-based assumption, which is fine since scales are positive.

  2. int6 kernel: unconditional scale_row[g_base + 1] read is OOB for gs=32. backends/cuda/runtime/shims/int6_plain_mm.cuh:251 always loads scale_row[g_base + 1]. For the shipped gs=16 this is safe (g_base is always even and g_base+1 < n_groups). But the comment at lines 246-248 explicitly claims gs in {16, 32} is supported — and for gs=32 a uint4 spans exactly one group, so g_base runs up to n_groups-1 and scale_row[g_base+1] reads one past the row (the ws1 value is then unused, since wpg_shift = gs_shift-3 = 2 makes every w>>2 == 0). This is a latent OOB read, not currently exercised because only gs=16 is used/tested. Either guard the ws1 load (g_base+1 < n_groups) or tighten the comment to state only gs=16 is supported. Fix this →

  3. Stale docstring in _concat_coalesced_int4_along_n. examples/models/gemma4_31b/cuda_source_transformations.py:700 describes zero_step as [N, 1] bf16, but the actual encoding is [N, K/256] fp16 (as the rest of the PR documents). Minor, but worth fixing since the surrounding docstring is otherwise precise about shapes/dtypes. Fix this →

Things done well

  • int4 warp-shuffle deadlock avoidance (int4_plain_mm.cuh:189-197): the warp-aligned trip count so all 32 lanes reach __shfl_sync(0xffffffff, ...) even when K_half_16 < 32 is the correct fix for full-mask shuffle on tiny shapes, and the "safe dummy load (index 0), contribute 0" for inactive lanes is right. Good that the test suite includes small shapes to exercise this.
  • z_pack (packing both fp16 steps into one 32-bit word, one leader shuffle per 8-lane subgroup) keeps the dp4a dot products bit-identical to the scale-only baseline while adding zero-step decode at no extra shuffle — nicely reasoned and matches the "+0.74 dB at op-level-neutral" claim.
  • Kernel-side validation (n_super = K/256, zero_step.size(1) == scale_step.size(1), K % 256 == 0) is thorough, and the error messages are detailed.
  • Encode/decode agreement: rounding uses the fp16-rounded step (step_f) so pack-time and kernel-time reconstruction match — this is the subtle correctness detail that's easy to get wrong, and it's handled.

Test coverage

Solid — test_int4_dispatch.py / test_int6_dispatch.py and the two .cpp kernel tests were substantially expanded (single/multi super-block, wide-N, small shapes). One gap: no test exercises gs=32 for int6, which is why note #2 slipped through. If gs=32 is genuinely intended to be supported, add a case; otherwise the comment should say gs=16 only.

CI

The listed failures (test_int4_weights_preserved, the export-model-cuda-artifact jobs) are worth confirming are unrelated infra/merge-base noise vs. this metadata change — the TestInt4Inference::test_int4_weights_preserved failure in particular is on a path this PR touches. Recommend confirming it's green after the merge with main.

None of these are blocking; #1 and #2 are the ones I'd address (or explicitly wontfix) before merge.
· branch cuda-int4-int6-metadata-opt

Comment thread examples/models/gemma4_31b/cuda_source_transformations.py

_lib.define(
"int4_plain_mm(Tensor self, Tensor qdata, Tensor scale, Tensor zero, int group_size) -> Tensor"
"int4_plain_mm(Tensor self, Tensor qdata, Tensor scale, Tensor scale_step, Tensor zero, Tensor zero_step, int group_size) -> Tensor"

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.

what about non k-quant users?

Comment thread backends/cuda/quantize_op_dispatch/int4_dispatch.py
TEST_F(AOTITorchInt4PlainMMTest, SingleSuperBlock) {
int64_t M = 1, K = 256, N = 8, gs = 32;
int64_t ng = K / gs; // 8
int64_t n_super = K / 256; // 1
// clang-format off
uint8_t qdata_host[] = {

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.

check in the script to recreate these

Comment thread backends/cuda/coalesced_int4_tensor.py Outdated
"scale",
"scale_step",
"zero_point",
"zero_step",

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.

s/zero_step/zero_point_step - Nit

Comment thread backends/cuda/coalesced_int4_tensor.py Outdated
Comment on lines +137 to +139
scale_step: torch.Tensor,
zero_point: torch.Tensor,
zero_step: torch.Tensor,

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.

Is this Tensor only for k-quants?

@digantdesai digantdesai 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.

Looks good except the fact that I am not sure if we want to use the plain kernel for other 4/6b weights or just k-quant with quantized/nested scales.

@Gasoonjia

Copy link
Copy Markdown
Contributor Author

Thanks @digantdesai for review.

Tensor now is k-quant only but the pipeline doesn't. We convert the original hdd int4 and int6 tensor into q4_k and q6_k and reuse k-quant pipeline. The precision are the same and only have ~0.7db weight during the conversion.

@digantdesai

Copy link
Copy Markdown
Contributor

Thanks @digantdesai for review.

Tensor now is k-quant only but the pipeline doesn't. We convert the original hdd int4 and int6 tensor into q4_k and q6_k and reuse k-quant pipeline. The precision are the same and only have ~0.7db weight during the conversion.

What about performance? Running Q4_0 as nested k-quant kernel must cost some perf, no?

@digantdesai digantdesai 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.

stamping it to unblock you

@Gasoonjia

Copy link
Copy Markdown
Contributor Author

Thanks @digantdesai for review.
Tensor now is k-quant only but the pipeline doesn't. We convert the original hdd int4 and int6 tensor into q4_k and q6_k and reuse k-quant pipeline. The precision are the same and only have ~0.7db weight during the conversion.

What about performance? Running Q4_0 as nested k-quant kernel must cost some perf, no?

for kernel-base test perf is even better cuz now bpw is lower.

@Gasoonjia Gasoonjia merged commit 7b1122c into main Jul 9, 2026
280 of 286 checks passed
@Gasoonjia Gasoonjia deleted the cuda-int4-int6-metadata-opt branch July 9, 2026 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ciflow/cuda CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants