RAM++ (Recognize Anything Plus) β€” LiteRT on-device image tagging

RAM++ (Apache-2.0) re-authored for LiteRT: give it a photo, get the tags it recognizes from a 4,585-tag open vocabulary β€” per-tag sigmoid, no fixed class head. Four graphs β€” the Swin-L encoder stages 0-2 and the Query2Label tag head run on the CompiledModel GPU; the last Swin stage and the 479 MB frozen tag bank run on CPU (the deep Swin block fp16-miscomputes on the Mali delegate β€” see below).

Verified on a Pixel 8a: Swin 0-2 GPU (corr 0.998) + stage-3/reweight CPU (exact) + tag head GPU (corr 0.9987, ~270 ms). Sample photo (a dog on a couch) β†’ 14 tags in ~2 s, all correct: dog Β· couch Β· living room Β· sit Β· carpet Β· picture frame Β· plant Β· armchair Β· lamp Β· pillow ….

Files

file graph in β†’ out delegate
ram_swin_s012_fp16.tflite Swin stages 0-2 image [1,3,384,384] β†’ feat [1,144,1536] GPU
ram_stage3_tail_fp16.tflite Swin stage 3 + norm + proj feat β†’ image_embeds [1,145,512] CPU
ram_reweight_fp16.tflite multi-grained reweight cls [1,512] β†’ tag queries [1,4585,768] CPU
ram_taghead_fp16.tflite Query2Label tag head queries + image_embeds β†’ logits [1,4585] GPU
ram_tag_list.txt, ram_tag_threshold.bin host assets (4585 tags + per-class thresholds) β€” β€”

Pipeline

image β†’[ImageNet norm]β†’ [GPU Swin 0-2]β†’ feat β†’[CPU Swin-3 + norm + proj]β†’ image_embeds[1,145,512]
       token0 = cls β†’[CPU reweight over the 4585Γ—51 tag bank]β†’ queries[1,4585,768]
       (queries, image_embeds) β†’[GPU Q2L tag head]β†’ logits β†’[sigmoid + per-class threshold]β†’ tags

Why the GPU/CPU split β€” a Mali fp16 finding

The Swin-L encoder is fully GPU-convertible, but its last stage miscomputes in fp16 on the Mali delegate. Bisecting the four stages on-device: stage 0 = 0.9999, stage 1 = 0.9999, stage 2 = 0.9983, stage 3 = 0.709. It is not head_dim (stage 2 shares head_dim 32) and not overflow (every stage-3 value < 848 β‰ͺ fp16 max 65504; a round-to-fp16-between-ops simulation reproduces fp32 at corr 0.99999997) β€” it is Mali's fp16 matmul accumulation in the deep, high-magnitude blocks (the residual stream grows to absmax 847; the 6144-wide fc2 and 48-head attention accumulate in fp16). Those 2 blocks run on CPU; everything else stays on GPU. The reweight bakes the tag bank once as fp16 (229 MB, not 686 MB).

Minimal usage (Python)

import numpy as np
from PIL import Image
from ai_edge_litert.interpreter import Interpreter

def run(path, x, *ins):                                  # single-input or size-matched multi-input
    it = Interpreter(model_path=path); it.allocate_tensors()
    ind = it.get_input_details()
    if not ins:
        it.set_tensor(ind[0]["index"], x)
    else:
        for d in ind:
            n = int(np.prod(d["shape"]))
            it.set_tensor(d["index"], x if n == x.size else ins[0])
    it.invoke()
    return it.get_tensor(it.get_output_details()[0]["index"])

# preprocess (ImageNet)
img = Image.open("photo.jpg").convert("RGB").resize((384, 384))
a = np.asarray(img, np.float32) / 255.0
a = (a - [0.485, 0.456, 0.406]) / [0.229, 0.224, 0.225]
x = a.transpose(2, 0, 1)[None].astype(np.float32)        # [1,3,384,384]

feat = run("ram_swin_s012_fp16.tflite", x)               # [1,144,1536]
iemb = run("ram_stage3_tail_fp16.tflite", feat)          # [1,145,512]
cls = iemb[:, 0, :]                                       # [1,512]
queries = run("ram_reweight_fp16.tflite", cls)           # [1,4585,768]
logits = run("ram_taghead_fp16.tflite", queries, iemb)   # [1,4585]

probs = 1 / (1 + np.exp(-logits[0]))
thr = np.fromfile("ram_tag_threshold.bin", np.float32)
tags = [t for t in open("ram_tag_list.txt").read().splitlines()]
print([tags[i] for i in np.where(probs > thr)[0]])

Minimal usage (Kotlin, LiteRT CompiledModel)

val g1 = CompiledModel.create("ram_swin_s012_fp16.tflite", CompiledModel.Options(Accelerator.GPU), null)
val c2 = CompiledModel.create("ram_stage3_tail_fp16.tflite", CompiledModel.Options(Accelerator.CPU), null)
val rw = CompiledModel.create("ram_reweight_fp16.tflite", CompiledModel.Options(Accelerator.CPU), null)
val th = CompiledModel.create("ram_taghead_fp16.tflite", CompiledModel.Options(Accelerator.GPU), null)

g1In[0].writeFloat(preprocess(bitmap)); g1.run(g1In, g1Out)           // -> feat[1,144,1536]
c2In[0].writeFloat(g1Out[0].readFloat()); c2.run(c2In, c2Out)        // -> image_embeds[1,145,512]
val iemb = c2Out[0].readFloat(); val cls = iemb.copyOfRange(0, 512)
rwIn[0].writeFloat(cls); rw.run(rwIn, rwOut)                          // -> queries[1,4585,768]
val q = rwOut[0].readFloat()
for (b in thIn) { val n = b.readFloat().size; b.writeFloat(if (n == q.size) q else iemb) }
th.run(thIn, thOut)                                                  // -> logits[1,4585]
// sigmoid(logits[i]) > threshold[i] -> tag[i]

A complete Android sample (image pick β†’ tags) is in google-ai-edge/litert-samples.

Upstream

xinyu1205/recognize-anything Β· xinyu1205/recognize-anything-plus-model (Apache-2.0). Paper: Open-Set Image Tagging with Multi-Grained Text Supervision.

Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for litert-community/RAM-Plus-LiteRT

Finetuned
(1)
this model