TV-03 – Internalisation du raisonnement (CoT -> calcul interne) – v4 chaines CoT informatives (indirection)

Cinquieme tranche d’execution de l’Epic #17540 (Russell & Norvig arc B, raisonnement internalise). Cette v3 livre :

  1. 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.
  2. 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).
  3. 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.

  1. 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 math
import sys
import time

import torch
import torch.nn.functional as F

sys.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

1. Vocabulaires

Deux vocabulaires de même structure :

  • Single-hop : N_MARQUEURS=8, N_REMPLISSAGE=10, N_QUESTIONS=1 (le QUESTION(0) trivial). Vocab total 20 tokens.
  • Multi-sauts : N_MARQUEURS=8, N_REMPLISSAGE=10, N_QUESTIONS=3 (la cible est le q-ième marqueur, avec q choisi uniformément sur [0, 3)). Vocab total 22 tokens.

Hasard exactitude : 1/8 dans les deux cas (la cible est l’un des 8 marqueurs ; le mécanisme à apprendre est la sélection conditionnelle, pas la mémorisation).

T = 64  # longueur de sequence commune aux deux taches
T_COT = T + 7  # 7 = 2*max_q+1 = 2*3+1 (chaine CoT)
v_single = Vocab(N_MARQUEURS=8, N_REMPLISSAGE=10, N_QUESTIONS=1)
v_multi = Vocab(N_MARQUEURS=8, N_REMPLISSAGE=10, N_QUESTIONS=3)

MAX_Q = 3  # q_idx in [0, 3) -- borne sup pour le vocabulaire CoT
VOCAB_COT = v_multi.VOCAB + 1 + MAX_Q  # VOCAB + JETON_PAS + MAX_Q jetons de recap

def fabrique(vocab):
    """Fabrique un MHA vierge pour taches sans CoT (VOCAB standard)."""
    return PetitLM(
        vocab=vocab.VOCAB,
        d_model=64,
        n_heads=4,
        n_kv_heads=4,
        window=None,
        n_couches=2,
    )

def fabrique_cot(vocab):
    """Fabrique un MHA vierge pour tache CoT (VOCAB etendu avec PAS + RECAP)."""
    return PetitLM(
        vocab=VOCAB_COT,
        d_model=64,
        n_heads=4,
        n_kv_heads=4,
        window=None,
        n_couches=2,
    )

print(f'Vocab single-hop : VOCAB={v_single.VOCAB}, N_QUESTIONS={v_single.N_QUESTIONS}')
print(f'Vocab multi-sauts : VOCAB={v_multi.VOCAB}, N_QUESTIONS={v_multi.N_QUESTIONS}')
print(f'VOCAB_COT (multi-sauts + CoT) : {VOCAB_COT} (= {v_multi.VOCAB} + 1 JETON_PAS + {MAX_Q} jetons RECAP)')
print(f'Hasard exactitude : 1 / {v_single.N_MARQUEURS} = {1/v_single.N_MARQUEURS:.4f}')
Vocab single-hop : VOCAB=20, N_QUESTIONS=1
Vocab multi-sauts : VOCAB=22, N_QUESTIONS=3
VOCAB_COT (multi-sauts + CoT) : 26 (= 22 + 1 JETON_PAS + 3 jetons RECAP)
Hasard exactitude : 1 / 8 = 0.1250

2. Mesure multi-seed single-hop (4 graines, 300 pas)

Single-hop est trivialement resolue par MHA 102K params : on s’attend a 1.0000 +/- ~0 sur 4 graines.

GRAINES = [0, 1, 7, 42]
PAS = 300

result_s = entrainer_multi_seed(
    fabrique, v_single, T=T, multi_hop=False, graines=GRAINES, pas=PAS
)
print(f'SINGLE-HOP multi-seed ({len(GRAINES)} graines, {PAS} pas) :')
print(f'  EXACTITUDE = {result_s["acc_moy"]:.4f} +/- {result_s["acc_std"]:.4f}  (hasard = 0.1250)')
print(f'  PERPLEXITE = {result_s["ppl_moy"]:.4f} +/- {result_s["ppl_std"]:.4f}  (hasard = 8.0)')
print(f'  Secondes total = {result_s["secondes"]:.2f} s')
for graine, acc, ppl, sec in result_s["brut"]:
    print(f'    graine {graine} : acc={acc:.4f} ppl={ppl:.4f} sec={sec:.2f}')
