Login / Sign up
Discover Bonzai
Terms of Use
Legal notice
Privacy
Region
Language
matyo91
matyo91
14
Subscribers
Facebook
X
Whatsapp
Telegram
👉 You must follow matyo91 to access chat.
Feed Shop About

📚 Quand l'IA devient une bibliothèque : l'expérience locale de Nolife

Facebook
Twitter
Whatsapp
Telegram
10 hours ago

La plupart des développements en IA que j'observe gravitent encore autour d'un même objectif : des modèles de langage généralistes toujours plus vastes. Ce réflexe est compréhensible. Un modèle capable d'écrire de la poésie, de raisonner sur des contrats, de générer du code PHP, de résumer des documents et de répondre à des questions apparaît comme un outil universel. Dès lors que chaque problème semble vaguement relever de l'IA, la tentation est grande de l'envoyer vers la même solution.

Mais de nombreux problèmes d'application ne nécessitent pas un modèle capable de tout cela. Ils ont besoin d'un seul modèle qui réalise une tâche de calcul de manière extrêmement performante.

Nolife Local est une expérimentation autour de cette alternative. Au lieu d'envoyer une image à un LLM multimodal générique, j'ai intégré un modèle de vision spécialisé directement dans une application Symfony et l'ai inclus dans son pipeline d'exécution normal. L'étude de cas concrète est Depth Anything 3 : estimation de profondeur monoculaire, exécutée localement sur Apple Silicon via PHP FFI, orchestrée avec Darkwood Flow et construite avec un agent de codage dans une boucle dont la configuration a évolué au fil du développement du projet.

Au début, cette boucle semblait familière :

idea → ask agent to implement → inspect result

Au final, ça ressemblait plutôt à :

observe → collect evidence → formulate problem → define constraints → let agent investigate → implement → measure → validate

La seconde boucle est plus lente au démarrage, mais beaucoup plus fiable. L'article décrit à la fois l'architecture d'exécution et l'évolution de la définition même du travail.

Il ne s'agit pas ici de dire que les modèles linéaires locaux sont mauvais, que l'IA locale est toujours meilleure, ou que les petits modèles remplacent les modèles généraux. La conclusion la plus pertinente est plus simple :

La tâche doit déterminer le modèle et l'architecture.

Les modèles généralistes restent excellents lorsque le problème exige un raisonnement sémantique étendu. Les modèles spécialisés deviennent intéressants lorsque le résultat souhaité est lui-même un calcul spécialisé.

Tous les problèmes d'IA ne sont pas des problèmes de LLM

Le réflexe par défaut des développeurs en 2026 est bien connu :

problem → large model → prompt → parse text

Cette méthode fonctionne si souvent qu'on cesse de se demander si c'est l'abstraction appropriée. Pour de nombreuses tâches, elle l'est. Pour d'autres, c'est comme utiliser un tableur pour retoucher des pixels : possible en théorie, coûteux en pratique, et le résultat est incorrect.

Ce que je voulais voir, c'était la forme inverse :

problem → specialized model → dense numerical result → application code

L'estimation de profondeur constitue un bon test de robustesse pour cette idée. Le résultat escompté n'est pas une simple description spatiale, mais un ensemble de nombres (une valeur par pixel) pouvant se transformer en palette de couleurs, en estimation de la caméra ou en reconstruction 3D. Le langage est un support peu adapté à ce résultat. Un modèle entraîné à générer des informations de profondeur est bien plus approprié.

Cette distinction est essentielle avant même d'aborder PHP, FFI ou Symfony. Si le calcul lui-même est spécialisé, le modèle doit l'être également. Ce même réflexe m'a ensuite guidé dans l'utilisation de l'agent de codage : des requêtes trop générales produisent un travail trop général (et souvent erroné), tandis que des problèmes précis engendrent des modifications plus ciblées et vérifiables.

Profondeur à partir d'une image

L'estimation de profondeur monoculaire répond à une question d'apparence simple : à partir d'une seule photographie RVB, peut-on estimer la distance qui sépare les objets de la caméra ?

single RGB image ↓ Depth Anything 3 ↓ dense geometric estimation

Les humains le font constamment. Une photographie de montagnes donne déjà une impression de relief. Le logiciel doit restituer cette structure sans paires stéréoscopiques, LiDAR ni second point de vue. Le modèle doit prendre en compte l'occlusion, les gradients de texture, la perspective et les contours des objets, mais le résultat est numérique, non narratif.

Depth Anything 3 (ByteDance) est une famille de modèles basés sur ViT avec une interface DualDPT. Le portage de l'équipe LocalAI depth-anything.cpp les exécute localement via ggml. Aucun script Python n'est utilisé lors de l'inférence. Aucun environnement d'exécution PyTorch n'est requis. Aucune API de vision dans le cloud n'est nécessaire.

Dans Nolife Local, nous utilisons DA3-BASE. Grâce à l'API C native, une inférence dense renvoie :

  • une carte de profondeur (largeur × hauteur flottants)

  • une carte de confiance (même résolution, le cas échéant)

  • paramètres extrinsèques de la caméra (3×4)

  • paramètres intrinsèques de la caméra (3×3)

  • un indicateur précisant si la profondeur est métrique

