Downloads · 30 days
115
39% of all-time downloads
litert-community/RF-DETR-Seg-Nano-LiteRT
RF-DETR-Seg-Nano-LiteRT is a image segmentation model from litert-community. Use it for the image segmentation task on the model card, and read the license before you ship it in a product. It is set up for litert. The card lists the license as apache-2.0.
Measured on device (edge-compat, rfdetrseggraphafp16): Galaxy S26 · LiteRT 2.2.0 · GPU (ML Drift) · 35.3 ms p50 (2026-08-26); Galaxy S26 · LiteRT 2.2.0 · NPU (QNN/HTP) · 27.8 ms p50 (2026-08-26); Raspberry Pi 5 · Lite…
Downloads · 30 days
115
39% of all-time downloads
All-time downloads
292
Public
Repo size
64.9 MB
Likes
1
Public
Click a slice to open those files.
.tflite61.8 MB · 95%
From the Hugging Face model README
Measured on device (edge-compat, rfdetrseg_grapha_fp16): Galaxy S26 · LiteRT 2.2.0 · GPU (ML Drift) · 35.3 ms p50 (2026-08-26); Galaxy S26 · LiteRT 2.2.0 · NPU (QNN/HTP) · 27.8 ms p50 (2026-08-26); Raspberry Pi 5 · LiteRT 2.2.0.dev20260804 · CPU/XNNPACK, 4 threads · 695 ms p50 (2026-08-31); browser · Chromium 151 on M4 Max · LiteRT.js 2.5.3 · WebGPU · 24.9 ms p50 · output differs from CPU (max rel diff 0.19) (2026-08-20). Record: https://github.com/john-rocky/edge-compat/blob/main/cards/rf-detr-seg-nano__rfdetrseg_grapha_fp16/CARD.md
Measured on device (edge-compat, rfdetrseg_graphb_fp16): Galaxy S26 · LiteRT 2.2.0 · GPU (ML Drift) · 22.7 ms p50 (2026-08-25); Galaxy S26 · LiteRT 2.2.0 · NPU (QNN/HTP) · 64.1 ms p50 (2026-08-25); Raspberry Pi 5 · LiteRT 2.2.0.dev20260804 · CPU/XNNPACK, 4 threads · 325 ms p50 (2026-08-31); browser · Chromium 151 on M4 Max · LiteRT.js 2.5.3 · WebGPU · 16.7 ms p50 · output differs from CPU (max rel diff 15) (2026-08-20). Record: https://github.com/john-rocky/edge-compat/blob/main/cards/rf-detr-seg-nano__rfdetrseg_graphb_fp16/CARD.md
Live output on a Pixel 8a (photo: Pexels, free license).
RF-DETR-Seg (Roboflow, rf-detr 1.9.3) instance segmentation, converted to LiteRT and running 100% on the CompiledModel GPU (ML Drift) on a phone — a DETR-family segmenter (DINOv2-S/12 backbone + deformable-attention decoder + ConvNeXt-style mask head, 33.6M params, COCO seg AP<sub>50</sub> 63.0) with no CPU/ONNX fallback.
Off-the-shelf it is GPU-incompatible (deformable grid_sample → GATHER_ND, two-stage query selection →
TOPK/GATHER, SDPA → rank-3 batched matmuls, and several large baked constants that the GPU delegate
executes incorrectly). Here it is converted with litert-torch, re-authored op-by-op, and split into
two GPU graphs with a tiny host step between them.
| File | What it is | Size |
|---|---|---|
rfdetrseg_graphA_fp16.tflite | backbone + encoder + proposal heads → enc_class[1,676,91], enc_delta[1,676,4], memory×2[1,676,256] | 47.0 MB |
rfdetrseg_graphB_fp16.tflite | decoder + box/class heads + mask head → boxes[1,100,4] (cxcywh), logits[1,100,91], masks[1,100,78,78] | 14.8 MB |
clspos.bin | host-fed constant cls_token + pos_embed[:, :1], float32 [1,1,384] | 1.5 KB |
pospatch.bin | host-fed constant patch pos-embed, float32 [1,676,384] | 1.0 MB |
query_feat.bin | host-fed constant decoder query embedding, float32 [1,100,256] | 100 KB |
refpoint_embed.bin | learned reference points for the host reparam, float32 [1,100,4] | 1.6 KB |
image[1,3,312,312] + clspos[1,1,384] + pospatch[1,676,384]
→[GPU Graph A]→ enc_class[1,676,91], enc_delta[1,676,4], memory×2[1,676,256]
→[host: ÷2 → proposal-grid combine → top-100 by max class score
→ gather → reparam with refpoint_embed]→ refpoint[1,100,4]
→[GPU Graph B (memory, refpoint, query_feat)]→ boxes, logits, masks[1,100,78,78]
→[host: sigmoid + threshold + per-class NMS]→ instances (mask inside = logit > 0)
The proposal grid is image-independent (26×26, cxcy = (grid+0.5)/26, wh = 0.05), so the host step is
pure elementwise math plus a topk.
Why three constants are graph inputs: the ML Drift GPU delegate silently mis-executes compute
chains that consume large baked-constant tensors (fp32 and fp16 return identical wrong numbers — not
a precision issue). The cls+pos embedding, the patch pos-embed and the decoder query embedding are
therefore fed at runtime from the .bin files above, and the reparam combine (which would consume the
baked refpoint_embed) runs on the host. Graph A also emits memory×2 because a [1,N,C] tensor that
is both consumed and output comes back zeroed on the delegate — halve it on the host.
Android (Kotlin, CompiledModel GPU)
val env = Environment.create() // ONE shared env for both graphs
val ga = CompiledModel.create(pathA, CompiledModel.Options(Accelerator.GPU), env)
val gb = CompiledModel.create(pathB, CompiledModel.Options(Accelerator.GPU), env)
val aIn = ga.createInputBuffers(); val aOut = ga.createOutputBuffers()
val bIn = gb.createInputBuffers(); val bOut = gb.createOutputBuffers()
// resolve slots by float size (converter order is arbitrary); write the .bin constants once:
aIn[clsposSlot].writeFloat(clspos); aIn[pospatchSlot].writeFloat(pospatch)
bIn[qfSlot].writeFloat(queryFeat)
aIn[imageSlot].writeFloat(chw) // [1,3,312,312] RGB, ImageNet mean/std
ga.run(aIn, aOut)
// host: memory = memory2 * 0.5; proposal combine + top-100 + gather + reparam -> refpoint[1,100,4]
bIn[memSlot].writeFloat(memory); bIn[refSlot].writeFloat(refpoint)
gb.run(bIn, bOut)
val boxes = bOut[boxSlot].readFloat() // [1,100,4] cxcywh in [0,1]
val logits = bOut[logitSlot].readFloat() // [1,100,91] -> sigmoid + threshold + NMS
val masks = bOut[maskSlot].readFloat() // [1,100,78,78] full-image logits, inside = > 0
Python (desktop verification, CompiledModel API)
import numpy as np
from PIL import Image
from ai_edge_litert.compiled_model import CompiledModel
R, NP_, NQ, NC, H, M, G = 312, 676, 100, 91, 256, 78, 26
img = Image.open("photo.jpg").convert("RGB").resize((R, R))
x = np.asarray(img, np.float32) / 255.0
x = ((x - [0.485, 0.456, 0.406]) / [0.229, 0.224, 0.225]).astype(np.float32).transpose(2, 0, 1)[None]
def run(path, feeds): # feed/fetch buffers by float count (converter slot order is arbitrary)
m = CompiledModel.from_file(path)
ins, outs = m.create_input_buffers(0), m.create_output_buffers(0)
for i in range(len(ins)):
n = m.get_input_buffer_requirements(i)["buffer_size"] // 4
ins[i].write(np.ascontiguousarray(feeds[n].ravel(), np.float32))
m.run_by_index(0, ins, outs)
return {m.get_output_buffer_requirements(j)["buffer_size"] // 4:
outs[j].read(m.get_output_buffer_requirements(j)["buffer_size"] // 4, np.float32)
for j in range(len(outs))}
clspos = np.fromfile("clspos.bin", np.float32) # [1,1,384]
pospatch = np.fromfile("pospatch.bin", np.float32) # [1,676,384]
rp = np.fromfile("refpoint_embed.bin", np.float32).reshape(NQ, 4)
qf = np.fromfile("query_feat.bin", np.float32) # [1,100,256]
a = run("rfdetrseg_graphA_fp16.tflite", {x.size: x, clspos.size: clspos, pospatch.size: pospatch})
enc_cls = a[NP_ * NC].reshape(NP_, NC)
delta = a[NP_ * 4].reshape(NP_, 4)
mem = a[NP_ * H] * 0.5 # graph outputs memory*2
gy, gx = np.mgrid[0:G, 0:G] # proposal grid (image-independent)
prop = np.stack([(gx + .5) / G, (gy + .5) / G], -1).reshape(NP_, 2)
cxcy = delta[:, :2] * 0.05 + prop
wh = np.exp(delta[:, 2:]) * 0.05
top = np.argsort(-enc_cls.max(-1))[:NQ] # top-100 by max class logit
ts = np.concatenate([cxcy, wh], -1)[top]
ref = np.concatenate([rp[:, :2] * ts[:, 2:] + ts[:, :2], np.exp(rp[:, 2:]) * ts[:, 2:]], -1)
b = run("rfdetrseg_graphB_fp16.tflite", {mem.size: mem, ref.size: ref, qf.size: qf})
boxes = b[NQ * 4].reshape(NQ, 4) # cxcywh in [0,1]
logits = b[NQ * NC].reshape(NQ, NC) # 91-way (index = COCO category id)
masks = b[NQ * M * M].reshape(NQ, M, M) # full-image raw logits, inside = > 0
score = 1 / (1 + np.exp(-logits.max(-1))); cls = logits.argmax(-1)
for q in np.where((score > 0.5) & (cls > 0))[0]: # + per-class NMS (IoU 0.6) in a real app
print(f"COCO id {cls[q]:2d} {score[q]:.2f} cxcywh={np.round(boxes[q], 3)} mask px={int((masks[q] > 0).sum())}")
| graph | nodes on GPU | time |
|---|---|---|
| Graph A | 1293/1293 LITERT_CL, 1 partition | 17.5 ms |
| Graph B | 884/884 LITERT_CL, 1 partition | 9.1 ms |
Real-image end-to-end (device chain vs the official PyTorch RFDETRSegNano.predict, threshold 0.5):
every detection matches with box IoU ≥ 0.99, mask IoU ≥ 0.995 and identical classes on the test
images (4/4 and 2/2 detections).
Street scene on the Pixel 8a: 10 instances (photo: Pexels, free license).
[0.485,0.456,0.406] / [0.229,0.224,0.225]), NCHW,
plus the two constant embedding inputs (clspos.bin, pospatch.bin).boxes are cxcywh normalized to [0,1]; logits are 91-way (index = COCO category id, id 0 unused);
masks are per-query full-image 78×78 raw logits (sigmoid > 0.5 ⇔ logit > 0), upsample bilinearly to the frame.Converted from the PyTorch checkpoint (rfdetr 1.9.3, RFDETRSegNano) with litert-torch (NCHW
preserved) + fp16 weights via ai-edge-quantizer float-casting. GPU re-authoring, all numerically exact:
nn.MultiheadAttention → manual rank-4 attention (the delegate mis-executes rank-3 batched matmuls).grid_sample → GATHER/CAST-free tent-matmul bilinear sampler (rank-4 BMM).[B,HW,C] detour.dim_t baked, interleave via reshape; seg einsum → rank-4 matmul.Original project: roboflow/rf-detr (RF-DETR-Seg Nano, tag 1.9.3) —
Apache-2.0. The rfdetr package and the
Apache-designated checkpoints (including RF-DETR-Seg-N) are Apache 2.0; only the separate rfdetr_plus
components are under PML 1.0 (not used here).
rfdetrseg_graphA_fp16.tflite — the NPU is 1.27x faster than the GPU (27.83 ms against 35.28 ms) and loads 9.10x faster (194 ms against 1762 ms).rfdetrseg_graphB_fp16.tflite — the GPU is faster: 22.66 ms against 64.07 ms on the NPU, a factor of 2.83. The NPU still loads 10.98x faster (141 ms against 1550 ms).| file | backend | compiled | inference (median / min) | load |
|---|---|---|---|---|
rfdetrseg_graphA_fp16.tflite | NPU (Hexagon v81) | on-device JIT | 27.83 ms / 27.42 ms | 194 ms |
rfdetrseg_graphA_fp16.tflite | GPU (Adreno) | — | 35.28 ms / 34.45 ms | 1762 ms |
rfdetrseg_graphB_fp16.tflite | NPU (Hexagon v81) | on-device JIT | 64.07 ms / 63.02 ms | 141 ms |
rfdetrseg_graphB_fp16.tflite | GPU (Adreno) | — | 22.66 ms / 20.93 ms | 1550 ms |
Measured on a Samsung Galaxy S26 (Snapdragon 8 Elite Gen 5 / SM8850, Hexagon v81, Android 16) with LiteRT CompiledModel 2.2.0, one accelerator per process, 5 warm-up runs then N=50 timed runs, median reported. Every run held thermal status NONE throughout. Headroom 0.77–0.82, where 1.0 is the throttling threshold.
The NPU rows ran the published file unchanged. LiteRT compiled it for the Hexagon on the device at first load. Those first compiles took 28 s to 37 s here. The load column above is the cached load every later run pays. Recipe and the runtime libraries it needs: NPU guide.
GPU wiring: GPU guide.
Measured on a Raspberry Pi 5 Model B Rev 1.1 (8 GB, Raspberry Pi OS 64-bit) with the LiteRT benchmark_model tool from litert-cli-nightly 0.2.0.dev20260805: CPU inference (XNNPACK, 4 threads), 3 invocations per file of 10 warm-up plus 50 timed runs (the tool caps a phase at 150 s, so very slow graphs run fewer — the Runs column is the actual timed total). The latency is the median across invocations; the spread is the min–max over all timed runs. No thermal throttling occurred during these runs (vcgencmd get_throttled stayed 0x0).
| File | Inference (median) | Spread (min–max) | Runs | Peak memory |
|---|---|---|---|---|
rfdetrseg_graphA_fp16.tflite | 695.3 ms | 691.3–701.8 ms | 150 | 182 MB |
rfdetrseg_graphB_fp16.tflite | 324.5 ms | 322.4–328.7 ms | 150 | 138 MB |