HFQ (Hash-based Quantization)#

TL;DR — HFQ (Hash-based Quantization) utilise des fonctions de hachage pour déterminer les paramètres de quantification ou les affectations de codebooks. Au lieu d'apprendre un codebook coûteux (comme AQLM), HFQ utilise une hash function déterministe pour assigner chaque poids à un « bucket » quantifié. Résultat : une compression rapide, sans calibration et à footprint minimal, particulièrement adaptée à l'edge computing.


Le problème fondamental (expliqué pour un néophyte)#

Les méthodes de vector quantization comme AQLM ou QuIP# doivent apprendre un codebook — un dictionnaire de vecteurs de référence. C'est puissant mais coûteux : il faut des heures de calcul, des données de calibration, et stocker le codebook lui-même.

L'idée de HFQ : et si on remplaçait le codebook appris par une simple fonction mathématique déterministe (une hash function) ? Au lieu de chercher « quel vecteur du codebook ressemble le plus à mes poids ? », on calcule simplement hash(poids) → bucket. C'est comme remplacer un dictionnaire de 65 000 entrées par une formule qui donne la réponse instantanément.

flowchart LR subgraph CB["Approche Codebook (AQLM, QuIP#)"] V1["Vecteur de poids (8 dim)"] --> S1["Recherche dans codebook (256×8) + beam search"] --> I1["Index du meilleur vecteur"] CB_N["❌ Lent : nécessite un codebook appris (heures de training)\n❌ Stockage : le codebook prend de la place\n✅ Qualité : approximation très précise"] end subgraph HASH["Approche Hash (HFQ)"] W1["Poids w"] --> S2["hash(w) mod N_bins = bucket assignment"] --> B1["Bucket q(w)"] HASH_N["✅ Rapide : O(1), pas de recherche\n✅ Pas de codebook à stocker\n✅ Pas de calibration\n⚠️ Qualité : moins précis qu'un codebook optimisé"] end

Contexte scientifique#

La quantification par hachage s'inspire du « hashing trick » (feature hashing), une technique classique en machine learning pour projeter des données dans un espace réduit de manière déterministe. Pour les réseaux de neurones, cette idée a été popularisée par HashedNets (2015) qui utilise le hachage pour le weight sharing — tous les poids dans le même « hash bucket » partagent une seule valeur.

Travail Lien avec HFQ Source
HashedNets (2015) Hachage pour weight sharing — compression de réseaux Chen et al., ICML 2015
Structured Multi-Hashing (2020) Multi-hash structuré pour compression de modèles Eban et al., CVPR 2020
Balanced Weight-Sharing (2023) Weight sharing par hash déterministe et équilibré arXiv:2312.08401
Vector Quantization + Hash Hash pour l'assignation de codebook en VQ Liens avec CRVQ, VPTQ

Pour les LLMs spécifiquement, HFQ reste un domaine plus niche que GPTQ ou AWQ, mais les concepts de hash-based quantization sont très pertinents pour l'edge computing et les scénarios où le temps de quantification doit être minimal.


Papier de référence principal#

Élément Détail
Titre Compressing Neural Networks with the Hashing Trick (HashedNets)
Auteurs Wenlin Chen, Wilson, Tygert, Yann LeCun (NVIDIA / Facebook AI / NYU)
Date Avril 2015
Publication ICML 2015
Lien proceedings.mlr.press/v37/chenc15.pdf
Code HashedNets

HashedNets est le travail fondateur du hachage pour la compression de réseaux de neurones. Il introduit le concept de virtual weights (poids apparemment nombreux) mappés à un petit ensemble de physical weights (poids réels) via une fonction de hachage.


Mécanisme#

Niveau néophyte#

Imagine que tu veux ranger 1000 objets dans 100 boîtes. Deux stratégies :

  1. Codebook classique : tu analyses chaque objet, tu trouves la boîte « la plus proche » en comparant avec le contenu de chaque boîte. Lent mais précis.
  2. Hash function : tu utilises une formule qui, à partir de l'objet, te donne directement le numéro de la boîte. Instantané, mais la répartition n'est pas optimale.