Pour DA3-BASE, cette indication est fausse. Les valeurs de profondeur sont relatives, et non exprimées en mètres. Sur notre photographie de référence, la plage observée était d'environ 0,43 à 3,47 en unités relatives. Interpréter ces valeurs comme des distances en mètres serait une erreur. La profondeur métrique existe dans d'autres variantes de DA3 (trajets imbriqués/mono) ; cette démonstration ne les utilise pas.

À partir de ces données, la bibliothèque native peut également générer un nuage de points glTF/GLB en rétroprojetant les pixels valides en coordonnées mondiales. C'est ainsi que la démo Symfony aboutit à une visionneuse 3D, sans avoir à créer de géométrie en PHP.

Le cheminement de la recherche à l'exécution ressemble à ceci :

ByteDance PyTorch checkpoint ↓ convert once to GGUF (Python, offline) GGUF weights on disk ↓ ModelLoader (C++) ggml compute graph ↓ Metal / CPU / other backends depth + confidence + camera ↓ optional GLB export application artifacts

Nous n'avons pas entraîné Depth Anything 3. Nous ne l'avons pas affiné, n'avons pas appliqué LoRA ni extrait de réseau étudiant. La spécialisation consiste ici à choisir un modèle pré-entraîné existant et à l'intégrer dans un pipeline d'exécution local spécialisé. La quantification (q4_k ou f32) est une dimension distincte : un choix d'exécution/de représentation du modèle, et non d'entraînement.

Avant d'écrire du code PHP, la première étape a consisté à comprendre le fonctionnement de la pile native : quelles fonctions de l'API C existaient, quels contrats de mémoire ils impliquaient et quelles sorties étaient réelles, par opposition à celles déduites des noms des modèles. Cette investigation a constitué le premier « problème » du projet : comprendre l'inférence avant de la lier. Négliger cette étape aurait été l'équivalent, pour l'agent, de choisir un modèle générique simplement parce qu'il semble performant.

Modèle spécialisé versus modèle multimodal général

Le contraste ne se limite pas à la taille.

Parcours multimodal général :

image ↓ large general model ↓ semantic interpretation ↓ text / structured answer

Chemin de profondeur spécialisé :

image ↓ DA3 ↓ dense numerical representation ↓ depth / confidence / camera / geometry

Un LLM multimodal peut décrire les relations spatiales par le langage. C'est une fonctionnalité réelle, et pour certains produits, c'est exactement ce que vous recherchez. Ce n'est pas la même chose que d'émettre hors ligne un champ flottant déterministe de 504×280 à partir d'un fichier GGUF d'environ 99 Mo, avec des paramètres de caméra structurés et un chemin d'exportation GLB.

La différence réside dans la nature du calcul. Une fois que le résultat est une estimation géométrique dense, le modèle cesse de ressembler à un chatbot et commence à ressembler à une primitive d'application : quelque chose que l'on appelle, mesure, met en cache et compose.

C’est le changement architectural que Nolife Local explore à l’exécution. Un changement parallèle se produirait lors du développement : au lieu de demander une amélioration générale, il faudrait demander l’étape de calcul ou d’intégration spécifique dont l’application a réellement besoin.

La partie inhabituelle : PHP

Vient ensuite la contrainte délibérément gênante.

L'objectif n'était pas :

Appeler un microservice Python depuis Symfony.

Ni:

Envoyer l'image à une API d'IA.

L'expérience demandait :

Dans quelle mesure pouvons-nous intégrer directement le modèle natif dans une application Symfony ?

Cela conduit à PHP FFI.

PHP n'exécute pas lui-même les noyaux des réseaux neuronaux. Affirmer le contraire relèverait du marketing, et non de l'ingénierie. La limite de propriété dans l'application finale est approximativement :

Browser ↓ Symfony ↓ Darkwood Flow ↓ NativeDepthBridge ↓ PHP FFI ↓ libdepthanything ↓ ggml / Metal ↓ Depth Anything 3

Les résultats natifs sont ensuite renvoyés à PHP pour le traitement au niveau de l'application.

PHP gère la gestion HTTP et le téléchargement, la validation, l'orchestration, le timing des étapes du flux, la coordination du cycle de vie natif, la représentation des résultats, le rendu de la palette de couleurs GD, l'interface utilisateur Twig / Symfony, les outils CLI, les surfaces d'observabilité et la gestion des artefacts sous var/inference/.

Le code natif gère le prétraitement (redimensionnement, normalisation, alignement des patchs), les opérations tensorielles, l'architecture ViT, les têtes de profondeur/confiance DualDPT, l'estimation de la caméra, l'exécution Metal et la reconstruction GLB native.

Symfony reste un framework web. Depth Anything reste un moteur de vision natif. FFI assure la liaison entre les deux.

