Skip to content

FourOverSix - #776

Open
Roderick-Wu wants to merge 2 commits into
mainfrom
FourOverSix
Open

FourOverSix#776
Roderick-Wu wants to merge 2 commits into
mainfrom
FourOverSix

Conversation

@Roderick-Wu

Copy link
Copy Markdown
Collaborator

Merging over from https://github.com/krishnateja95/compressed-tensors/tree/FourOverSix
Tested with this branch for llm-compressor:
https://github.com/vllm-project/llm-compressor/tree/Roderick-Wu/tested-FourOverSix

Evaluation results with Llama-3-8B, Qwen3-30B-A3B, Llama-3-70B

Tested on A100s:
FP4 activation quantization is not natively supported and falls back silently to the Marlin W4A16 kernel. W4A4 and W4A16 produce identical outputs on this hardware (slightly different results here bc sampling was not greedy). Real distinction observable on SM100+.

Model MMLU GSM8K HellaSwag WinoGrande
Llama-3-8B-base 0.614 0.808 0.708 0.588
Llama-3-8B-W4A16 0.618 0.796 0.684 0.592
Llama-3-8B-W4A4 0.614 0.794 0.684 0.594
Qwen3-30B-A3B-base 0.842 0.944 0.768 0.840
Qwen3-30B-A3B-W4A16 0.854 0.942 0.760 0.808
Qwen3-30B-A3B-W4A4 0.836 0.932 0.750 0.816
Llama-3-70B-base 0.780 0.936 0.798 0.772
Llama-3-70B-W4A16 0.764 0.932 0.792 0.772
Llama-3-70B-W4A4 0.764 0.934 0.792 0.772

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b2d6e42b-0ffc-4459-8d8c-4a6bd23006d6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Adds a four_over_six boolean flag to QuantizationArgs, enabling an adaptive scale-selection mode for NVFP4 4-bit float quantization. Implements MSE-based comparison between standard and 1.5x-scaled quantization in both the NVFP4 compressor and forward quantize-dequantize path, updates generate_gparam, and registers two new preset schemes.

Changes

Four Over Six adaptive scaling

