Darkwood · Symfony · PHP FFI · Flow
The default reflex
The question
Depth is not language
"The mountains are far away."
504 × 280
depth values
relative units · not meters
Depth Anything 3
DA3-BASE · relative depth · not meters
Research → runtime
What “specialized” means
The unusual constraint
Architecture
flowchart TB B["Browser"] S["Symfony"] F["Darkwood Flow"] N["NativeDepthBridge"] FFI["PHP FFI"] L["libdepthanything"] G["ggml / Metal"] D["Depth Anything 3"] B --> S --> F --> N --> FFI --> L --> G --> D
Ownership
HTTP · validation · Flow orchestration · GD colormap Twig · CLI · observability
ViT · DPT · depth confidence · camera Metal · GLB export
PHP does not perform matrix multiplication.
PHP FFI
$ffi->da_capi_depth_dense(
$ctx, $imagePath, …
);
Application pipeline
Darkwood Flow · explicit steps · per-step timings
Observability
The benchmark that did not make sense
| Step | ms |
|---|---|
| Validate | ~3 |
| InferDepth | ~2639 |
| Colormap | ~86 |
| ExportScene | ~2535 |
| Total | ~5260 |
Why is ExportScene another 2.5 seconds?
Before
image
├── inference → depth
└── inference → GLB
da_capi_depth_dense da_capi_export_glb → depth_pose_native() × 2
After
image
↓
one inference
↓
native result
├── depth
├── confidence
├── camera
└── GLB
da_capi_export_glb_cached · ABI v11
Measured impact
504×336 · q4_k · warm · Apple M4 / Metal
Measurement discipline
| Source | Processed | Infer median |
|---|---|---|
| 1280×720 | 504×280 | ~2495 ms |
| 1024×680 | 504×336 | ~3.0–3.2 s |
Compare processed resolution — not headline numbers alone.
Quantization
Warm infer-only · 504×280 · Apple M4 / Metal
Local means local
AI becomes infrastructure
php bin/console app:depth-check
php bin/console app:depth-bench
php bin/console app:depth-stress
php bin/console app:depth-cleanup
Flow / PHP overhead ~114 ms · infer dominates
Development loop
idea
→ ask agent
→ inspect
observe → evidence
→ issue → agent
→ implement → measure
Issue-first
Observation:
Two native inference passes.
Evidence:
Infer ≈ 2.6 s · GLB ≈ 2.5 s
Constraint:
One inference per request.
Expected:
Reuse native dense result.
Non-goal:
No Messenger / concurrency layer.
Synthesis
general model
↓
specialized model
general prompt
↓
well-defined issue
What I learned
Conclusion
Resources
github.com/matyo91/nolife-local
github.com/matyo91/slidewire
github.com/mudler/depth-anything.cpp