Plusieurs pistes intéressantes ont été délibérément écartées : l’inférence de tenseurs en PHP pur, une ferme de workers Messenger, l’architecture DDD en couches, le CQRS, ou encore, tant qu’à faire, un portage Linux. Aucune de ces solutions ne résolvait le problème actuel. Elles auraient élargi le périmètre de l’expérience, non pas parce que l’expérience l’exigeait, mais parce qu’un agent pouvait générer ces tenseurs. J’ai appris que le périmètre doit être défini par l’humain et consigné par écrit avant que l’agent ne commence à écrire.

PHP FFI comme limite

L'interface FFI est importante car elle permet à PHP de gérer le cycle de vie d'un contexte natif au lieu de passer par une interface de ligne de commande et d'espérer que tout se passe bien.

La première question pertinente n'était pas de savoir quelle proportion du code C++ je pouvais réécrire en PHP, mais plutôt où se situait la limite de ce qui était réellement utile. Le cahier des charges du pont FFI était volontairement restreint.

Use da_capi_depth_dense, not a CLI wrapper. Honor da_capi_free_floats for native buffers. Respect that da_ctx is not thread-safe. Produce an observable CLI or HTTP result.

Voilà déjà un problème en miniature : problème, contraintes, résultat attendu.

NativeDepthBridge charge libdepthanything.dylib, lie l'API C de config/da_capi.h et expose une surface étroite au reste de l'application : charger le contexte, exécuter l'inférence dense, exporter GLB depuis le cache, libérer les tampons.

Voici un extrait ciblé de la limite :

$rc = $ffi->da_capi_depth_dense( $this->ctx, $imagePath, FFI::addr($outH), FFI::addr($outW), FFI::addr($outDepthPtr), FFI::addr($outConfPtr), FFI::addr($outSkyPtr), $ext, $intr, FFI::addr($isMetric), ); $depthPtr = $this->ffi->cast('float*', $outDepthPtr); $depth = $this->copyFloatsFromPtr($depthPtr, $count);

Le motif est volontairement ennuyeux :

PHP → native function → native result pointer → copy into PHP arrays → da_capi_free_floats

La durée de vie des ressources est un élément important. Le pont conserve un da_ctx tant que le chemin du modèle reste inchangé, le libère lors de sa destruction ou d'un changement de modèle, et sérialise l'accès à l'aide d'un verrou de fichier car le contexte natif n'est pas thread-safe. Les tampons de nombres flottants renvoyés par l'API C sont copiés dans des tableaux PHP puis libérés immédiatement.

Le cahier des charges était incomplet. Lors de l'implémentation, la conversion d'un grand float* en float[141120] a provoqué une erreur de segmentation sous PHP 8.5. Les lectures indexées ($ptr[$i]) fonctionnaient. Ce constat a dû être ajouté au contexte de travail : un comportement observé plutôt qu'une explication plausible. Les agents sont très doués pour paraître sûrs d'eux concernant l'organisation de la mémoire. La reproduction a prévalu.

Nous avons ensuite testé la durée de vie de cette ressource au sein d'un processus PHP :

php -d ffi.enable=1 bin/console app:depth-stress \ -i public/samples/mountains.jpg \ -r 10

Dix exécutions complètes du pipeline en interne ont permis de maintenir l'exportation GLB sur le chemin mis en cache (environ 20 ms chacune) et n'ont pas nécessité de recharger le modèle entre les itérations. C'est ce type de validation qui transforme le constat « FFI fonctionne une fois » en « FFI fonctionne comme infrastructure applicative ».

En faire une fonctionnalité Symfony

Une fois le pont établi, le modèle natif devient une fonctionnalité ordinaire de Symfony plutôt qu'un service externe mystérieux.

Le flux utilisateur est le suivant :

upload image ↓ validate ↓ run local inference ↓ extract depth / confidence / camera ↓ generate visualization (PHP GD turbo colormap) ↓ reuse native result for GLB ↓ build result ↓ render Symfony UX

La page de téléchargement est volontairement réduite : déposez une image, choisissez q4_k ou f32, puis soumettez. Pendant le traitement de la requête, l’interface utilisateur affiche l’état de traitement. Quelques secondes plus tard, la page de résultats affiche la photographie originale à côté de la carte de profondeur en couleurs, un panneau de confiance, des résumés de la caméra, les temps de traitement et un <model-viewer> pour le GLB.

Aucun moteur WebGL personnalisé. Aucun processus Messenger. Aucun sidecar Python. L'application Symfony gère l'interaction ; la bibliothèque native effectue les calculs complexes ; le navigateur se charge uniquement de l'affichage.

Les outils en ligne de commande suivent le même principe. Les outils de diagnostic traitent le modèle comme n'importe quelle autre dépendance :

FFI OK GD OK Native library OK q4_k model OK f32 model OK Inference dir OK READY

Ce résultat provient de php bin/console app:depth-check. Le modèle n'est plus un point d'accès IA opaque, mais une infrastructure applicative dotée d'un contrôle de disponibilité.

L'inférence en ligne de commande affiche le même historique du pipeline que l'interface utilisateur Web :

