Instructions to use mlboydaisuke/Depth-Anything-3-LiteRT with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LiteRT
How to use mlboydaisuke/Depth-Anything-3-LiteRT with LiteRT:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
Upload convert_da3.py with huggingface_hub
Browse files- convert_da3.py +227 -0
convert_da3.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Produce the full GPU-clean DA3-SMALL mono tflite:
|
| 3 |
+
weight-prefix fix + C14 (RoPE const) + C12 (qkv-decompose) + pos_embed bilinear + ConvTranspose->bilinear+1x1.
|
| 4 |
+
Measures depth corr vs the ORIGINAL model, op-checks, then FP16-quantizes."""
|
| 5 |
+
import sys, types, math, numpy as np, json, collections
|
| 6 |
+
class _Dummy:
|
| 7 |
+
def __getattr__(self, n): return lambda *a, **k: None
|
| 8 |
+
_pp = types.ModuleType('scipy.sparse.linalg._propack')
|
| 9 |
+
for nm in ('_spropack', '_dpropack', '_cpropack', '_zpropack'): setattr(_pp, nm, _Dummy())
|
| 10 |
+
sys.modules['scipy.sparse.linalg._propack'] = _pp
|
| 11 |
+
for _n, _t in (("bool", bool), ("float", float), ("int", int), ("object", object), ("str", str)):
|
| 12 |
+
if not hasattr(np, _n): setattr(np, _n, _t)
|
| 13 |
+
sys.path.insert(0, "src")
|
| 14 |
+
import torch, torch.nn as nn, torch.nn.functional as F, types as _ty
|
| 15 |
+
from PIL import Image
|
| 16 |
+
from huggingface_hub import hf_hub_download
|
| 17 |
+
from safetensors.torch import load_file
|
| 18 |
+
from depth_anything_3.cfg import create_object
|
| 19 |
+
from depth_anything_3.model.dinov2.layers.attention import Attention
|
| 20 |
+
import depth_anything_3.model.dinov2.layers.rope as _rope
|
| 21 |
+
|
| 22 |
+
# C14: RoPE max_position = int(positions.max())+1 is data-dependent (torch.export aborts). Fix = constant
|
| 23 |
+
# 128 (>= the 33 a 518 grid needs); extra table rows are unused -> numerically identical to stock.
|
| 24 |
+
def _rope_fwd(self, tokens, positions):
|
| 25 |
+
fd = tokens.size(-1) // 2
|
| 26 |
+
c, s = self._compute_frequency_components(fd, 128, tokens.device, tokens.dtype)
|
| 27 |
+
v, h = tokens.chunk(2, dim=-1)
|
| 28 |
+
v = self._apply_1d_rope(v, positions[..., 0], c, s)
|
| 29 |
+
h = self._apply_1d_rope(h, positions[..., 1], c, s)
|
| 30 |
+
return torch.cat((v, h), dim=-1)
|
| 31 |
+
_rope.RotaryPositionEmbedding2D.forward = _rope_fwd
|
| 32 |
+
|
| 33 |
+
cfg = json.load(open(hf_hub_download("depth-anything/DA3-SMALL", "config.json")))
|
| 34 |
+
net = create_object(cfg["config"]).eval()
|
| 35 |
+
sd = load_file(hf_hub_download("depth-anything/DA3-SMALL", "model.safetensors"))
|
| 36 |
+
net.load_state_dict({(k[6:] if k.startswith("model.") else k): v for k, v in sd.items()}, strict=False)
|
| 37 |
+
|
| 38 |
+
class Mono(nn.Module):
|
| 39 |
+
def __init__(s, n): super().__init__(); s.b, s.h = n.backbone, n.head
|
| 40 |
+
def forward(s, x):
|
| 41 |
+
H, W = x.shape[-2], x.shape[-1]
|
| 42 |
+
f, _ = s.b(x.unsqueeze(1), cam_token=None)
|
| 43 |
+
return s.h(f, H, W, patch_start_idx=0)["depth"]
|
| 44 |
+
m = Mono(net).eval()
|
| 45 |
+
|
| 46 |
+
# input H,W (multiples of 14). argv[2]=H, argv[3]=W (default square). Native aspect (no padding) matches official.
|
| 47 |
+
H_IN = int(sys.argv[2]) if len(sys.argv) > 2 else 448
|
| 48 |
+
W_IN = int(sys.argv[3]) if len(sys.argv) > 3 else H_IN
|
| 49 |
+
S = H_IN # back-compat alias used below for the square-ish paths
|
| 50 |
+
img = Image.open(sys.argv[1]).convert("RGB").resize((W_IN, H_IN), Image.BILINEAR)
|
| 51 |
+
a = (np.asarray(img, np.float32)/255.0 - [0.485,0.456,0.406]) / [0.229,0.224,0.225]
|
| 52 |
+
x_img = torch.from_numpy(np.transpose(a, (2,0,1))[None].astype(np.float32))
|
| 53 |
+
with torch.no_grad():
|
| 54 |
+
d_orig = m(x_img)[0,0].numpy() # ORIGINAL (all stock)
|
| 55 |
+
|
| 56 |
+
# ---- C12: qkv-decompose -> 4D manual attention ----
|
| 57 |
+
def _attn(self, x, pos=None, attn_mask=None):
|
| 58 |
+
B,N,C = x.shape; H=self.num_heads; Hd=C//H
|
| 59 |
+
q=self.q_lin(x).reshape(B,N,H,Hd).permute(0,2,1,3); k=self.k_lin(x).reshape(B,N,H,Hd).permute(0,2,1,3)
|
| 60 |
+
v=self.v_lin(x).reshape(B,N,H,Hd).permute(0,2,1,3); q,k=self.q_norm(q),self.k_norm(k)
|
| 61 |
+
if self.rope is not None and pos is not None: q=self.rope(q,pos); k=self.rope(k,pos)
|
| 62 |
+
q=q*self.scale; attn=(q@k.transpose(-2,-1)).softmax(-1)
|
| 63 |
+
return self.proj_drop(self.proj((attn@v).transpose(1,2).reshape(B,N,C)))
|
| 64 |
+
Attention.forward = _attn
|
| 65 |
+
for mod in net.modules():
|
| 66 |
+
if isinstance(mod, Attention):
|
| 67 |
+
C=mod.qkv.in_features; w=mod.qkv.weight; b=mod.qkv.bias
|
| 68 |
+
for nm,sl in (("q_lin",slice(0,C)),("k_lin",slice(C,2*C)),("v_lin",slice(2*C,3*C))):
|
| 69 |
+
lin=nn.Linear(C,C,bias=b is not None)
|
| 70 |
+
with torch.no_grad():
|
| 71 |
+
lin.weight.copy_(w[sl]); b is not None and lin.bias.copy_(b[sl])
|
| 72 |
+
setattr(mod,nm,lin)
|
| 73 |
+
|
| 74 |
+
# ---- LayerScale bake (GPU FC-layout fix): fold ls1/ls2 gamma into attn.proj / mlp.fc2, ls->Identity.
|
| 75 |
+
# The LayerScale MUL (FC output [N,C] * gamma [C]) makes ML Drift mis-lay-out the token dim
|
| 76 |
+
# ({1,1,1025,384} vs {1025,1,1,384}) -> GPU compile fails. Baking eliminates the MUL. (MoGe's fix.)
|
| 77 |
+
def bake_layerscale(model):
|
| 78 |
+
cnt = 0
|
| 79 |
+
for block in model.modules():
|
| 80 |
+
if hasattr(block, "ls1") and hasattr(getattr(block, "ls1"), "gamma") and hasattr(block, "attn"):
|
| 81 |
+
g = block.ls1.gamma.data.squeeze()
|
| 82 |
+
with torch.no_grad():
|
| 83 |
+
block.attn.proj.weight.data.mul_(g.unsqueeze(1))
|
| 84 |
+
if block.attn.proj.bias is not None: block.attn.proj.bias.data.mul_(g)
|
| 85 |
+
block.ls1 = nn.Identity(); cnt += 1
|
| 86 |
+
if hasattr(block, "ls2") and hasattr(getattr(block, "ls2"), "gamma") and hasattr(block, "mlp"):
|
| 87 |
+
g = block.ls2.gamma.data.squeeze()
|
| 88 |
+
last = None
|
| 89 |
+
for ch in reversed(list(block.mlp.children())):
|
| 90 |
+
if isinstance(ch, nn.Linear): last = ch; break
|
| 91 |
+
if last is None and hasattr(block.mlp, "fc2"): last = block.mlp.fc2
|
| 92 |
+
if last is not None:
|
| 93 |
+
with torch.no_grad():
|
| 94 |
+
last.weight.data.mul_(g.unsqueeze(1))
|
| 95 |
+
if last.bias is not None: last.bias.data.mul_(g)
|
| 96 |
+
block.ls2 = nn.Identity(); cnt += 1
|
| 97 |
+
print(f"baked {cnt} LayerScale into Linear")
|
| 98 |
+
return cnt
|
| 99 |
+
bake_layerscale(net)
|
| 100 |
+
|
| 101 |
+
# ---- C15: pos_embed BAKE (interpolating the constant pos_embed emits RESIZE_BILINEAR with 0 runtime
|
| 102 |
+
# inputs -> GPU rejects). Precompute the bilinear-resized pos_embed as a constant buffer -> no resize op. ----
|
| 103 |
+
GH, GW = H_IN // 14, W_IN // 14
|
| 104 |
+
for mod in net.modules():
|
| 105 |
+
if hasattr(mod, "interpolate_pos_encoding") and hasattr(mod, "pos_embed"):
|
| 106 |
+
with torch.no_grad():
|
| 107 |
+
N = mod.pos_embed.shape[1] - 1; Mb = int(math.sqrt(N)); dim = mod.pos_embed.shape[-1]
|
| 108 |
+
pe = mod.pos_embed.float(); cls = pe[:, 0]; patch = pe[:, 1:]
|
| 109 |
+
patch = F.interpolate(patch.reshape(1, Mb, Mb, dim).permute(0, 3, 1, 2),
|
| 110 |
+
size=(GH, GW), mode="bicubic", antialias=False) # bicubic = match official (baked const)
|
| 111 |
+
patch = patch.permute(0, 2, 3, 1).view(1, -1, dim)
|
| 112 |
+
baked = torch.cat((cls.unsqueeze(0), patch), dim=1).to(mod.pos_embed.dtype) # [1,1025,dim]
|
| 113 |
+
mod.register_buffer("_baked_pos", baked)
|
| 114 |
+
mod.interpolate_pos_encoding = _ty.MethodType(lambda self, x, w, h: self._baked_pos, mod)
|
| 115 |
+
|
| 116 |
+
# ---- SELECT_V2 fix: `x[:, :, 0] = cam_token` (in-place index assign) lowers to SELECT_V2 with a
|
| 117 |
+
# broadcast 'else' shape the GPU delegate rejects. Replace with an equivalent torch.cat (exact, GPU-clean).
|
| 118 |
+
# (alt_start must stay on — the camera-token / global-attn path DOES affect mono depth.) ----
|
| 119 |
+
import depth_anything_3.model.dinov2.vision_transformer as _vt
|
| 120 |
+
def _patched_gil(self, x, n=1, export_feat_layers=[], **kwargs):
|
| 121 |
+
B, S, _, H, W = x.shape
|
| 122 |
+
x = self.prepare_tokens_with_masks(x)
|
| 123 |
+
output, total_block_len, aux_output = [], len(self.blocks), []
|
| 124 |
+
blocks_to_take = range(total_block_len - n, total_block_len) if isinstance(n, int) else n
|
| 125 |
+
pos, pos_nodiff = self._prepare_rope(B, S, H, W, x.device)
|
| 126 |
+
local_x = x
|
| 127 |
+
for i, blk in enumerate(self.blocks):
|
| 128 |
+
if i < self.rope_start or self.rope is None:
|
| 129 |
+
g_pos, l_pos = None, None
|
| 130 |
+
else:
|
| 131 |
+
g_pos, l_pos = pos_nodiff, pos
|
| 132 |
+
if self.alt_start != -1 and (i == self.alt_start - 1) and x.shape[1] >= _vt.THRESH_FOR_REF_SELECTION and kwargs.get("cam_token", None) is None:
|
| 133 |
+
b_idx = _vt.select_reference_view(x, strategy=kwargs.get("ref_view_strategy", "saddle_balanced"))
|
| 134 |
+
x = _vt.reorder_by_reference(x, b_idx); local_x = _vt.reorder_by_reference(local_x, b_idx)
|
| 135 |
+
if self.alt_start != -1 and i == self.alt_start:
|
| 136 |
+
if kwargs.get("cam_token", None) is not None:
|
| 137 |
+
cam_token = kwargs.get("cam_token")
|
| 138 |
+
else:
|
| 139 |
+
ref_token = self.camera_token[:, :1].expand(B, -1, -1)
|
| 140 |
+
src_token = self.camera_token[:, 1:].expand(B, S - 1, -1)
|
| 141 |
+
cam_token = torch.cat([ref_token, src_token], dim=1)
|
| 142 |
+
x = torch.cat([cam_token.unsqueeze(2), x[:, :, 1:]], dim=2) # was: x[:, :, 0] = cam_token
|
| 143 |
+
if self.alt_start != -1 and i >= self.alt_start and i % 2 == 1:
|
| 144 |
+
x = self.process_attention(x, blk, "global", pos=g_pos, attn_mask=kwargs.get("attn_mask", None))
|
| 145 |
+
else:
|
| 146 |
+
x = self.process_attention(x, blk, "local", pos=l_pos); local_x = x
|
| 147 |
+
if i in blocks_to_take:
|
| 148 |
+
out_x = torch.cat([local_x, x], dim=-1) if self.cat_token else x
|
| 149 |
+
if x.shape[1] >= _vt.THRESH_FOR_REF_SELECTION and self.alt_start != -1 and "b_idx" in locals():
|
| 150 |
+
out_x = _vt.restore_original_order(out_x, b_idx)
|
| 151 |
+
output.append((out_x[:, :, 0], out_x))
|
| 152 |
+
if i in export_feat_layers:
|
| 153 |
+
aux_output.append(x)
|
| 154 |
+
return output, aux_output
|
| 155 |
+
_vt.DinoVisionTransformer._get_intermediate_layers_not_chunked = _patched_gil
|
| 156 |
+
|
| 157 |
+
# ---- TRANSPOSE_CONV (Pixel 8a reject): ConvTranspose2d(k=s,stride=s) -> bilinear-resize + 1x1 conv ----
|
| 158 |
+
# 1x1 conv weight = mean of the transposed kernel over its s*s positions (preserves channel mixing;
|
| 159 |
+
# spatial upsampling handled by bilinear). 1x1 conv commutes with bilinear so order is exact for the mix.
|
| 160 |
+
# EXACT GPU-clean equivalent: ConvTranspose2d(k=s,stride=s) == zero-stuff (nearest-upsample × top-left
|
| 161 |
+
# mask) + Conv2d(flipped weight). Matches the learned upsampler to ~1e-7 → depth stays as sharp as the
|
| 162 |
+
# original (a bilinear approx blurred it). Mask is a precomputed constant buffer (no index-assign/broadcast).
|
| 163 |
+
class ZeroStuffConvT(nn.Module):
|
| 164 |
+
def __init__(self, ct, H, W):
|
| 165 |
+
super().__init__(); self.s = ct.stride[0]; self.k = ct.kernel_size[0]
|
| 166 |
+
self.register_buffer("w", ct.weight.flip(2, 3).transpose(0, 1).contiguous())
|
| 167 |
+
self.register_buffer("b", ct.bias.detach().clone() if ct.bias is not None else torch.zeros(ct.out_channels))
|
| 168 |
+
s = self.s; mk = np.zeros((H*s, W*s), np.float32); mk[::s, ::s] = 1.0
|
| 169 |
+
self.register_buffer("mask", torch.from_numpy(mk)[None, None])
|
| 170 |
+
def forward(self, x):
|
| 171 |
+
H, W = x.shape[-2], x.shape[-1]; s, k = self.s, self.k
|
| 172 |
+
xn = F.interpolate(x, size=(H*s, W*s), mode="nearest")
|
| 173 |
+
y = F.conv2d(xn * self.mask, self.w, bias=self.b, padding=k-1)
|
| 174 |
+
return y[:, :, :H*s, :W*s]
|
| 175 |
+
# discover each ConvTranspose input size via a dry run, then swap with the exact equivalent
|
| 176 |
+
_ct_hw, _hooks = {}, []
|
| 177 |
+
for _nm, _mod in net.named_modules():
|
| 178 |
+
if isinstance(_mod, nn.ConvTranspose2d):
|
| 179 |
+
def _mk(nm):
|
| 180 |
+
def _h(m, inp, out): _ct_hw[nm] = (inp[0].shape[-2], inp[0].shape[-1])
|
| 181 |
+
return _h
|
| 182 |
+
_hooks.append(_mod.register_forward_hook(_mk(_nm)))
|
| 183 |
+
with torch.no_grad(): m(x_img)
|
| 184 |
+
for _hk in _hooks: _hk.remove()
|
| 185 |
+
def swap_ct(module, prefix=""):
|
| 186 |
+
for name, ch in module.named_children():
|
| 187 |
+
full = f"{prefix}.{name}" if prefix else name
|
| 188 |
+
if isinstance(ch, nn.ConvTranspose2d):
|
| 189 |
+
H, W = _ct_hw[full]; setattr(module, name, ZeroStuffConvT(ch, H, W))
|
| 190 |
+
else: swap_ct(ch, full)
|
| 191 |
+
swap_ct(net)
|
| 192 |
+
|
| 193 |
+
# ---- DPT head: align_corners=True -> False (banned RESIZE_BILINEAR) + drop _add_pos_embed expand (BROADCAST_TO) ----
|
| 194 |
+
import depth_anything_3.model.utils.head_utils as _hu
|
| 195 |
+
import depth_anything_3.model.dualdpt as _dd
|
| 196 |
+
import depth_anything_3.model.dpt as _dpt
|
| 197 |
+
_orig_ci = _hu.custom_interpolate
|
| 198 |
+
def _ci_no_ac(x, size=None, scale_factor=None, mode="bilinear", align_corners=True):
|
| 199 |
+
return _orig_ci(x, size=size, scale_factor=scale_factor, mode=mode, align_corners=False)
|
| 200 |
+
_hu.custom_interpolate = _ci_no_ac; _dd.custom_interpolate = _ci_no_ac; _dpt.custom_interpolate = _ci_no_ac
|
| 201 |
+
# head pos-embed-again (UV sincos, ratio 0.1): make_sincos broadcast emits BROADCAST_TO. Baking it as
|
| 202 |
+
# constants matches official ~0.0002 better but adds ~64 MB (full [1,C,H,W] per shape) — not worth it.
|
| 203 |
+
# Disable it (the ratio-0.1 UV refinement is negligible vs the size cost).
|
| 204 |
+
_n_pe = 0
|
| 205 |
+
for mod in net.modules():
|
| 206 |
+
if isinstance(mod, _dd.DualDPT) and getattr(mod, "pos_embed", False):
|
| 207 |
+
mod.pos_embed = False; _n_pe += 1
|
| 208 |
+
print(f"disabled head pos_embed on {_n_pe} DualDPT")
|
| 209 |
+
|
| 210 |
+
with torch.no_grad():
|
| 211 |
+
d_clean = m(x_img)[0,0].numpy() # FULLY GPU-CLEAN
|
| 212 |
+
corr = np.corrcoef(d_orig.flatten(), d_clean.flatten())[0,1]
|
| 213 |
+
print(f"depth corr (original vs full GPU-clean) = {corr:.6f} mean-rel-diff = {np.abs(d_orig-d_clean).mean()/(np.abs(d_orig).mean()+1e-9)*100:.3f}%")
|
| 214 |
+
|
| 215 |
+
# ---- convert + op-check (canonical banned list; SELECT_V2 is NOT banned) ----
|
| 216 |
+
dummy = torch.rand(1, 3, H_IN, W_IN)
|
| 217 |
+
import litert_torch
|
| 218 |
+
litert_torch.convert(m.eval(), (dummy,)).export("da3_small_gpu.tflite")
|
| 219 |
+
from ai_edge_litert.interpreter import Interpreter
|
| 220 |
+
BANNED={'GATHER_ND','GATHER','TOPK_V2','PACK','SPLIT','FLEX_ERF','ERF','TRANSPOSE_CONV','BROADCAST_TO'}
|
| 221 |
+
it=Interpreter(model_path="da3_small_gpu.tflite"); it.allocate_tensors()
|
| 222 |
+
ops=collections.Counter(d.get('op_name','?') for d in it._get_ops_details())
|
| 223 |
+
bad={k:v for k,v in ops.items() if k in BANNED}
|
| 224 |
+
over=sum(1 for d in it.get_tensor_details() if len(d.get('shape',[]))>4)
|
| 225 |
+
print(f"op-check FP32: banned {bad or 'NONE'} | >4D {over} | GELU {ops.get('GELU',0)} | SELECT_V2 {ops.get('SELECT_V2',0)}")
|
| 226 |
+
print("VERDICT:", "GPU-CLEAN" if not bad and not over else "BLOCKERS REMAIN")
|
| 227 |
+
import os; print("FP32 size %.1f MB" % (os.path.getsize("da3_small_gpu.tflite")/1e6))
|