weight-space-programming

Améliorez un petit LLM open source (ex. Qwen3.5-0.8B) en modifiant directement ses poids, sans entraînement (« programmation dans l’espace des poids »). Presets prêts pour les maths (MATH-500) et les sciences (MMLU-Pro) ; tout autre benchmark ou comportement fonctionne à partir de vos propres prompts et paires d’exemples adaptés (format, langage, verbosité, sycophance, boucles). Trouve la direction qui sépare le comportement indésirable du souhaité, édite les colonnes down_proj du MLP qui l’écrivent dans une copie du modèle sur disque, filtre.

Qu'est-ce que weight-space-programming ?

Cette compétence permet d’améliorer un petit modèle de langage open source en modifiant directement ses poids, sans aucune optimisation ou entraînement. Elle implémente l’« expérience 8 » de l’article « Sparks of Weight-Space Programming » du MIT CSAIL (2026) où, par exemple, Qwen3.5-0.8B a vu son score MATH-500 passer de 7,9 % à 48,1 % grâce à une seule édition de rang 1 sur 64 colonnes down_proj. Le processus fonctionne en trouvant la direction qui sépare un comportement indésirable (par exemple des boucles de pensée infinies) du comportement souhaité, en identifiant les colonnes du MLP qui l’écrivent, et en les éditant dans une copie persistante du modèle sur disque. La compétence propose des presets clés en main pour les mathématiques et les sciences, mais s’adapte à n’importe quel objectif défini par l’utilisateur via des paires d’exemples et ses propres prompts. Elle garantit un processus robuste : vérification intermédiaire sur un sous-ensemble pratique, escalade en cas d’échec, et verdict clair à la fin. Elle est conçue pour fonctionner avec Claude Code, Cursor, Codex et d’autres agents, et s’exécute sur GPU avec des scripts Python prêts à l’emploi.

Compatible avec✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/di37/weight-space-programming-skill/tree/HEAD

Installed? Explore more Développement et programmation skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Programmation dans l'espace des poids

Améliorez un petit modèle open source pour un objectif en modifiant directement ses poids, sans optimiseur. Cela suit l'Expérience 8 de « Sparks of Weight-Space Programming » (Takehi, Teoh, Zhang, Khattab, MIT CSAIL, 2 nov. 2026) : les boucles de pensée de Qwen3.5-0.8B ont été attribuées à une direction résiduelle unique écrite par les MLP des couches 15 et 19, et une édition de rang 1 sur 64 colonnes down_proj par couche a fait passer MATH-500 de 7,9 % à 48,1 %.

Suivez le chemin rapide ci-dessous dans l'ordre. Chaque script est fourni dans scripts/ et testé. Ne reconcevez pas le pipeline ni n'écrivez de nouvel outil sauf si une étape échoue.

Ce que la compétence garantit, et ce qu'elle ne peut pas. Aucune méthode ne peut promettre qu'une édition aide : certains modèles n'ont pas d'habitude unique à désactiver. La compétence garantit plutôt le processus :

  • pas de travail perdu : les plantages redémarrent avec un lot plus petit et reprennent ;
  • aucune édition n'atteint le test réservé sans passer une vérification mesurée sur la pratique ;
  • un échec de vérification escalade à travers des étapes fixes au lieu de s'arrêter ;
  • chaque exécution se termine par un verdict clair et des chiffres, et la copie est restaurée si rien ne fonctionne.

Références, pour le détail uniquement :

  • references/goals.md : autres objectifs ; comment écrire des paires adaptées ; correcteurs ; recettes.
  • references/engines.md : quel moteur sur quel OS et GPU, installation, dimensionnement, pièges, test de fumée.
  • references/evaluation.md : divisions, protocole d'échantillonnage, spécificités de MMLU-Pro, statistiques appariées, modèle de rapport.
  • references/method.md : les mathématiques de chaque étape et la recette exacte des auteurs.

Chemin rapide

Définissez quatre variables une fois (PowerShell : $env:SKILL = "...", puis $env:SKILL) :

SKILL=<dossier de cette compétence>  BASE=<dossier du modèle de base>  COPY=<dossier du modèle de base>-<objectif>  WORK=<dossier des résultats>