SINGLE-HOP multi-seed (4 graines, 300 pas) :
  EXACTITUDE = 1.0000 +/- 0.0000  (hasard = 0.1250)
  PERPLEXITE = 1.0016 +/- 0.0001  (hasard = 8.0)
  Secondes total = 35.11 s
    graine 0 : acc=1.0000 ppl=1.0017 sec=8.87
    graine 1 : acc=1.0000 ppl=1.0015 sec=7.90
    graine 7 : acc=1.0000 ppl=1.0015 sec=7.82
    graine 42 : acc=1.0000 ppl=1.0015 sec=7.88

3. Mesure multi-seed multi-sauts (4 graines, 300 pas)

Multi-sauts 3 questions : le discriminant H.2 doit tenir sur 4 graines (moyenne significativement au-dessus du hasard, sans atteindre 1.0).

result_m = entrainer_multi_seed(
    fabrique, v_multi, T=T, multi_hop=True, graines=GRAINES, pas=PAS
)
print(f'MULTI-SAUTS multi-seed ({len(GRAINES)} graines, {PAS} pas, 3 sauts) :')
print(f'  EXACTITUDE = {result_m["acc_moy"]:.4f} +/- {result_m["acc_std"]:.4f}  (hasard = 0.1250)')
print(f'  PERPLEXITE = {result_m["ppl_moy"]:.4f} +/- {result_m["ppl_std"]:.4f}  (hasard = 8.0)')
print(f'  Secondes total = {result_m["secondes"]:.2f} s')
for graine, acc, ppl, sec in result_m["brut"]:
    print(f'    graine {graine} : acc={acc:.4f} ppl={ppl:.4f} sec={sec:.2f}')
MULTI-SAUTS multi-seed (4 graines, 300 pas, 3 sauts) :
  EXACTITUDE = 0.4019 +/- 0.0161  (hasard = 0.1250)
  PERPLEXITE = 2.9156 +/- 0.0943  (hasard = 8.0)
  Secondes total = 32.51 s
    graine 0 : acc=0.3945 ppl=2.9109 sec=8.01
    graine 1 : acc=0.4180 ppl=3.0707 sec=8.00
    graine 7 : acc=0.4160 ppl=2.8310 sec=8.06
    graine 42 : acc=0.3789 ppl=2.8499 sec=8.03

4. Bilan tranche v2

Multi-seed >=4 sur single-hop vs multi-sauts – discriminant H.2 mesure sur 4 graines.

print('BILAN multi-seed :')
print(f'  single-hop : {result_s["acc_moy"]:.4f} +/- {result_s["acc_std"]:.4f}')
print(f'  multi-sauts : {result_m["acc_moy"]:.4f} +/- {result_m["acc_std"]:.4f}')
rapport = result_m["acc_moy"] / result_s["acc_moy"] if result_s["acc_moy"] > 0 else float("inf")
print(f'  rapport multi/single = {rapport:.3f}')
print(f'  secondes total (4 graines single + 4 graines multi) = {result_s["secondes"] + result_m["secondes"]:.2f} s')
print()
if result_s['acc_moy'] >= 0.99 and result_m['acc_moy'] < result_s['acc_moy']:
    print('Conclusion : Single-hop trivialement resolu (>=0.99) ; multi-sauts plus bas avec >=4 graines.')
    print('Discriminant H.2 multi-seed : OK (single >> multi, multi > hasard).')
else:
    print(f'ATTENTION : single-hop = {result_s["acc_moy"]:.4f}, multi-sauts = {result_m["acc_moy"]:.4f}.')
    print('Le discriminant H.2 n est PAS clairement tenu. Investiguer la graine fautive.')
BILAN multi-seed :
  single-hop : 1.0000 +/- 0.0000
  multi-sauts : 0.4019 +/- 0.0161
  rapport multi/single = 0.402
  secondes total (4 graines single + 4 graines multi) = 67.62 s

Conclusion : Single-hop trivialement resolu (>=0.99) ; multi-sauts plus bas avec >=4 graines.
Discriminant H.2 multi-seed : OK (single >> multi, multi > hasard).

5. Comparaison CoT supervisée vs answer-only (4 graines, 300 pas)

Le discriminant central du programme #17540 : la supervision CoT ameliore-t-elle significativement la memorisation multi-sauts ?

  • answer-only (v2 cellule 7) : entrainer_multi_seed avec multi_hop=True (cible = marqueur direct)
  • 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).

