Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Change log

## [v1.3.4] 2026-09-07

### Bug Fix

- Fix scale layout handling for `groupsize=-1` in RTN fallback. This fallback is used when an MoE expert receives no routed calibration tokens, because GPTQ cannot compute activation-based statistics for that expert. The fix keeps the fallback result compatible with GPTQ's per-channel dequantization path.

### Documentation

- Clarify the confirmed Qwen3.6 `save_format="full_wrapper"` workflows, including vLLM serving and the current GGUF export workflow.

## [v1.3.3] 2026-09-03

### Enhancement
Expand Down
12 changes: 12 additions & 0 deletions docs/user-guide/basic-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,18 @@ runner.save_dequantized_model("./output/dequantized")
runner.save_quantized_model("./output/quantized")
```

!!! note "Qwen3.6 save format"
Qwen3.6 is quantized through its text-model layout. For confirmed
downstream workflows that require the full Hugging Face wrapper layout,
including vLLM serving and the current GGUF export workflow, save Qwen3.6
checkpoints with `save_format="full_wrapper"`:

`runner.save_quantized_model("./output/qwen36_quantized", save_format="full_wrapper")`

The option is specific to Qwen3.6 and raises `RuntimeError` for other
models. Leave `save_format` at its default (`"auto"`) for other
architectures.

!!! note "vLLM serving is method-specific"
`save_quantized_model()` produces a model loadable by the OneComp loader for any
quantizer that supports saving (see the table below). vLLM serving, however, is only
Expand Down
6 changes: 6 additions & 0 deletions docs/user-guide/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,12 @@ runner.save_quantized_model("./output/my_quantized_model")
runner.save_dequantized_model("./output/my_dequantized_model")
```

