Darkwood Blog Blog
  • Articles
  • Veille
  • Releases
  • CrĂ©ateurs
fr
  • de
  • en
Connexion
  • Blog
  • Articles
  • Veille
  • Releases
  • CrĂ©ateurs

📚 Quand l'IA devient une bibliothĂšque : l'expĂ©rience locale de Nolife

le 23 août 2026

Connectez-vous pour réagir à cet article

🚀 1

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 :

Étape ms
Valider ~3
Inférer la profondeur ~2639
Palette de couleurs ~86
ExportScene ~2535
Total ~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) :

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

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

Étape MĂ©diane (ms)
Valider ~3
Inférer la profondeur ~3165
Palette de couleurs ~88
ExportScene (en cache) ~19
Total ~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é :

Source Traitée Médiane à inférence chaude uniquement (q4_k)
Photo de rĂ©fĂ©rence 1280×720 504×280 ~2495 ms
Exemples de dĂ©monstration 1024×680 504×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) :

Variante Médiane (ms) Taille du modÚle
q4_k 2494,9 ~99 Mo
f32 2495,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 :

Variante Taille du modÚle Charge Médiane déduite RSS de pointe
q4_k ~99 Mo ~6869 ms Ă  froid* 2589 ms ~457 Mo
f32 ~393 Mo ~194 ms de préchauffage 2580 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éclamation Résultat
Réseau requis pour l'inférence Non
Ré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

Connectez-vous pour réagir à cet article

🚀 1

Site

  • Plan du Site
  • Contact
  • Mentions lĂ©gales

Network

  • Hello
  • Blog
  • Apps
  • Photos

Social

Darkwood 2026, tous droits réservés