Aller au contenu

Gated DeltaNet

Les 69 couches d'attention linéaire, projection par projection.


Ce que ce chapitre reconstruit

config.json donne huit valeurs pour l'attention linéaire :

"linear_num_value_heads": 128,
"linear_num_key_heads": 16,
"linear_key_head_dim": 128,
"linear_value_head_dim": 128,
"linear_conv_kernel_dim": 4,
"output_gate_type": "swish",
"mamba_ssm_dtype": "float32",
"model_type": "qwen3_5_moe_text"

Huit valeurs, et le nom de classe Qwen3_5MoeForCausalLM qui identifie le code exécuté. De cela découle toute la structure décrite ci-dessous.

Prérequis

Le mécanisme conceptuel — état récurrent, règle delta, portes — est introduit dans Fondations · 03. Ce chapitre en donne la réalisation concrète dans ce modèle précis.


Le chemin dans une couche

   x  (8 192)
   │
   ├──→ in_proj_qkvz  ──→  q (2 048) │ k (2 048) │ v (16 384) │ z (16 384)
   │                        │           │           │             │
   ├──→ in_proj_ba    ──→  β (128)  │  a (128)      │             │
   │                        │           │           │             │
   │                   ┌────┴───────────┴───────────┘             │
   │                   │  convolution causale, noyau 4            │
   │                   ▼                                          │
   │            ┌──────────────────────────────┐                  │
   │            │  récurrence Gated DeltaNet   │                  │
   │            │  S ← S(I − βkkᵀ)·α + βkvᵀ    │                  │
   │            └──────────────┬───────────────┘                  │
   │                           ▼                                  │
   │                       o (16 384)  ──────→  × swish(z)  ←──────┘
   │                                                │
   │                                                ▼
   │                                          out_proj (16 384 → 8 192)
   │                                                │
   └────────────────────────── (+) ─────────────────┘
                                │
                                ▼   vers le bloc MoE

Les projections d'entrée

in_proj_qkvz — la grosse

Une seule matrice produit quatre tenseurs :

Tenseur Rôle Dimension Calcul
\(q\) requêtes 2 048 \(16 \times 128\)
\(k\) clés 2 048 \(16 \times 128\)
\(v\) valeurs 16 384 \(128 \times 128\)
\(z\) porte de sortie 16 384 \(128 \times 128\)

Soit une matrice \(8\,192 \times 36\,864\) :

\[ 8\,192 \times (2\,048 + 2\,048 + 16\,384 + 16\,384) = 301\,989\,888 \]

302 M de paramètres, soit 69 % du coût de la couche.

La dissymétrie 16 / 128 est le point clé

16 têtes de clé, mais 128 têtes de valeur. Huit têtes de valeur partagent chaque tête de clé.

C'est le même principe que GQA dans l'attention classique, appliqué ici à l'espace de la mémoire récurrente : l'état \(S\) est de taille \(d_k \times d_v\), donc réduire \(d_k\) réduit l'état quadratiquement. Avec 16 têtes de clé au lieu de 128, l'état est huit fois plus petit.

in_proj_ba — la petite

Produit deux scalaires par tête de valeur :

Tenseur Rôle
\(\beta\) taux d'écriture de la règle delta, une valeur par tête
\(a\) facteur de décroissance \(\alpha\), une valeur par tête
\[ 8\,192 \times (128 + 128) = 2\,097\,152 \]

Deux millions de paramètres pour piloter, jeton par jeton et tête par tête, ce qu'on écrit et ce qu'on oublie. C'est peu de paramètres pour beaucoup de contrôle.


La convolution causale

"linear_conv_kernel_dim": 4

Avant la récurrence, \(q\), \(k\) et \(v\) passent par une convolution causale de noyau 4 : chaque position est mélangée avec les trois précédentes.

\[ (2\,048 + 2\,048 + 16\,384) \times 4 = 81\,920 \text{ paramètres} \]

Négligeable en taille, mais pas en fonction.

Pourquoi cette convolution existe

L'état récurrent est une mémoire approximative. La convolution donne au mécanisme une mémoire exacte sur une fenêtre courte — de quoi capter les dépendances locales immédiates (accord grammatical, ouverture et fermeture d'un délimiteur) sans les faire transiter par l'état.

C'est un héritage direct des architectures à espace d'états, Mamba en tête. Le champ mamba_ssm_dtype dans la configuration trahit d'ailleurs cette filiation.


La récurrence

Le cœur, tête par tête :