OK id=… 504x336 infer=3179.4ms … validate: 3.49ms infer: 3180.28ms colormap: 75.52ms glb: 19.65ms result: 0.00ms

Pour les lecteurs des benchmarks, il est important de comprendre que infer= désigne l'étape d'inférence de Flow, et non un synonyme caché de « requête complète ». Une fois ce vocabulaire partagé entre les présentations CLI, web et agent, les arguments concernant la latence deviendront beaucoup moins obscurs.

Orchestrer l'inférence avec Darkwood Flow

Après la première version fonctionnelle, l'orchestration était centralisée dans une seule méthode de service : validation, inférence, colorisation, exportation, assemblage. Le pipeline était réel, mais implicite. Les temps d'exécution étaient soit absents, soit regroupés en une seule valeur qui mélangeait des tâches très différentes.

J'ai introduit Darkwood Flow non pas comme un placement de produit, mais parce qu'un modèle local coûteux a besoin des mêmes propriétés d'ingénierie que tout autre calcul coûteux : étapes explicites, timings, gestion des pannes et un endroit où regarder quand quelque chose ne va pas.

Le pipeline final est :

Validate ↓ Infer ↓ Colormap ↓ Export scene ↓ Build result

Flow se situe au-dessus du pont FFI. NativeDepthBridge reste une primitive étroite. Flow gère le séquençage et les mesures d'horloge système pas à pas.

Il s'est avéré que rendre le pipeline explicite importait plus que de le rendre complexe. L'itération d'intégration a débuté par une observation précise et bien circonscrite — l'orchestration était monolithique — et un objectif concret : l'affichage des temps d'exécution par étape dans l'interface de ligne de commande, puis dans l'interface web. Ce travail, structuré autour d'un problème, précède même la définition du modèle.

Flow n'a pas accéléré le raisonnement. Il a simplement rendu visibles les étapes complexes. La visibilité est une condition essentielle à la formulation honnête des problèmes.

Le test de référence qui n'avait aucun sens

Au début, l'inférence Symfony ressemblait à peu près à ceci :

~2.6 seconds

Les chiffres obtenus correspondaient suffisamment bien aux valeurs natives pour que je leur fasse confiance. La démo a duré environ trois secondes. Puis Flow a rendu chaque étape visible, et les calculs ont cessé d'être corrects.

Un test à chaud après l'intégration de Flow ressemblait plutôt à ceci :

ÉtapemsValider~3Inférer la profondeur~2639Palette de couleurs~86ExportScene~2535Total~5260

L'étape InferDepth à elle seule semblait toujours correspondre au délai précédent d'environ 2,6 secondes. La requête complète pour laquelle l'utilisateur a attendu a duré près de cinq secondes.

ExportScene ne « écrivait pas un GLB ». Il payait pour une autre inférence native.

À ce moment-là, la contribution précieuse n'était plus « veuillez optimiser le code PHP ». Il s'agissait d'un problème d'ingénierie formulé avec précision :

Observation: The complete pipeline takes much longer than infer-only. Evidence: Flow timings show another expensive native operation during GLB export. InferDepth ≈ 2.6 s. ExportScene ≈ 2.5 s. Constraint: Preserve depth, confidence, camera, and GLB output. Desired result: One native inference per request. Non-goal: Do not solve this by adding concurrency or extra architecture layers.

Comparez cela à une consigne initiale comme « améliorer les performances ». La seconde consigne est plus précise et bien plus efficace. L'agent n'a pas besoin de plus de liberté, mais d'un contexte plus clair.

Trouver la deuxième inférence

Le chemin d'appel avant la correction était le suivant :

InferDepth → da_capi_depth_dense() → full ViT + DPT inference ExportScene → da_capi_export_glb() → full ViT + DPT inference again → write GLB

Les fonctions da_capi_depth_dense() et da_capi_export_glb() étaient des points d'entrée indépendants. Le chemin d'exportation préparait la profondeur, la confiance, la pose et les données RGB en exécutant à nouveau depth_pose_native(). Il n'y avait pas de cache entre elles. Du point de vue de l'application, nous disposions déjà du résultat de profondeur ; du point de vue de l'API native, l'exportation GLB n'en avait pas connaissance.

Ce type de bug ressemble à un problème de performance, mais il s'agit en réalité d'un problème d'architecture. Sans gestion du temps d'exécution, la seconde passe se dissimule dans une simple attente visible par l'utilisateur. Avec la gestion du temps d'exécution, cela devient évident :

InferDepth ≈ 2.6 s ExportScene ≈ 2.5 s

Deux étapes coûteuses presque identiques sont rarement le fruit du hasard.

L'enquête s'est fondée sur des preuves, et non sur des spéculations. Je n'ai pas commencé par incriminer Metal, la quantification, la surcharge PHP ou la bande passante mémoire. J'ai analysé les temps d'exécution jusqu'au chemin d'appel, puis examiné le code de préparation à l'exportation natif. Cette rigueur était essentielle, car un agent de programmation peut générer très rapidement des explications erronées tout aussi plausibles. Les comportements observés, les journaux, les temps d'exécution, les chemins d'exécution et la reproduction du problème doivent primer sur les explications.

