Preserve DA3 Small source behavior in FP16 model

#1
Files changed (2) hide show
  1. README.md +43 -95
  2. da3_small_gpu_fp16.tflite +2 -2
README.md CHANGED
@@ -8,112 +8,51 @@ tags:
8
  - depth-estimation
9
  - monocular-depth
10
  - on-device
11
- - gpu
12
  - depth-anything
13
  base_model: depth-anything/DA3-SMALL
14
  base_model_relation: quantized
15
  ---
16
 
17
- # Depth Anything 3 (Small) — LiteRT GPU, monocular depth
18
 
19
- On-device **LiteRT / TFLite** conversion of [**Depth Anything 3 — Small**](https://huggingface.co/depth-anything/DA3-SMALL)
20
- (ByteDance-Seed, Apache-2.0) for **monocular depth**, running fully on the mobile **GPU** via the LiteRT
21
- `CompiledModel` API (ML Drift delegate). No CPU fallback ops — the whole graph is GPU-compatible.
22
 
23
- | | |
24
  |---|---|
25
- | Task | Monocular depth (single RGB → depth) |
26
- | Backbone | DINOv2 ViT-S + RoPE, DPT/DualDPT depth head |
27
- | Input | `[1, 3, 896, 504]` NCHW float32, ImageNet-normalized, **native portrait aspect** |
28
- | Output | `[1, 1, 896, 504]` depth |
29
- | Precision / size | FP16, **55 MB** |
30
- | Device | Pixel 8a, LiteRT GPU (`Accelerator.GPU`), **~0.9 s / image** (FP16, CompiledModel.Run) |
31
- | Fidelity | **corr 0.99948** vs official PyTorch; on-device **GPU-vs-CPU cos 0.99993** (re-verified, see below) |
32
-
33
- ![Depth Anything 3 Small — input photo and on-device depth map (Pixel 8a, LiteRT GPU)](sample-depth.png)
34
-
35
- ## Why a fixed 896×504 (native aspect, not square)
36
-
37
- DA3 processes images at their **native aspect ratio** (`upper_bound_resize`, longer side → 896, multiple of 14).
38
- Forcing a square `896×896` and letterbox-padding drops the match to corr **0.977** (the black padding leaks into
39
- the content through global attention). Converting at the native rectangle restores **corr 0.9994** and is also
40
- faster (fewer tokens). This checkpoint is built for **portrait ~9:16**. For another aspect, re-convert at that
41
- shape (or your camera's fixed aspect) with the script below.
42
-
43
- ## Preprocessing (must match)
44
-
45
- ```
46
- resize to 504×896 (W×H) → x/255 → (x - mean) / std
47
- mean = [0.485, 0.456, 0.406], std = [0.229, 0.224, 0.225] # ImageNet, RGB, NCHW
48
- ```
49
-
50
- ## GPU-clean conversion (what was patched)
51
-
52
- Converted with `litert-torch`. DA3 is not GPU-clean out of the box; the following exact, GPU-clean rewrites
53
- were applied (all numerically faithful unless noted):
54
-
55
- 1. checkpoint `model.` key-prefix strip (load fix)
56
- 2. RoPE `max_position = int(positions.max())+1` → constant (torch.export data-dependent)
57
- 3. fused-QKV attention → 3 separate Linears + 4D attention (avoids 5D RESHAPE; exact, 1e-6)
58
- 4. **LayerScale** `gamma` folded into `attn.proj` / `mlp.fc2` (the LayerScale MUL otherwise mis-lays-out the
59
- token dim on the GPU delegate: `fully_connected {1,1,N,C} vs {N,1,1,C}`)
60
- 5. `pos_embed` bicubic interpolation **baked** to a constant (the interpolate of a constant emits `GATHER_ND`
61
- on desktop and `RESIZE_BILINEAR` with 0 runtime inputs on device)
62
- 6. **ConvTranspose2d(k=s,stride=s)** → zero-stuff (nearest-upsample × top-left mask) + `Conv2d` (flipped
63
- weight) — exact equivalent (~1e-7), because the Pixel-8a GPU rejects `TRANSPOSE_CONV` and the conv+
64
- depth-to-space alternative needs >4D
65
- 7. DPT-head `custom_interpolate` `align_corners=True → False` (GPU bans `align_corners=True` resize) — **the
66
- only non-exact rewrite**; source of the residual ~0.05 % vs the official model
67
- 8. head UV pos-embed-again disabled (its `make_sincos` broadcast emits `BROADCAST_TO`; ratio-0.1 refinement)
68
- 9. camera-token insertion `x[:, :, 0] = cam_token` → `torch.cat` (in-place index-assign → `SELECT_V2`)
69
-
70
- Net result: `GATHER_ND = 0`, no `>4D` tensors, no `TRANSPOSE_CONV` / `BROADCAST_TO` / banned ops.
71
-
72
- ## Fidelity note (honest)
73
-
74
- corr **0.99948** vs the official FP32 PyTorch pipeline. FP16 is **not** a factor (FP32≡FP16, corr 1.0). The
75
- residual ~0.05 % is the `align_corners=True→False` change in (7), which the mobile GPU forces — an irreducible
76
- hardware constraint, not a conversion error. Structure and edge sharpness are visually identical.
77
-
78
- ## On-device GPU verification (re-confirmed)
79
-
80
- Re-verified on a Pixel 8a with the official LiteRT C++ runtime + ML Drift accelerator: the model compiles
81
- to **`Replacing 1460 out of 1460 node(s) with delegate (LITERT_CL)`** (full residency, single partition,
82
- no XNNPACK CPU fallback), and the **on-device GPU output matches the CPU/XNNPACK reference at cos 0.99993 /
83
- Pearson 0.99975** for the same input — i.e. the GPU result is numerically faithful, not merely "resident"
84
- (GPU full residency does not by itself guarantee a correct result).
85
-
86
- ## Minimal usage
87
-
88
- **Android (Kotlin, CompiledModel GPU)**
89
-
90
- ```kotlin
91
- val model = CompiledModel.create(context.assets, "da3_small_gpu_fp16.tflite",
92
- CompiledModel.Options(Accelerator.GPU), null)
93
- val inputs = model.createInputBuffers()
94
- val outputs = model.createOutputBuffers()
95
- inputs[0].writeFloat(chw) // [1,3,896,504] ImageNet-normalized, NCHW (portrait)
96
- model.run(inputs, outputs)
97
- val depth = outputs[0].readFloat() // [1,1,896,504]
98
- ```
99
-
100
- **Python (desktop verification)**
101
 
102
  ```python
103
- MEAN = np.array([0.485, 0.456, 0.406], np.float32)
104
- STD = np.array([0.229, 0.224, 0.225], np.float32)
105
  import numpy as np
106
  from PIL import Image
107
  from ai_edge_litert.interpreter import Interpreter
108
 
109
- img = Image.open("portrait.jpg").convert("RGB").resize((504, 896)) # W=504, H=896
110
- x = ((np.asarray(img, np.float32) / 255 - MEAN) / STD).transpose(2, 0, 1)[None]
 
 
 
111
 
112
- it = Interpreter(model_path="da3_small_gpu_fp16.tflite"); it.allocate_tensors()
113
- it.set_tensor(it.get_input_details()[0]["index"], x); it.invoke()
114
- d = it.get_tensor(it.get_output_details()[0]["index"])[0, 0] # [896,504]
115
- d = (d - d.min()) / (d.max() - d.min())
116
- Image.fromarray((d * 255).astype(np.uint8)).save("depth.png")
117
  ```
118
 
119
  ## Training data & PII
@@ -129,6 +68,15 @@ release and [paper](https://arxiv.org/abs/2511.10647) for full dataset details.
129
 
130
  ## Performance
131
 
 
 
 
 
 
 
 
 
 
132
  Measured on a **Pixel 8a** (Tensor G3, Android 16) with the standard TFLite [`benchmark_model`](https://ai.google.dev/edge/litert/models/measurement) tool — 10 warm-up runs then 50 timed runs, reported as the tool's mean.
133
 
134
  | Runtime | Backend | Graph on GPU | Latency |
@@ -163,7 +111,7 @@ Measured on a Raspberry Pi 5 Model B Rev 1.1 (8 GB, Raspberry Pi OS 64-bit) with
163
  |---|---:|---:|---:|---:|
164
  | `da3_small_gpu_fp16.tflite` | 4,201.3 ms | 4,055.0–4,251.9 ms | 109 | 445 MB |
165
 
166
- ## License
167
-
168
- Apache-2.0, inherited from the upstream [Depth Anything 3](https://github.com/ByteDance-Seed/depth-anything-3).
169
- This is a format conversion; all credit to the original authors (ByteDance-Seed).
 
8
  - depth-estimation
9
  - monocular-depth
10
  - on-device
 
11
  - depth-anything
12
  base_model: depth-anything/DA3-SMALL
13
  base_model_relation: quantized
14
  ---
15
 
16
+ # Depth Anything 3 Small — LiteRT
17
 
18
+ [Depth Anything 3 Small](https://huggingface.co/depth-anything/DA3-SMALL)
19
+ for single-image monocular depth estimation in LiteRT/TFLite format. The model
20
+ preserves the source depth head's interpolation and UV positional refinement.
21
 
22
+ | Property | Value |
23
  |---|---|
24
+ | File | `da3_small_gpu_fp16.tflite` |
25
+ | Size | 54.77 MB |
26
+ | Weights | FP16 storage for supported weights; other constants can remain FP32 |
27
+ | Input | RGB, float32 NCHW `[1, 3, 896, 504]` |
28
+ | Output | Float32 depth `[1, 1, 896, 504]` |
29
+ | Signature | `serving_default(args_0) -> output_0` |
30
+ | Input tensor | `serving_default_args_0` |
31
+ | Output tensor | `serving_default_output_0_output` |
32
+
33
+ Resize the RGB image directly to **504 × 896 pixels (width × height)** with
34
+ Pillow bilinear interpolation. Divide pixels by 255, then normalize using
35
+ ImageNet mean `[0.485, 0.456, 0.406]` and standard deviation
36
+ `[0.229, 0.224, 0.225]`. The fixed portrait shape uses a direct resize, without
37
+ cropping or letterboxing.
38
+
39
+ Python dependencies: `ai-edge-litert`, `numpy`, and `Pillow`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  ```python
 
 
42
  import numpy as np
43
  from PIL import Image
44
  from ai_edge_litert.interpreter import Interpreter
45
 
46
+ image = Image.open("image.jpg").convert("RGB")
47
+ image = image.resize((504, 896), Image.Resampling.BILINEAR)
48
+ pixels = np.asarray(image, dtype=np.float32) / 255.0
49
+ normalized = (pixels - [0.485, 0.456, 0.406]) / [0.229, 0.224, 0.225]
50
+ x = np.ascontiguousarray(normalized.transpose(2, 0, 1)[None], dtype=np.float32)
51
 
52
+ interpreter = Interpreter(model_path="da3_small_gpu_fp16.tflite", num_threads=4)
53
+ interpreter.allocate_tensors()
54
+ run = interpreter.get_signature_runner("serving_default")
55
+ depth = run(args_0=x)["output_0"][0, 0] # float32, shape [896, 504]
 
56
  ```
57
 
58
  ## Training data & PII
 
68
 
69
  ## Performance
70
 
71
+ These benchmark results are retained from the [previously published model card
72
+ at revision `5cd25d9`](https://huggingface.co/litert-community/Depth-Anything-3-Small/blob/5cd25d936e3fde2edef68f53b4123401454ac9c8/README.md).
73
+ The Pixel 8a, Snapdragon NPU, and Raspberry Pi measurements below refer to that
74
+ previous model file. Measurements of the proposed replacement are in [PR #1](https://huggingface.co/litert-community/Depth-Anything-3-Small/discussions/1).
75
+
76
+ The original card also reports approximately **0.9 seconds per image** on a
77
+ **Pixel 8a** through LiteRT `CompiledModel` GPU (`LITERT_CL`), with
78
+ **1,460 / 1,460 operators delegated**.
79
+
80
  Measured on a **Pixel 8a** (Tensor G3, Android 16) with the standard TFLite [`benchmark_model`](https://ai.google.dev/edge/litert/models/measurement) tool — 10 warm-up runs then 50 timed runs, reported as the tool's mean.
81
 
82
  | Runtime | Backend | Graph on GPU | Latency |
 
111
  |---|---:|---:|---:|---:|
112
  | `da3_small_gpu_fp16.tflite` | 4,201.3 ms | 4,055.0–4,251.9 ms | 109 | 445 MB |
113
 
114
+ Source checkpoint:
115
+ [`depth-anything/DA3-SMALL` at `e08cab65ca0ec38e7826075418411ab90cab4da3`](https://huggingface.co/depth-anything/DA3-SMALL/tree/e08cab65ca0ec38e7826075418411ab90cab4da3).
116
+ See the [Depth Anything 3 project](https://github.com/ByteDance-Seed/Depth-Anything-3)
117
+ for the original model. License: Apache 2.0.
da3_small_gpu_fp16.tflite CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:e170369a72ba1bba7486a4d2de555639fccd0595a9bb5b5349f7733ed4aebd1f
3
- size 55035456
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ae153854383ad4599651eb8ac0a6606c9101a29b41609ffad8f43703cf78d7df
3
+ size 54766600