Image-Text-to-Text
Transformers
Safetensors
lfm3_vl
feature-extraction
liquid
lfm3
edge
decision
classification
extraction
calibration
system-one
multimodal
decision-model
custom_code
conversational
Instructions to use LiquidAI/d1-2B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use LiquidAI/d1-2B with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="LiquidAI/d1-2B", trust_remote_code=True) 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)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("LiquidAI/d1-2B", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use LiquidAI/d1-2B with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "LiquidAI/d1-2B" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "LiquidAI/d1-2B", "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/LiquidAI/d1-2B
- SGLang
How to use LiquidAI/d1-2B 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 "LiquidAI/d1-2B" \ --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": "LiquidAI/d1-2B", "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 "LiquidAI/d1-2B" \ --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": "LiquidAI/d1-2B", "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 LiquidAI/d1-2B with Docker Model Runner:
docker model run hf.co/LiquidAI/d1-2B
File size: 13,012 Bytes
97c5704 | 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 | # Vendored from transformers' LFM3 code (not in a release yet) at 69da1b47a11e362fbb15bfe5d5c8da0790c688c5, src/transformers/models/lfm3_vl/processing_lfm3_vl.py:
# its relative imports made absolute, nothing else changed.
# π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨
# This file was automatically generated from src/transformers/models/lfm3_vl/modular_lfm3_vl.py.
# Do NOT edit this file manually as any edits will be overwritten by the generation of
# the file from the modular. If any change should be done, please apply the change to the
# modular_lfm3_vl.py file directly. One of our CI enforces this.
# π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨π¨
# Copyright 2026 The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import math
import numpy as np
from transformers.image_utils import is_pil_image, is_valid_image, make_flat_list_of_images
from transformers.processing_utils import MultiModalData, ProcessingKwargs, ProcessorMixin
from transformers.utils import auto_docstring, is_numpy_array, is_torch_tensor, logging
from transformers.video_utils import VideoMetadata, is_valid_video, make_batched_videos
logger = logging.get_logger(__name__)
class Lfm3VlProcessorKwargs(ProcessingKwargs, total=False):
_defaults = {
"text_kwargs": {
"padding": False,
"return_token_type_ids": False,
"return_mm_token_type_ids": False,
},
"videos_kwargs": {"return_metadata": True},
}
def _check_video_fps(metadata: VideoMetadata) -> None:
if metadata.fps is not None and not (math.isfinite(metadata.fps) and metadata.fps > 0):
raise ValueError(f"`video_metadata.fps` must be a positive number, got {metadata.fps}")
def _media_per_prompt(media, is_item) -> list[int] | None:
# rows name the prompt that owns them while the expansion consumes media in flat order; a row is a list of whole
# items or one stacked batch, and a list of single frames is one video, not a row
if not isinstance(media, (list, tuple)):
return None
counts = []
for row in media:
if isinstance(row, (list, tuple)) and all(is_item(item) for item in row):
counts.append(len(row))
elif (is_numpy_array(row) or is_torch_tensor(row)) and row.ndim == 4 and (len(row) == 0 or is_item(row[0])):
counts.append(len(row))
else:
return None
return counts
def _check_vision_placeholder_counts(processor, prompts, images, videos) -> None:
for name, token, media, flatten, is_item in (
(
"images",
processor.image_token,
images,
make_flat_list_of_images,
lambda item: is_valid_image(item) and (is_pil_image(item) or item.ndim == 3),
),
(
"videos",
processor.video_token,
videos,
make_batched_videos,
lambda item: isinstance(item, str) or (not is_pil_image(item) and is_valid_video(item)),
),
):
num_placeholders = sum(prompt.count(token) for prompt in prompts)
num_media = 0 if media is None else len(flatten(media))
if num_placeholders != num_media:
raise ValueError(f"Found {num_placeholders} {token} tokens in the text but received {num_media} {name}")
# one prompt cannot take another's media
per_prompt = _media_per_prompt(media, is_item) if len(prompts) > 1 else None
in_text = [prompt.count(token) for prompt in prompts]
if per_prompt is not None and per_prompt != in_text:
raise ValueError(f"Found {in_text} {token} tokens per prompt but received {per_prompt} {name} per prompt")
@auto_docstring
class Lfm3VlProcessor(ProcessorMixin):
valid_processor_kwargs = Lfm3VlProcessorKwargs
def __init__(self, image_processor=None, tokenizer=None, video_processor=None, chat_template=None, **kwargs):
self.image_token = getattr(tokenizer, "image_token", "<|image|>")
self.video_token = getattr(tokenizer, "video_token", "<|video|>")
self.vision_start_token = getattr(tokenizer, "vision_start_token", "<|vision_start|>")
self.vision_end_token = getattr(tokenizer, "vision_end_token", "<|vision_end|>")
self.image_token_id = tokenizer.convert_tokens_to_ids(self.image_token)
self.video_token_id = tokenizer.convert_tokens_to_ids(self.video_token)
self.vision_start_token_id = tokenizer.convert_tokens_to_ids(self.vision_start_token)
self.vision_end_token_id = tokenizer.convert_tokens_to_ids(self.vision_end_token)
super().__init__(image_processor, tokenizer, video_processor, chat_template=chat_template)
def replace_image_token(self, image_inputs: dict, image_idx: int, **kwargs) -> str:
merge_length = self.image_processor.merge_size**2
num_image_tokens = image_inputs["image_grid_thw"][image_idx].prod() // merge_length
return self.image_token * num_image_tokens
def replace_video_token(self, video_inputs: dict, video_idx: int, **kwargs) -> str:
metadata = video_inputs["video_metadata"][video_idx]
if metadata.frames_indices is None:
raise ValueError("Frame timestamps need the sampled frame indices in `video_metadata`")
_check_video_fps(metadata)
merge_length = self.video_processor.merge_size**2
num_frames = video_inputs["video_grid_thw"][video_idx][0]
frame_seqlen = video_inputs["video_grid_thw"][video_idx][1:].prod() // merge_length
metadata = video_inputs["video_metadata"][video_idx]
video_placeholder = ""
if metadata.fps is None:
logger.warning_once(
"Lfm3Vl requires frame timestamps to construct prompts, but the `fps` of the input video could not be inferred. "
"Probably `video_metadata` was missing from inputs and you passed pre-sampled frames. "
"Defaulting to `fps=24`. Please provide `video_metadata` for more accurate results."
)
metadata.fps = 24 if metadata.fps is None else metadata.fps
# if timestamps are not provided, calculate them
curr_timestamp = self._calculate_timestamps(
metadata.frames_indices,
metadata.fps,
self.video_processor.temporal_patch_size,
)
for frame_idx in range(num_frames):
curr_time = curr_timestamp[frame_idx]
video_placeholder += f"<{curr_time:.1f} seconds>"
video_placeholder += self.vision_start_token + self.video_token * frame_seqlen + self.vision_end_token
return video_placeholder
def _get_num_multimodal_tokens(self, image_sizes=None, video_sizes=None, **kwargs):
"""
Computes the number of placeholder tokens needed for multimodal inputs with the given sizes.
Args:
image_sizes (`list[list[int]]`, *optional*):
The input sizes formatted as (height, width) per each image.
video_sizes (`list[list[int]]`, *optional*):
The input sizes formatted as (num_frames, height, width) per each video.
Returns:
`MultiModalData`: the number of tokens per provided modality.
"""
vision_data = {}
if image_sizes is not None:
# copy: `update` on the class-level defaults would leak kwargs into every later call
images_kwargs = dict(Lfm3VlProcessorKwargs._defaults.get("images_kwargs", {}))
images_kwargs.update(kwargs)
merge_size = images_kwargs.get("merge_size") or self.image_processor.merge_size
num_image_patches = [
self.image_processor.get_number_of_image_patches(*image_size, images_kwargs)
for image_size in image_sizes
]
num_image_tokens = [(num_patches // merge_size**2) for num_patches in num_image_patches]
vision_data.update({"num_image_tokens": num_image_tokens, "num_image_patches": num_image_patches})
if video_sizes is not None:
videos_kwargs = dict(Lfm3VlProcessorKwargs._defaults.get("videos_kwargs", {}))
videos_kwargs.update(kwargs)
merge_size = videos_kwargs.get("merge_size") or self.video_processor.merge_size
num_video_patches = [
self.video_processor.get_num_of_video_patches(*video_size, videos_kwargs) for video_size in video_sizes
]
num_video_tokens = [(num_patches // merge_size**2) for num_patches in num_video_patches]
vision_data["num_video_tokens"] = num_video_tokens
return MultiModalData(**vision_data)
def post_process_image_text_to_text(
self, generated_outputs, skip_special_tokens=True, clean_up_tokenization_spaces=False, **kwargs
):
"""
Post-process the output of the model to decode the text.
Args:
generated_outputs (`torch.Tensor` or `np.ndarray`):
The output of the model `generate` function. The output is expected to be a tensor of shape `(batch_size, sequence_length)`
or `(sequence_length,)`.
skip_special_tokens (`bool`, *optional*, defaults to `True`):
Whether or not to remove special tokens in the output. Argument passed to the tokenizer's `batch_decode` method.
clean_up_tokenization_spaces (`bool`, *optional*, defaults to `False`):
Whether or not to clean up the tokenization spaces. Argument passed to the tokenizer's `batch_decode` method.
**kwargs:
Additional arguments to be passed to the tokenizer's `batch_decode method`.
Returns:
`list[str]`: The decoded text.
"""
return self.tokenizer.batch_decode(
generated_outputs,
skip_special_tokens=skip_special_tokens,
clean_up_tokenization_spaces=clean_up_tokenization_spaces,
**kwargs,
)
@property
def model_input_names(self):
return list(
dict.fromkeys(
self.tokenizer.model_input_names
+ self.image_processor.model_input_names
+ self.video_processor.model_input_names
)
)
def _calculate_timestamps(self, indices: list[int] | np.ndarray, video_fps: float, merge_size: int = 2):
if not isinstance(indices, list):
indices = indices.tolist()
if len(indices) % merge_size != 0:
indices.extend(indices[-1] for _ in range(merge_size - len(indices) % merge_size))
timestamps = [idx / video_fps for idx in indices]
# @JJJYmmm frames are merged by self.merge_size, \
# so we need to average the timestamps between the first/last frame within the temporal patch
timestamps = [
(timestamps[i] + timestamps[i + merge_size - 1]) / 2 for i in range(0, len(timestamps), merge_size)
]
return timestamps
def prepare_inputs_layout(self, images=None, text=None, videos=None, audio=None, **kwargs):
for name, media in (("images", images), ("videos", videos)):
if isinstance(media, (list, tuple)) and len(media) == 0:
raise ValueError(f"`{name}` must hold at least one item; pass `{name}=None` for prompts without one")
return super().prepare_inputs_layout(images=images, text=text, videos=videos, audio=audio, **kwargs)
def validate_inputs(self, images=None, text=None, videos=None, audio=None, **kwargs):
super().validate_inputs(images=images, text=text, videos=videos, audio=audio, **kwargs)
if text is not None:
_check_vision_placeholder_counts(self, [text] if isinstance(text, str) else text, images, videos)
def _process_videos(self, videos, **kwargs):
# the frame timestamps need the metadata whatever the caller's `return_metadata`; `__call__` drops it from
# the output unless it was asked for
kwargs["return_metadata"] = True
return super()._process_videos(videos, **kwargs)
__all__ = ["Lfm3VlProcessor"]
|