Une inférence, plusieurs résultats

La solution n'était ni le recours aux workers asynchrones, ni Messenger, ni une refonte complète. C'était la réutilisation.

Conceptuellement :

BEFORE image ├── infer → depth └── infer again → GLB

AFTER image ↓ one inference ↓ native result ├── depth ├── confidence ├── camera └── GLB

Côté natif, cela est devenu ABI v11 : da_capi_export_glb_cached(). L’inférence dense remplit un cache d’exportation sur le contexte ; l’écriture GLB consomme ce cache sans avoir besoin d’un second depth_pose_native(). Côté PHP, ExportScene appelle exportGlbCached().

Le signal décisif était l'étape GLB, et non une milliseconde contestée sur InferDepth.

Avant (exécution de Flow à chaud ayant révélé le bug) :

ÉtapemsValider~3Inférer la profondeur~2639Palette de couleurs~86ExportScene~2535Total~5260

Après (experiments/004-pipeline-single-pass, mountains.jpg → 504×336, ​​q4_k, warm, Apple M4 / Metal) :

ÉtapeMédiane (ms)Valider~3Inférer la profondeur~3165Palette de couleurs~88ExportScene (en cache)~19Total~3274

Inférences natives par requête : 2 → 1. ExportScene a été réduit d'une inférence complète à une écriture depuis le cache d'environ 20 ms. La valeur d'InferDepth est plus élevée dans le tableau « après » car cette mesure utilise le tenseur de l'échantillon de démonstration, plus grand (504 × 336) ; l'optimisation correspond à la suppression de la seconde passe, et non à une amélioration de la vitesse d'InferDepth elle-même.

La modification du code qui en a résulté était relativement mineure. Le plus important était d'identifier précisément les tâches dupliquées. L'observabilité a permis de détecter ces doublons. L'amélioration des performances est venue de leur suppression.

Cette leçon est plus pertinente que le simple fait d'« ajouter du cache quelque part ». Le calcul coûteux a déjà produit tous les tenseurs nécessaires à l'exportateur GLB. L'architecture n'en a tout simplement pas tenu compte.

Que disent réellement les mesures ?

Les résultats ci-dessous ont été obtenus sur Apple M4, macOS arm64, Metal. Ils ne correspondent pas aux benchmarks universels de Depth Anything 3. La taille des tenseurs traités influe sur la charge de travail.

Mesurer avant d'optimiser est devenu une règle récurrente. Il ne s'agissait plus de dire « PHP est lent », mais plutôt : inférence native, étapes Flow/PHP, exportation GLB, pipeline complet – chaque étape ayant ses limites clairement définies.

La résolution fait partie du critère de référence

DA3 redimensionne de sorte que le côté le plus long mesure environ 504 pixels, avec des dimensions alignées sur la taille de patch 14. Le rapport d'aspect de la source modifie donc le tenseur traité :

SourceTraitéeMédiane à inférence chaude uniquement (q4_k)Photo de référence 1280×720504×280~2495 msExemples de démonstration 1024×680504×336~3,0–3,2 s

Ces chiffres ne sont pas contradictoires. Une résolution de 504×336 possède environ 20 % de pixels de plus qu'une résolution de 504×280. Les comparer sans préciser la résolution utilisée est une méthode courante pour inventer de fausses régressions.

Quand un test affichait environ 2495 ms et un autre environ 3175 ms, la prochaine étape logique n'était pas de relancer une optimisation. Il fallait mieux définir le problème : même matériel, même variante, dimensions traitées différentes. Face à cette divergence de résultats, l'investigation primait sur la conjecture.

Inférence chaude uniquement à 504×280

À partir de experiments/003-warm-q4k-f32 (app:depth-bench, warmup 1, repeat 10, same PHP process) :

VarianteMédiane (ms)Taille du modèleq4_k2494,9~99 Mof322495,1~393 Mo

Le démarrage à froid est un phénomène différent

Le premier chargement de q4_k dans un processus a pris environ 6,9 secondes lors de l'expérience de référence en ligne de commande, compilation du shader Metal incluse. Les chargements suivants, à chaud, durent de quelques dizaines à quelques centaines de millisecondes. Intégrer le démarrage à froid dans un tableau de « latence » sans l'étiqueter est incohérent.

Surcharge Flow / PHP

Utilisation de temps d'exécution identiques sur un pipeline plein et chaud (« experiments/005-flow-overhead », mountains.jpg) :

validate ~3.5 ms colormap ~90 ms glb ~20 ms (cached) result ~0 ms

Le temps total des étapes Flow/PHP non natives était d'environ 114 ms. Dans cette expérience, l'orchestration représentait environ 0,1 seconde de la requête. L'inférence neuronale était prédominante.

Flow n'a pas rendu Depth Anything plus rapide. Il a permis de détecter le bug de double passage, et ensuite, il a rendu le reste de la surcharge acceptable.

Référence CLI (native da3-cli, 504×280)