result_cot = entrainer_multi_seed_cot(
    fabrique_cot, v_multi, T_cot=T_COT, graines=GRAINES, pas=PAS, batch=32, max_q=MAX_Q
)
print(f'CoT multi-seed ({len(GRAINES)} graines, {PAS} pas, 3 sauts) :')
print(f'  EXACTITUDE = {result_cot["acc_moy"]:.4f} +/- {result_cot["acc_std"]:.4f}  (hasard = 0.1250)')
print(f'  PERPLEXITE = {result_cot["ppl_moy"]:.4f} +/- {result_cot["ppl_std"]:.4f}  (hasard = 8.0)')
print(f'  Secondes total = {result_cot["secondes"]:.2f} s')
for graine, acc, ppl, sec in result_cot["brut"]:
    print(f'    graine {graine} : acc={acc:.4f} ppl={ppl:.4f} sec={sec:.2f}')
CoT multi-seed (4 graines, 300 pas, 3 sauts) :
  EXACTITUDE = 0.4146 +/- 0.0257  (hasard = 0.1250)
  PERPLEXITE = 3.0279 +/- 0.0852  (hasard = 8.0)
  Secondes total = 35.22 s
    graine 0 : acc=0.4453 ppl=2.9318 sec=8.79
    graine 1 : acc=0.4121 ppl=3.1603 sec=8.68
    graine 7 : acc=0.4258 ppl=3.0383 sec=8.54
    graine 42 : acc=0.3750 ppl=2.9810 sec=8.78

6. Bilan tranche v3 – comparaison discriminante

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 :

  1. 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].
  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.5
print(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.

7. Tâche d’indirection — chaîne CoT informative (v4)

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 pointeur v ; 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 v puis 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 marqueurs
T_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]}")
Indirection simple (2 lectures dependantes), 4 graines, 300 pas :
  answer-only    : acc = 0.7043 +/- 0.0144  (ppl 2.060, hasard 0.125, 28.5 s)
  CoT [v, cible] : acc = 0.6406 +/- 0.0207  (pas v = 0.9985, 25.9 s)
  RAPPORT CoT / answer-only = 0.910
    ao  graine 0: acc=0.6885 ppl=2.092
    ao  graine 1: acc=0.6953 ppl=2.049
    ao  graine 7: acc=0.7266 ppl=1.849
    ao  graine 42: acc=0.7070 ppl=2.252
    cot graine 0: acc=0.6191 pas=[0.999]
    cot graine 1: acc=0.6650 pas=[0.9961]
    cot graine 7: acc=0.6572 pas=[1.0]
    cot graine 42: acc=0.6211 pas=[0.999]

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.

T_IND_COT2 = T_IND + 3  # + chaine [v, w, cible]

print('Double indirection (3 lectures dependantes), 4 graines, 300 pas :')
res_dbl_ao = entrainer_indirection_multi_seed(fabrique_ind, v_ind, T_IND, GRAINES, double=True)
print(f"  answer-only       : acc = {res_dbl_ao['acc_moy']:.4f} +/- {res_dbl_ao['acc_std']:.4f}"
      f"  (ppl {res_dbl_ao['ppl_moy']:.3f}, {res_dbl_ao['secondes']:.1f} s)")
res_dbl_cot = entrainer_indirection_cot_multi_seed(fabrique_ind, v_ind, T_IND_COT2, GRAINES, double=True)
print(f"  CoT [v, w, cible] : acc = {res_dbl_cot['acc_moy']:.4f} +/- {res_dbl_cot['acc_std']:.4f}"
      f"  (pas v = {res_dbl_cot['pas_moy'][0]:.4f}, pas w = {res_dbl_cot['pas_moy'][1]:.4f},"
      f" {res_dbl_cot['secondes']:.1f} s)")
rapport_dbl = res_dbl_cot['acc_moy'] / res_dbl_ao['acc_moy']
print(f"  RAPPORT CoT / answer-only = {rapport_dbl:.3f}")
for g, acc, ppl, sec in res_dbl_ao['brut']:
    print(f"    ao  graine {g}: acc={acc:.4f} ppl={ppl:.3f}")
for g, acc, pas, sec in res_dbl_cot['brut']:
    print(f"    cot graine {g}: acc={acc:.4f} pas v={pas[0]:.4f} w={pas[1]:.4f}")
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 :

  1. 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.
  2. 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.
  3. 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.

10. Selfcheck invariant attn_banded ≡ attn_masked

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 sys
sys.path.insert(0, '.')
from tv import selfcheck_attn_equivalence

r = 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']}"
max_abs_diff global : 1.788e-07
passed : True
  W=  8 : max_abs_diff = 1.788e-07
  W= 16 : max_abs_diff = 1.490e-07
  W= 32 : max_abs_diff = 1.192e-07

11. Statut du carnet

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.

Retour au sommet