Transformers documentation

Изображение и текст в текст

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.2).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Изображение и текст в текст

Модели «изображение-текст-в-текст», также известные как мультимодальные языковые модели (VLM), — это языковые модели, которые принимают на вход изображение. Такие модели способны решать различные задачи, от визуального ответа на вопросы до сегментации изображений. Эта задача во многом похожа на «изображение-в-текст», но имеет пересекающиеся сценарии использования, например, генерацию описаний изображений. Модели «изображение-в-текст» принимают только изображения на вход и обычно выполняют конкретную задачу, тогда как VLM принимают произвольный текст и изображения и являются более универсальными моделями.

В этом руководстве мы кратко рассматриваем VLM и показываем, как использовать их с Transformers для инференса.

Для начала стоит отметить, что существует несколько типов VLM:

  • базовые модели, используемые для дообучения
  • модели, дообученные для диалогов
  • модели, дообученные на инструкциях

В этом руководстве рассматривается инференс с использованием модели, прошедшей instruction-tuning.

Начнем с установки зависимостей.

pip install -q transformers accelerate 
pip install flash-attn --no-build-isolation

Инициализируем модель и процессор.

from transformers import AutoProcessor, AutoModelForImageTextToText
from accelerate import Accelerator
import torch

device = Accelerator().device
model = AutoModelForImageTextToText.from_pretrained(
    "Qwen/Qwen3-VL-4B-Instruct",
    dtype=torch.bfloat16,
    attn_implementation="flash_attention_2",
).to(device)

Модели с динамическим разрешением, такие как Qwen3-VL и Qwen3.5, по умолчанию используют значение longest_edge около 16 Мп, что может приводить к генерации более 10 000 визуальных токенов на изображение, замедлению инференса или исчерпанию памяти (OOM) на потребительских GPU. Чтобы ограничить это, задайте min_pixels и max_pixels при загрузке процессора.

processor = AutoProcessor.from_pretrained(
    "Qwen/Qwen3-VL-4B-Instruct",
    min_pixels=224 * 224,
    max_pixels=1024 * 1024,
)

Эта модель имеет шаблон чата, который помогает пользователю обрабатывать выходные данные чата. Кроме того, модель также может принимать несколько изображений в качестве входных данных в рамках одного разговора или сообщения. Теперь мы подготовим входные данные.

Входные изображения выглядят следующим образом.

Two cats sitting on a net
A bee on a pink flower

Структурируйте диалог, как показано ниже, для одного запроса с входными данными в виде изображения и текста.