Exécutez chaque travail de génération en arrière-plan (Claude Code : l'outil Bash avec run_in_background, pas un & du shell) et attendez sa notification d'achèvement au lieu de scruter. La génération domine le temps, alors informez l'utilisateur du budget à l'avance : 1,5 à 3 heures sur un GPU grand public pour les presets, beaucoup moins avec vLLM sur un GPU de centre de données.

0. Ne demandez que l'essentiel (une question). Demandez l'objectif. S'il s'agit de maths ou de sciences, utilisez le preset : pas d'autres questions. Sinon, demandez également, ou aidez à écrire :

  • des prompts qui montrent l'échec, sous forme de lignes JSON {"id", "prompt", "answer"?, "group"?} ;
  • comment les noter (references/goals.md section 3) ;
  • pour les comportements autres que les boucles, 12+ paires adaptées (section 2).

Confirmez « éditer les poids, pas d'entraînement ».

1. Vérifiez la machine (2 min) : python $SKILL/scripts/device.py --model $BASE --jobs 2. Sur une nouvelle machine, exécutez également le test de fumée dans references/engines.md section 6.

2. Copiez le modèle (1 min). Chaque édition va dans les fichiers de cette copie ; chaque évaluation charge la copie.

python $SKILL/scripts/copy_model.py --base $BASE --copy $COPY

3. Divisions (secondes).

  • Preset sciences : python $SKILL/scripts/make_splits.py --task mmlu --source <MMLU-Pro test parquet> --out $WORK/splits.
  • Preset maths : le même avec --task math --source <MATH-500 test.jsonl> --practice 25 --heldout 75.
  • Objectif personnel : divisez votre fichier de prompts en practice.jsonl et heldout.jsonl selon une règle fixe, plus un test_ids.txt d'environ 30 identifiants réservés, tous choisis avant qu'aucune réponse ne soit vue.

Ci-dessous, T est la tâche (mmlu, math ou jsonl) et G est les options du correcteur (aucune pour les presets ; voir references/goals.md sinon).

4. Lancez deux exécutions de base en même temps :

python $SKILL/scripts/run_eval.py --task $T --model $BASE --label base --data $WORK/splits/practice.jsonl --out $WORK/practice/base.jsonl --max-tokens 16384 --jobs 2
python $SKILL/scripts/run_eval.py --task $T --model $BASE --label base --data $WORK/splits/heldout.jsonl --ids-file $WORK/splits/test_ids.txt --out $WORK/test/base.jsonl --jobs 2

Utilisez l'échantillonnage de la fiche du modèle pour d'autres modèles (--profile, ou les indicateurs de remplacement ; references/engines.md). L'exécution réservée ne dépend pas de l'édition.

5. Obtenez le contraste.

  • Boucles (presets, ou tout objectif où les réponses atteignent la limite) : les boucles n'apparaissent que lorsqu'une réponse atteint la limite. Comptez-les avec grep -c '"length"' $WORK/practice/base.jsonl ; à 15 ou plus, arrêtez l'exécution pratique.
  • Autres comportements : vous avez besoin des paires, et l'exécution pratique doit seulement finir pour donner la référence.

Ensuite, corrigez :

python $SKILL/scripts/grade.py --task $T $G $WORK/practice/base.jsonl --out $WORK/practice/summary.json

Go/no-go. Estimez la marge de gain : la part de réponses montrant le comportement, multipliée par le score sur les réponses qui ne le montrent pas. Si elle est inférieure à environ 10 %, dites à l'utilisateur de s'attendre à peu avant de continuer.

6. Lisez la direction, construisez les candidats, filtrez (minutes, passages avant uniquement). Boucles :

python $SKILL/scripts/probe.py collect --task $T --model $BASE --data $WORK/splits/practice.jsonl --runs $WORK/practice/base.jsonl --out $WORK/probes

Paires (ajoutez --runs, --data et --task pour des fenêtres d'effets secondaires propres) :

python $SKILL/scripts/probe.py collect-pairs --model $BASE --pairs pairs.jsonl --thinking on --out $WORK/probes

Ensuite, construisez les candidats du premier tour, le contrôle, et filtrez :

for spec in "15 19:128:6" "15 19:256:6" "15 19:64:6" "11 15 17 19 21:128:6"; do IFS=: read L K A <<< "$spec"; python $SKILL/scripts/probe.py build --probes $WORK/probes --model $BASE --layers $L --k $K --alpha $A --out "$WORK/edits/L${L// /-}_k${K}_a${A}.safetensors"; done
python $SKILL/scripts/probe.py build --probes $WORK/probes --model $BASE --layers 15 19 --k 128 --alpha 6 --control random --out $WORK/edits/random.safetensors
python $SKILL/scripts/probe.py screen --probes $WORK/probes --model $BASE --edits $WORK/edits/*.safetensors

Sur Windows PowerShell, exécutez chaque ligne build individuellement. Les couches ci-dessus conviennent à un modèle à 24 couches. Pour d'autres, utilisez les couches à environ 60 % et 80 % de la profondeur, une propagation sur cinq couches, et la plus profonde seule.

Classez les candidats par la baisse sur unwanted, en ne gardant que ceux dont les baisses wanted et clean sont inférieures au tiers de celle-ci. Le contrôle aléatoire doit rester proche de 0 ; s'il bouge autant que le meilleur candidat, la direction n'est pas spécifique, donc allez à l'étape d'arrêt.

7. Écrivez le meilleur candidat dans la copie et vérifiez (1 min). N'évaluez jamais une édition en mémoire.

python $SKILL/scripts/apply_edit.py --base $BASE --copy $COPY --edit $WORK/edits/<candidat>.safetensors
python $SKILL/scripts/diff_edit.py --base $BASE --edited $COPY --out $WORK/edit_report.json

La différence doit montrer seulement les tenseurs down_proj prévus, changed_columns = k, une part de rang 1 d'environ 1 et un α médian d'environ le α choisi.

8. Vérification pratique : la porte (15–25 min, parallèlement à l'exécution réservée de base). Exécutez la copie sur chaque question pratique que la base a échoué (coupée, fausse ou signalée), plus 6 qu'elle a bien traitées :

python -c "import json; g=json.load(open('$WORK/practice/summary.json'))['graded']['base']; bad=[x['id'] for x in g if x['cut'] or x['correct'] is False or x['flagged']]; ok=[x['id'] for x in g if x['id'] not in bad]; open('$WORK/check_ids.txt','w').write('\n'.join(bad+ok[::max(1,len(ok)//6)][:6])+'\n')"
python $SKILL/scripts/run_eval.py --task $T --model $COPY --label copy --data $WORK/splits/practice.jsonl --ids-file $WORK/check_ids.txt --out $WORK/practice/copy.jsonl --max-tokens 16384 --jobs 2
python $SKILL/scripts/grade.py --task $T $G $WORK/practice/base.jsonl $WORK/practice/copy.jsonl

La porte passe seulement si les trois conditions sont remplies :

  • réparés ≥ max(3, 20 % des échecs de base vérifiés),
  • cassés ≤ réparés ÷ 4,
  • et, pour les objectifs de boucle, la part de coupures baisse d'au moins un quart.

Réussite : allez à l'étape 10. Échec : étape 9.

9. Escaladez dans cet ordre ; chaque échelon est une vérification pratique supplémentaire.

  1. Le candidat suivant dans le classement. Restaurez la copie (copy_model.py --base $BASE --copy $COPY --restore), appliquez-le, et répétez les étapes 7–8. Faites cela pour au maximum deux autres candidats.
  2. Tour 2, lorsque le comportement a bougé plutôt que disparu. Par exemple, la pensée se ferme maintenant mais la boucle redémarre après réponse, ou un nouveau motif indésirable apparaît dans les réponses de la copie. Traitez la copie éditée comme la nouvelle base :
    • créez COPY2 avec copy_model.py --base $COPY --copy $COPY2 ;
    • exécutez probe.py collect (ou collect-pairs) avec --model $COPY --runs $WORK/practice/copy.jsonl ;
    • construisez et filtrez avec --model $COPY ;
    • appliquez avec --base $COPY --copy $COPY2 ;
    • vérifiez COPY2 comme à l'étape 8.
  3. Arrêtez. Pas de gain fiable après trois vérifications (ou après le budget de temps défini par l'utilisateur). Restaurez la copie, et rapportez le verdict « pas de gain fiable » avec les chiffres de chaque candidat. Pour Qwen3.5-0.8B, proposez l'édition maths publiée comme comparaison (elle a réduit la part de coupures de MMLU-Pro de 28,9 % à 0,5 % dans les logs des auteurs ; voir la fin de ce fichier). Utilisez-la seulement si l'utilisateur est d'accord.

N'utilisez jamais les résultats réservés pour choisir ; seules les vérifications pratiques décident.

10. Test réservé à partir de la copie (30–60 min). Lancez-le lorsque l'exécution réservée de base a dépassé ses réponses les plus longues :

python $SKILL/scripts/run_eval.py --task $T --model $COPY --label copy --data $WORK/splits/heldout.jsonl --ids-file $WORK/splits/test_ids.txt --out $WORK/test/copy.jsonl --jobs 2
python $SKILL/scripts/grade.py --task $T $G $WORK/test/base.jsonl $WORK/test/copy.jsonl --out $WORK/test/summary.json

Donnez le verdict à partir du résultat apparié :

  • « Gain clair » lorsque réparés > cassés et test des signes p < 0,05.
  • « Cohérent avec un gain, non prouvé » lorsque réparés > cassés mais p ≥ 0,05 ; recommandez un test plus large.
  • « Pas de gain » lorsque réparés ≤ cassés ; recommandez de restaurer la copie.

11. Rapport avec le modèle dans references/evaluation.md :

  • le score réservé avant et après, et les comptes appariés réparés/cassés ;
  • les taux de comportement (coupures, signalés, pensée fermée) ;
  • chaque candidat avec ses chiffres de filtre et de vérification, et le contrôle aléatoire ;
  • les poids modifiés ;
  • la mise en garde : désactiver une habitude n'est pas une nouvelle capacité.

Erreurs qui coûtent des heures avant (ne pas répéter)

  • Lot trop grand, ou deux longs travaux à leurs pics. Un plantage hors mémoire tue les réponses en cours, qui sont les boucles. Utilisez --batch auto --jobs 2, et ne démarrez jamais un deuxième travail 32k alors que le premier est sur ses réponses les plus longues. Le superviseur redémarre les exécutions plantées avec la moitié du lot.
  • Attendre des exécutions entières. Environ 15 boucles suffisent.
  • Éditions en mémoire pour l'évaluation. Seule probe.py screen applique des éditions en mémoire, et seulement pour des lectures forcées par l'enseignant.
  • Faire confiance au seul filtre. Une baisse de filtre de −0,10 sur les boucles n'a cassé que 3 des 26 boucles en génération, donc exécutez toujours la porte.
  • Corriger une exécution partielle, ou choisir des questions de test par celles qui ont fini en premier.
  • Arrêter des travaux avec pkill -f <texte>, ce qui tue aussi les observateurs dont la ligne de commande contient ce texte.

Règles (comme dans l'article)

Gardez-les et dites dans le rapport que vous l'avez fait.

  • Pas d'entraînement. Pas de passages arrière, autograd, optimiseurs, ou ajustement itératif des poids à une perte. Ajuster α ou les poids jusqu'à ce qu'une métrique s'améliore compte comme ajustement. Choisir parmi la liste fixe de candidats ci-dessus non.
  • Les passages avant pour la mesure sont autorisés : génération, probabilités log forcées par l'enseignant, états cachés et activations MLP, et statistiques de ceux-ci ou des poids (moyennes, différences, SVD).
  • Hors de portée :
    • les embeddings, la tête de sortie (liée) et la norme finale (weights.py les refuse) ;
    • multiplier un tenseur entier par une constante ;
    • affinage ou aplatissement global ;
    • forcer la pensée à se fermer tôt indépendamment du problème ;
    • supprimer le comportement de sécurité : refus, sauvegardes, conformité aux demandes nuisibles.
  • Seules les valeurs des poids changent. Conservez les noms, formes, dtype, configuration, tokenizer, modèle de chat et paramètres de décodage.
  • Discipline de division. Ensembles pratique et réservé disjoints, fixes avant toute édition. Regardez les résultats réservés une fois, après que l'édition est figée.
  • Sélection divulguée. Le rapport liste chaque candidat essayé et la métrique qui a choisi le gagnant.

Pourquoi chaque étape est ainsi

  • Contraste : la direction est la différence moyenne, à la sortie de chaque couche, entre le texte indésirable et le texte souhaité qui est par ailleurs adapté.
    • Pour les boucles, loops.py prend les 1 024 tokens juste avant le début de la boucle et 1 024 tokens à l'intérieur : même question, même réponse, presque la même position. Le début est le premier point où 90 % des 512 tokens suivants répètent un 16-gramme antérieur.
    • Pour les autres comportements, les paires donnent l'adaptation.
  • Colonnes d'écriture : score_j = (moyenne indésirable − activation moyenne souhaitée du neurone j) × (u · W_down[:, j]) ; prenez les top k. L'écriture statique u·W seule ne suffit pas : seulement 21–22 des 64 colonnes publiées par couche sont dans ses top 64.
  • L'édition : W'[:, S] = W[:, S] − α u (uᵀ W[:, S]), calculé en float32 à partir du modèle en cours d'édition. α = 1 supprime la composante, ce qui n'a rien fait pour les boucles ; α = 6 l'inverse à −5c, de sorte que ces neurones écrivent contre le comportement.
  • Filtrage : le changement de probabilité log moyenne avec l'édition appliquée. Le texte indésirable devrait devenir moins probable ; le texte souhaité et propre devrait rester. Une direction aléatoire normée sur les mêmes colonnes est le contrôle.
  • La porte : les chiffres de filtre ne prédisent la génération que lâchement (voir les résultats ci-dessous), donc une courte vérification de génération sur les questions pratiques décide avant le test réservé.
  • Sauvegarde : weights.py réécrit seulement les fragments safetensors touchés à partir des fichiers bruts. Les moteurs d'inférence réécrivent ou suppriment les tenseurs au chargement, donc ne sauvegardez jamais les poids depuis un.
  • Protocole : pensée activée, température 0,6, top_p 0,95, top_k 20, pénalité de présence 1,5 sur les tokens générés uniquement, 32 768 nouveaux tokens (--profile thinking). Tous les moteurs correspondent à la sémantique de vLLM.
  • Appariement : les moteurs et les tailles de lot changent les nombres aléatoires, donc appairez par question dans une même comparaison. Ne comparez jamais des scores mesurés sur des questions différentes ou avec des moteurs différents.

R

…

[truncated for translation]

Skills associés