Image-Text-to-Text
Transformers
Safetensors
English
qwen3_5
decision-model
typed-decisions
one-pass
option-probabilities
conversational
Instructions to use thegovind/blink-mimo-9b with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use thegovind/blink-mimo-9b with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="thegovind/blink-mimo-9b") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] pipe(text=messages)# Load model directly from transformers import AutoProcessor, AutoModelForMultimodalLM processor = AutoProcessor.from_pretrained("thegovind/blink-mimo-9b") model = AutoModelForMultimodalLM.from_pretrained("thegovind/blink-mimo-9b", device_map="auto") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] inputs = processor.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=40) print(processor.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use thegovind/blink-mimo-9b with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "thegovind/blink-mimo-9b" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "thegovind/blink-mimo-9b", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker
docker model run hf.co/thegovind/blink-mimo-9b
- SGLang
How to use thegovind/blink-mimo-9b 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 "thegovind/blink-mimo-9b" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "thegovind/blink-mimo-9b", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'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 "thegovind/blink-mimo-9b" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "thegovind/blink-mimo-9b", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }' - Docker Model Runner
How to use thegovind/blink-mimo-9b with Docker Model Runner:
docker model run hf.co/thegovind/blink-mimo-9b
File size: 19,739 Bytes
8c9e2ac 7c0a3b8 284955f 7c0a3b8 284955f 8c9e2ac 284955f 8c9e2ac 54ec0c5 8c9e2ac 7c0a3b8 8c9e2ac 7c0a3b8 8c9e2ac 7c0a3b8 54ec0c5 8c9e2ac 284955f 8c9e2ac 7c0a3b8 284955f 7c0a3b8 54ec0c5 8c9e2ac 284955f 7c0a3b8 284955f 7c0a3b8 284955f 54ec0c5 7c0a3b8 284955f 7c0a3b8 284955f 7c0a3b8 54ec0c5 8c9e2ac 284955f 8c9e2ac 54ec0c5 7c0a3b8 8c9e2ac 7c0a3b8 8c9e2ac 7c0a3b8 8c9e2ac 54ec0c5 7c0a3b8 8c9e2ac 7c0a3b8 8c9e2ac 54ec0c5 8c9e2ac 7c0a3b8 8c9e2ac 7c0a3b8 8c9e2ac 7c0a3b8 8c9e2ac 7c0a3b8 284955f 54ec0c5 284955f 54ec0c5 284955f 7c0a3b8 284955f 54ec0c5 8c9e2ac 7c0a3b8 8c9e2ac 284955f 7c0a3b8 284955f 7c0a3b8 8c9e2ac 7c0a3b8 | 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 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 | """blink server: a Jev-compatible decision endpoint (the TypeSafe API's wire format).
pip install "torch==2.13.0" "transformers==5.17.0" "flash-linear-attention==0.5.2" accelerate safetensors huggingface_hub
pip install "torchvision==0.28.0" # required when image input is enabled
hf download thegovind/blink-4b --revision v1.0 --local-dir blink-4b
python blink-4b/serve.py --model ./blink-4b --port 8000
POST /v1/systemone {"state": ..., "model": ..., "questions": {...}, "images": [...]} ->
{"model", "answers", "usage"}
GET /v1/models -> {"models": [{"name", "description", "release_date", "accepts_images"}]}
GET /healthz -> {"ok", "model", "revision", "weights_verified", "hub_offline", "warmup", "kernels", "versions",
"batching", "api_key_required", "accepts_images"}
Image data URIs in state and the sibling images list are self-host extensions, not fields supported by hosted
TypeSafe models. Image requests alone add image_pixels and visual_tokens (for the shared image prefix) to usage;
answers keep their ordinary noul, choice and score fields. Text-only responses stay unchanged.
A client written for the TypeSafe API works unchanged against this server once its base URL points here (for the
official SDKs: TYPESAFE_BASE_URL=http://127.0.0.1:8000). The request's "model" is accepted and not used: this
server answers with the one model it serves and names it in the response. Any Authorization header is accepted and
ignored unless a key is set with --api-key or BLINK_API_KEY; then /v1/systemone and /v1/models need
"Authorization: Bearer <key>" and answer HTTP 401 without it (/healthz stays open).
Requests are served one at a time. A request blink can't answer (a malformed question, or one over a limit: options
per choice, context length, questions per request) gets HTTP 422 with the reason; nothing is truncated. A body that
isn't a JSON object gets HTTP 400. Error bodies are {"error": reason, "detail": ...}, where detail is a list of
{"loc", "msg", "type"} for a 400 or 422 and the reason again otherwise. Every response carries an
x-typesafe-request-id header. With weights.sha256 beside the weights, every listed file is hashed before serving
(weights_verified). Serving a local folder switches the Hugging Face libraries to offline mode before any of them
loads (hub_offline reports the setting the libraries actually use); the server does not otherwise restrict the network.
Opt-in cross-request batching (--batch-window-ms, default 0 = off): requests that arrive within the window are
decided together, up to --max-batch-requests; at most --max-queued-requests wait, and past that a request gets
HTTP 529 (overloaded) with Retry-After. Each request still gets its own answers or its own error.
"""
from __future__ import annotations
import argparse
import hashlib
import hmac
import json
import os
import socket
import sys
import threading
import uuid
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
WARMUP = ("Order 4471 arrived with a cracked screen. The customer attached photos and wants a replacement.",
{"route": {"type": "choice", "instructions": "Which team should handle this?",
"criteria": {"returns": "Damaged or wrong items", "billing": "Charges and refunds",
"shipping": "Late or lost parcels"}},
"urgent": {"type": "noul", "instructions": "Does this need a reply today?"}})
def sha256(path: str) -> str:
h = hashlib.sha256()
with open(path, "rb") as fh:
for block in iter(lambda: fh.read(1 << 24), b""):
h.update(block)
return h.hexdigest()
def verify(root: str):
"""True/False against weights.sha256 ("<sha256> <file>" lines) beside the weights; None without one."""
manifest = os.path.join(root, "weights.sha256")
if not os.path.exists(manifest):
return None, []
bad = []
with open(manifest, encoding="utf-8") as fh:
for line in fh:
if line.strip():
digest, name = line.split(None, 1)
name = name.strip()
path = os.path.join(root, name)
if not os.path.exists(path) or sha256(path) != digest:
bad.append(name)
return not bad, bad
def versions() -> dict:
out = {}
for mod in ("torch", "transformers", "fla"):
try:
out[mod] = __import__(mod).__version__
except Exception:
out[mod] = None
return out
def window_ms(value: str) -> float:
"""--batch-window-ms: 0 (off) or a finite window of at most 1000 ms."""
try:
ms = float(value)
except ValueError:
raise argparse.ArgumentTypeError(f"not a number: {value!r}") from None
if not 0 <= ms <= 1000: # also rejects nan and inf
raise argparse.ArgumentTypeError("must be 0 (off) to 1000 ms")
return ms
def int_range(lo: int, hi: int):
def parse(value: str) -> int:
try:
n = int(value)
except ValueError:
raise argparse.ArgumentTypeError(f"not an integer: {value!r}") from None
if not lo <= n <= hi:
raise argparse.ArgumentTypeError(f"must be {lo}-{hi}")
return n
return parse
def api_key(value: str):
"""--api-key / BLINK_API_KEY: empty means open; a key must fit in an Authorization header (printable ASCII, no
whitespace), as TypeSafe's SDKs require of theirs."""
key = value.strip()
if not key:
return None
if not (key.isascii() and key.isprintable()) or " " in key:
raise argparse.ArgumentTypeError("must be printable ASCII without whitespace")
return key
def tower_spec(value: str) -> str:
repo, separator, revision = value.rpartition("@")
if not separator or not repo or not revision:
raise argparse.ArgumentTypeError("must be a repo@revision")
return value
def bearer(header) -> str | None:
"""The token of an "Authorization: Bearer <token>" header, else None."""
scheme, _, token = (header or "").strip().partition(" ")
if scheme.lower() != "bearer":
return None
return token.strip() or None
def error_loc(blink, req: dict, message: str) -> list:
"""Where a refused request went wrong, as a FastAPI-style location (TypeSafe's 422 body): the question the reason
opens with (blink's refusals that name one start "question '<key>' "), else the first question blink can't read,
else the questions map. Only the opening counts: a key quoted later in a reason, say inside a bad type, isn't it."""
questions = req.get("questions")
if isinstance(questions, dict) and 0 < len(questions) <= getattr(blink, "MAX_QUESTIONS", 512):
for key in questions:
if message.startswith(f"question {key!r} "):
return ["body", "questions", key]
for key, q in questions.items():
try:
blink.question_options(q)
except Exception: # noqa: BLE001 - only locating the refusal, which is already decided
return ["body", "questions", key]
return ["body", "questions"]
def main() -> None:
ap = argparse.ArgumentParser(description="Serve blink over a Jev-compatible HTTP API.")
ap.add_argument("--model", default=os.environ.get("BLINK_MODEL", "thegovind/blink-4b"))
ap.add_argument("--model-name", default=os.environ.get("BLINK_MODEL_NAME") or None,
help="logical model family when the checkpoint is stored in a renamed directory (BLINK_MODEL_NAME)")
ap.add_argument("--revision", default=os.environ.get("BLINK_REVISION"))
ap.add_argument("--host", default="127.0.0.1")
ap.add_argument("--port", type=int, default=8000)
# a string default is parsed only when the flag is absent, so a stray BLINK_BATCH_WINDOW_MS can't block an explicit 0
ap.add_argument("--batch-window-ms", type=window_ms, default=os.environ.get("BLINK_BATCH_WINDOW_MS", "0"),
help="opt-in cross-request batching: requests arriving within this window are decided in one "
"GPU call (0 = one request at a time, the evaluated default; at most 1000)")
ap.add_argument("--max-batch-requests", type=int_range(1, 64), default=16,
help="most requests decided together (1-64)")
ap.add_argument("--max-queued-requests", type=int_range(1, 1024), default=64,
help="most requests waiting for a batch; past this a request gets HTTP 529 (1-1024)")
# a string default goes through api_key() too, so BLINK_API_KEY is checked the same way
ap.add_argument("--api-key", type=api_key, default=os.environ.get("BLINK_API_KEY", ""),
help="require 'Authorization: Bearer <key>' on /v1/systemone and /v1/models (default: open, any "
"Authorization header is accepted and ignored); BLINK_API_KEY keeps it out of the process list")
ap.add_argument("--vision", action="store_true",
default=os.environ.get("BLINK_VISION", "0").lower() in ("1", "true", "on", "yes"),
help="accept images with this model's own vision tower (BLINK_VISION=1)")
ap.add_argument("--vision-tower", type=tower_spec, default=os.environ.get("BLINK_VISION_TOWER") or None,
help="opt-in a matching text model's base vision tower from repo@revision (BLINK_VISION_TOWER)")
ap.add_argument("--image-layout", choices=("inline", "first"),
default=os.environ.get("BLINK_IMAGE_LAYOUT") or None,
help="place image tokens at state placeholders or before the user text (BLINK_IMAGE_LAYOUT)")
ap.add_argument("--max-images", type=int_range(1, 8), default=os.environ.get("BLINK_MAX_IMAGES", "2"))
ap.add_argument("--max-image-bytes", type=int_range(1024, 32 * 1024 * 1024),
default=os.environ.get("BLINK_MAX_IMAGE_BYTES", str(8 * 1024 * 1024)))
ap.add_argument("--max-image-pixels", type=int_range(65_536, 16_777_216),
default=os.environ.get("BLINK_MAX_IMAGE_PIXELS", str(1920 * 1088)))
ap.add_argument("--max-image-source-pixels", type=int_range(65_536, 64_000_000),
default=os.environ.get("BLINK_MAX_IMAGE_SOURCE_PIXELS", "20000000"))
a = ap.parse_args()
model_name = a.model_name or a.model.rstrip("/").split("/")[-1]
if a.vision and not a.vision_tower and model_name not in (
"blink-mimo-9b", "Qwen3.5-4B"
):
ap.error("--vision needs a model with its own vision tower or --vision-tower for a text model")
local = os.path.isdir(a.model)
if local:
# the Hub libraries read these once, when they are first imported, so they must be set before that
for var in ("HF_HUB_OFFLINE", "TRANSFORMERS_OFFLINE", "HF_HUB_DISABLE_TELEMETRY"):
os.environ.setdefault(var, "1")
os.environ["BLINK_ENGINE"] = "torch"
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import blink
if a.image_layout is not None and a.image_layout not in blink.IMAGE_LAYOUTS:
ap.error("--image-layout must be inline or first")
if a.vision_tower:
try:
blink.vision_source(model_name, a.vision_tower)
except blink.BlinkError as exc:
ap.error(str(exc))
limits = blink.ImageLimits(a.max_images, a.max_image_bytes, a.max_image_pixels, a.max_image_source_pixels)
busy = getattr(blink, "BlinkBusy", ()) # an older blink.py has no batching (and no BlinkBusy)
if local:
root = a.model
else:
from huggingface_hub import snapshot_download
root = snapshot_download(a.model, revision=a.revision)
verified, bad = verify(root)
if verified is False:
sys.exit(f"weights.sha256 mismatch: {', '.join(bad)}")
options = ({"vision": a.vision, "vision_tower": a.vision_tower, "limits": limits,
"model_name": model_name, "image_layout": a.image_layout}
if a.vision or a.vision_tower else {})
engine = blink.TorchEngine(root, None, blink.TEMPERATURE, **options)
blink._ENGINE = engine
first = blink.decide(*WARMUP)["answers"]
repeat_identical = blink.decide(*WARMUP)["answers"] == first
ver = versions()
kernels = f"flash-linear-attention {ver['fla']}" if ver["fla"] else "reference (much slower; install flash-linear-attention)"
try:
from huggingface_hub import constants as hub_constants
hub_offline = bool(hub_constants.HF_HUB_OFFLINE)
except Exception:
hub_offline = None
health = {"ok": True, "model": a.model, "revision": a.revision, "weights_verified": verified,
"hub_offline": hub_offline, "warmup": {"repeat_identical": repeat_identical}, "kernels": kernels,
"versions": ver, "accepts_images": bool(getattr(engine, "accepts_images", False))}
if health["accepts_images"]:
health["image_layout"] = getattr(engine, "image_layout", a.image_layout or blink.IMAGE_LAYOUT_DEFAULT)
lock = threading.Lock()
batcher = (blink.Batcher(a.batch_window_ms / 1000.0, a.max_batch_requests, max_queued=a.max_queued_requests)
if a.batch_window_ms > 0 else None)
health["batching"] = ({"window_ms": a.batch_window_ms, "max_requests": a.max_batch_requests,
"max_queued": a.max_queued_requests} if batcher else None)
health["api_key_required"] = a.api_key is not None
# TypeSafe's model list, for clients that ask which names the model field takes; any name is accepted here
listing = {"models": [{
"name": a.model,
"description": "blink: typed decisions (noul, choice, score) with option probabilities from one forward pass. "
"This server serves one model; a request's model field is accepted and not used.",
"release_date": "",
"accepts_images": health["accepts_images"],
}]}
def current_health() -> dict:
if batcher is None:
return health
alive = batcher.alive()
return {**health, "ok": health["ok"] and alive,
"batching": {**health["batching"], "worker_alive": alive, "queued": batcher.q.qsize()}}
class Handler(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
def log_message(self, fmt, *args): # quiet by default
pass
def setup(self):
super().setup()
# a response is two writes (headers, then body); without TCP_NODELAY the body waits
# on the client's delayed ACK, a flat ~40 ms on every request of a kept-alive connection
self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
def _send(self, code: int, obj: dict, headers: dict | None = None, reason: str | None = None) -> None:
body = json.dumps(obj, ensure_ascii=False).encode("utf-8")
self.send_response(code, reason)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.send_header("x-typesafe-request-id", uuid.uuid4().hex) # read by TypeSafe's SDKs
for name, value in (headers or {}).items():
self.send_header(name, value)
self.end_headers()
self.wfile.write(body)
def _fail(self, code: int, reason: str, detail=None, headers: dict | None = None, *, unread: bool = False,
status_text: str | None = None) -> None:
"""An error body: blink's {"error"} plus TypeSafe's (FastAPI's) {"detail"}. With the request body still
unread, the connection is closed rather than left holding it."""
if unread:
self.close_connection = True
headers = {**(headers or {}), "Connection": "close"}
self._send(code, {"error": reason, "detail": reason if detail is None else detail}, headers, status_text)
def _refused(self, code: int, reason: str, loc: list, kind: str) -> None:
self._fail(code, reason, [{"loc": loc, "msg": reason, "type": kind}])
def _authorized(self) -> bool:
if a.api_key is None:
return True
token = bearer(self.headers.get("Authorization"))
return token is not None and hmac.compare_digest(token.encode("utf-8"), a.api_key.encode("utf-8"))
def _unauthorized(self, unread: bool) -> None:
self._fail(401, "missing or invalid API key: send Authorization: Bearer <key>",
headers={"WWW-Authenticate": "Bearer"}, unread=unread)
def do_GET(self):
path = self.path.rstrip("/")
if path in ("/healthz", "/health"):
return self._send(200, current_health())
if path == "/v1/models":
if not self._authorized():
return self._unauthorized(unread=False)
return self._send(200, listing)
return self._fail(404, "not found")
def do_POST(self):
if self.path.rstrip("/") != "/v1/systemone":
return self._fail(404, "not found", unread=True)
if not self._authorized():
return self._unauthorized(unread=True)
try:
size = int(self.headers.get("Content-Length") or 0)
req = json.loads(self.rfile.read(size) or b"{}")
except (ValueError, json.JSONDecodeError) as exc:
return self._refused(400, f"invalid JSON: {exc}", ["body"], "json_invalid")
if not isinstance(req, dict):
return self._refused(400, "the body must be a JSON object", ["body"], "value_error")
try:
opts = {}
if "images" in req or blink.contains_image_uri(req.get("state")):
if "images" in req:
opts["images"] = req["images"]
opts["limits"] = limits
if batcher is not None:
out = batcher.submit(req.get("state"), req.get("questions"), **opts)
else:
with lock:
out = blink.decide(req.get("state"), req.get("questions"), **opts)
except blink.BlinkError as exc:
location = exc.loc if isinstance(exc, blink.ImageError) else error_loc(blink, req, str(exc))
return self._refused(422, str(exc), location, "value_error")
except busy as exc:
return self._fail(529, str(exc), headers={"Retry-After": "1"}, status_text="Overloaded")
except Exception as exc: # noqa: BLE001 - report, keep serving
return self._fail(500, f"{type(exc).__name__}: {exc}")
usage = {"input_tokens": out["meta"]["input_tokens"], "output_tokens": 0}
if "image_pixels" in out["meta"]:
usage["image_pixels"] = out["meta"]["image_pixels"]
usage["visual_tokens"] = out["meta"]["visual_tokens"]
for name in ("image_preprocess_ms", "vision_encoder_ms", "lm_prefill_ms"):
if name in out["meta"]:
usage[name] = out["meta"][name]
return self._send(200, {
"model": a.model,
"answers": out["answers"],
"usage": usage,
})
server = ThreadingHTTPServer((a.host, a.port), Handler)
print(f"blink serving {a.model} on http://{a.host}:{a.port} ({kernels}; weights_verified={verified}; "
f"api key {'required' if a.api_key else 'not required'})", flush=True)
server.serve_forever()
if __name__ == "__main__":
main()
|