Skip to content

Commit a8877c5

Browse files
committed
Update JointQ
1 parent d9c0152 commit a8877c5

13 files changed

Lines changed: 1259 additions & 569 deletions

File tree

CHANGELOG.md

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,18 @@
116116
- `chunking.py`: shared chunking strategies (`concat_chunk`, `concat_chunk_align`, `concat_rand`, `drop_head`, `drop_rand`) extracted as reusable helpers
117117
- Added `calibration_dataset` parameter to `AutoBitQuantizer` to specify the calibration data source (`onecomp/quantizer/autobit/_autobit.py`)
118118

119+
### JointQ:
120+
121+
- Added `incremental_lambda` regularization mode (`lambda_mode="incremental_lambda"`): for each layer, tries increasing lambda values from `lambda_list` with warm start, accepting candidates that improve weight error without substantially degrading output error. Stops at the first rejection. Controlled by `lambda_list`, `incremental_eps_y`, `incremental_eps_w` parameters
122+
- Added `incremental_initial_skip_ew_threshold` to skip an unstable initial `lambda=0.0` candidate when its relative weight error is excessively large
123+
- Added `accepted_lambda` field to `JointQResult` to record the per-layer lambda chosen in incremental mode
124+
- Added `execute_post_processing` override to log accepted lambda statistics (mean, median, min, max, per-value counts) after all layers are quantized
125+
- Added `regularization_mode` parameter: `"identity"` (standard Tikhonov λI) or `"diagonal"` (default, importance-aware λ·diag(a) where a_i scales with activation magnitude). Diagonal mode reduces over-regularization of less important columns. Only supported with `fixed_lambda` mode
126+
- Added `regularization_gamma` parameter (default 0.5): exponent for diagonal weights in `"diagonal"` mode; smaller values reduce the spread between weak and strong columns
127+
- Added initialization strategy control: `enable_clip_optimize`, `enable_clip_optimize_ep`, `enable_gptq` parameters to `JointQ` class
128+
- Added `gptq` attribute (`GPTQ` instance) to `JointQ` class for customizing GPTQ parameters (`blocksize`, `percdamp`, `mse`, `q_grid`, `q_norm`). Default GPTQ is auto-created from `bits`/`group_size`/`symmetric`
129+
- Replaced JointQ internal GPTQ module (`jointq/core/gptq.py`) with OneComp GPTQ (`onecomp.quantizer.gptq.GPTQ`); GPTQ initial solution is now generated via the shared GPTQ implementation
130+
119131
### Breaking Changes
120132

121133
- **`AutoBitQuantizer.enable_fused_groups` now defaults to `True`** (`onecomp/quantizer/autobit/_autobit.py`)
@@ -126,12 +138,21 @@
126138
- Quantisation levels unified to unsigned `[0, 2^b − 1]` (symmetric uses centred zero point); rounding order changed from `round(x/s + z)` to `round(x/s) + z`. Outputs are not bit-exact with prior RTN versions
127139
- Changed `prepare_rotated_model` defaults: `rotation_mode` `"random"``"random_hadamard"`, `num_calibration_samples` `128``512`
128140
- Introduced `CalibrationConfig` dataclass; see CalibrationConfig section above for migration details
141+
- `JointQ` class: removed `batch_size` parameter (use `onecomp.quantizer.jointq.core.quantize()` directly if batch processing is needed)
142+
- `JointQ`: GPTQ initial solution is now generated via OneComp GPTQ instead of the internal implementation. Quantization results are not bit-exact with prior versions (quality is equivalent or improved)
143+
- **`JointQ` regularization defaults changed** (`onecomp/quantizer/jointq/_jointq.py`)
144+
- `regularization_lambda`: `0.2``0.1`
145+
- `regularization_mode`: `"identity"``"diagonal"`
146+
- Quantization results are not bit-exact with prior versions.
147+
- **Migration:** to reproduce the previous behavior, pass
148+
`JointQ(..., regularization_lambda=0.2, regularization_mode="identity")` explicitly.
129149

130150
### Bug Fix
131151