!!! note "Qwen3.6 save format"
For confirmed downstream workflows that require the full Hugging Face
wrapper layout, including vLLM serving and the current GGUF export workflow,
save Qwen3.6 models with `save_format="full_wrapper"`. See
[Basic Usage](basic-usage.md#step-5-save-the-model).

### Load a saved quantized model

```python
Expand Down
21 changes: 6 additions & 15 deletions docs/user-guide/vllm-inference.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,24 +126,15 @@ runner.run()
runner.save_quantized_model("./Llama-3.1-8B-Instruct-gptq-4bit")
```

!!! note "Qwen3.6: use `save_format=\"full_wrapper\"`"
Qwen3.6 quantizes as a text-only checkpoint, whose native Hugging Face
layout (`model.layers.*`) does not match what vLLM's composite
`Qwen3_5ForConditionalGeneration` loader expects (`model.language_model.layers.*`).
Pass `save_format="full_wrapper"` to `save_quantized_model()` to remap the
checkpoint for vLLM serving:

```python
runner.save_quantized_model("./Qwen3.6-gptq-4bit-vllm", save_format="full_wrapper")
```

This option is specific to Qwen3.6 and will raise `RuntimeError` for any
other model. Leave `save_format` at its default (`"auto"`) for everything
else, including other VLMs.
!!! note "Qwen3.6 save format"
Qwen3.6 checkpoints used for vLLM serving should be saved with
`save_quantized_model(..., save_format="full_wrapper")`. See
[Basic Usage](basic-usage.md#step-5-save-the-model) for the shared
Qwen3.6 save-format guidance, including other confirmed workflows.

For MoE variants (e.g. Qwen3.6-A3B), `full_wrapper` also drops each expert's
trivial `g_idx` buffer, since vLLM's GPTQ `FusedMoE` kernel has no `g_idx`
parameter and an unmapped one would crash weight loading see the `desc_act`/
parameter and an unmapped one would crash weight loading -- see the `desc_act`/
`actorder` warning above for when this isn't safe to drop.

### 2. Serve with vLLM
Expand Down
2 changes: 1 addition & 1 deletion onecomp/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@

"""

__version__ = "1.3.3"
__version__ = "1.3.4"
15 changes: 11 additions & 4 deletions onecomp/qep/_quantize_with_qep_arch.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,17 +298,24 @@ def _rtn_fallback_result(module: nn.Module, quantizer: Quantizer, name: str) ->

result_dict = run_rtn(module, wbits=wbits, groupsize=groupsize, sym=quantizer.sym)

# RTN's raw scale/zero are (out_features, num_groups); GPTQResult
# expects (num_groups, out_features).
scales = result_dict["scale"]
qzeros = result_dict["zero"]

if groupsize != -1:
# RTN's raw scale/zero are (out_features, num_groups); GPTQResult
# expects (num_groups, out_features).
scales = scales.T
qzeros = qzeros.T

return GPTQResult(
dequantized_weight=result_dict["dequantized_weight"],
wbits=wbits,
groupsize=groupsize,
actorder=False,
sym=quantizer.sym,
qweight=result_dict["quantized_weight"],
scales=result_dict["scale"].T,
qzeros=result_dict["zero"].T,
scales=scales,
qzeros=qzeros,
perm=None,
)

Expand Down
8 changes: 5 additions & 3 deletions tests/onecomp/test_qep_expert_recovery_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import logging

import pytest
import torch
import torch.nn as nn
import torch.nn.functional as F
Expand All @@ -24,7 +25,7 @@
# in_features of every expert Linear must be divisible by the 4-bit pack factor
# (32 // 4 == 8): gate/up consume HIDDEN, down consumes INTERMEDIATE.
HIDDEN = 8
INTERMEDIATE = 8
INTERMEDIATE = 16
NUM_EXPERTS = 3
SEQ_LEN = 3

Expand Down Expand Up @@ -230,7 +231,8 @@ def test_expert_never_selected_falls_back_to_rtn(monkeypatch, caplog):
assert all(quantizer.results[n].actorder is False for n in names)


def test_rtn_fallback_weight_is_actually_applied_to_the_module(monkeypatch, caplog):
@pytest.mark.parametrize("groupsize", [-1, 2])
def test_rtn_fallback_weight_is_actually_applied_to_the_module(monkeypatch, caplog, groupsize):
"""The RTN-fallback ``GPTQResult`` must actually flow into the module's

live weight, not just sit unused in ``quantizer.results``. A
Expand All @@ -248,7 +250,7 @@ def test_rtn_fallback_weight_is_actually_applied_to_the_module(monkeypatch, capl
"onecomp.qep._quantize_with_qep_arch.prepare_calibration_dataset",
_fake_prepare_calibration_dataset,
)
quantizer = GPTQ(wbits=4, groupsize=2, sym=True, include_layer_keywords=["experts"])
quantizer = GPTQ(wbits=4, groupsize=groupsize, sym=True, include_layer_keywords=["experts"])
qep_config = QEPConfig(device="cpu", percdamp=0.01, perccorr=0.5)

caplog.set_level(logging.INFO, logger="onecomp.qep._quantize_with_qep_arch")
Expand Down
13 changes: 11 additions & 2 deletions tests/onecomp/test_rtn_fallback_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Copyright 2025-2026 Fujitsu Ltd.
"""

import pytest
import torch
import torch.nn as nn

Expand Down Expand Up @@ -84,18 +85,26 @@ def test_dequantized_weight_matches_shape(self):
result = _rtn_fallback_result(module, quantizer, "mlp.experts.0.down_proj")
assert result.dequantized_weight.shape == module.weight.data.shape

def test_compute_dequantized_weight_roundtrip(self):
@pytest.mark.parametrize("groupsize", [-1, 32])
def test_compute_dequantized_weight_roundtrip(self, groupsize):
"""The packaged qweight/scales/qzeros must reconstruct a weight

consistent with the shapes GPTQResult.compute_dequantized_weight expects
(this is what create_inference_layer / export relies on downstream).
"""
module = _linear(in_features=32, out_features=16)
quantizer = GPTQ(wbits=4, groupsize=16, sym=True)
quantizer = GPTQ(wbits=4, groupsize=groupsize, sym=True)
result = _rtn_fallback_result(module, quantizer, "mlp.experts.0.down_proj")

reconstructed = result.compute_dequantized_weight()
expected = result.dequantized_weight.to(dtype=reconstructed.dtype)
assert reconstructed.shape == module.weight.data.shape
torch.testing.assert_close(
reconstructed,
expected,
rtol=0,
atol=2e-4,
)


class TestResolveGptqForRtnFallback:
Expand Down