Cinquieme tranche d’execution de l’Epic #17540 (Russell & Norvig arc B, raisonnement internalise). Cette v3 livre :
Le module tv/task.py etendu : nouvelles fonctions lot_multi_hop_cot(), evaluer_multi_hop_cot(), entrainer_cot(), entrainer_multi_seed_cot() qui implementent la supervision CoT (chain-of-thought) sur la tache multi-sauts.
Comparaison multi-seed (4 graines) answer-only vs CoT supervisée sur multi-sauts 3 sauts : mesure discriminante de l’internalisation du raisonnement (cf Huang et al. 2026).
Cycle de mesure complet : entrainement CoT avec perte supervisée sur la chaine entiere (chaque token PAS/RECAP_j/cible doit etre predit correctement).
Pourquoi cette v3
v1 (c.808) posait le discriminant H.2 sur 1 graine. v2 (c.811) etendait a 4 graines et validait le protocole PR review-discipline §C (edge >= 2sigma cross-seed, 17.2sigma). Cette v3 livre la COMPARAISON discriminante : réponse only vs CoT supervisée, les deux sur la même architecture MHA 64-dim.
Mesure v4 : la chaine v3 etait constante (aucune information d’entree) ; la v4 introduit les taches d’indirection (lectures dependantes) ou la chaine recite des valeurs dependantes de l’entree – le discriminant honnete reste negatif a cette echelle (rapports 0.910 et 0.809, cf. bilan section 9).
Ce qui n’est PAS dans cette tranche
Lecture SAE des representations internes (autre lane po-2027:CoursIA).
Modeles plus grands (la discrimination MHA 64-dim est suffisante pour la preuve de concept).
Optimisation du vocabulaire CoT (les jetons PAS/RECAP_j sont des placeholders pedagogiques ; un tokenization appris est un autre grain).
import mathimport sysimport timeimport torchimport torch.nn.functional as Fsys.path.insert(0, '.')from tv import ( # noqa: E402 PetitLM, Vocab, evaluer_single_hop, evaluer_multi_hop, evaluer_multi_hop_cot, entrainer, entrainer_multi_seed, entrainer_cot, entrainer_multi_seed_cot,)print(f'torch {torch.__version__} | python {sys.version_info.major}.{sys.version_info.minor}')print('package tv charge OK : single-hop + multi-hop + multi-hop-CoT + multi-seed')
torch 2.13.0+cu126 | python 3.13
package tv charge OK : single-hop + multi-hop + multi-hop-CoT + multi-seed
CoT supervise (v3) : entrainer_multi_seed_cot avec max_q=3 (cible finale + chaine PAS/RECAP)
Tell c.1493 strict fondateur nuance : la mesure CoT supervise la chaine entiere via une perte massee par item (les jetons au-dela du q_idx reel sont ignores ; voir entrainer_cot dans tv/task.py).
Compare answer-only (multi-hop) vs CoT supervisée (multi-hop + chain-of-thought) sur la même architecture MHA 64-dim et 4 graines communes.
Corrections de mesure appliquees en v3
L’exactitude CoT mesuree en cellule 11 etait 0.0000 +/- 0.0000 sur 4 graines (ppl = 17 994). Deux defauts de tv/task.py en sont la cause, corriges dans cette tranche :
evaluer_multi_hop_cot (ligne 372) : lisait modele(lot.x)[:, -1] – les logits a la derniere position predisent au-dela de la sequence. La derniere position predictive entrainee par entrainer_cot est T_seq - 2 (couvre [start_pred - 1, T - 1)). Corrige en [:, -2].
entrainer_cot (lignes 421-436) : le masque (pas_positions <= q) n’incluait jamais le slot cible. La chaine etant de taille fixe (cf lot_multi_hop_cot), la cible est toujours en fin de chaine (j = 2*max_q), quel que soit q_idx – le slot j = 2*q + 1 ne porte que RECAP_q. Le modele n’etait donc jamais entraine a produire la cible. Corrige en (pas_positions <= q) | (j == 2*max_q).
Mesure de controle (env coursia-sae, torch 2.13, CPU, 300 pas, batch 32, cf commentaire ai-01 c.5331913672) sur la tete b71ef3782b + les deux corrections :
Correctif
Graine 0
Graine 1
Lecture -1 (defaut 1)
0.0000
0.0000
Lecture -2 seule (defaut 2 reste)
0.0000
0.0000
Lecture -2 + cible dans la perte
0.4336 (ppl 3.02)
0.4219 (ppl 3.07)
Les deux corrections ensemble ramènent le CoT supervise au niveau d’answer-only (0.4019 +/- 0.0161) – la conclusion “CoT < answer-only” disparait.
Caveat pedagogique (porte par ai-01)
La chaine generee PAS, RECAP_0, PAS, RECAP_1, PAS, RECAP_2 (cf tv/task.py ligne 224 et suivantes) est constante pour tous les items : elle ne porte aucune information tirée de l’entree. Meme corrigee, la comparaison oppose answer-only a answer-only precede d’un prefixe constant. Pour tester l’internalisation du raisonnement, les jetons intermediaires doivent dependre de la sequence (par exemple, les marqueurs visites a chaque saut). C’est un autre grain.
Cette v3 delivre donc un protocole fonctionnel (CoT supervise evalue sur la position correcte + cible dans la perte), pas une mesure d’internalisation au sens fort de Huang et al. 2026. La mesure elle-même (égalite CoT ≈ answer-only) reflete le caractere trivial de la chaine, pas une absence de capacite du CoT supervise.
rapport_cot = result_cot['acc_moy'] /max(result_m['acc_moy'], 1e-9)ecart_cot = result_cot['acc_moy'] - result_m['acc_moy']# Std combine des deux moyennes (variances independantes, 4 graines chacune)std_combine = (result_m['acc_std']**2+ result_cot['acc_std']**2) **0.5print(f' Rapport CoT / answer-only = {rapport_cot:.3f} (ecart {ecart_cot:+.4f}, std combine {std_combine:.4f})')print()if ecart_cot >2* std_combine:print('Conclusion : CoT supervisee > answer-only au-dela de 2*std combine.')print('La supervision de la chaine aide le modele au-dela du bruit de mesure.')elif ecart_cot <-2* std_combine:print('Conclusion : CoT supervisee < answer-only au-dela de 2*std combine.')print('Surprenant -- generer la chaine entiere en 300 pas est difficile. Investiguer.')else:print("Conclusion : egalite answer-only / CoT supervisee a l'interieur du bruit.")print("Cohérent avec le caveat de la cellule précédente : la chaîne supervisée est constante")print("(préfixe fixe, aucune information d'entrée), donc la comparaison oppose answer-only à")print("answer-only précédé d'un préfixe constant -- le protocole v3 valide la boucle CoT,")print("pas une internalisation du raisonnement.")
Rapport CoT / answer-only = 1.032 (ecart +0.0127, std combine 0.0303)
Conclusion : egalite answer-only / CoT supervisee a l'interieur du bruit.
Cohérent avec le caveat de la cellule précédente : la chaîne supervisée est constante
(préfixe fixe, aucune information d'entrée), donc la comparaison oppose answer-only à
answer-only précédé d'un préfixe constant -- le protocole v3 valide la boucle CoT,
pas une internalisation du raisonnement.
La chaîne supervisée de la v3 était constante (préfixe PAS/RECAP_j identique pour tout item) : elle ne portait aucune information d’entrée, et la comparaison mesurait answer-only contre answer-only précédé d’un préfixe constant (rapport 1.032, section 6). La v4 introduit une tâche où la chaîne est informative :
Tâche : [M_0, M_1, M_2, M_3, remplissage, QUESTION_IND, REQUETE]. La valeur de M_0 est un pointeurv ; la cible est la valeur du marqueur en position v. Deux lectures dépendantes : la seconde dépend du résultat de la première (et pas du seul jeton QUESTION, cf. multi-sauts section 3).
Chaîne CoT : le modèle récite vpuis la cible — chaque token de chaîne est une valeur dépendante de l’entrée, supervisée par entropie croisée. Le pas intermédiaire porte une information : c’est le critère que la chaîne v3 ne satisfaisait pas.
Hasard : 1/8 = 0.125 (la cible est une valeur de marqueur).
C’est le régime que Huang et al. 2026 formalisent : si la supervision de la chaîne aide à composer les lectures, l’écart CoT/answer-only doit se matérialiser. La mesure ci-dessous tranche.
from tv import ( question_indirection, vocab_indirection, entrainer_indirection_multi_seed, entrainer_indirection_cot_multi_seed,)v_ind = Vocab(N_MARQUEURS=8, N_REMPLISSAGE=10, N_QUESTIONS=3) # Q = N_QUESTIONS + 1 = 4 marqueursT_IND =64# answer-only : meme longueur que le regime v3 (T=64)T_IND_COT = T_IND +2# + chaine [v, cible]def fabrique_ind(vocab):"""MHA vierge sur le vocabulaire etendu (+1 jeton QUESTION_IND)."""return PetitLM( vocab=vocab_indirection(vocab), d_model=64, n_heads=4, n_kv_heads=4, window=None, n_couches=2, )print('Indirection simple (2 lectures dependantes), 4 graines, 300 pas :')res_ind_ao = entrainer_indirection_multi_seed(fabrique_ind, v_ind, T_IND, GRAINES)print(f" answer-only : acc = {res_ind_ao['acc_moy']:.4f} +/- {res_ind_ao['acc_std']:.4f}"f" (ppl {res_ind_ao['ppl_moy']:.3f}, hasard 0.125, {res_ind_ao['secondes']:.1f} s)")res_ind_cot = entrainer_indirection_cot_multi_seed(fabrique_ind, v_ind, T_IND_COT, GRAINES)print(f" CoT [v, cible] : acc = {res_ind_cot['acc_moy']:.4f} +/- {res_ind_cot['acc_std']:.4f}"f" (pas v = {res_ind_cot['pas_moy'][0]:.4f}, {res_ind_cot['secondes']:.1f} s)")rapport_ind = res_ind_cot['acc_moy'] / res_ind_ao['acc_moy']print(f" RAPPORT CoT / answer-only = {rapport_ind:.3f}")for g, acc, ppl, sec in res_ind_ao['brut']:print(f" ao graine {g}: acc={acc:.4f} ppl={ppl:.3f}")for g, acc, pas, sec in res_ind_cot['brut']:print(f" cot graine {g}: acc={acc:.4f} pas={[round(p, 4) for p in pas]}")
8. Double indirection — trois lectures dépendantes
Escalade principée : si la composition de deux lectures reste dans la capacité d’un seul forward (section 7), trois lectures la dépassent-elles ? M_0 pointe v ; le marqueur M_v pointe à son tour w ; la cible est M_w. Les valeurs de pointeur sont contraintes à [1, Q) : chaque indice émis désigne une position valide et la cible n’est jamais le porteur lui-même. La chaîne récite [v, w, cible] — deux pas intermédiaires informatifs.
Double indirection (3 lectures dependantes), 4 graines, 300 pas :
answer-only : acc = 0.8279 +/- 0.0251 (ppl 1.534, 23.8 s)
CoT [v, w, cible] : acc = 0.6694 +/- 0.0367 (pas v = 0.9995, pas w = 0.9023, 25.0 s)
RAPPORT CoT / answer-only = 0.809
ao graine 0: acc=0.8555 ppl=1.480
ao graine 1: acc=0.7900 ppl=1.665
ao graine 7: acc=0.8213 ppl=1.529
ao graine 42: acc=0.8447 ppl=1.464
cot graine 0: acc=0.6855 pas v=0.9980 w=0.9170
cot graine 1: acc=0.7031 pas v=1.0000 w=0.9199
cot graine 7: acc=0.6816 pas v=1.0000 w=0.8857
cot graine 42: acc=0.6074 pas v=1.0000 w=0.8867
9. Bilan tranche v4 — la séparation ne se reproduit pas (résultat négatif mesuré)
Contrairement à l’attente du régime de Huang et al. 2026, la chaîne informative n’aide pas — sur les deux tâches, multi-seed (4 graines, 300 pas, hasard 0.125) :
Tâche
answer-only
CoT informatif
pas intermédiaires
Rapport
Indirection (2 lectures)
0.7043 ± 0.0144
0.6406 ± 0.0207
v : 0.9985
0.910
Double indirection (3 lectures)
0.8279 ± 0.0251
0.6694 ± 0.0367
v : 0.9995, w : 0.9023
0.809
Trois lectures :
La supervision de chaîne fonctionne : le pas v est appris quasi parfaitement (0.9985-0.9995), w à 0.90 en double — la chaîne informative est bien produite, contrairement à la chaîne constante v3.
Answer-only compose les lectures en un seul forward (0.70-0.83) : le transformer jouet à 2 couches n’est pas dépassé par la composition à cette échelle (Q = 4 positions, valeurs de pointeur dans [1, 4)). La double indirection est même mieux résolue que la simple — l’espace de pointeurs restreint rend la tâche partiellement plus facile, pas plus difficile.
Répiter v avant de répondre ne rend pas la lecture positionnelle plus facile (rapports 0.809-0.910, en dessous de 1) : réciter la valeur du pointeur n’économise pas le binding valeur→position que la réponse finale exige toujours.
Cause de design (mesurée) : chaque étape de la chaîne embarque le même binding valeur→position que la composition directe — la chaîne ne décompose pas le calcul difficile en étapes plus simples. La séparation de Huang exige une tâche dont chaque pas de chaîne est individuellement plus simple que la composition complète ; la famille marqueur/pointeur ne satisfait pas cette précondition, et c’est une mesure, pas une intuition.
Ce que ce carnet démonture honnêtement : le protocole complet (supervision de chaîne informative, multi-seed, témoin négatif answer-only) est en place et falsifiable ; le résultat sur ce régime jouet est l’absence de séparation, mesurée deux fois sur deux designs distincts (chaîne constante v3, chaîne informative v4). Une tranche suivante qui viserait la séparation devrait construire une famille de tâches à pas strictement plus simples (par exemple copies locales chaînées) plutôt qu’escalader la profondeur d’indirection.
Ancre first-hand (Tell c.1493 strict fondateur nuance strict) de l’invariant documente dans la docstring de tv/model.py. TV-00b cellules 16 et 31 mesurent ~1.2e-07 ; le selfcheck ici produit max_abs_diff = 1.788e-07 (global, W=8) avec [W=8: 1.788e-07, W=16: 1.490e-07, W=32: 1.192e-07] – une mesure du même ordre de grandeur que TV-00b (1.2e-07), l’ecart W=8 vs W=32 tenant a la discretisation fp32 cumulee dans le produit QK^T. Seed 42, T=64, dh=64, W in (8, 16, 32). Verdict : passed = max_abs_diff <= 1e-5.
import syssys.path.insert(0, '.')from tv import selfcheck_attn_equivalencer = selfcheck_attn_equivalence()print(f"max_abs_diff global : {r['max_abs_diff']:.3e}")print(f"passed : {r['passed']}")for s in r['samples']:print(f" W={s['window']:>3d} : max_abs_diff = {s['max_abs_diff']:.3e}")assert r['passed'], f"selfcheck rate : {r['max_abs_diff']:.3e} > tol {r['tol']}"
Ce carnet présente la méthode et les résultats : Chain-of-Thought supervisée multi-graines, comparaison réponse-seule contre CoT, et vérification d’invariant (selfcheck attn_banded égal attn_masked). Il fait partie de la série TransformerVariants, qui alterne entre carnets d’exercices (TV-01, TV-02) et carnets de démonstration (TV-03, TV-04).
Pas de section d’exercices dans ce carnet : c’est un choix de série. Pour pratiquer, reprendre les carnets TV-01 et TV-02 de la même série, ou la série SelfCheck qui détaille le mécanisme de vérification.
Les sorties des cellules de code sont commitées et exploitables telles quelles.