132152
- Fixed `model_config.py`: `load_model()` VLM fallback did not trigger for models raising `"Unrecognized configuration class"` (e.g. Cohere2VisionForConditionalGeneration). Added the error pattern to `_vlm_hints`
133153
- Fixed `gptq/_gptq.py`: Cholesky decomposition in `run_gptq` could fail with `LinAlgError` on ill-conditioned Hessians (observed on large VLMs at deeper layers). Extracted `_compute_inverse_hessian()` with progressive damping fallback (up to 5 retries, 10x damping increase per retry). No impact on normal operation
134154
- Fixed `TypeError` in `QuantLinear.forward` when `S_qk` scaling was applied to MLP layers (`onecomp/pre_process/quant_models.py`)
155+
- Fixed `JointQ` `group_size=None` (per-channel quantization) raising `TypeError`
135156
- Fixed wrong module grouping in `make_grouped_module` where GC-driven `id()` reuse caused attention projections (q/k/v) and MLP projections (gate/up) to be merged into the same group. (`qep/_quantize_with_qep_arch.py`)
136157
- Fixed silent weight corruption in `GPTQLinear` when `qzero=0` was stored through the GPTQ v1 zero-point path (`onecomp/quantizer/gptq/gptq_layer.py`)
137158
- Root cause: AutoGPTQ v1 stores `raw_zero - 1`, so `qzero=0` becomes `-1`; without masking, its sign-extended bits corrupted neighboring packed slots
@@ -140,14 +161,6 @@
140161
- Added regression tests for per-slot pack corruption, packed/unpacked forward paths, the `gptq_v2` branch, and the `from_saved_state` path for GPTQ v1 tensors
141162
- **NOTE**: If you have GPTQ models quantized with previous versions, please re-quantize them with this release, as they may contain corrupted internal data.
142163

143-
### Packaging
144-
145-
- Bumped minimum `transformers` requirement from `>=5.3.0` to `>=5.5.0` (`pyproject.toml`)
146-
- Added `cu130` optional-dependency extra and the `pytorch-cu130` wheel index (`https://download.pytorch.org/whl/cu130`) for CUDA 13 hosts (e.g. NVIDIA B200) (`pyproject.toml`)
147-
- Pinned the `vllm` extra to `vllm>=0.10` to prevent uv from falling back to legacy versions whose source build requires `CUDA_HOME` (`pyproject.toml`)
148-
- Added uv `conflicts` declarations between the `vllm` extra and the `cpu` / `cu118` / `cu121` / `cu124` / `cu126` / `cu128` extras: vLLM `>=0.20` requires `torch>=2.10`, which is only published for `cu130`. This forces `vllm` to be installed only with `--extra cu130` and prevents silent fallback to a `vllm` version incompatible with `transformers>=5` at runtime (`pyproject.toml`)
149-
- Restricted `tool.uv.environments` to `sys_platform == 'linux'` and `python_full_version >= '3.12', < '3.14'` to skip lock splits for unused Windows and out-of-range Python versions (`pyproject.toml`)
150-
151164
### Examples
152165

153166
- Added `example/example_custom_calibration.py`: Demonstrates `CalibrationConfig` with a custom calibration dataset (Python code snippets in `example/data/python_calibration.txt`). Quantizes TinyLlama with GPTQ 3-bit using both default C4 and custom Python-code calibration, then compares inference outputs across multiple prompts to show how calibration data choice affects quantization quality.
@@ -158,6 +171,7 @@
158171

159172
### Documentation
160173

174+
- Updated `docs/algorithms/jointq.md`: added incremental lambda mode description, acceptance criteria, diagonal regularization mode, new parameters (`lambda_mode`, `lambda_list`, `incremental_eps_y`, `incremental_eps_w`, `incremental_initial_skip_ew_threshold`, `regularization_mode`, `regularization_gamma`), and usage examples
161175
- Added `docs/algorithms/lpcd.md`: LPCD overview, motivation, supported submodule groups, usage examples, QEP relationship, parameters, and current support
162176
- Added `docs/api/lpcd_config.md` and updated `mkdocs.yml` navigation to include LPCD in the Algorithms and API Reference sections
163177
- Updated LPCD references across docs: `docs/index.md`, `docs/algorithms/overview.md`, `docs/user-guide/basic-usage.md`, `docs/user-guide/configuration.md`, `docs/user-guide/examples.md`, and `docs/getting-started/quickstart.md`
@@ -177,14 +191,17 @@
177191
- Added `docs/api/quantizers/onebit.md` (OneBit API reference)
178192
- Updated `mkdocs.yml` nav: added AutoBit/JointQ algorithm pages, OneBit API page; renamed Post-Process nav title to include Block-wise PTQ
179193
- Added example script links to `docs/user-guide/pre-process.md`
194+
- Updated `docs/algorithms/jointq.md`: added initialization strategy parameters, GPTQ customization examples, removed `batch_size` from parameter table, added `group_size=None` per-channel documentation
180195
- Added a Troubleshooting section to `docs/user-guide/vllm-inference.md` describing how to bypass the unconditional DeepGEMM (FP8) kernel warmup for non-FP8 quantization (GPTQ / DBF / Mixed-GPTQ) by setting `VLLM_USE_DEEP_GEMM=0` and `VLLM_DEEP_GEMM_WARMUP=skip`
181196

