Vision-Language Models#
Furiosa-LLM serves Vision-Language (VL) models that accept text and
images through the OpenAI-compatible Chat Completions API and the
Responses API. The first
supported architecture is Qwen3-VL (e.g.,
Qwen/Qwen3-VL-32B-Instruct); see Supported Models for the
authoritative list.
Image inputs may be passed as a remote https:// URL, a base64
data: URL, or a local file:// path. Preprocessing (resizing,
patching, prompt-token expansion) is handled by the Hugging Face
AutoProcessor that ships with the model.
Note
Video input is not currently supported. Support is planned for Furiosa SDK 2026.5.0.
Quick Start#
Launch a server with a vision-language model that includes a compatible FXB:
furiosa-llm serve furiosa-ai/Qwen3-VL-32B-Instruct
Send a chat-completion request with an image and a text prompt:
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen3-VL-32B-Instruct",
"messages": [
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": "https://developer.furiosa.ai/latest/en/_images/Furiosa_RNGD_image.png"
}
},
{"type": "text", "text": "Describe this image in one short sentence."}
]
}
],
"max_tokens": 128
}' \
| python -m json.tool
(see the Python Example section below for a Python client example).
Image Input Formats#
The image_url.url field in a chat-completion message accepts three
URL schemes:
Remote URL (
http:///https://) — the server fetches the image withhttpx.{ "type": "image_url", "image_url": {"url": "https://example.com/photo.png"} }
Base64 data URL — useful for inline payloads:
{ "type": "image_url", "image_url": {"url": "data:image/png;base64,iVBORw0KGgoAAAANSUh..."} }
Local file (
file://) — disabled by default; requires the--allowed-local-media-pathserver flag (see below).{ "type": "image_url", "image_url": {"url": "file:///srv/media/photo.png"} }
A request may contain multiple image_url parts interleaved with
text parts. The model’s chat template controls how image
placeholders are rendered into the prompt:
OpenAI-format chat templates (the common case) iterate the
contentarray themselves, so image placeholders land at their original positions automatically — no extra server flag needed.String-format chat templates flatten
contentinto a single text string. By default, placeholders for any images are prepended to the prompt; pass--interleave-mm-stringsto keep them at their original positions instead.
Server Options#
The following furiosa-llm serve flags configure multimodal
behaviour. Requests that violate any of them are rejected with HTTP
400 before reaching the NPU.
Flag |
Default |
Description |
|---|---|---|
|
unlimited |
Maximum number of images allowed per request. Exceeding the limit returns HTTP 400. |
|
unlimited |
Maximum number of videos allowed per request. Video input is not currently supported (see the note above), so this flag has no effect yet. |
|
disabled |
Allow |
|
all allowed |
Whitelist of remote domains for SSRF protection. When set, only images from listed domains are fetched; others return HTTP 400. |
|
off |
See Image Input Formats above. No-op for the common OpenAI-format chat templates. |
|
|
Size of the multimodal preprocessing cache in GiB. Set to |
|
|
Default image-fetch options, keyed by modality. See Multimodal Options for timeouts and request overrides. |
|
|
Processor defaults with optional |
Example with multiple allowed domains (nargs="+"):
furiosa-llm serve Qwen/Qwen3-VL-32B-Instruct \
--fxb /path/to/qwen3-vl-32b.fxb \
--allowed-media-domains developer.furiosa.ai cdn.example.com
Warning
Local file access (file://) is disabled unless
--allowed-local-media-path is set. Treat it as a security
boundary — only point it at directories whose contents you are
willing to expose to chat-completion clients.
Tip
For production deployments fetching from the public internet,
restrict --allowed-media-domains to the smallest set you need to
limit SSRF and bandwidth-amplification risk.
Multimodal Options#
media_io_kwargs uses an image object. mm_processor_kwargs accepts
processor options and optional images_kwargs.
videos_kwargs and audio_kwargs are currently rejected because only image
processing is supported. Set server defaults with the CLI:
furiosa-llm serve furiosa-ai/Qwen3-VL-32B-Instruct \
--media-io-kwargs '{"image": {"timeout": 10}}' \
--mm-processor-kwargs '{"min_pixels": 1024}'
Chat Completions and Responses requests can override these defaults with
media_io_kwargs and mm_processor_kwargs in the JSON request body.
With the OpenAI Python client, pass these fields through extra_body:
import os
from openai import OpenAI
client = OpenAI(
base_url=os.getenv("OPENAI_BASE_URL", "http://localhost:8000/v1"),
api_key=os.getenv("OPENAI_API_KEY", "EMPTY"),
)
response = client.chat.completions.create(
model=client.models.list().data[0].id,
messages=[{
"role": "user",
"content": [
{"type": "image_url", "image_url": {
"url": "https://developer.furiosa.ai/latest/en/_images/Furiosa_RNGD_image.png"
}},
{"type": "text", "text": "Describe this image."},
],
}],
max_completion_tokens=32,
extra_body={
"media_io_kwargs": {"image": {"timeout": 5}},
"mm_processor_kwargs": {"min_pixels": 4096},
},
)
print(response.choices[0].message.content)
Overrides apply only to that request and do not change server defaults.
Omitting either dictionary, setting it to null, or passing {} retains
defaults. The two dictionaries have different merge rules, described below.
Image-fetch options#
Request option keys override defaults within each modality. Passing
{"image": {}} retains default image-fetch options.
The following fetch option is supported:
Option |
Validation |
Default |
Behavior |
|---|---|---|---|
|
Finite positive number of seconds; strings, booleans, and |
30 seconds when omitted from both server defaults and the request. |
Controls HTTP fetching, not image processing or generation. Has no effect on local files, inline data URLs, or references without a URL. |
The server’s local-path and domain restrictions still apply and cannot be overridden through this dictionary.
Processor options#
Request keys replace matching top-level processor defaults, including entire
nested objects. For example,
an override of size replaces the entire default size object rather
than merging its dimensions.
Image-specific options can be supplied in images_kwargs:
{"mm_processor_kwargs": {"min_pixels": 1024, "images_kwargs": {"max_pixels": 200704}}}
This uses both controls. A request’s images_kwargs replaces the entire
default images_kwargs object; {"images_kwargs": {}} clears that group
and keeps the remaining top-level options. An option must not appear both at
the top level and inside images_kwargs, including after defaults and request
options are combined. This applies to all option names, even when their values
are equal.
Registered image processor options#
The catalog lists recommended runtime controls, rather than every upstream
processor argument. The common options below apply to Qwen-VL, EXAONE 4.5,
and Gemma 4. EXAONE uses the Qwen image processor with additional
window-aligned resizing.
Defaults come from the loaded processor configuration. The table shows the
Transformers 5.8 class fallbacks; checkpoint configuration can override them,
including normalization values. Omitted options use those loaded defaults.
An explicit null is forwarded as such and is not generally equivalent to
omission.
Option |
Accepted values |
Default |
Behavior |
|---|---|---|---|
|
Boolean or |
|
Convert inputs to RGB. EXAONE performs this before window-aligned resizing. |
|
Integer |
|
Pillow filter: nearest (0), Lanczos (1), bilinear (2), bicubic (3), box (4), or Hamming (5). The selected backend determines supported filters. |
|
Boolean or |
|
Multiply pixel values by |
|
Finite number or |
|
Pixel rescaling multiplier, used when |
|
Boolean or |
Qwen: |
Normalize pixels with |
|
Number, sequence of numbers, or |
Qwen: |
Scalar or per-channel mean; used when normalization is enabled. |
|
Number, sequence of numbers, or |
Qwen: |
Scalar or per-channel standard deviation; used when normalization is enabled. |
|
String or |
|
Input channel placement: |
|
Device string or |
|
Device for image processing, such as |
Model-specific controls are listed below. Omitted sizing controls retain Furiosa’s automatic sizing. EXAONE always performs window-aligned resizing.
Option |
Model family |
Accepted values |
Default and behavior |
|---|---|---|---|
|
Qwen-VL / Gemma 4 |
Boolean or |
|
|
Qwen-VL / EXAONE 4.5 |
Boolean or |
|
|
Qwen-VL / EXAONE 4.5 |
Positive integer |
With FXB buckets, |
|
Qwen-VL / EXAONE 4.5 |
Positive integer |
Selected automatically from the image and FXB vision buckets. Maximum resize pixel count; EXAONE also enforces window alignment. |
|
Gemma 4 |
Integer: |
Selected automatically for the image and compiled capacity, starting
from the loaded processor limit (HF fallback: |
Without FXB vision buckets, sizing falls back to the loaded processor’s
settings. images and return_tensors are supplied by the runtime and
are not user processor options.
Strings, booleans, floats, and null are rejected for min_pixels,
max_pixels, and max_soft_tokens. Defaults, request overrides, and their
merged result are validated. Cross-field bounds are checked on the combined
options. Unregistered options continue to pass through; the loaded processor
determines whether they are supported. These options also participate in cache
identity.
Processing remains subject to the loaded FXB’s compiled vision capacity. A valid option value alone does not guarantee that an image fits. The converter continues to use the loaded processor’s patch and merge geometry for capacity checks and output conversion; geometry overrides must agree with it.
Preprocessing cache hits skip image processing. Their keys include the supplied
processor kwargs after server defaults and request overrides are merged and
images_kwargs is flattened. Changing a supplied option such as
do_rescale selects a separate cache entry. Supply the image again when
requesting an option set that has not been cached; UUIDs still identify the
original image.
Processor configuration defaults and automatically selected sizing values are
not added to the preprocessing key. Omitting an option and explicitly supplying
its default can therefore select different entries. Reuse still applies the
existing min_pixels / max_pixels checks for Qwen-VL and EXAONE 4.5, or the
max_soft_tokens check for Gemma 4. Entries outside the requested bounds
are dropped from the hits and reprocessed. These checks do not guarantee that
an automatically sized cached result matches a fresh processing run.
Validation errors#
Invalid request options return HTTP 400 or 422; invalid offline options raise
ValueError. Invalid CLI option structures or fetch options are argument
errors, while model-specific processor validation occurs when loading the
model. These checks also apply to requests without images.
If native vision encoding or embedding reuse returns a token count that
disagrees with the processed image layout, the request is aborted before text
generation.
Chat Completions and Responses return HTTP 500 InternalServerError as JSON,
including for streaming requests, before sending any SSE events.
Offline callers receive MultimodalTokenCountError (a RuntimeError);
LLMEngine delivers it through step(), and AsyncLLMEngine raises it
while iterating generate().
See Tokenizer API for tokenization-specific behavior and Multi-Turn Conversations for option handling with stored conversations.
Caching Multimodal Inputs with UUID#
Images are automatically hashed by content, and their processed results are
cached even when no uuid is supplied. Repeated images can reuse cached
results and skip pre-processing, subject to the processor-bound checks
described in Multimodal Options.
Cached results contain image preprocessing output, independent of the prompt. You can reuse an image with different text or at a different position in a later prompt. Each request still validates its image placeholders and expanded context length; reusing an image does not reuse a previous response.
After a preprocessing cache miss, the server preprocesses the supplied image again before checking for reusable encoded embeddings. Automatic resizing can change with the other images in a request. Encoded embeddings are reused only when the media identity, supplied processor options, processed pixels, and layout match; this applies with or without a UUID.
You can optionally tag each image with a stable uuid to reference its
cached result on follow-up requests without resending the image. UUIDs provide
an additional lookup mechanism; omitting a UUID does not opt out of caching.
Add uuid next to image_url on the content part:
{
"type": "image_url",
"image_url": {"url": "https://example.com/receipt.png"},
"uuid": "receipt-2026-05-01-abc"
}
A follow-up may then omit the image bytes ("image_url": null) and
reference the upload by uuid alone:
{
"type": "image_url",
"image_url": null,
"uuid": "receipt-2026-05-01-abc"
}
If no reusable entry exists for that UUID and the effective processor options
(for example, an unknown UUID, eviction, changed options, or an entry outside
the requested bounds), the server re-runs pre-processing when image_url is also supplied;
otherwise it returns HTTP 400 unknown multimodal uuid, and the
client should retry with the image attached. A UUID-only reference also skips
fetching; supplying a URL still fetches and decodes the image before checking
the processing cache. Offline callers can use multi_modal_uuids for the
same reuse behavior (see Offline API below).
Cache capacity is controlled by --mm-processor-cache-gb (default
4.0, 0 disables preprocessing caching). The cap counts the pixel buffers
in preprocessing entries, rather than total server memory. The
least-recently-used entries are evicted when the cache is full. Encoded embeddings have a separate lifetime and can still be
reused after preprocessing eviction or when preprocessing caching is disabled.
UUID-only requests require a resident preprocessing entry; a retained encoded
embedding alone cannot satisfy them.
The cache and UUID mappings are local to the process and reset on restart.
Python Example#
Using the OpenAI Python client against a Furiosa-LLM server:
"""
OpenAI-Compatible Server - Vision-Language Chat Completion Example
Sends a chat-completion request containing an image and a text prompt
to a running Furiosa-LLM server.
The image URL below is hosted on the Furiosa developer documentation
site and is intended to be stable. For other image-input forms (base64
``data:`` URLs and local ``file://`` paths), see the Vision-Language
Models documentation page.
"""
import os
from openai import OpenAI
base_url = os.getenv("OPENAI_BASE_URL", "http://localhost:8000/v1")
api_key = os.getenv("OPENAI_API_KEY", "EMPTY")
client = OpenAI(base_url=base_url, api_key=api_key)
IMAGE_URL = "https://developer.furiosa.ai/latest/en/_images/Furiosa_RNGD_image.png"
response = client.chat.completions.create(
model=client.models.list().data[0].id,
messages=[
{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": IMAGE_URL}},
{"type": "text", "text": "Describe this image in one short sentence."},
],
}
],
max_completion_tokens=32,
temperature=0.0,
)
print(response.choices[0].message.content)
Offline API#
The same image inputs are accepted by the offline LLM / LLMEngine
APIs via the multi_modal_data field on the prompt dict. Pass PIL
images keyed by modality:
from PIL import Image
from furiosa_llm import LLM, SamplingParams
image = Image.open("photo.png")
prompts = [
{
"prompt": "<|vision_start|><|image_pad|><|vision_end|>Describe this image.",
"multi_modal_data": {"image": [image]},
"mm_processor_kwargs": {"min_pixels": 4096},
}
]
with LLM(
"Qwen/Qwen3-VL-32B-Instruct",
fxb="/path/to/qwen3-vl-32b.fxb",
mm_processor_kwargs={"min_pixels": 1024},
) as llm:
outputs = llm.generate(prompts, SamplingParams(max_tokens=64))
print(outputs[0].outputs[0].text)
LLM(mm_processor_kwargs=...) sets processor defaults. Each prompt’s
mm_processor_kwargs overrides those defaults using the same
processor merge rules as the server; the example
uses min_pixels=4096 for this prompt. Offline callers supply decoded images,
so media_io_kwargs is not an offline LLM option.
Content-based caching and the UUID references described in
Caching Multimodal Inputs with UUID above are also available through the
offline API. Add multi_modal_uuids alongside multi_modal_data, in the
same order; pass None for items that do not need a UUID reference. Those
items still participate in content-based caching.
Each modality’s UUID list must have exactly as many entries as its image list. A bare UUID string is also accepted for a single image. Repeating a UUID keeps each occurrence in the prompt: provide a corresponding image placeholder for every occurrence, even when they reuse the same cached result.
For example:
prompts = [
{
"prompt": "<|vision_start|><|image_pad|><|vision_end|>Describe this image.",
"multi_modal_data": {"image": [image]},
"multi_modal_uuids": {"image": ["receipt-2026-05-01-abc"]},
}
]
A follow-up prompt may then omit the image and reference it by UUID
alone — pass None in the multi_modal_data slot:
followup = [
{
"prompt": "<|vision_start|><|image_pad|><|vision_end|>Translate to Korean.",
"multi_modal_data": {"image": [None]},
"multi_modal_uuids": {"image": ["receipt-2026-05-01-abc"]},
}
]
See API Reference for the full API surface.