dhad-vision / code /infer.py
Alshami's picture
dhad-1-vision-preview: stage-1 connector + unfinished stage-2 step675 snapshot (Apache-2.0)
e3f7919 verified
Raw History Blame Contribute Delete
2.77 kB
"""describe(image_path_or_feats, prompt) -> text, with the frozen Jais trunk + trained connector (stage 1: no LoRA expert).
image: a path / PIL image / bytes / list of those (encoded with encoder.py, SigLIP2 on CPU by default), or cached features (np/torch [576,768] or [B,576,768]).
CLI: python infer.py IMAGE [prompt]
env: CONNECTOR=dir holding connector.pt (default: the folder that contains this code/ folder);
DEVICE=cpu|cuda (SigLIP2 encoder device, default cpu); LOW=1 for the 64-token mode;
JAIS_PATH / SIGLIP2_PATH to use local base-model copies instead of downloading from the Hub."""
import io, os, sys, numpy as np, torch
HERE = os.path.dirname(os.path.abspath(__file__)); sys.path.insert(0, HERE)
from graft import Graft, load_trunk
_STATE = {}
def load_graft(connector_dir=None, device=None, trunk=None, tok=None):
"""one Graft per process; the connector file is 14 MB (7.08M params bf16 / 28 MB fp32 state dict)"""
# connector.pt sits next to the code/ folder in a downloaded release
connector_dir = connector_dir or os.environ.get("CONNECTOR") or os.path.dirname(HERE)
key = (connector_dir, device)
if key in _STATE: return _STATE[key]
device = device or ("cuda" if torch.cuda.is_available() else "cpu")
if trunk is None: tok, trunk = load_trunk(device)
g = Graft(trunk, tok); g.load_state(torch.load(os.path.join(connector_dir, "connector.pt"), map_location="cpu")); g.eval()
_STATE[key] = g; return g
def to_feats(image, enc_device="cpu"):
"""-> fp16/fp32 tensor [B,576,768]"""
if isinstance(image, torch.Tensor): f = image
elif isinstance(image, np.ndarray) and image.ndim >= 2 and image.shape[-2:] == (576, 768): f = torch.from_numpy(image)
else:
import encoder
from PIL import Image
items = image if isinstance(image, (list, tuple)) else [image]
items = [Image.open(io.BytesIO(x)) if isinstance(x, (bytes, bytearray)) else x for x in items]
f = torch.from_numpy(encoder.encode(items, device=enc_device))
return f.unsqueeze(0) if f.dim() == 2 else f
@torch.no_grad()
def describe(image, prompt="صِف هذه الصورة", graft=None, max_new=96, low=False, enc_device="cpu", **kw):
g = graft or load_graft(); f = to_feats(image, enc_device).to(g.markers.device)
qs = [prompt] * f.shape[0]
with torch.autocast("cuda" if g.markers.device.type == "cuda" else "cpu", dtype=torch.bfloat16):
out = g.generate(f, qs, max_new=max_new, low=low, **kw)
return out[0] if len(out) == 1 else out
if __name__ == "__main__":
img = sys.argv[1]; q = sys.argv[2] if len(sys.argv) > 2 else "صِف هذه الصورة"
print(describe(img, q, low=os.environ.get("LOW") == "1", enc_device=os.environ.get("DEVICE", "cpu")))