182197
### Tests
183198

199+
- Updated `test_jointq.py`: added `incremental_lambda` boundary parameters (`lambda_mode`, `lambda_list`, `incremental_eps_y`, `incremental_eps_w`, `incremental_initial_skip_ew_threshold`), abnormal parameter cases (invalid mode, empty list, negative values), GPU integration tests (`test_incremental_lambda_basic`, `test_incremental_lambda_single_step`), `_accept_candidate` unit tests covering all acceptance rules and edge cases; removed `batch_size` from boundary/abnormal parameter tests
184200
- Added `test_prepare_rotated_model.py`: validation, E2E pipeline, output threshold (80 combinations), save/load round-trip
185201
- Added `test_weight_quantizer.py`: RTN/GPTQ consistency, symmetric/asymmetric, group-wise, MSE, STE
186202
- Expanded `test_rtn.py`: MSE boundary/abnormal parameters
187203
- Added vLLM mixed group-size tests (`tests/vllm_plugins/gptq/test_mixed_gptq.py`, `tests/vllm_plugins/gptq/test_mixed_gptq_e2e.py`)
204+
- Updated `regression_quantize_helper.py`: updated `EXPECTED_MSE` baseline for OneComp GPTQ integration
188205
- Added LPCD tests (`tests/onecomp/lpcd/`, 25 cases)
189206
- `test_lpcd_config.py`: `LPCDConfig` default / custom values, dataclass field set, top-level `from onecomp import LPCDConfig` (CPU only)
190207
- `test_lpcd_metrics.py`: `make_lpcd_metrics()` dispatch on synthetic Llama / Qwen3 blocks for every `enable_*` flag combination, `NotImplementedError` for unsupported architectures, `LpcdMetricGroup.mark_as_ready` / `is_refineable` state transitions (CPU only, no weight download)
@@ -205,6 +222,11 @@
205222

206223
### Packaging
207224

225+
- Bumped minimum `transformers` requirement from `>=5.3.0` to `>=5.5.0` (`pyproject.toml`)
226+
- Added `cu130` optional-dependency extra and the `pytorch-cu130` wheel index (`https://download.pytorch.org/whl/cu130`) for CUDA 13 hosts (e.g. NVIDIA B200) (`pyproject.toml`)
227+
- Pinned the `vllm` extra to `vllm>=0.10` to prevent uv from falling back to legacy versions whose source build requires `CUDA_HOME` (`pyproject.toml`)
228+
- Added uv `conflicts` declarations between the `vllm` extra and the `cpu` / `cu118` / `cu121` / `cu124` / `cu126` / `cu128` extras: vLLM `>=0.20` requires `torch>=2.10`, which is only published for `cu130`. This forces `vllm` to be installed only with `--extra cu130` and prevents silent fallback to a `vllm` version incompatible with `transformers>=5` at runtime (`pyproject.toml`)
229+
- Restricted `tool.uv.environments` to `sys_platform == 'linux'` and `python_full_version >= '3.12', < '3.14'` to skip lock splits for unused Windows and out-of-range Python versions (`pyproject.toml`)
208230
- Added `hydra` extra to `pyproject.toml` so `hydra-core` (used by `example/example_autobit.py` and the `model_validation/{gptq,qep_gptq,autobit,autobit_qep,jointq}/validate_*.py` scripts) installs in one step via `uv sync --extra <cuXXX> --extra hydra` or `pip install "onecomp[hydra]"`, instead of a separate `pip install hydra-core` after sync. Documented the new extra in `README.md` and the `model_validation/*/README.md` files. The `model_validation/gptq/` Phase 3 (vLLM inference) additionally requires the `vllm` extra (`uv sync --extra <cuXXX> --extra hydra --extra vllm` or `pip install "onecomp[hydra]" vllm`).
209231

210232
## [v1.0.2] 2026-03-31

docs/algorithms/jointq.md

Lines changed: 125 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,35 +26,95 @@ with group index \(g\) for group-wise quantization.
2626

2727
### Initialization Strategies
2828

29-
JointQ supports multiple initialization strategies for the local search:
29+
JointQ supports multiple initialization strategies for the local search.
30+
Each strategy can be independently enabled or disabled:
3031

31-
1. **Clip-Optimize**: Finds optimal clipping range, then quantizes
32-
2. **Clip-Optimize with Error Propagation**: Adds GPTQ-style error propagation to initialization
33-
3. **GPTQ**: Uses GPTQ solution as the starting point for joint optimization
32+
1. **Clip-Optimize** (`enable_clip_optimize`): Finds optimal clipping range, then quantizes
33+
2. **Clip-Optimize with Error Propagation** (`enable_clip_optimize_ep`): Adds GPTQ-style error propagation to initialization
34+
3. **GPTQ** (`enable_gptq`): Uses OneComp GPTQ solution as the starting point for joint optimization
35+
36+
By default, Clip-Optimize and GPTQ are enabled, while Clip-Optimize with Error
37+
Propagation is disabled. The best solution among all enabled strategies is
38+
selected row-by-row based on the reconstruction error.
3439

3540
### Regularization
3641

3742
To prevent overfitting to calibration data, JointQ applies Tikhonov regularization:
3843

3944
\[
40-
X^T X + n \lambda I
45+
X^T X + n \lambda R
4146
\]
4247

43-
where \(\lambda\) controls the regularization strength (default: 0.2). This stabilizes
44-
the optimization, especially when the number of calibration samples is small relative
45-
to the model dimension.
48+
where \(\lambda\) controls the regularization strength and \(R\) is the
49+
regularization matrix controlled by `regularization_mode`:
50+
51+
- **`"identity"`**: \(R = I\). Standard Tikhonov — all input
52+
dimensions are protected equally.
53+
- **`"diagonal"`** (default): \(R = \mathrm{diag}(a)\) where
54+
\(a_i = \bigl(\mathrm{diag}(X^TX)_i \;/\; \mathrm{mean}(\mathrm{diag}(X^TX))\bigr)^{\gamma}\).
55+
Columns with larger activations receive stronger regularization while
56+
less important columns are given more freedom, reducing over-protection.
57+
Only supported with `lambda_mode="fixed_lambda"`.
58+
59+
JointQ provides two lambda selection modes, selected by `lambda_mode`.
60+
61+
#### Fixed lambda mode (default)
62+
63+
Uses a single fixed \(\lambda\) for all layers (`lambda_mode="fixed_lambda"`).
64+
The default strength is `regularization_lambda=0.1`.
65+
66+
#### Incremental lambda mode
67+
68+
`lambda_mode="incremental_lambda"` tries increasing \(\lambda\) values from
69+
`lambda_list` for each layer, keeping the solution as long as it improves
70+
weight error without substantially degrading output error.
71+
72+
For each layer, the algorithm:
73+
74+
1. Quantizes with the first (smallest) \(\lambda\).
75+
- If the first candidate uses `lambda=0.0` and its relative weight error
76+
is extremely large, JointQ can skip that candidate and move to the next
77+
lambda via `incremental_initial_skip_ew_threshold`.
78+
2. For each subsequent \(\lambda\), re-quantizes using the previous accepted
79+
solution as a warm start.
80+
3. Accepts the candidate if the relative weight error \(E_w\) decreases
81+
without the relative output error \(E_y\) worsening beyond tolerance.
82+
4. Stops at the first rejection and returns the last accepted solution.
83+
84+
The acceptance criteria are:
85+
86+
- Both \(E_w\) and \(E_y\) decreased → accept.
87+
- \(E_w\) increased → reject.
88+
- \(E_y\) worsened within `incremental_eps_y` and \(E_w\) improved by at
89+
least `incremental_eps_w` → accept.
90+
- Otherwise → reject.
91+
92+
Where:
93+
94+
- \(E_w = \|W_q - W\|_F^2 \;/\; \|W\|_F^2\)
95+
- \(E_y = \|(W_q - W) X^T\|_F^2 \;/\; \|W X^T\|_F^2\)
4696

4797
## Parameters
4898

49-
| Parameter | Type | Description | Default |
50-
|-------------------------|---------|-----------------------------------------------------|----------|
51-
| `bits` | `int` | Quantization bit-width (1--4) | `4` |
52-
| `symmetric` | `bool` | Symmetric quantization | `False` |
53-
| `group_size` | `int` | Group size for group-wise quantization | `128` |
54-
| `batch_size` | `int` | Batch size for processing rows (None = all at once) | `None` |
55-
| `regularization_lambda` | `float` | Tikhonov regularization strength | `0.2` |
56-
| `actorder` | `bool` | Reorder columns by activation magnitude | `False` |
57-
| `device` | `torch.device` | Device for computation | `None` |
99+
| Parameter | Type | Description | Default |
100+
|-------------------------|----------------|------------------------------------------------------------|----------|
101+
| `bits` | `int` | Quantization bit-width (1--4) | `4` |
102+
| `symmetric` | `bool` | Symmetric quantization | `False` |
103+
| `group_size` | `int` or `None`| Group size for group-wise quantization (None = per-channel) | `128` |
104+
| `lambda_mode` | `str` | `"fixed_lambda"` or `"incremental_lambda"` | `"fixed_lambda"` |
105+
| `regularization_lambda` | `float` or `None` | Tikhonov regularization strength (fixed mode) | `0.1` |
106+
| `regularization_mode` | `str` | `"identity"` (λI) or `"diagonal"` (λ·diag(a), fixed mode only) | `"diagonal"` |
107+
| `regularization_gamma` | `float` | Exponent for diagonal weights (`"diagonal"` mode) | `0.5` |
108+
| `lambda_list` | `list[float]` or `None` | Lambda values to try (incremental mode) | `[0.001, 0.01, ..., 0.5]` |
109+
| `incremental_eps_y` | `float` | Max tolerated relative output-error increase | `0.03` |
110+
| `incremental_eps_w` | `float` | Min required relative weight-error decrease | `0.10` |
111+
| `incremental_initial_skip_ew_threshold` | `float` or `None` | Skip initial `lambda=0.0` candidate when Ew is too large | `0.3` |
112+
| `actorder` | `bool` | Reorder columns by activation magnitude | `False` |
113+
| `device` | `torch.device` | Device for computation | `None` |
114+
| `enable_clip_optimize` | `bool` | Enable Clip-Optimize initialization | `True` |
115+
| `enable_clip_optimize_ep`| `bool` | Enable Clip-Optimize with Error Propagation initialization | `False` |
116+
| `enable_gptq` | `bool` | Enable GPTQ initialization | `True` |
117+
| `gptq` | `GPTQ` or `None` | Custom GPTQ instance for initial solution generation | `None` |
58118

59119
## Usage
60120

@@ -73,6 +133,26 @@ runner = Runner(model_config=model_config, quantizer=jointq, qep=False)
73133
runner.run()
74134
```
75135

136+
### With incremental lambda
137+
138+
```python
139+
jointq = JointQ(
140+
bits=4,
141+
group_size=128,
142+
lambda_mode="incremental_lambda",
143+
)
144+
```
145+
146+
### With identity regularization
147+
148+
```python
149+
jointq = JointQ(
150+
bits=4,
151+
group_size=128,
152+
regularization_mode="identity",
153+
)
154+
```
155+
76156
### With activation ordering
77157

78158
```python
@@ -85,10 +165,36 @@ jointq = JointQ(bits=4, group_size=128, actorder=True)
85165
jointq = JointQ(bits=4, symmetric=True, group_size=128)
86166
```
87167

168+
### With all initialization strategies
169+
170+
```python
171+
jointq = JointQ(
172+
bits=4,
173+
group_size=128,
174+
enable_clip_optimize=True,
175+
enable_clip_optimize_ep=True,
176+
enable_gptq=True,
177+
)
178+
```
179+
180+
### Custom GPTQ parameters
181+
182+
```python
183+
from onecomp.quantizer.gptq import GPTQ
184+
185+
jointq = JointQ(
186+
bits=4,
187+
group_size=128,
188+
gptq=GPTQ(wbits=4, groupsize=128, sym=False, mse=True, percdamp=0.05),
189+
)
190+
```
191+
88192
## Notes
89193

90194
- JointQ requires GPU for computation (CUDA-based local search).
91195
- Group-wise quantization (`group_size > 0`) is recommended for accuracy.
92-
- The `batch_size` parameter controls memory usage: smaller values reduce peak GPU memory
93-
at the cost of slower processing.
196+
Set `group_size=None` for per-channel quantization.
94197
- JointQ currently supports dequantized-model evaluation only (not packed quantized inference).
198+
- Incremental lambda mode runs `quantize()` multiple times per layer (once per
199+
lambda value until rejection), so quantization time increases compared to
200+
fixed lambda mode.

onecomp/analyzer/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
77
"""
88

9+
from .quantization_error import plot_quantization_errors
910
from .weight_outlier import (
1011
LayerOutlierStats,
1112
WeightOutlierAnalysis,
@@ -19,5 +20,6 @@
1920
"WeightOutlierAnalysis",
2021
"WeightOutlierAnalyzer",
2122
"analyze_weight_outliers",
23+
"plot_quantization_errors",
2224
"save_weight_distribution_plots",
2325
]

0 commit comments

Comments
 (0)