Rapport technique interactif · 24 août 2026

Construire une usine d’entraînement 3D, pas juste une jolie chaise.

Deux sessions Codex ont transformé une idée de GRPO pour Three.js en environnement exécutable : code paramétrique → sandbox → GLB → vues cachées → tests structurels et d’édition → récompense → mise à jour des poids.

38/38 tests TypeScript7/7 tests Python162 PNG réelsRunPod L4 validé

Le verdict en 60 secondes

Le projet répond à une question difficile : comment récompenser un programme qui produit un vrai objet 3D éditable, plutôt qu’une image qui trompe une seule caméra ?

20+1

surface ModelAPI

20 opérations de géométrie/structure, plus params. Le modèle ne contrôle ni caméra, ni rendu, ni réseau.

25

vues par artefact

Clay, beauty, angles cachés, dessous, silhouettes, normales, profondeur et wireframe.

0.2953

écart-type des rewards

Le groupe n’est ni uniforme ni saturé : il contient un signal relatif exploitable par GRPO.

La cible d’apprentissage

« Produire une construction 3D exécutable, paramétrique, correcte depuis des vues inconnues, structurellement sensée, exportable et modifiable sans dégâts collatéraux. »

État réel

Validé pipeline complet, tests, GLB, reward, OpenRouter live, update CPU + CUDA.

Non fait campagne SFT+GRPO longue et convergence d’un modèle de production.

Deux sessions, dans l’ordre

Les journaux bruts ont été relus chronologiquement. La première session construit la tranche verticale. La seconde reprend la liste de lacunes, élargit les contrats et clôt la vérification.

Départ : traduire l’idée en architecture exécutableInventaire du runtime, choix TypeScript/Three.js, Docker, Chromium, OpenRouter et RunPod.
Première verticale : ModelAPI, AST, sandbox, GLB, evaluatorL’API restreinte et la frontière de confiance existent. Les premières vues et les premiers tests détectent déjà les faux objets plans.
Pipeline initial, références, juge pairwise, training natifLe système sait constituer un groupe, scorer, écrire des rollouts et effectuer une mise à jour locale. Mais l’évidence visuelle et les rewards restent trop étroits.
Arrêt administratif, pas techniqueL’ancien goal avait un plafond arbitraire de 250 000 tokens et passe en budget_limited. Les fichiers restent intacts ; les manques sont consignés dans TODO et QUEUED_FOLLOWUP.
Continuation sans plafondAudit du travail existant, puis plan de finition en huit blocs. Le socle est conservé et les contrats sont étendus.
Passage au bundle 25 vues et reward orthogonaleAjout des vues clay/beauty/hidden/diagnostics, spécification privée, contact graph, edit tests, corrélations, stages et suite de tests complète.
Incidents récupérésVitest comptait les copies compilées ; Docker manquait d’espace ; deux premiers essais RunPod ont buté sur le champ SSH puis PEP 668. Chaque pod a été supprimé et chaque cause corrigée.
Preuves finalesSmoke complet, update local, initialisation TRL, appel Gemini live, update CUDA sur L4, récupération du checkpoint, suppression du pod et ledger final.

Architecture : où passe la confiance ?

Le principe décisif est la séparation. Le programme candidat ne voit ni les tests privés, ni les caméras cachées, ni les références. Son GLB est rechargé dans un autre processus.

Prompt public+ 4–8 programmesAST allowlistcontrat build(m)Dockerréseau coupéroot read-onlyExport GLB+ manifesteNouveau processvues cachées + tests+ editsReward 35/25/20/15/5+ matrice corrélationGRPOupdate relatif

Dans la sandbox

Seulement candidateId, le source et les overrides de paramètres. Exécution bornée, utilisateur non privilégié, réseau désactivé, filesystem en lecture seule.

Sur l’hôte privé

Spécification cachée, caméras, références, tests de contact et d’édition. Ces données ne sont jamais montées dans le conteneur candidat.

Qui fait quoi ? Tous les modèles, sans ambiguïté