Extrait de experiments/001-reference-cpp :

VarianteTaille du modèleChargeMédiane déduiteRSS de pointeq4_k~99 Mo~6869 ms à froid*2589 ms~457 Mof32~393 Mo~194 ms de préchauffage2580 ms~1028 Mo

  • Le chargement à froid de q4_k inclut l'initialisation de la bibliothèque Metal lors de la première exécution.

q4_k contre f32

La quantification est souvent présentée comme un gain de vitesse gratuit. Cette expérience n'a pas confirmé cette affirmation sur ce matériel.

La question n'était pas de savoir « lequel devrait paraître plus rapide ? ». Il s'agissait d'une comparaison contrôlée : même processus, mêmes données d'entrée, échauffement puis dix répétitions, résolution enregistrée. Le numéro 009 a rendu compte de ce briefing avant sa mise en œuvre.

Lors de l'inférence à chaud en 504×280, les médianes de q4_k et f32 étaient pratiquement identiques (environ 2495 ms). La différence majeure résidait dans l'espace de stockage et l'empreinte mémoire : environ 99 Mo contre 393 Mo sur disque, et un RSS de pointe bien plus élevé pour f32 lors de l'exécution de référence en ligne de commande.

La conclusion étayée n'est donc pas « q4_k est quatre fois plus rapide ». Ce n'était pas le cas.

La conclusion étayée est plus proche de :

La quantification a considérablement réduit le stockage du modèle dans cette expérience sans pour autant produire d'amélioration de la latence correspondante sur cette charge de travail Apple M4 / Metal particulière.

Je n'ai pas désigné de solution plus précise. Sans données de profondeur réelles pour les photos de démonstration, comparer les deux serait illusoire. Le choix pratique pour la configuration par défaut de la démonstration est q4_k, car le fichier est plus petit et la latence à chaud correspond à celle de f32. Une commande de variation de profondeur sans données réelles a été explicitement écartée pour la même raison.

Que signifie « local » ?

L'inférence locale et une application entièrement hors ligne sont des affirmations liées, mais non identiques.

Après l'installation, Nolife Local n'effectue aucun appel à l'API d'IA distante. L'inférence se fait via FFI → libdepthanything → GGUF sur disque → Metal. Cela était vrai dès le début.

L'exigence plus stricte concernant l'exécution de la démo a nécessité plus de travail. La page de résultats chargeait initialement celle de Google. Le script était exécuté depuis un CDN alors que l'inférence restait locale. Cela crée un piège de crédibilité : l'IA est locale, mais pas la page. Nous avons donc intégré public/vendor/model-viewer.min.js au répertoire du client, supprimé la référence au CDN du chemin de démonstration et vérifié le comportement par des contrôles HTTP et une inspection HTML : il s'agissait bien d'un comportement observé, et non d'une supposition.

Vérifié après cette modification :

RéclamationRésultatRéseau requis pour l'inférenceNonRéseau requis pour l'exécution de la démo (page de résultats)Non

L'installation nécessite toujours une connexion réseau : pour le téléchargement des paquets Composer, du modèle et la compilation native. Les scripts CDN de rechargement à chaud de FrankenPHP ne s'affichent que si l'option d'environnement correspondante est activée ; la démo de publication utilise le serveur PHP intégré sans cette option.

L’expression exacte n’est donc pas « l’IA dans le cloud ». Ce n’est pas non plus « l’univers entier est hors ligne pour toujours ». C’est :

Après installation, cette démo peut être exécutée même avec le réseau désactivé. Le modèle est une bibliothèque locale.

Les modèles d'IA en tant qu'infrastructure d'application

Dès lors qu'un modèle spécialisé se comporte comme une bibliothèque, les outils qui l'entourent commencent à paraître familiers à tout développeur Symfony.

php bin/console app:depth-check php -d ffi.enable=1 bin/console app:depth-infer -i public/samples/mountains.jpg -m q4_k php -d ffi.enable=1 bin/console app:depth-bench -i public/samples/mountains.jpg -w 1 -r 5 --json php -d ffi.enable=1 bin/console app:depth-stress -i public/samples/mountains.jpg -r 10 php bin/console app:depth-cleanup --keep=20

Une phase de préchauffage est prévue pour les démonstrations. Un nettoyage est nécessaire car chaque exécution génère des artefacts. Une phase de charge est également prévue car la durée de vie du cache natif lors d'appels répétés au sein du même processus fait partie intégrante du contrat. Les métadonnées sont écrites à côté des images. Les totaux du pipeline sont calculés une seule fois dans BuildResult, et non recalculés différemment dans Twig et l'interface de ligne de commande.

Après un certain temps, la sensation surprenante est que Depth Anything cesse d'être perçu comme « une fonctionnalité d'IA » et commence à être perçu comme « une dépendance native avec des outils de diagnostic ».

C'est bien là le problème.

Le contexte en tant qu'artefact d'ingénierie