messages = [
    {
        "role": "user",
        "content": [
            {"type": "image", "image": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/cats.png"},
            {"type": "text", "text": "What do we see in this image?"},
        ]
    }
]

Чередуйте роли user и assistant, чтобы обеспечить модели контекст предыдущих сообщений и получить более качественные ответы.

messages = [
    {
        "role": "user",
        "content": [
            {"type": "image", "image": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/cats.png"},
            {"type": "text", "text": "What do we see in this image?"},
        ]
    },
    {
        "role": "assistant",
        "content": [
            {"type": "text", "text": "In this image we can see two cats on the nets."},
        ]
    },
    {
        "role": "user",
        "content": [
            {"type": "image", "image": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg"},
            {"type": "text", "text": "And how about this image?"},
        ]
    },
]

Теперь мы вызовем метод apply_chat_template() процессоров, чтобы предобработать их вывод вместе с входными изображениями.

inputs = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt").to(device)

Теперь мы можем передать предобработанные входные данные в модель.

input_len = len(inputs.input_ids[0])

with torch.no_grad():
    generated_ids = model.generate(**inputs, max_new_tokens=200)
generated_texts = processor.batch_decode(generated_ids[:, input_len:], skip_special_tokens=True)

print(generated_texts)
## ['In this image we can see flowers, plants and insect.']

Пайплайн

Самый быстрый способ начать работу — использовать API Pipeline. Укажите задачу "image-text-to-text" и модель, которую вы хотите использовать.

from transformers import pipeline
pipe = pipeline("image-text-to-text", model="llava-hf/llava-interleave-qwen-0.5b-hf")

Приведённый ниже пример использует шаблоны чата для форматирования текстовых входных данных.

messages = [
     {
         "role": "user",
         "content": [
             {
                 "type": "image",
                 "image": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg",
             },
             {"type": "text", "text": "Describe this image."},
         ],
     },
     {
         "role": "assistant",
         "content": [
             {"type": "text", "text": "There's a pink flower"},
         ],
     },
 ]

Передайте в Pipeline текст, отформатированный с помощью chat template, и изображение, и установите return_full_text=False, чтобы исключить входные данные из сгенерированного результата.

outputs = pipe(text=messages, max_new_tokens=20, return_full_text=False)
outputs[0]["generated_text"]
#  with a yellow center in the foreground. The flower is surrounded by red and white flowers with green stems

Если хотите, вы также можете загрузить изображения отдельно и передать их в конвейер следующим образом:

import requests
from PIL import Image
from transformers import pipeline

pipe = pipeline("image-text-to-text", model="HuggingFaceTB/SmolVLM-256M-Instruct")

img_urls = [
    "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/cats.png",
    "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg",
]
images = [
    Image.open(requests.get(img_urls[0], stream=True).raw),
    Image.open(requests.get(img_urls[1], stream=True).raw),
]

messages = [
    {
        "role": "user",
        "content": [
            {"type": "image"},
            {"type": "image"},
            {"type": "text", "text": "What do you see in these images?"},
        ],
    }
]
outputs = pipe(text=messages, images=images, max_new_tokens=50, return_full_text=False)
outputs[0]["generated_text"]
" In the first image, there are two cats sitting on a plant. In the second image, there are flowers with a pinkish hue."

Изображения по-прежнему будут включены в поле "input_text" результата:

outputs[0]['input_text']
"""
[{'role': 'user',
  'content': [{'type': 'image',
    'image': <PIL.PngImagePlugin.PngImageFile image mode=RGBA size=622x412>},
   {'type': 'image',
    'image': <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=5184x3456>},
   {'type': 'text', 'text': 'What do you see in these images?'}]}]
"""

Потоковая передача

Мы можем использовать потоковую передачу текста для улучшения процесса генерации. Transformers поддерживает потоковую передачу с помощью классов TextStreamer или TextIteratorStreamer. Мы будем использовать TextIteratorStreamer с IDEFICS-8B.

Предположим, у нас есть приложение, которое хранит историю чата и принимает новый ввод пользователя. Мы обработаем входные данные в обычном порядке и инициализируем TextIteratorStreamer для обработки генерации в отдельном потоке. Это позволяет передавать токены сгенерированного текста в реальном времени. Любые аргументы генерации можно передать в TextIteratorStreamer.

import time
from transformers import TextIteratorStreamer
from threading import Thread

def model_inference(
    user_prompt,
    chat_history,
    max_new_tokens,
    images
):
    user_prompt = {
        "role": "user",
        "content": [
            {"type": "image"},
            {"type": "text", "text": user_prompt},
        ]
    }
    chat_history.append(user_prompt)
    streamer = TextIteratorStreamer(
        processor.tokenizer,
        skip_prompt=True,
        timeout=5.0,
    )

    generation_args = {
        "max_new_tokens": max_new_tokens,
        "streamer": streamer,
        "do_sample": False
    }

    # add_generation_prompt=True makes model generate bot response
    prompt = processor.apply_chat_template(chat_history, add_generation_prompt=True)
    inputs = processor(
        text=prompt,
        images=images,
        return_tensors="pt",
    ).to(device)
    generation_args.update(inputs)

    thread = Thread(
        target=model.generate,
        kwargs=generation_args,
    )
    thread.start()

    acc_text = ""
    for text_token in streamer:
        time.sleep(0.04)
        acc_text += text_token
        if acc_text.endswith("<end_of_utterance>"):
            acc_text = acc_text[:-18]
        yield acc_text

    thread.join()

Теперь вызовем функцию model_inference, которую мы создали, и потоково передадим значения.

generator = model_inference(
    user_prompt="And what is in this image?",
    chat_history=messages[:2],
    max_new_tokens=100,
    images=images
)

for value in generator:
  print(value)

# In
# In this
# In this image ...

Обучение моделей на менее мощном оборудовании

VLMs часто имеют большой размер и требуют оптимизации для работы на менее мощном оборудовании. Transformers поддерживает множество библиотек квантования моделей, и здесь мы продемонстрируем только int8-квантование с помощью Quanto. Int8-квантование позволяет сократить потребление памяти до 75 процентов (при условии, что квантованы все веса). Однако это не бесплатно: поскольку 8-битная точность не является нативной для CUDA, веса квантуются и деквантуются «на лету», что приводит к увеличению задержки.

Сначала установите зависимости.

pip install -U optimum-quanto bitsandbytes

Чтобы выполнить квантование модели при загрузке, сначала необходимо создать QuantoConfig. Затем загрузите модель как обычно, но передайте quantization_config при инициализации модели.

from transformers import AutoModelForImageTextToText, QuantoConfig

model_id = "Qwen/Qwen3-VL-4B-Instruct"
quantization_config = QuantoConfig(weights="int8")
quantized_model = AutoModelForImageTextToText.from_pretrained(
    model_id, device_map="auto", quantization_config=quantization_config
)

messages = [
    {
        "role": "user",
        "content": [
            {"type": "image", "image": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/cats.png"},
            {"type": "text", "text": "What do we see in this image?"},
        ]
    },
]
inputs = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt").to(quantized_model.device)
input_len = len(inputs.input_ids[0])

with torch.no_grad():
    generated_ids = quantized_model.generate(**inputs, cache_implementation="static", max_new_tokens=100)
generated_texts = processor.batch_decode(generated_ids[:, input_len:], skip_special_tokens=True)

print(generated_texts[0])
## ['In this image, we see two tabby cats resting on a large, tangled pile of fishing nets. The nets are a mix of brown, orange, and red colors, with some blue and green ropes visible in the background. The cats appear relaxed and comfortable, nestled into the fibers of the nets. One cat is in the foreground, looking slightly to the side, while the other is positioned further back, looking directly at the camera. The scene suggests a coastal or fishing-related setting, possibly near']

И это всё: модель можно использовать так же, без каких-либо изменений.

Итеративный чат с кэшированием

Если вы разрабатываете мультимодальных чат-агентов, вы, вероятно, передаёте одни и те же изображения или аудио между ходами. Кэширование позволяет повторно использовать их закодированные представления вместо повторной обработки каждый раз, что сокращает избыточные вычисления.

Как и модели, работающие только с текстом, мультимодальные модели отслеживают историю диалога с помощью шаблона чата. Главное отличие состоит в том, что шаблон чата применяется только в формате "text", а новый мультимодальный контент обрабатывается отдельно. Если применить шаблон с обработкой, то на каждом ходу будет заново перерабатываться весь диалог, поскольку Jinja не может отличить ранее закодированные входные данные от новых.

Приведённый ниже пример демонстрирует этот паттерн. Для моделей, работающих только с текстом, см. руководство по итеративной генерации.

Ниже приведён простой пример на Python, демонстрирующий этот паттерн. Пример с моделями, работающими только с текстом, см. в этом руководстве.

import torch
from transformers import AutoProcessor, Gemma4ForConditionalGeneration, TextStreamer
from transformers.cache_utils import DynamicCache
from transformers.image_utils import load_image
from transformers.audio_utils import load_audio

model_id = 'google/gemma-4-E2B-it'
processor = AutoProcessor.from_pretrained(model_id)
model = Gemma4ForConditionalGeneration.from_pretrained(model_id, dtype=torch.float32, device_map='cpu')

past_key_values = DynamicCache()
streamer = TextStreamer(processor.tokenizer, skip_prompt=True, skip_special_tokens=True)

## Turn 1: multimodal input (text + image + audio)
audio = load_audio("https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav")
image = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg")
messages = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": "In detail, describe the following audio and image."},
            {"type": "audio"},
            {"type": "image"},
        ],
    },
]
input_string = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
inputs = processor(text=input_string, images=[image], audio=[audio], return_tensors='pt')
output = model.generate(**inputs, past_key_values=past_key_values, max_new_tokens=1024, do_sample=False, streamer=streamer)
first_response = processor.decode(output[0][inputs['input_ids'].shape[1]:], skip_special_tokens=True)

## Turn 2: text-only follow-up (reuses KV cache, no new media)
messages.append({"role": "assistant", "content": [{"type": "text", "text": first_response}]})
cached_string = processor.apply_chat_template(messages, tokenize=False)
messages.append({"role": "user", "content": [{"type": "text", "text": "Summarize the previous descriptions in one sentence."}]})
full_string = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
new_string = full_string[len(cached_string):]

new_input_ids = processor.tokenizer(new_string, add_special_tokens=False, return_tensors='pt')['input_ids']
# Build and pass an attention mask only if batched input or there is padding
# attention_mask = torch.ones(1, past_key_values.get_seq_length() + new_input_ids.shape[1], dtype=torch.long)

output2 = model.generate(input_ids=new_input_ids, past_key_values=past_key_values, max_new_tokens=1024, do_sample=False, streamer=streamer)
second_response = processor.decode(output2[0][new_input_ids.shape[1]:], skip_special_tokens=True)

## Turn 3: new image (must go through processor for image token expansion + encoding)
image2 = load_image("https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/artemis.jpeg")
messages.append({"role": "assistant", "content": [{"type": "text", "text": second_response}]})
cached_string2 = processor.apply_chat_template(messages, tokenize=False)
messages.append({"role": "user", "content": [{"type": "text", "text": "Describe this image."}, {"type": "image"}]})
full_string2 = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
new_string2 = full_string2[len(cached_string2):]

new_inputs2 = processor(text=new_string2, images=[image2], add_special_tokens=False, return_tensors='pt')
# new_inputs2['attention_mask'] = torch.ones(1, past_key_values.get_seq_length() + new_inputs2['input_ids'].shape[1], dtype=torch.long)
new_inputs2['past_key_values'] = past_key_values

output3 = model.generate(**new_inputs2, max_new_tokens=1024, do_sample=False, streamer=streamer)

Дополнительные материалы

Вот ещё несколько ресурсов для задачи «изображение и текст в текст».

Update on GitHub