Layer / File(s) Summary
Configuration field and preset schemes
src/compressed_tensors/quantization/quant_args.py, src/compressed_tensors/quantization/quant_scheme.py
Adds four_over_six boolean field to QuantizationArgs and defines NVFP4A16_46/NVFP4_46 presets registered in PRESET_SCHEMES.
Global scale generation
src/compressed_tensors/quantization/utils/helpers.py
generate_gparam accepts four_over_six, using a fixed scale_max of 256.0 instead of scale_data.max when enabled.
Compressor-time scale adjustment
src/compressed_tensors/compressors/nvfp4/base.py
Adds _adjust_scale_for_four_over_six, comparing per-group MSE between the original scale and a 1.5x alternative, wired into compress(...) before quantization and packing.
Forward quantize-dequantize adaptive path
src/compressed_tensors/quantization/lifecycle/forward_helpers.py
Adds _four_over_six_quantize_dequantize, dispatched from _quantize_dequantize when conditions match, selecting between standard-scale and 1.5x-scale dequantized results via per-group MSE.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Compressor as NVFP4PackedCompressor
  participant ScaleAdjust as _adjust_scale_for_four_over_six
  participant Forward as _quantize_dequantize
  participant FourOverSix as _four_over_six_quantize_dequantize

  Compressor->>Compressor: compress(weight, scale, global_scale)
  alt weights.four_over_six enabled
    Compressor->>ScaleAdjust: compute MSE(scale) vs MSE(scale*1.5)
    ScaleAdjust-->>Compressor: adjusted scale
  end
  Compressor->>Compressor: quantize(weight, adjusted scale) and pack weight_packed

  Forward->>Forward: _quantize_dequantize(x, scale, args)
  alt args.four_over_six and num_bits==4 and type==FLOAT
    Forward->>FourOverSix: dispatch(x, scale, zero_point, global_scale)
    FourOverSix->>FourOverSix: quantize-dequantize with scale
    FourOverSix->>FourOverSix: quantize-dequantize with scale*1.5
    FourOverSix->>FourOverSix: compute per-group MSE, select via torch.where
    FourOverSix-->>Forward: chosen dequantized tensor
  end
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is only the branch name and does not describe the actual change. Use a concise descriptive title like 'Add FourOverSix adaptive scaling for NVFP4 quantization'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description is related to the change set and summarizes the merged FourOverSix work and evaluation results.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch FourOverSix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Signed-off-by: Roderick Wu <Roderick-Wu@a100-06.nemg-001.lab.rdu2.dc.redhat.com>
@Roderick-Wu
Roderick-Wu marked this pull request as ready for review July 9, 2026 16:55

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/compressed_tensors/quantization/lifecycle/forward_helpers.py`:
- Around line 264-270: The MSE comparison in the helper currently reduces over
an empty `group_dims`, so `_process_group()` ends up making one global choice
instead of one per group. Update the logic in `forward_helpers.py` to use the
actual group dimension after `unflatten()` when computing `mse_a` and `mse_b`,
and keep `torch.where` selecting between `dq_a` and `dq_b` on a per-group basis
rather than over the full tensor.

In `@src/compressed_tensors/quantization/quant_args.py`:
- Around line 227-237: The four_over_six quantization flag is being excluded
from serialization, so it is lost in saved configs and not restored on reload.
Update the four_over_six field in QuantizationArgs to be included in
model_dump()/config serialization, and ensure the serialized quantization config
preserves this NVFP4_46 setting so input_activations reloads correctly in
forward_quantize().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ff18ea5c-da2d-44e4-8cbc-c92c45a818b5

📥 Commits

Reviewing files that changed from the base of the PR and between a4ee36a and a0a9dcd.

📒 Files selected for processing (5)
  • src/compressed_tensors/compressors/nvfp4/base.py
  • src/compressed_tensors/quantization/lifecycle/forward_helpers.py
  • src/compressed_tensors/quantization/quant_args.py
  • src/compressed_tensors/quantization/quant_scheme.py
  • src/compressed_tensors/quantization/utils/helpers.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

Comment on lines +264 to +270
# --- Per-group MSE comparison ---
group_dims = tuple(range(scale.ndim, x.ndim))
mse_a = ((x - dq_a) ** 2).mean(dim=group_dims, keepdim=True)
mse_b = ((x - dq_b) ** 2).mean(dim=group_dims, keepdim=True)

use_b = mse_b < mse_a
return torch.where(use_b, dq_b, dq_a)

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect how _quantize_dequantize is invoked for group strategy and the ranks of x vs scale
rg -nP '_quantize_dequantize' src/compressed_tensors/quantization/lifecycle -C3
rg -nP 'def _process_quantization|reshape|unflatten|unsqueeze|repeat_interleave|expand' \
  src/compressed_tensors/quantization/lifecycle/forward.py -C2

Repository: vllm-project/compressed-tensors

Length of output: 3541


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant helper file first, then inspect the precise implementation slices.
ast-grep outline src/compressed_tensors/quantization/lifecycle/forward_helpers.py --view expanded

echo '--- forward_helpers.py lines 1-340 ---'
sed -n '1,340p' src/compressed_tensors/quantization/lifecycle/forward_helpers.py

echo '--- search for four_over_six and group-related helpers ---'
rg -n "four_over_six|group_size|group_dims|scale.ndim|reshape|expand|unsqueeze|repeat_interleave" \
  src/compressed_tensors/quantization/lifecycle/forward_helpers.py

Repository: vllm-project/compressed-tensors

Length of output: 11525


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the call chain into _process_group / _apply_quantize_op and inspect shape assumptions.
rg -n "_process_group\(|_apply_quantize_op\(" src/compressed_tensors -C3

echo '--- forward.py slices around the group dispatch ---'
sed -n '1,260p' src/compressed_tensors/quantization/lifecycle/forward.py

echo '--- any tests covering four_over_six group behavior ---'
rg -n "four_over_six|_four_over_six|group_size|group quant" tests src/compressed_tensors -C2

Repository: vllm-project/compressed-tensors

Length of output: 50387


🏁 Script executed (no clone):

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import torch

x = torch.randn(2, 3, 4)
scale = torch.randn(2, 3, 1)

group_dims = tuple(range(scale.ndim, x.ndim))
print("x.ndim =", x.ndim)
print("scale.ndim =", scale.ndim)
print("group_dims =", group_dims)

m = (x ** 2).mean(dim=group_dims, keepdim=True)
print("mean shape =", tuple(m.shape))
print("mean equals input:", torch.allclose(m, x ** 2))
print("mse selection shape =", tuple(torch.where(m < (m + 1), x, x).shape))
PY

Length of output: 247


Reduce the MSE over the group axis here
_process_group() passes x and scale.unsqueeze(-1) at the same rank, so group_dims is empty in this helper. mean(dim=()) collapses each candidate to a single scalar, and torch.where then applies one branch to the entire tensor instead of one decision per group. Use the actual group dimension after unflatten().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/compressed_tensors/quantization/lifecycle/forward_helpers.py` around
lines 264 - 270, The MSE comparison in the helper currently reduces over an
empty `group_dims`, so `_process_group()` ends up making one global choice
instead of one per group. Update the logic in `forward_helpers.py` to use the
actual group dimension after `unflatten()` when computing `mse_a` and `mse_b`,
and keep `torch.where` selecting between `dq_a` and `dq_b` on a per-group basis
rather than over the full tensor.

