Transformers documentation
Hình ảnh-văn bản-sang-văn bản
Hình ảnh-văn bản-sang-văn bản
Mô hình từ ảnh-văn-bản, còn được gọi là mô hình ngôn ngữ thị giác (VLM), là các mô hình ngôn ngữ nhận đầu vào là hình ảnh. Những mô hình này có thể xử lý nhiều tác vụ khác nhau, từ trả lời câu hỏi về hình ảnh đến phân đoạn hình ảnh. Tác vụ này có nhiều điểm tương đồng với từ ảnh-văn-bản, nhưng cũng có một số trường hợp sử dụng chồng chéo như chú thích hình ảnh. Mô hình từ ảnh-văn-bản chỉ nhận đầu vào là hình ảnh và thường thực hiện một tác vụ cụ thể, trong khi VLM nhận đầu vào văn bản và hình ảnh mở, đồng thời là các mô hình tổng quát hơn.
Trong hướng dẫn này, chúng tôi cung cấp một cái nhìn tổng quan ngắn gọn về VLM và cho thấy cách sử dụng chúng với Transformers để suy luận.
Để bắt đầu, có nhiều loại VLM khác nhau:
- các mô hình cơ sở được sử dụng để tinh chỉnh
- các mô hình chat được tinh chỉnh cho hội thoại
- các mô hình được tinh chỉnh theo hướng dẫn
Hướng dẫn này tập trung vào suy luận với một mô hình được tinh chỉnh theo hướng dẫn.
Hãy bắt đầu cài đặt các phụ thuộc.
pip install -q transformers accelerate pip install flash-attn --no-build-isolation
Hãy khởi tạo mô hình và bộ xử lý.
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)Các mô hình có độ phân giải động như Qwen3-VL và Qwen3.5 mặc định sử dụng
longest_edgekhoảng 16MP, điều này có thể tạo ra hơn 10.000 token thị giác cho mỗi ảnh và gây nghẽn suy luận hoặc OOM trên GPU tiêu dùng. Hãy đặtmin_pixelsvàmax_pixelskhi nạp processor để giới hạn điều này.
processor = AutoProcessor.from_pretrained(
"Qwen/Qwen3-VL-4B-Instruct",
min_pixels=224 * 224,
max_pixels=1024 * 1024,
)Mô hình này có chat template giúp người dùng phân tích các đầu ra của cuộc trò chuyện. Hơn nữa, mô hình cũng có thể nhận nhiều hình ảnh làm đầu vào trong một cuộc trò chuyện hoặc một tin nhắn duy nhất. Chúng ta sẽ chuẩn bị các đầu vào ngay bây giờ.
Đầu vào hình ảnh trông như sau.