À ce stade, j'avais cessé de considérer l'agent de codage comme un outil chargé d'« améliorer le projet ». Chaque itération débutait par un problème observé plus restreint, des données probantes, des contraintes et une condition de réussite. L'implémentation devenait la dernière étape, et non plus la première.

C’est ce que j’entends par développement axé sur les problèmes — non pas une mise en scène de la gestion de projet, mais un cahier des charges concis que l’agent peut réellement utiliser.

Un problème peut devenir une représentation condensée de tout ce que l'agent doit comprendre à son sujet. Pas seulement « quelque chose est cassé », mais :

what happened where it happened environment evidence why it matters constraints expected behavior what not to change

Pour Nolife Local, ce contexte a finalement inclus le code source, l'API native, la variante du modèle, la résolution traitée, les temps d'exécution de Flow, la méthodologie de test de performance, les messages d'erreur, l'historique Git, les limitations connues, les résultats attendus et les objectifs non atteints explicitement. Cette combinaison s'est avérée bien plus efficace qu'une demande d'implémentation vague.

Les fichiers de problèmes du projet suivent cette structure. Le problème n° 007 concerne la double inférence. Le problème n° 009 porte sur la compatibilité entre q4_k et f32. Le problème n° 003 concerne le pont FFI et a été mis à jour lorsque PHP 8.5 a remis en question la première hypothèse concernant la mémoire. Le fichier ITERATIONS.md enregistre la même boucle de manière chronologique : observation, problème sélectionné, modification, validation, mesure, candidat suivant.

observe → issue → agent → implementation → measure → observe

L'approche « priorité à l'émission » ne signifie pas céder la propriété à l'agent.

Humain : choisit le problème, définit l'intention, fixe les contraintes, décide de la portée, évalue les compromis, valide les résultats.

Agent : inspecte, met en œuvre, mesure, documente, itère dans le cadre du cahier des charges.

L'agent peut générer une grande quantité de code. Cela ne signifie pas pour autant qu'il est responsable des décisions concernant l'orientation du projet. Un correctif soumis peut toujours contenir des informations pertinentes, une compréhension de l'architecture et une expérience en conception d'API. L'approche « problème d'abord » ne rend pas le code inutile. Elle valorise la compréhension du problème lorsque la mise en œuvre est peu coûteuse.

Spécialiser le modèle, spécialiser le contexte

Il y a un parallèle à établir, et je le considère comme une interprétation issue du projet plutôt que comme une loi universelle.

Pour le calcul :

general model ↓ specialized task model ↓ more constrained computation

Pour le développement :

general prompt ↓ well-defined issue ↓ more constrained implementation

Du côté du modèle, il est déconseillé d'utiliser le modèle le plus général lorsque la tâche est déjà bien définie. Du côté de l'agent, il est déconseillé de fournir une consigne trop générale lorsque le problème d'ingénierie peut être défini avec précision. Dans les deux cas, réduire l'espace de recherche peut améliorer le résultat.

Spécialisez le modèle lorsque le calcul est spécialisé. Spécialisez le contexte lorsqu'un agent est sur le point d'implémenter la prochaine modification.

Ce que l'expérience m'a appris

Quelques surprises, synthétisées plutôt que simplement reprises :

L'interface PHP FFI était suffisante pour l'intégration. Le coût élevé ne résidait pas dans l'orchestration PHP. L'observabilité a permis de détecter les inférences redondantes plus efficacement que l'intuition. La résolution du traitement a expliqué des résultats de benchmarks apparemment incohérents. q4_k a permis de réaliser des économies substantielles d'espace disque sans gain notable de latence à chaud sur cette charge de travail Metal. Enfin, grâce à des outils adaptés, un modèle de vision peut s'apparenter davantage à une bibliothèque native qu'à un service d'IA.

Du point de vue du processus : la mise en œuvre a baissé en coût, mais la compréhension du problème de fond, elle, est restée inchangée. La configuration, l’environnement, les journaux, les mesures et la reproduction étaient tout aussi importants que le code lui-même. Les spéculations générées par l’agent n’ont jamais pu se substituer aux observations directes.

L'argument principal de Darkwood Flow dans ce projet n'est pas que chaque application Symfony a besoin de Flow. Il est que, dès lors que les modèles d'IA deviennent des composants applicatifs classiques, ils requièrent les mêmes propriétés que tout calcul complexe : orchestration, gestion du temps, gestion des erreurs, gestion du cycle de vie et observabilité. Dans Nolife Local, cette visibilité a révélé des duplications de code natif. Il s'agit là d'une preuve concrète, et non d'un slogan.

Limitations

Cette expérience a un périmètre défini.

La validation a été effectuée uniquement sur macOS arm64 / Metal. Aucune vérification sous Linux ou Windows n'a été réalisée, et aucun test de performance n'a été effectué en production avec PHP-FPM. Nous n'avons pas entraîné ni affiné le modèle Depth Anything 3. Les performances dépendent du matériel. La démo utilise un modèle spécialisé, et non un maillage de production multi-modèles. La qualité GLB a été validée pour les exemples de publication ; l'objectif n'était pas la précision géométrique absolue par rapport à la profondeur réelle.