Comment on lines +227 to +237
four_over_six: bool = Field(
default=False,
exclude=True,
description=(
"Enable Four Over Six (4/6) adaptive block scaling for NVFP4 "
"quantization. For each group of values, tries scaling to both 4 "
"and 6, selecting the scale with lower MSE. Reduces quantization "
"error for near-maximal values in FP4. Only applies to FP4 "
"quantization (num_bits=4, type=float)."
),
)

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether four_over_six is expected to survive serialization and be consumed at runtime for activations
rg -nP 'exclude=True' src/compressed_tensors/quantization/quant_args.py -C1
rg -nP 'four_over_six' -C2 src/ 
# How dynamic/LOCAL activations flow into the forward quantize-dequantize path
rg -nP '_quantize_dequantize|forward_quantize|maybe_calibrate_or_quantize' src/compressed_tensors/quantization/lifecycle -C2

Repository: vllm-project/compressed-tensors

Length of output: 11776


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect how QuantizationArgs are serialized and reloaded
ast-grep outline src/compressed_tensors/quantization/quant_args.py --view expanded || true
rg -n "model_dump|dict\(|json|serialize|deserialize|exclude=.*four_over_six|exclude=True" src/compressed_tensors -C 2

# Inspect the quantization config / scheme load-save path
rg -n "QuantizationArgs|QuantizationScheme|quant_scheme|from_dict|to_dict|from_json|save.*quant|load.*quant|model_validate" src/compressed_tensors -C 2

# Inspect how forward quantization consumes the flag for activations
sed -n '1,260p' src/compressed_tensors/quantization/lifecycle/forward_helpers.py

Repository: vllm-project/compressed-tensors

Length of output: 50390


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the relevant serialization and runtime paths with line numbers
sed -n '180,260p' src/compressed_tensors/quantization/quant_args.py
sed -n '150,240p' src/compressed_tensors/quantization/quant_config.py
sed -n '1,170p' src/compressed_tensors/quantization/lifecycle/forward_helpers.py
sed -n '240,330p' src/compressed_tensors/quantization/lifecycle/forward.py
sed -n '190,240p' src/compressed_tensors/compressors/model_compressors/model_compressor.py

Repository: vllm-project/compressed-tensors

Length of output: 17632


Persist four_over_six in serialized quantization configs (src/compressed_tensors/quantization/quant_args.py:227-237)

exclude=True drops this flag from model_dump(), so saved config.json loses the NVFP4_46 activation setting. Reloaded input_activations then fall back to standard FP4 scaling in forward_quantize().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/compressed_tensors/quantization/quant_args.py` around lines 227 - 237,
The four_over_six quantization flag is being excluded from serialization, so it
is lost in saved configs and not restored on reload. Update the four_over_six
field in QuantizationArgs to be included in model_dump()/config serialization,
and ensure the serialized quantization config preserves this NVFP4_46 setting so
input_activations reloads correctly in forward_quantize().

@Roderick-Wu

Copy link
Copy Markdown
Collaborator Author

Model: Meta-Llama-3-8B-Instruct
Benchmark: Wikitext-2 perplexity (sliding window, max_length=2048, stride=512)
Quantization: W4A16 NVFP4 (4-bit float weights, FP8 E4M3 block scales, group_size=16)

Model Perplexity
Unquantized (BF16) 7.3725
GPTQ + FourOverSix 7.5622
GPTQ + Standard 7.6033
RTN + FourOverSix 7.5774
RTN + Standard 7.7060

Sanity check to make sure it is actually choosing different scales, computed by re-running the FourOverSix observer on the original unquantized weights.

  • Total blocks: 436,207,616
  • M=4 selected: 188,138,994 (43.1%)
  • M=6 selected: 248,068,622 (56.9%)

@mergify

mergify Bot commented Aug 17, 2026

Copy link
Copy Markdown

Merge Protections

🔴 1 of 1 protections blocking · waiting on 👀 reviews

Protection Waiting on
🔴 Require one maintainer review 👀 reviews

🔴 Require one maintainer review

Waiting for any of

  • approved-reviews-by=HDCharles
  • approved-reviews-by=brian-dellabetta
  • approved-reviews-by=dsikka
  • approved-reviews-by=kylesayrs
This rule is failing.

All PRs must have at least one approving review from a maintainer before merging.

  • any of:
    • approved-reviews-by=HDCharles
    • approved-reviews-by=brian-dellabetta
    • approved-reviews-by=dsikka
    • approved-reviews-by=kylesayrs
  • #changes-requested-reviews-by = 0

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant