ParoQuant (Pairwise Rotation Quantization)#

ParoQuant est une méthode PTQ weight-only (INT4, groupes de 128 canaux) qui applique avant quantification une transformation dite scaled pairwise rotation : un empilement de petites rotations par paires de canaux, apprises couche par couche. Publiée par Z Lab / NVIDIA / MIT (ICLR 2026), elle atteint la meilleure précision des méthodes de quantification linéaires — devançant AWQ, EfficientQAT, SpinQuant et OmniQuant, et talonnant les méthodes vectorielles (QTIP, QuIP#) — tout en restant quasi aussi rapide qu'AWQ (< 10 % d'overhead runtime). Son terrain de jeu privilégié : les LLM de raisonnement, où l'erreur de quantification s'accumule le long des longues chaînes de pensée.


Fiche papier#

Titre ParoQuant: Pairwise Rotation Quantization for Efficient Reasoning LLM Inference
Auteurs Yesheng Liang (algorithme), Haisheng Chen (système), Zihan Zhang, Song Han, Zhijian Liu
Affiliations NVIDIA, MIT, UC San Diego (Z Lab)
arXiv 2511.10645 (v1 : 13 nov. 2025)
Venue ICLR 2026 (accepté)
GitHub z-lab/paroquant — PyPI : pip install paroquant
Blog / projet z-lab.ai/projects/paroquant — vidéo : youtu.be/fISG4CkizLM
Modèles HF Collection z-lab/paroquant (checkpoints suffixe -PARO)
Claim central +2,4 % de précision moyenne vs AWQ sur tâches de raisonnement, < 10 % d'overhead runtime

Relation avec QuaRot#

ParoQuant appartient à la même famille que QuaRot — la quantification par rotation : les deux partent du constat que quelques canaux « outliers » cassent la quantification INT4, et appliquent une rotation orthogonale avant quantification pour uniformiser les magnitudes. La différence clé :

QuaRot (2024) ParoQuant (2025)
Rotation Hadamard fixe (non apprise) Rotations de Givens par paires apprises
Cible W4A4K4 (poids + activations + KV cache) W4 weight-only, focus LLM de raisonnement
Coût Quasi gratuit, sans calibration Optimisation 2 stages (~9 h / 8B sur H200)
Vitesse — Proche d'AWQ, kernel CUDA fusionné unique

ParoQuant remplace la matrice de Hadamard générique par des rotations adaptées à chaque couche, tout en restant aussi rapide qu'une méthode linéaire classique.


Le problème : les outliers et la redondance des rotations#

Pour un néophyte#

Un LLM contient quelques valeurs anormalement grandes (« outliers ») qui gâchent la quantification : comme quelques cris dans une conversation calme, elles forcent tout le monde à « parler fort » et on perd la précision. Les méthodes par rotation font pivoter le modèle dans un espace mathématique équivalent où ces valeurs extrêmes sont réparties uniformément — le modèle dit exactement la même chose, mais devient facile à compresser en 4 bits.

L'intuition propre à ParoQuant : une rotation complète qui mélange tous les canaux entre eux est massivement redondante. Optimiser seulement le top 10 % des paires de canaux (celles dont les magnitudes diffèrent le plus) suffit à obtenir la même réduction d'erreur qu'une rotation complète — à une fraction du coût.

Le mécanisme en trois étapes#

  1. Rotations de Givens : au lieu d'une matrice orthogonale n×n complète, on utilise un petit ensemble de rotations par paires (2 canaux) G(i, j, θ) — chacune n'est qu'un simple multiply-add vectorisable.

  2. Rotations indépendantes : chaque canal apparaît dans au plus une paire par ensemble de rotations → aucune dépendance, parallélisme GPU total (token / groupe de canaux / paire), le tout fusionné dans un seul kernel CUDA.

  3. Empilement + scaling : on chaîne K = 8 ensembles de rotations indépendantes (jusqu'à 64 paires chacun par groupe de 128 canaux) + un scaling par canal diag(α). Le scaling uniformise les magnitudes entre canaux ; les rotations alignent les valeurs au sein des paires (elles se regroupent autour de la diagonale x = y).

La transformation finale, appliquée par groupe de 128 canaux :

T(W) = (∏ R(P_t, Θ_t)) · diag(α) · W,   t = 1..8

Optimisation en deux stages#

Minimisation couche par couche de ‖Q(D)(X′) − D(X)‖ :

Stage Ce qui est optimisé Réglages
1 Angles de rotation θ + scaling α AdamW, lr 0,05
2 Fine-tune QAT-like des poids + paramètres s, z (style EfficientQAT) lr 1e-5 / 1e-6
  • Calibration : 2048 échantillons (WikiText2 + C4 + RedPajama), 10 époques par stage.
  • Coût : ~9 h sur H200 pour Llama-3-8B (vs AWQ : minutes ; EfficientQAT : ~3 h ; QTIP : ~20 h).
  • Robuste au sous-échantillonnage : 128 échantillons de calibration suffisent presque (PPL 7,30 vs 7,27).

À l'inférence#

Les rotations inverses sont appliquées aux activations en ligne dans un unique kernel CUDA fusionné : groupe de 128 canaux en shared memory, paramètres de rotation dans les registres → < 10 % d'overhead vs AWQ. Sur Apple Silicon, un backend MLX avec kernels Metal remplit le même rôle.


Bits et formats#

  • W4A16 (principal) : INT4 weight-only, quantification linéaire block-wise, groupes de 128 canaux (taille optimale selon les scaling laws de Dettmers).
  • W4A4 (extension) : avec compensation d'erreur de type GPTQ sur les poids — matche FlatQuant et bat SpinQuant (INT4) ; la variante MXFP4 bat MR-GPTQ.
  • Choix d'une quantification linéaire (et non vectorielle) délibéré, pour la compatibilité frameworks (vLLM, Transformers) — la méthode reste extensible à la vector quantization.
  • Paramètres de rotation : 8 rotations indépendantes × jusqu'à 64 paires par groupe de 128 canaux.
  • Pas de résultats publiés sous 4 bits.

Avantages / inconvénients#

Avantages ✅#

  • Précision SOTA de la quantification linéaire : +2,4 % en moyenne vs AWQ sur tâches de raisonnement, +6,3 % vs EfficientQAT, +0,9 % vs QTIP.
  • Vitesse proche d'AWQ : < 10 % d'overhead ; 15–30 % plus rapide que QTIP (~25 % plus rapide à précision égale).
  • Spécialisé raisonnement : seulement ~10 % de dégradation moyenne vs FP16 ; l'erreur résiste à l'accumulation sur les longues chaînes de pensée (AIME-24/25 préservés).
  • Robuste en calibration : 128 échantillons presque aussi bons que 2048.
  • Kernel unique, hardware-friendly : fusion totale, parallélisme à 3 niveaux (token, groupe, paire).

Inconvénients ❌#

  • Temps de quantification : ~9 h sur H200 pour un 8B (AWQ : minutes) — le coût de l'optimisation en 2 stages.
  • ~10 % plus lent qu'AWQ pur en décodage (throughput 2,1× vs 2,3× sur Qwen3-4B).
  • Pas de compensation de second ordre à la GPTQ dans le chemin W4A16 (réservée au W4A4).
  • Écosystème jeune : pas de support llama.cpp / GGUF — kernels custom requis (vLLM ≥ 0.10.1, Transformers, MLX).
  • Pas de résultats < 4 bits publiés.
  • Weight-only : pas d'accélération compute des activations (contrairement au W4A4 de QuaRot).

Benchmarks#

Perplexité WikiText-2 / C4 (4-bit, groupes de 128) — Table 5.1 du papier#

Méthode Type L3-8B W2 L3-70B W2 L2-7B W2 Q3-1.7B W2 Q3-4B W2 Q3-8B W2 Q3-14B W2 L3-8B C4
FP16 – 5,54 2,56 5,12 8,32 7,01 6,24 5,70 7,10
QuIP# vector 5,81 2,99 5,19 – – – – 7,32
QTIP vector 5,69 2,75 5,17 8,46 7,09 6,28 5,75 7,22
AWQ linear 5,92 2,96 5,23 8,80 7,36 6,45 5,85 7,42
SpinQuant linear 5,83 – 5,21 – – – – 7,41
EfficientQAT linear 5,87 3,33 5,22 8,60 7,19 6,37 5,82 7,36
ParoQuant linear 5,73 2,82 5,17 8,44 7,10 6,29 5,75 7,27

(C4 ParoQuant : L3-70B 5,86 · L2-7B 6,73 · Q3-1.7B 8,74 · Q3-4B 7,70 · Q3-8B 7,04 · Q3-14B 6,59)

→ Meilleure PPL de toutes les méthodes linéaires, proche de QTIP (vector), à ~92 % de la vitesse AWQ. Bat AWQ de 0,15–0,36 PPL W2 sur tous les modèles.

⚠️ Pas de comparaison directe vs GPTQ Q4, GGUF Q4_K_M ou Q6_K dans le papier ni dans les évaluations tierces trouvées. Les baselines sont AWQ, EfficientQAT, QTIP, QuIP#, OmniQuant, SpinQuant. Contexte indirect : AWQ surpasse généralement GPTQ en 4-bit ; les k-quants GGUF sont une famille distincte sans face-à-face publié.

Raisonnement — Qwen3-4B 4-bit (accuracy %)#

Méthode MMLU-Pro GPQA AIME24 AIME25 Moy. Speedup
FP16 71,0 50,0 75,6 62,2 64,7 1,0×
QTIP 69,7 55,2 67,8 58,9 62,9 1,5×
AWQ 68,2 52,2 62,2 53,3 59,0 2,3×
EfficientQAT 67,5 49,8 45,6 44,4 51,8 2,3×
ParoQuant 70,1 53,7 73,3 63,3 65,1 2,1×

À noter : AIME25 ParoQuant (63,3) > FP16 (62,2). Sur les tâches non-raisonnement (BoolQ/ARC/HSwag), ParoQuant ≈ FP16 (69,9 vs 70,1).

Throughput (tokens/s, batch 1, RTX A6000)#

Q3-1.7B Q3-4B L3-8B Q3-14B L3-70B
FP16 170 78 45 25 OOM
AWQ 320 176 120 70 17
QTIP 209 117 95 55 15
ParoQuant 278 160 112 65 16

Ablations (Llama-3-8B, PPL C4)#

RTN 7,56 → scaling seul 7,40 → 8 rotations seules 7,50 → rotations + scaling 7,35 → + fine-tune stage 2 7,27. Optimum à 8 rotations ; 128 échantillons de calibration → 7,30. Scaling et rotations sont complémentaires.


Outils et implémentations#

Élément Détail
GitHub z-lab/paroquant — reproductible sur la branche legacy ; branche main = dev actif
PyPI pip install "paroquant[vllm]" (CUDA 12.9/13.0) ou "paroquant[mlx]" (Apple Silicon)
Serving vllm serve $MODEL, python -m paroquant.cli.serve (API OpenAI-compatible), .cli.chat, .cli.agent (tool calling)
Apple Silicon Runtime oMLX / backend MLX (kernels Metal)
Docker ghcr.io/z-lab/paroquant:{chat,serve,latest,eval,chat-cu129}
Modèles pré-quantifiés HF Qwen3 (0.6B–14B), Qwen3.5 (0.8B–35B-A3B), Qwen3.6 (27B, 35B-A3B), Gemma 4 (E2B–31B), Llama-2-7B, Llama-3-8B, Llama-3.1-8B-Instruct — 43 modèles citent le papier
llama.cpp / GGUF ❌ Non supporté (pas d'équivalent k-quants ; demande ouverte : mlx-lm issue #977)

Exemple pratique#

# Utiliser un modèle déjà quantifié (NVIDIA, CUDA 12.9/13.0)
pip install "paroquant[vllm]"
vllm serve z-lab/Qwen3-4B-PARO --port 8000
# ou chat direct :
python -m paroquant.cli.chat --model z-lab/Qwen3-4B-PARO
# ou API OpenAI-compatible :
python -m paroquant.cli.serve --model z-lab/Qwen3-4B-PARO --port 8000

# Quantifier son propre modèle
git clone https://github.com/z-lab/paroquant && cd paroquant
pip install -e ".[optim,eval]"
experiments/optimize/4bit.sh Qwen/Qwen3-8B    # ~9h sur H200
python -m paroquant.cli.convert \
  --model Qwen/Qwen3-8B \
  --result-dir output/Qwen3-8B \
  --output-path models/Qwen3-8B-PARO          # --mode real (INT4) / pseudo (FP16)

Quand choisir ParoQuant ?#

Situation Verdict
LLM de raisonnement (AIME, GPQA, longues CoT) en 4-bit sur GPU ✅ Premier choix — la dégradation y est la plus faible
Serving vLLM / Apple Silicon avec checkpoints prêts ✅ Bon fit (modèles -PARO sur HF)
Inférence CPU / llama.cpp / GGUF ❌ Non supporté → GGUF Q4_K_M
Quantification rapide d'un modèle one-shot ❌ ~9 h d'optimisation → AWQ ou GPTQ
Compression extrême ≤ 3 bits ❌ Pas de résultats publiés → QuIP# ou AQLM

Conversion MLX (Apple Silicon)#

Principe : pas de commande d'export séparée#

Il n'existe aucune commande convert --mlx : le checkpoint exporté par paroquant.cli.convert --mode real (format HuggingFace, packing AWQ-compatible) est directement consommable par le backend MLX. La conversion vers le format MLX est effectuée automatiquement au chargement par paroquant/inference/backends/mlx/load.py, qui :

  1. détecte le format d'entrée — PARO natif (clés .qlinear.qweight) ou AutoAWQ (clés .qweight) ;
  2. décompresse les poids int32 AWQ → uint32 compact MLX (_convert_autoawq()), ou int16 nibbles → uint32 (_convert_paro_native()) ;
  3. remappe les paramètres de rotation (theta, pairs, channel_scales) vers un espace de noms plat par couche ;
  4. remplace les nn.Linear par des RotateQuantizedLinear (rotation Givens via kernel Metal + mx.quantized_matmul 4-bit).

Installation (macOS)#

pip install "paroquant[mlx]"   # tire mlx, mlx-lm, mlx-vlm
xcode-select --install         # requis : le kernel Metal est compilé JIT à l'exécution

Utiliser un checkpoint sur Apple Silicon#

# Backend auto-détecté (→ mlx sur Mac) :
python -m paroquant.cli.chat --model z-lab/Qwen3.5-4B-PARO
python -m paroquant.cli.serve --model z-lab/Qwen3.5-4B-PARO --port 8000

# Ou forcer explicitement le backend MLX (chat / benchmark / evaluate) :
python -m paroquant.cli.chat --model z-lab/Qwen3.5-4B-PARO --backend mlx

# Serve : --vlm pour charger les composants vision, --llm-only pour texte seul (défaut)

Quantifier son propre modèle puis l'exécuter en MLX#

⚠️ L'optimisation et le convert exigent CUDA (RuntimeError: CUDA required.) — le pipeline complet est donc bi-machine :

# 1. Sur GPU NVIDIA : optimisation (~9 h / 8B sur H200) + export HF
experiments/optimize/4bit.sh Qwen/Qwen3-8B
python -m paroquant.cli.convert \
  --model Qwen/Qwen3-8B --result-dir output/Qwen3-8B \
  --output-path models/Qwen3-8B-PARO --mode real

# 2. Sur Mac : charger directement le checkpoint exporté — la conversion
#    MLX (dépacking AWQ → uint32, rotations) se fait au vol au chargement
python -m paroquant.cli.chat --model models/Qwen3-8B-PARO

Notes techniques#

  • Le kernel Metal (rotation par paires) est JIT-compilé via mx.fast.metal_kernel() avec deux tailles de tuiles : 1 (prefill) et 4 (décodage batch).
  • MoE : les poids par expert sont empilés en un tenseur unique (RotateSwitchGLU), les rotations étant partagées entre experts.
  • embed_tokens et lm_head sont quantifiés nativement par mlx_nn.quantize() ; les couches ParoQuant restent intactes.

Sources#

ia llm quantification paroquant rotation int4 weight-only ptq raisonnement vllm mlx