Ce ne sont pas des excuses. Ce sont les limites de l'affirmation — des contraintes choisies, et non un manque d'ambition.

Conclusion

Je me suis fixé pour objectif de poser une question pratique : au lieu d’envoyer une image à un LLM multimodal à usage général, que se passe-t-il si un modèle de vision spécialisé devient partie intégrante du pipeline normal d’une application Symfony ?

Ce qui s'est passé, c'est que Depth Anything 3 s'est comporté moins comme un « produit d'IA » que comme une bibliothèque : on la charge, on l'appelle, on la libère, on la mesure, on la diagnostique et on réutilise ses résultats. PHP n'est pas devenu un framework de ML. Il a orchestré un composant natif spécialisé via FFI, rendu le pipeline observable avec Flow et transformé une double inférence cachée en une architecture à passe unique.

Le changement plus global pourrait être le suivant : à mesure que l'IA se développe, l'ingénierie s'oriente davantage vers le choix du calcul approprié, la définition du problème adéquat et la validation du résultat.

Dans cette expérience, trois couches sont superposées :

Depth Anything 3 → specializes computation Darkwood Flow → makes computation observable and composable Issue-first briefs → specialize the context given to the coding agent

La conclusion n'est pas « arrêtez d'utiliser les LLM ».

Il est plus proche de :

Cessez de supposer que chaque problème lié à l'IA doit être résolu par le même type de modèle — ou par le même type d'invite.

Une application d'IA moderne peut combiner un modèle linéaire de données (LLM), un modèle de vision, un modèle d'intégration, un modèle de parole, un modèle de profondeur, du code applicatif classique et des bibliothèques natives. Chaque élément doit exceller dans sa fonction principale. Symfony n'a pas besoin de réimplémenter GGML. Il doit simplement pouvoir gérer l'interface entre ces différents composants.

Historiquement, contribuer à un projet open source impliquait souvent de trouver un problème, d'écrire un correctif et de soumettre une pull request. Les agents de développement remettent en question l'idée que la rédaction du correctif soit nécessairement l'élément le plus difficile à résoudre. Une contribution future peut également s'avérer extrêmement précieuse lorsqu'un contributeur fournit une reproduction précise du problème, des détails sur l'environnement, les journaux, la configuration, le cas d'utilisation, le comportement attendu et les contraintes, car ces éléments permettent à un mainteneur et à un agent d'implémenter la correction adéquate. Les pull requests ne sont pas obsolètes, pas plus qu'une description de problème bien rédigée. Lorsque l'implémentation peut être générée rapidement, la rareté intéressante se déplace vers l'intention et les preuves.

Nolife Local est une version de cette architecture :

Symfony + PHP + FFI + Darkwood Flow + specialized native model

sans qu'aucune API d'inférence à distance ne soit requise après l'installation.

Ressources

Projets en amont et projets connexes :

  • depth-anything.cpp

  • Code source : https://github.com/matyo91/nolife-local

  • Diapositives : https://github.com/matyo91/slidewire

Follow matyo91 to comment
matyo91

matyo91

Je t'aide à automatiser tes process
14
Visit this Bonzai
Follow matyo91 to get the latest updates.

💫 Créateur de Hacker News - Scrap (2006)

10 hours ago
2

💫 Créateur Reddit - r/opensource : crabwalk - rust en Python !

11 hours ago
6

💫 Bonzai Créateur - Dada | Continuum⍟

16 hours ago
5

🤖 Veille Darkwood - 2026-08-23

19 hours ago
5

💫 GitHub Creator - phpstan/phpstan: 2.2.9

1 day ago
12

💫 Bonzaï Créateur - Fabien WAGE-MENRI See More

1 day ago
10

🤖 Veille Darkwood - 2026-08-22

1 day ago
12

💫 Bonzai Creator - 🌿🩸VITALITÉ<>OREXIS 🌿🩸

2 days ago
16

🤖 Veille Darkwood - 2026-08-21

2 days ago
15

💫 Créateur de Hacker News - Windows révèle le test de Rorschach qui sommeille en chacun (2003)

3 days ago
17

💫 Créateur Reddit - r/opensource : DeepSeek Harness me semble encore être un jouet.

3 days ago
15

💫 GitHub Creator - kubernetes/kubernetes: v1.37.0-rc.1

3 days ago
17

💫 Bonzaï Créateur - Nathalie See More

3 days ago
18

🤖 Veille Darkwood - 2026-08-20

3 days ago
19

💫 GitHub Creator - Laravel: laravel/framework: v13.26.1

4 days ago
20

💫 Créatrice de bonsaïs - Niokoz

4 days ago
21

🤖 Veille Darkwood - 2026-08-19

4 days ago
20

💫 Créateur Reddit - r/kubernetes : Dépréciation des fournisseurs CAPI de Talos

5 days ago
23

🤖 Veille Darkwood - 2026-08-18

5 days ago
25

💫 Créateur Bonzaï - Mehdi Amrani

5 days ago
20
© 2026 Bonzai Privacy Legal notice Terms of Use