Image-Text-to-Text
Transformers
Safetensors
Arabic
English
arabic
vision
image-captioning
image-understanding
siglip2
jais
Instructions to use dhadlab/dhad-vision with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use dhadlab/dhad-vision with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="dhadlab/dhad-vision")# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("dhadlab/dhad-vision", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use dhadlab/dhad-vision with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "dhadlab/dhad-vision" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "dhadlab/dhad-vision", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/dhadlab/dhad-vision
- SGLang
How to use dhadlab/dhad-vision with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "dhadlab/dhad-vision" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "dhadlab/dhad-vision", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "dhadlab/dhad-vision" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "dhadlab/dhad-vision", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use dhadlab/dhad-vision with Docker Model Runner:
docker model run hf.co/dhadlab/dhad-vision
File size: 4,594 Bytes
e3f7919 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | """Vision connector: SigLIP2 NaFlex patch features [B,576,768] -> soft tokens [B,144 (or 64),1536] for the frozen Jais-3-0.9B.
pixel-shuffle 2x2 over the 24x24 grid (576x768 -> 144x3072) -> Linear(3072,1536) -> GELU -> Linear(1536,1536) -> per-token L2 normalise * learnable scalar
(initialised to the Jais embedding mean row norm 0.896, so soft tokens start at the scale of real text embeddings). 7.08M parameters.
low-token mode (64 tokens): the 12x12 shuffled grid is adaptively average-pooled to 8x8 (same MLP, so one connector serves both modes)."""
import torch, torch.nn as nn, torch.nn.functional as F
EMBED_NORM = 0.896 # mean row norm of Jais embed_tokens (design doc, section 2.2)
def pixel_shuffle(x, grid=24, r=2):
"""[B, grid*grid, D] -> [B, (grid/r)^2, D*r*r]; each output token concatenates a rxr block of neighbouring patches"""
B, N, D = x.shape
assert N == grid * grid, f"expected {grid*grid} patches, got {N}"
g = grid // r
x = x.view(B, g, r, g, r, D).permute(0, 1, 3, 2, 4, 5)
return x.reshape(B, g * g, r * r * D)
class Connector(nn.Module):
def __init__(self, in_dim=768, hidden=1536, out_dim=1536, grid=24, r=2, target_norm=EMBED_NORM, low_grid=8):
super().__init__()
self.grid, self.r, self.low_grid = grid, r, low_grid
self.fc1 = nn.Linear(in_dim * r * r, hidden)
self.fc2 = nn.Linear(hidden, out_dim)
self.scale = nn.Parameter(torch.tensor(float(target_norm))) # learnable output norm
def tokens(self, feats, low=False):
"""feats [B,576,768] -> shuffled tokens [B,144,3072] (or [B,64,3072] in low mode)"""
x = pixel_shuffle(feats, self.grid, self.r)
if low:
g = self.grid // self.r
x = x.transpose(1, 2).reshape(x.shape[0], -1, g, g)
x = F.adaptive_avg_pool2d(x, self.low_grid).flatten(2).transpose(1, 2)
return x
def forward(self, feats, low=False):
x = self.tokens(feats.to(self.fc1.weight.dtype), low)
y = self.fc2(F.gelu(self.fc1(x)))
# normalise in fp32 for stability under bf16 autocast, then rescale to the embedding norm
return (F.normalize(y.float(), dim=-1) * self.scale.float()).to(y.dtype)
# ---------------------------------------------------------------- PORTAL-style warm start (stage-1 ablation; default OFF)
@torch.no_grad()
def portal_init(self, feats_sample, embed_matrix, k=512, min_norm=0.3):
"""Bures-Wasserstein (Gaussian optimal transport) initialisation after PORTAL (arXiv 2610.18598), as summarised in docs/research/vision_design.md s.3:
W1 = V_t diag(sigma_t/sigma_s) V_s^T, b1 = mu_t - W1 mu_s, W2 = identity. Source = shuffled patch tokens (3072-d) of an unlabeled image pool
(feats_sample [N,576,768], 1-2k images are enough), target = LLM input-embedding rows (rows with norm < min_norm = reserved/unused are dropped).
CAVEATS: written from the design-doc description, NOT from the paper's Appendix F code, and not validated against it; the GELU after layer 1 is kept
(as in the summary), so layer-1 output is only approximately in the embedding distribution. The 2-layer MLP has hidden=out=1536 so W2 = I fits.
Use: conn.portal_init(feats[:1024].float(), model.get_input_embeddings().weight)"""
dev = self.fc1.weight.device
src = self.tokens(feats_sample.float().to(dev)).reshape(-1, self.fc1.in_features)
tgt = embed_matrix.float().to(dev); tgt = tgt[tgt.norm(dim=1) > min_norm]
def stats(X, k):
mu = X.mean(0); Xc = X - mu
k = min(k, X.shape[1], X.shape[0] - 1)
U, S, V = torch.pca_lowrank(Xc, q=k, center=False, niter=3)
sigma = (Xc @ V).std(0) # per-axis spread along each principal direction
return mu, V, sigma
mu_s, Vs, sg_s = stats(src, k); mu_t, Vt, sg_t = stats(tgt, k)
kk = min(Vs.shape[1], Vt.shape[1])
W1 = Vt[:, :kk] @ torch.diag(sg_t[:kk] / sg_s[:kk].clamp_min(1e-6)) @ Vs[:, :kk].T # [1536, 3072]
self.fc1.weight.copy_(W1); self.fc1.bias.copy_(mu_t - W1 @ mu_s)
self.fc2.weight.copy_(torch.eye(self.fc2.out_features, self.fc2.in_features)); self.fc2.bias.zero_()
return {"k": kk, "src_tokens": src.shape[0], "tgt_rows": tgt.shape[0]}
def n_params(m): return sum(p.numel() for p in m.parameters())
if __name__ == "__main__":
c = Connector(); print("params", n_params(c))
x = torch.randn(2, 576, 768); print(c(x).shape, c(x, low=True).shape, c(x).norm(dim=-1).mean().item())
|