relu-clip / example_infer.py
jiaheguo521's picture
Add efflite4 ViT-L/14 weights, text embeddings, and the 18-run int8 evidence set
3e2da47 verified
Raw
History Blame Contribute Delete
3.51 kB
#!/usr/bin/env python3
"""Minimal zero-shot inference for ReLU-CLIP. Depends only on numpy, Pillow and a
TFLite runtime -- no torch, no CLIP, no code from this project.
python example_infer.py cat.jpg
python example_infer.py cat.jpg --text text/demo_prompt_emb_vitl14.npz
The image tower is a plain int8 TFLite graph; classification is a cosine between its
L2-normalised output and a stored text-embedding matrix. To classify your own classes,
build a new matrix once with sources/make_prompt_emb.py (needs torch + open_clip, on any
machine) and pass it with --text; the device side never needs those.
"""
import argparse
import json
import numpy as np
from PIL import Image
try: # the lightweight device runtime
from tflite_runtime.interpreter import Interpreter
except ImportError:
try: # its maintained successor
from ai_edge_litert.interpreter import Interpreter
except ImportError: # full tensorflow, if that is what you have
import tensorflow as tf
Interpreter = tf.lite.Interpreter
def preprocess(path, spec):
"""Resize shortest side to 224 (bicubic), centre-crop 224, scale to [0,1], CLIP-normalise."""
s = spec["input"]["size"]
img = Image.open(path).convert("RGB")
w, h = img.size
# torchvision Resize(int) truncates the long side -- int(), not round(). One pixel of
# difference here shifts the centre crop and silently costs accuracy.
nw, nh = (s, int(s * h / w)) if w <= h else (int(s * w / h), s)
img = img.resize((nw, nh), Image.BICUBIC)
# ...and CenterCrop rounds the offset rather than flooring it, so on an odd difference
# the two disagree by another pixel.
left, top = int(round((nw - s) / 2.0)), int(round((nh - s) / 2.0))
img = img.crop((left, top, left + s, top + s))
x = np.asarray(img, np.float32) / 255.0
x = (x - spec["input"]["mean"]) / spec["input"]["std"]
return x[None] # [1, 224, 224, 3] NHWC
def main():
ap = argparse.ArgumentParser()
ap.add_argument("image")
ap.add_argument("--model", default="efflite4-vitl14/student_int8.tflite")
ap.add_argument("--text", default="text/imagenet1k_text_emb_vitl14.npz")
ap.add_argument("--spec", default="preprocessing.json")
ap.add_argument("--topk", type=int, default=5)
a = ap.parse_args()
spec = json.load(open(a.spec))
z = np.load(a.text, allow_pickle=True)
key = "embs" if "embs" in z.files else "text_emb" # the two shipped files differ
T, labels = z[key].astype(np.float32), z["labels"]
T = T / np.linalg.norm(T, axis=1, keepdims=True) # idempotent; imagenet1k needs it
itp = Interpreter(a.model)
itp.allocate_tensors()
inp, out = itp.get_input_details()[0], itp.get_output_details()[0]
x = preprocess(a.image, spec)
s_in, z_in = inp["quantization"]
itp.set_tensor(inp["index"], np.clip(np.round(x / s_in + z_in), -128, 127).astype(inp["dtype"]))
itp.invoke()
s_out, z_out = out["quantization"]
emb = (itp.get_tensor(out["index"]).astype(np.float32) - z_out) * s_out
emb /= np.linalg.norm(emb, axis=-1, keepdims=True) # the graph emits it unnormalised
sims = (emb @ T.T)[0]
for i in sims.argsort()[::-1][:a.topk]:
print(f"{sims[i]:+.4f} {labels[i]}")
if __name__ == "__main__":
main()