Le port GPU Apple Silicon — Metal direct, MLX en oracle CPU
Le port GPU Apple Silicon : Metal direct, MLX en oracle CPU
Pourquoi un second port — et pourquoi par mesure, pas par argument
Le code Monte-Carlo d'origine cite, dans la lignée méthodologique de son auteur, un principe de validation : le seul pattern fiable est une seconde implémentation, écrite par une autre équipe, avec une méthode numérique différente, sur le même problème physique. Un test unitaire avec une tolérance écrite à la main n'est qu'une reformulation de la croyance du développeur. Une implémentation indépendante est un falsificateur.
Le portage Radience honore cette discipline avec deux monomères Apple-Silicon, mais à un niveau structurel plus profond que dans la version précédente de ce chapitre : la bifurcation d'architecture — quel moteur GPU pour ce workload — a été tranchée non pas par argument du panel, mais par une mesure. La première exécution réelle de MLX sur le GPU Apple — 50 lignes portant compton_mean_free_path_iron sur mlx_default_gpu_stream_new — a livré le verdict empirique :
- 0,22 % du pic FP32 du M4 Max sur le kernel ;
- ~50 % de la bande passante DRAM saturée ;
- matérialisation \(\times27\) : MLX écrit un tableau
[np]entier en DRAM par opération élémentaire, sans fusion cross-op ; - intensité arithmétique effective \(\approx\) 0,12 FLOP/byte, \(240\times\) sous le ridge roofline du M4 Max (29,7).
MLX-array-ops est bandwidth-bound sur ce workload — l'inverse exact de Conway (cf. automata ADR-0005), et le contre-grain d'un calcul Monte-Carlo branchy où chaque rayon est indépendant et lit ~zéro nouvel octet par étape. La conclusion n'est pas un jugement de l'outil MLX ; c'est un jugement de l'alignement entre l'outil et le grain du calcul.
Monomère A — Metal direct, le port de production
Le Monomère A est un port Metal direct : compute shader MSL hand-écrit, un thread GPU par rayon, intersection Möller-Trumbore brute-force sur les triangles avec hit le plus proche en registres, cascade gamma scalaire par thread (comme dans le pinhole_camera.cu original). Le patron est emprunté verbatim à automata/crates/automata-metal : metal = "0.33", Device::system_default, new_library_with_source, pipeline de compute, buffers StorageModeShared zéro-copie (mémoire unifiée Apple), command-buffer → encoder → dispatch_threads → commit → wait_until_completed, lecture par contents(). Le MetalRunner implémente le port hexagonal MonteCarloRunnerPort, sibling de MlxRunner et OptiX9Runner.
| Axe | Port OptiX 9 (cible NVIDIA) | Monomère A — Metal direct (cible Apple) | Monomère B — MLX array-ops (rétrogradé) |
|---|---|---|---|
| Matériel | NVIDIA L4 (Ada, sm_89) | Apple M-series (Metal) | Apple M-series (CPU) |
| Modèle d'exécution | un thread par photon (OptiX) | un thread par rayon (MSL compute) | tenseurs batchés (matérialisés) |
| Régime roofline | compute-bound | compute-bound (AI 125–500 vs ridge 29,7) | bandwidth-bound (AI eff. ~0,12) |
| Rôle | port de production NVIDIA | port de production Apple Silicon | oracle numérique CPU |
Sur Apple M4 Max, au régime saturé (np \(\geq\) 2,4M), Monomère A tient 848 ns par photon primaire soit 1,18 million de photons/seconde — un speedup de \(33,58\times\) sur la baseline CPU mono-thread scellée, confirmant la prédiction carnot de \(30–35\times\) (zone occupancy-limitée, levier d'optimisation futur : la pile de 10 slots hors registres). La radiographie IQI Sinus à 300M photons est rendue en 5 min 50 s sur le GPU du Mac.
Anti-fabrication : faire du mensonge GPU un type illégal
Une lumière verte qui ment coûte plus cher qu'une rouge. Le Monomère A ne se contente pas d'affirmer qu'il tourne sur GPU — il prouve chaque exécution par un témoin signé par le driver, typé non-falsifiable :
pub struct BackendIdentity {
pub api: BackendApi,
pub device: String,
pub executed_on: ExecutionWitness, // non-Default, non-Deserialize
}
pub enum ExecutionWitness {
Gpu { source: GpuWitnessSource, timestamps: GpuTimestamps /* sealed */ },
Cpu,
}
ExecutionWitness::Gpu n'est mintable qu'à partir d'un compteur driver scellé (MTLCommandBuffer.GPUStartTime / GPUEndTime). Le sysctl machdep.cpu.brand_string qui produisait l'ancien descripteur menteur « MLX / Apple M4 Max / metal » est retiré. Le contrôle négatif RADIENCE_FORCE_CPU=1 erre par contrat — un kernel GPU qui se replierait silencieusement sur le CPU est détecté avant publication.
Le gate G_gpu_actually_ran exige \(\geq\) 2 témoins forts sur 3 (driver-counter / powermetrics / MTL-capture) plus le contrôle négatif. Sans ces preuves, aucun nombre n'est publié — l'absence de témoin est un échec, pas un résultat.
Monomère B — MLX array-ops, rétrogradé en oracle CPU numérique
La mesure ayant tranché en B contre l'usage GPU, le Monomère B est conservé mais rétrogradé :
- Rôle : oracle numérique CPU dans la cross-validation 4-témoins (§ 04). Indépendamment ré-implémenté en Rust scalaire, RNG bit-exact, sections efficaces verbatim — il atteste la correction numérique du Monomère A, pas son substrat d'exécution.
- Re-entry test (janis,
backend-advocate-B) : B regagne le statut de port de production GPU si et seulement si une version fusionnée (viamlx.compileou kernels Metal custom appelés depuis MLX) collapse la matérialisation \(\times27\) vers ~1 et atterrit à \(<2\times\) du hand-Metal sur le même workload. C'est un autre artefact qu'aujourd'hui ; le re-entry test est inscrit dansfleet.toml.v5et préserve une dissidence rémunérée. - Discipline FFI préservée : le
sys.rsMLX hand-rollé (forgemaster path, 200 LOC,unsafeconfiné, modules frères#![forbid(unsafe_code)]au niveau fichier) reste un patron éprouvé — réutilisable le jour où B est ré-engagé.
Les 4 anomalies — préservées dans les deux monomères
Le code original porte quatre choix d'implémentation que l'analyse a identifiés comme intentionnels et à préserver (cf. § 04 + la persona auteur reconstituée) :
- Branche basse-énergie désactivée par tautologie (
||3==3) — désactivée par design, pas par bug ; constante-foldée en MSL \(\to\) coût runtime nul, fidélité préservée. - Profondeur de cascade fixée à 10 — calibrée empiriquement sur les runs IQI.
- Valeurs Z tungstène/fer codées en dur — les deux seuls matériaux de la campagne ; tables compile-time en MSL, jamais une branche runtime.
- Générateur RNG xorshift32 — choix idiomatique de l'époque (GPU Kepler/Maxwell 2017) ; bit-exact entre les monomères, RNG bit-stable est l'oracle qu'on certifie.
Ces anomalies sont préservées verbatim dans Monomère A (MSL) et Monomère B (Rust CPU). Un gate (G_anomalies_preserved_runtime) vérifie leur présence au niveau runtime/comportemental — pas seulement textuel, parce qu'un compilateur agressif pourrait réordonner ou éliminer le ||3==3 sous -ffast-math (interdit explicitement sur les TU de physique). Préserver une anomalie n'est pas la corriger : c'est honorer le fait que le code porté doit d'abord reproduire la baseline avant qu'on en discute les améliorations.