Le mot « modèle » recouvre cinq rôles différents ici. Le smoke final n’a pas été généré ni jugé live par les mêmes systèmes que le chemin de production configuré.

Nom
Statut
Rôle exact
gpt-5.6-sol
utilisé
Agent Codex qui a écrit, testé et réparé le dépôt pendant les deux sessions. Ce n’est ni le policy model entraîné ni le judge de reward.
Fixtures TypeScript
smoke final
Les 4 candidats excellent/good/pedestal/billboard ont été lus depuis des fichiers déterministes. Donc aucun sampler LLM n’a généré ces quatre candidats lors du smoke final.
openai/gpt-4.1-mini
sampler par défaut
Valeur par défaut de l’OpenRouterGroupSampler pour générer des programmes en ligne. Implémenté et configurable, mais non utilisé pour les quatre fixtures du smoke final.
DeterministicPairwiseJudge
scores smoke
C’est lui qui produit le composant pairwise des quatre scores 0.9342 / 0.9359 / 0.5447 / 0.2307. Il combine évidence sémantique, validité, volumétrie et edits. C’est un test double reproductible, pas un VLM.
google/gemini-2.5-flash
test live séparé
Juge multimodal OpenRouter validé sur une comparaison candidate excellent ↔ référence craftsman. Request ID : gen-1787591814-oBYfdhessH05rFtueOy6. Cet appel ne remplace pas rétroactivement les scores smoke.
sshleifer/tiny-gpt2
preuve d’update
Petit policy model utilisé pour démontrer une vraie modification des poids, une fois en CPU local puis une fois en CUDA sur NVIDIA L4. Choisi pour la vérification, pas pour la qualité 3D.
Qwen/Qwen2.5-Coder-1.5B-Instruct
production example
Exemple de policy model dans la configuration RunPod/TRL/vLLM de production. Le pipeline est câblé, mais ce Qwen n’a pas été entraîné jusqu’à convergence dans ce projet.

La réponse directe à « quel judge model ? »

Pour les quatre rewards publiées : aucun VLM, c’est DeterministicPairwiseJudge. Pour la validation live séparée via OpenRouter : google/gemini-2.5-flash. En production, le judge OpenRouter est sélectionnable par variable d’environnement.

Comment un programme devient une reward

Le contrat de sortie

export function build(m: ModelAPI) { const p = m.params({ seatWidth: 2.2, legHeight: 2.6 }); const seat = m.roundedBox({ name: "seat", ... }); const legs = m.array({ name: "legs", ... }); return m.group("stool", [seat, legs]); }

Une seule fonction synchrone. Noms sémantiques et paramètres conservés dans le manifeste.

Les triches refusées

ShaderMaterial, sprites, canvas/data textures, imports dynamiques, fetch/XHR/WebSocket, assets externes, loaders/URLs, caméras ou renderers candidats, post-processing et branchement selon la caméra d’évaluation.

Le billboard du smoke montre pourquoi : joli de face, inexistant de profil.

Reward après hard gate

35% spécification cachée25% pairwise multi-vues20% géométrie15% éditabilité5% efficacité

Tests cachés de structure

Comptage de pièces, contacts attendus, relations spatiales, matériaux/couleurs, ratios de dimensions, bounds globaux et parties posées au sol.

Tests contrefactuels

seatWidth ×1.25, seatHeight ×0.8, seatTilt +10°, matériau du siège → plastique bleu. Le bon élément doit changer ; les autres doivent rester stables ; les contacts doivent survivre.

Résultats réels, image par image

Chaque grande grille ci-dessous provient du smoke final. Elle combine les suites de vues qui empêchent un objet de cacher ses défauts derrière une caméra flatteuse.

0.9342
passe

Excellent. Spécification 1.0, géométrie 1.0, éditabilité 1.0. Siège volumétrique, quatre pieds posés, traverses cohérentes.

0.9359
passe

Good. Plus simple et légèrement mieux classé par la combinaison exacte des composantes. Structure et edits passent.

0.5447
mismatch

Pedestal. Vrai volume exportable, mais mauvaise structure : le piédestal ne respecte pas les quatre pieds et les traverses attendues.

