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
Download code/connector.py from dhadlab/dhad-vision: direct link, hf CLI and curl.
- Browser
- Download file 4.59 kB
-
https://huggingface.co/dhadlab/dhad-vision/resolve/main/code/connector.py
- Command line
-
hf download hf://dhadlab/dhad-vision/code/connector.py
-
curl -L -o connector.py https://huggingface.co/dhadlab/dhad-vision/resolve/main/code/connector.py
4.59 kB
| """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) | |
| 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()) | |