Cấu trúc cuộc trò chuyện của bạn như minh họa bên dưới cho một prompt duy nhất với đầu vào gồm hình ảnh và văn bản.
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?"},
]
}
]Đổi vai trò giữa user và assistant để cung cấp ngữ cảnh trước đó cho mô hình, giúp tạo ra các phản hồi tốt hơn.
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?"},
]
},
]Chúng ta sẽ gọi phương thức apply_chat_template() của các bộ xử lý để tiền xử lý đầu ra của chúng cùng với các đầu vào hình ảnh.
inputs = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt").to(device)Bây giờ chúng ta có thể truyền các đầu vào đã tiền xử lý vào mô hình.
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.']Pipeline
Cách nhanh nhất để bắt đầu là sử dụng API Pipeline. Chỉ định tác vụ "image-text-to-text" và mô hình bạn muốn sử dụng.
from transformers import pipeline
pipe = pipeline("image-text-to-text", model="llava-hf/llava-interleave-qwen-0.5b-hf")Ví dụ bên dưới sử dụng chat templates để định dạng các đầu vào văn bản.
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"},
],
},
]Truyền văn bản và hình ảnh đã định dạng theo chat template vào Pipeline và đặt return_full_text=False để loại bỏ phần đầu vào khỏi kết quả tạo ra.
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 stemsNếu bạn muốn, bạn cũng có thể tải các hình ảnh riêng lẻ và truyền chúng vào pipeline như sau:
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."Các hình ảnh vẫn sẽ được bao gồm trong trường "input_text" của kết quả đầu ra:
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?'}]}]
"""Streaming
Chúng ta có thể sử dụng text streaming để có trải nghiệm sinh nội dung tốt hơn. Transformers hỗ trợ streaming với các lớp TextStreamer hoặc TextIteratorStreamer. Chúng ta sẽ sử dụng TextIteratorStreamer với IDEFICS-8B.
Giả sử chúng ta có một ứng dụng lưu trữ lịch sử trò chuyện và nhận đầu vào mới từ người dùng. Chúng ta sẽ tiền xử lý các đầu vào như bình thường và khởi tạo TextIteratorStreamer để xử lý quá trình sinh trong một luồng riêng biệt. Điều này cho phép bạn phát trực tiếp các token văn bản được sinh ra theo thời gian thực. Bất kỳ đối số sinh nào cũng có thể được truyền vào 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()Bây giờ hãy gọi hàm model_inference mà chúng ta đã tạo và truyền trực tiếp các giá trị.
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 ...Đưa mô hình vào phần cứng nhỏ hơn
VLM thường có kích thước lớn và cần được tối ưu để chạy trên phần cứng nhỏ hơn. Transformers hỗ trợ nhiều thư viện lượng tử hóa mô hình, và ở đây chúng ta chỉ trình bày lượng tử hóa int8 bằng Quanto. Lượng tử hóa int8 mang lại cải thiện bộ nhớ lên đến 75 phần trăm (nếu tất cả trọng số đều được lượng tử hóa). Tuy nhiên, đây không phải là giải pháp miễn phí, vì 8-bit không phải là độ chính xác bản địa của CUDA, các trọng số sẽ được lượng tử hóa và giải lượng tử hóa liên tục trong quá trình chạy, điều này làm tăng độ trễ.
Đầu tiên, hãy cài đặt các phụ thuộc.
pip install -U optimum-quanto bitsandbytes
Để lượng tử hóa một mô hình trong quá trình tải, trước tiên chúng ta cần tạo QuantoConfig. Sau đó, tải mô hình như bình thường, nhưng truyền quantization_config trong khi khởi tạo mô hình.
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']Và đó là tất cả, chúng ta có thể sử dụng mô hình theo cách tương tự mà không cần thay đổi gì.
Trò chuyện lặp lại với bộ nhớ đệm
Nếu bạn đang xây dựng các tác nhân trò chuyện đa phương thức, bạn có thể đang truyền cùng một hình ảnh hoặc âm thanh qua nhiều lượt hội thoại. Caching cho phép bạn tái sử dụng các biểu diễn đã mã hóa của chúng thay vì xử lý lại mỗi lần, giúp giảm thiểu các phép tính trùng lặp.
Tương tự các mô hình chỉ xử lý văn bản, các mô hình đa phương thức theo dõi lịch sử hội thoại bằng chat template. Điểm khác biệt chính là chat template chỉ được áp dụng ở định dạng "text" và nội dung đa phương thức mới được xử lý riêng biệt. Nếu bạn áp dụng template kèm theo xử lý, toàn bộ cuộc hội thoại sẽ được xử lý lại ở mỗi lượt vì Jinja không thể phân biệt các đầu vào đã được mã hóa trước đó với các đầu vào mới.
Ví dụ dưới đây minh họa cho mẫu này. Đối với các mô hình chỉ xử lý văn bản, hãy xem hướng dẫn sinh lặp.
Dưới đây là một ví dụ Python đơn giản minh họa cho mẫu này. Để xem ví dụ với các mô hình chỉ có văn bản, hãy tham khảo hướng dẫn này.
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)Đọc thêm
Dưới đây là một số tài nguyên khác cho tác vụ từ ảnh và văn bản sang văn bản.
- Trang nhiệm vụ từ ảnh-văn-bản sang văn bản bao gồm các loại mô hình, trường hợp sử dụng, bộ dữ liệu và nhiều hơn nữa.
- Giải thích về Mô hình Ngôn ngữ Thị giác là một bài viết blog bao quát mọi khía cạnh về mô hình ngôn ngữ thị giác và tinh chỉnh có giám sát sử dụng TRL.
- Học cách tinh chỉnh mô hình ngôn ngữ thị giác bằng TRL