HFQ choisit la deuxième approche. La hash function agit comme un « aiguillage » automatique : chaque poids est envoyé dans un bucket sans qu'on ait à le comparer à quoi que ce soit.

Niveau intermédiaire#

HFQ remplace le codebook appris par une fonction de hachage h qui mappe l'espace des poids vers un ensemble discret de 2^b buckets (où b = nombre de bits) :

Quantification classique (codebook) :

  q(w) = argmin_i || w - c_i ||²     (recherche du nearest neighbor)

  où c_i sont les vecteurs du codebook (appris)

Quantification hash-based (HFQ) :

  q(w) = h(w) mod 2^b               (évaluation d'une fonction)

  où h est une hash function déterministe

Weight sharing par hachage (HashedNets) :

Le principe central de HFQ est que plusieurs poids partagent une seule valeur physique via le hachage. Pour une couche avec N connexions :

HashedNets : N connexions virtuelles → K << N poids physiques

  hash(i) mod K → bucket_id  →  poids physique W_phys[bucket_id]

  Connexion virtuelle i utilise : W_phys[hash(i) mod K]

  → On stocke seulement K valeurs au lieu de N
  → Compression ratio = N / K
Conn hash(i) bucket Poids physique
w₀ h(0)=3 bucket 3 W_phys[3]
w₁ h(1)=2 bucket 2 W_phys[2]
w₂ h(2)=1 bucket 1 W_phys[1]
w₃ h(3)=0 bucket 0 W_phys[0]
w₄ h(4)=3 bucket 3 W_phys[3] ← partagé!
w₅ h(5)=2 bucket 2 W_phys[2] ← partagé!
w₆ h(6)=1 bucket 1 W_phys[1] ← partagé!
w₇ h(7)=0 bucket 0 W_phys[0] ← partagé!

8 connexions → 4 poids physiques stockés → compression 2x · La hash function est la « table de correspondance »

Niveau tech avancé#

La théorie derrière HFQ repose sur trois piliers :

1. Le hashing trick (feature hashing)

Le hashing trick, introduit par Weinberger et al. (2009), projette un vecteur de dimension d vers une dimension k << d via :

φ(x)_i = Σ_{j: h(j)=i} ξ(j) × x_j

  où :
  h : {1,...,d} → {1,...,k}  (hash function)
  ξ : {1,...,d} → {-1, +1}   (sign hash, élimine le biais)

La lemma de Johnson-Lindenstrauss garantit que cette projection préserve approximativement les distances avec haute probabilité. Pour les poids de réseaux de neurones, cela signifie que les collisions de hash (deux poids dans le même bucket) introduisent un bruit borné.

2. Weight sharing forcé

HashedNets force un partage de poids dur (hard weight-sharing) : toutes les connexions dans le même bucket utilisent exactement la même valeur. Le nombre de paramètres libres passe de N à K. Pendant l'entraînement, les gradients de toutes les connexions d'un bucket sont sommés et appliqués au poids physique partagé.

3. Structured Multi-Hashing

Eban et al. (CVPR 2020) étendent l'idée avec du multi-hash structuré : au lieu d'une seule hash function, on utilise plusieurs fonctions de hachage en cascade pour créer une structure de partage hiérarchique :

Multi-Hash :

  Niveau 1 : h₁(i) mod K₁   → partage grossier
  Niveau 2 : h₂(i) mod K₂   → partage fin
  Niveau 3 : h₃(i) mod K₃   → partage residual

  w_phys(i) = W₁[h₁(i)] + W₂[h₂(i)] + W₃[h₃(i)]

  → Combinaison additive de tables de hash
  → Similaire à AQLM mais avec des tables hash au lieu de codebooks appris

4. Application aux LLMs

Pour les LLMs, deux variantes émergent :

  • HFQ-assign : la hash function assigne chaque poids à un niveau de quantification (remplace la recherche de nearest-neighbor dans le codebook)
  • HFQ-share : la hash function force le partage de poids (compression structurelle, pas seulement précision réduite)

Le trade-off : HFQ est moins précis qu'un codebook optimisé (car la répartition par hash est aléatoire, pas optimisée), mais beaucoup plus rapide à calculer et ne nécessite ni calibration ni entraînement de codebook.


Illustration : le process HFQ#

flowchart TD W["**Matrice de poids W (FP16)** — N paramètres"] W --> CHOICE{"Choix du mode HFQ ?"} CHOICE -->|"Quantification"| ASSIGN["**HFQ-ASSIGN** (quantification)
bucket = h(w_i) mod 2^b
q(w_i) = bin_centers[bucket]"] CHOICE -->|"Compression structurelle"| SHARE["**HFQ-SHARE** (compression structurelle)
bucket = h(i) mod K
w_i = W_phys[bucket]"] ASSIGN --> EVAL["**Évaluation de la hash function** — O(1) par poids"] SHARE --> EVAL EVAL --> NOOPT["**Pas d'optimisation nécessaire**
❌ Pas de calibration
❌ Pas de beam search
❌ Pas d'apprentissage de codebook
✅ La hash function FAIT tout"] NOOPT --> RES["**MODÈLE QUANTIFIÉ HFQ**
HFQ-assign : 2^b niveaux → ~b bits/poids
HFQ-share : K poids physiques partagés → N/K compression
Stockage hash function : O(1)"]

Hash function : choix et propriétés#

block-beta columns 1 t["FONCTIONS DE HASH POUR HFQ"] m1["1. MODULO HASH (simple) : h(i) = (a×i + c) mod p puis mod K — a, c = constantes aléatoires, p = premier grand — O(1), universelle"] m2["2. MURMURHASH / CITYHASH (pratique) : fonction non-cryptographique — Distribution uniforme excellente — Très rapide (SIMD optimisé)"] m3["3. SIGN HASH (élimine le biais) : h(i) → bucket, ξ(i) → ±1 — Élimine le biais systématique"] m4["4. MULTI-HASH (structured) : Combinaison de L hash functions indépendantes — Réduit les collisions, permet une structure additive"] p["Propriétés recherchées : ✅ Uniformité · ✅ Indépendance · ✅ Déterminisme · ✅ Rapidité (O(1)) · ❌ Pas besoin crypto"]

HFQ-assign vs HFQ-share : tableau comparatif#

Aspect HFQ-assign HFQ-share
Principe Hash → niveau de quantification Hash → poids physique partagé
Compression Réduction de précision (ex: 16→4 bit) Réduction de paramètres (N→K)
Codebook Remplacé par hash + bin_centers Remplacé par table de K poids
Calibration ❌ Non requise ❌ Non requise
Qualité ⚠️ Moindre (assignation non-optimale) ⚠️ Collisions de hash
Vitesse quantif. ⚡ Instantanée ⚡ Instantanée
Cas d'usage Edge, temps réel Compression structurelle
Équivalent Quantification uniforme accélérée HashedNets / LoRA-like

Bits / Formats supportés#

Mode Bits effectifs Compression Qualité Supporté
HFQ-assign 4-bit ~4 4x ⭐⭐⭐
HFQ-assign 2-bit ~2 8x ⭐⭐
HFQ-share (N/4) Variable 4x ⭐⭐⭐
HFQ-share (N/16) Variable 16x ⭐⭐
Multi-hash (L=3) ~b+overhead Variable ⭐⭐⭐⭐

HFQ est particulièrement adapté aux scénarios où la vitesse de quantification et le footprint mémoire sont plus critiques que la qualité maximale.


Résultats#

HashedNets — Compression de MLPs (résultats fondateurs)#

Configuration Params Accuracy Perte
Original 640K 98.4%
HashedNet 8× 80K 98.3% <0.1%
HashedNet 32× 20K 97.9% <0.5%

Compression ratio : 8× à 32× · Perte d'accuracy : < 1% (souvent négligeable)
Note : résultats sur petits modèles. Pour les LLMs modernes, HFQ n'a pas encore de benchmarks standardisés.

Positionnement pour les LLMs#

flowchart LR HFQ["⚡ HFQ
(hash)
<1s"] --> HQQ["HQQ
(data-free)
<5 min"] HQQ --> GPTQ["GPTQ
(calib)
minutes"] GPTQ --> AWQ["AWQ
(calib)
min"] AWQ --> AQLM["AQLM
(train)
heures"] AQLM --> CRVQ["CRVQ
(VQ)
heures"]

⚡ Instantané ← Vitesse maximale, qualité modérée · · · Qualité maximale, vitesse minimale → ⏳ Lent


Avantages et inconvénients#

✅ Avantages ❌ Inconvénients
Ultra-rapide : quantification en O(N), instantanée Qualité inférieure aux codebooks optimisés (AQLM, QuIP#)
Sans calibration : aucune donnée d'entrée nécessaire Collisions de hash inévitables (deux poids ≠ dans le même bucket)
Footprint minimal : pas de codebook à stocker Domaine niche pour les LLMs (peu d'implémentations matures)
Déterministe : résultats reproductibles Assignation non-optimale (la hash function ne « sait » rien des poids)
Idéal pour edge computing et inference temps réel Pas de récupération d'erreur (pas d'itération)
Multi-hash améliore la qualité sans calibration Support framework quasi inexistant pour LLMs
Combinable avec d'autres méthodes (post-processing) Pas de papier LLM spécifique de référence (contrairement à GPTQ/AWQ)

Exemple pratique#

Implémentation HFQ-share (weight sharing par hash) en PyTorch#

import torch
import torch.nn as nn
import hashlib

class HashedLinear(nn.Module):
    """
    Couche linéaire avec weight sharing par hash (HashedNets-style).
    N connexions virtuelles → K poids physiques.
    """
    def __init__(self, in_features, out_features, compression=8):
        super().__init__()
        self.in_features = in_features
        self.out_features = out_features
        self.N = in_features * out_features  # connexions virtuelles

        # K = nombre de poids physiques (compression ratio)
        self.K = max(1, self.N // compression)

        # Poids physiques (seuls ceux-ci sont stockés)
        self.physical_weights = nn.Parameter(
            torch.randn(self.K) * 0.02
        )

        # Table de hash précalculée : connexion → bucket
        self.register_buffer(
            'hash_table',
            self._build_hash_table()
        )

    def _build_hash_table(self):
        """Hash function : connexion_idx → bucket_idx"""
        # Hash simple : (a*i + c) mod K
        indices = torch.arange(self.N)
        a, c = 2654435761, 12345  # constantes de hash
        return ((a * indices + c) % self.K).long()

    def forward(self, x):
        # Reconstruire la matrice virtuelle à partir des poids physiques
        W_flat = self.physical_weights[self.hash_table]
        W = W_flat.view(self.out_features, self.in_features)
        return torch.nn.functional.linear(x, W)

# Usage
model_dim = 4096
layer = HashedLinear(model_dim, model_dim, compression=16)

# Compression : 4096×4096 = 16.7M params → 16.7M/16 ≈ 1M params
# Taille : 67 MB → 4.2 MB (16x compression)
# Quantification : instantanée (juste la construction de la hash table)
# Calibration : AUCUNE

HFQ-assign : quantification par hash#

import torch

def hfq_assign(weights, bits=4, seed=42):
    """
    Quantification par hash : chaque poids est assigné
    à un bin via une hash function.
    """
    num_bins = 2 ** bits

    # Centres des bins (distribution uniforme dans [-max, max])
    w_max = weights.abs().max()
    bin_centers = torch.linspace(-w_max, w_max, num_bins)

    # Hash des indices → bucket
    indices = torch.arange(weights.numel())
    a, c = 2654435761 * (seed + 1), 999983
    buckets = (a * indices + c) % num_bins

    # Assignation : poids → centre du bucket hashé
    quantized = bin_centers[buckets].view_as(weights)

    # Résultat :
    # ┌──────────────────────────────────────────────┐
    # │  Poids FP16  →  poids quantifiés par hash    │
    # │  Bits : 4                                     │
    # │  Calibration : AUCUNE                        │
    # │  Vitesse : O(N) instantané                    │
    # │  Qualité : modérée (non-optimale)             │
    # └──────────────────────────────────────────────┘
    return quantized

# Quantifier une matrice de poids
W = torch.randn(4096, 4096)  # couche de LLM
W_hfq = hfq_assign(W, bits=4)

# Comparaison erreur
mse = ((W - W_hfq) ** 2).mean()
print(f"HFQ 4-bit MSE: {mse:.6f}")

Quantifier un LLM avec HFQ-share (proof of concept)#

python -c "
import torch
from transformers import AutoModelForCausalLM

model = AutoModelForCausalLM.from_pretrained(
    'meta-llama/Llama-2-7b-hf',
    torch_dtype=torch.float16,
    device_map='auto'
)

# HFQ-share : remplacer chaque nn.Linear par HashedLinear
# (proof of concept — pas d'outil prêt à l'emploi pour LLMs)

# Résultat attendu (théorique) :
# ┌──────────────────────────────────────────────┐
# │  Llama-2-7b FP16      :  13.5 GB             │
# │  HFQ-share 8x         :  ~1.7 GB  (−87%)     │
# │  HFQ-share 16x        :  ~0.85 GB (−94%)     │
# │  Temps de quantif.    :  < 30 secondes        │
# │  Calibration          :  AUCUNE               │
# │  Perte de qualité     :  significative en     │
# │                          bas bitrate (à évaluer)│
# └──────────────────────────────────────────────┘

print('HFQ pour LLMs : concept prometteur pour edge,')
print('mais nécessite validation empirique et kernels optimisés.')
"

Comparaison avec les alternatives#

Méthode Codebook Calibration Vitesse quantif. Qualité 4-bit Adapté LLM ?
HFQ ❌ Hash function ❌ Aucune ⚡ Instantané ⭐⭐ ⚠️ Niche
HQQ ❌ Optimisation HQ ❌ Aucune ⚡ < 5 min ⭐⭐⭐ ✅ Oui
GPTQ ❌ Scalaire ✅ Requise Rapide ⭐⭐⭐⭐ ✅✅ Standard
AWQ ❌ Scalaire ✅ Requise Rapide ⭐⭐⭐⭐ ✅✅ Standard
AQLM ✅ Multi-codebook ✅ Requise ⏳ Heures N/A (2-bit) ✅ Oui
QuIP# ✅ E8 lattice ✅ Requise ⏳ Lente ⭐⭐⭐⭐⭐ ✅ Oui

HFQ est la méthode la plus rapide et la plus simple conceptuellement, mais aussi la moins précise pour les LLMs modernes. Son intérêt principal réside dans l'edge computing où la vitesse de quantification prime sur la qualité.


Références#

  • HashedNets — Chen et al., « Compressing Neural Networks with the Hashing Trick », ICML 2015 — PDF | NVIDIA Research
  • Structured Multi-Hashing — Eban et al., « Structured Multi-Hashing for Model Compression », CVPR 2020 — PDF
  • Balanced Weight-Sharing« Balanced and Deterministic Weight-sharing Helps Network Compression », 2023 — arXiv:2312.08401
  • Feature Hashing — Weinberger et al., « Feature Hashing for Large Scale Multitask Learning », ICML 2009 — fondement théorique du hashing trick
  • Johnson-Lindenstrauss — lemme de projection aléatoire qui garantit la préservation des distances par hash
  • Page HashedNetscse.wustl.edu/~yixin.chen/HashedNets
  • Page de référence — Voir Quantification LLM pour le panorama complet
  • Méthodes comparables — Voir HQQ (autre approche sans calibration), AQLM (codebook appris)
ia llm quantification hfq hash hashing-trick weight-sharing codebook edge-computing