0.2307
anti-triche

Billboard. Une surface plane qui s’effondre vue de côté. Le bundle révèle exactement la triche qu’un screenshot unique laisserait passer.

Du test à l’entraînement

Stage 0valider le judgeavec humainsStage 1SFT syntaxe+ représentationStage 2GRPO objetsone-shotStage 3repair + feedback1–2 révisionsStage 4édition+ préservationStage 5scènes / mondeencore gated

Preuve locale

tiny-gpt2, CPU, une étape. Checksum ba0dbc… → 7cf978…. Gradient fini, paramètres changés.

TRL + vLLM

TRL 1.10.0 initialise réellement GRPOTrainer, GRPOConfig, Dataset prompt-only et reward HTTP authentifiée. C’est du wiring, pas une convergence.

Preuve RunPod

NVIDIA L4, CUDA, une étape sur le vrai rollout. ba0dbc… → 1e24c5…. Checkpoint récupéré ; pod oxzeba7ql863gm supprimé.

Combien de temps pour le modèle final ?

L’estimation doit séparer quatre horloges : calcul, constitution des données, notation humaine et itérations de recherche. Le renderer est mesuré ; la convergence ne l’est pas encore.

≈113 s

smoke end-to-end

4 candidats + 2 références, chacun avec 25 vues, edits et rapports. C’est la mesure de pipeline observée, pas le temps d’une update GPU.

6.5–7 s

rendu pur / bundle

Somme des 25 renders pour les bons candidats : 6.92–6.98 s. Les autres bundles sont du même ordre.

3,000 + 500×6

cible pilote

3 000 exemples SFT validés ; 500 prompts RL ; 6 rollouts par prompt ; 100–200 prompts notés en Stage 0.

Calendrier réaliste si l’infrastructure et les annotateurs sont disponibles

Premier signal5–7 jours

Nettoyer/générer le SFT, lancer un premier SFT, établir Stage 0, faire une courte boucle GRPO et produire un modèle testable.

Pilote robuste10–20 jours

Plusieurs cycles reward ↔ humains ↔ entraînement, corrections de reward hacking, held-out edits, reproductibilité.

Formulation simple2–3 semaines

Ordre de grandeur à communiquer pour un bon modèle du domaine meubles/props — si les données humaines avancent en parallèle.

Généralisation1–3 mois

Plus de catégories, scènes et comportements ; montée en capacité du policy model ; études humaines plus larges ; Stage 5.

Pourquoi le calcul pur n’est pas le goulot unique

À 113 s par groupe séquentiel, 500 groupes représenteraient ≈15,7 h de pipeline brut. Mais 6 rollouts × 500 prompts, plusieurs époques, appels sampler/judge, retries, SFT et évaluations held-out déplacent le total. Le temps humain pour 100–200 prompts Stage 0 et l’analyse des « high-reward failures » peut dominer. Le vrai délai est donc un calendrier d’itération, pas une simple multiplication GPU.

Ce que les preuves autorisent — et interdisent — de dire

On peut affirmer

Le pipeline restreint le code, exporte/recharge des GLB, produit les bundles, teste structure et edits, calcule les rewards, expose TRL/vLLM, appelle Gemini via OpenRouter, et modifie réellement des poids sur CPU et L4.

On ne peut pas affirmer

Que Qwen est entraîné, que le système converge, que le judge est validé sur 100–200 prompts, que GEPA a tourné live, ou que scènes/physique/UI avancées fonctionnent.

Limites consignées dans le ledger

Fallback process non sécurisé ; seulement 8 comparaisons humaines Stage 0 (accord 0.875) au lieu de la cible 100–200 ; GEPA = adaptateur local ; updates = une seule étape ; Stage 5 = squelette gated.

Preuves primaires

artifacts/verification/final-ledger.json
docs/verification.md
artifacts/smoke/verification/reward-report.json
artifacts/openrouter-check/report.json
artifacts/training/local-one-step/training-report.json
artifacts/runpod/final-one-step/training-report.json