\[ S_t = \alpha_t \, S_{t-1}\left(I - \beta_t \mathbf{k}_t \mathbf{k}_t^{\top}\right) + \beta_t \, \mathbf{k}_t \mathbf{v}_t^{\top} \]
\[ \mathbf{o}_t = \mathbf{q}_t^{\top} S_t \]
Symbole Signification Dimensions ici
\(S_t\) état de la tête après le jeton \(t\) \(128 \times 128\)
\(\mathbf{k}_t, \mathbf{q}_t\) clé et requête 128
\(\mathbf{v}_t\) valeur 128
\(\alpha_t \in\, ]0,1[\) décroissance globale, issue de \(a\) scalaire
\(\beta_t \in\, ]0,1[\) taux d'écriture, issu de \(\beta\) scalaire

Trois lectures superposées :

  1. \(\alpha_t\) seul (\(\beta_t = 0\)) : l'état s'estompe uniformément. C'est l'attention linéaire gatée classique.
  2. \(\beta_t\) seul (\(\alpha_t = 1\)) : l'état remplace l'association liée à \(\mathbf{k}_t\) et ne touche pas au reste. C'est la règle delta pure.
  3. Les deux : oubli progressif du tout, remplacement précis du concerné. C'est Gated DeltaNet.

L'intuition en une image

Un tableau blanc. \(\alpha_t\) estompe légèrement tout ce qui est écrit. \(\beta_t\) efface une ligne précise et la réécrit.

Un modèle qui ne saurait qu'estomper perdrait tout uniformément ; un modèle qui ne saurait que réécrire finirait par saturer. Il faut les deux.


La porte de sortie

"output_gate_type": "swish"

La sortie de la récurrence est multipliée terme à terme par la projection \(z\) passée dans une fonction swish :

\[ \mathbf{y}_t = \mathbf{o}_t \odot \operatorname{swish}(\mathbf{z}_t), \qquad \operatorname{swish}(x) = x \cdot \sigma(x) \]

où \(\odot\) est le produit terme à terme et \(\sigma\) la sigmoïde.

Rôle : permettre à la couche de supprimer sa propre sortie quand elle n'a rien d'utile à apporter pour ce jeton. Sans porte, chaque couche injecte inconditionnellement quelque chose dans le flux résiduel, ce qui bruite les couches suivantes.

C'est le même principe qui a rendu les LSTM viables il y a trente ans, remis au goût du jour.


Le décompte

Composant Paramètres
in_proj_qkvz 301 989 888
in_proj_ba 2 097 152
convolution causale 81 920
out_proj (\(16\,384 \times 8\,192\)) 134 217 728
normalisation 128
Total par couche 438 386 816
× 69 couches 30,25 G

Vérifié par le script joint.


L'état, en mémoire

Par couche et par tête : \(128 \times 128 = 16\,384\) nombres. Pour 128 têtes :

\[ 128 \times 128 \times 128 = 2\,097\,152 \text{ nombres} \]

En float32 — mamba_ssm_dtype: "float32" — soit 8,4 Mo par couche, et 0,58 Go pour les 69 couches.

Ce chiffre ne bouge jamais

Mille jetons ou un million : l'état pèse 0,58 Go. C'est la propriété qui rend le contexte long abordable, et c'est exactement l'inverse du comportement d'un cache clé-valeur.

Voir Contexte 262 K et 1 M.

Le float32 n'est pas un détail

L'état est une accumulation sur potentiellement un million de mises à jour multiplicatives. En BF16, l'erreur d'arrondi s'accumulerait jusqu'à dégrader le rappel. Qwen impose donc le float32 pour cet état précis, alors que tout le reste du modèle est en BF16.

C'est une contrainte de mise en œuvre à connaître si l'on réimplémente.


À retenir

Ce chapitre en cinq points

  1. Une couche DeltaNet coûte 438 M de paramètres, dont 69 % dans la seule projection d'entrée.
  2. La dissymétrie 16 têtes de clé / 128 têtes de valeur réduit l'état récurrent d'un facteur 8.
  3. Une convolution causale de noyau 4 fournit une mémoire locale exacte en complément de l'état approximatif.
  4. La récurrence combine décroissance \(\alpha\) et règle delta \(\beta\) — oublier globalement, remplacer précisément.
  5. L'état pèse 0,58 Go au total, indépendamment de la longueur, et est maintenu en float32.

Chapitre suivant : L'attention complète gatée — les 23 couches d'ancrage.