오프라인 엔진으로 VLM 쿼리하기

오프라인 엔진으로 VLM 쿼리하기

이 튜토리얼은 SGLang의 오프라인 엔진 API를 써서 비전 언어 모델(VLM)에 질의하는 방법을 보여줘요. Qwen2.5-VL과 Llama 4를 예시로 들면서, 세 가지 방식으로 호출하는 흐름을 다룰게요.

출처: 공식문서

세 가지 입력 형식 이해하기

SGLang은 시각 데이터를 넘기는 세 가지 방식을 지원하고, 각 방식은 서로 다른 상황에 최적화돼 있어요.

1. 원본 이미지 (Raw Images) — 가장 간단한 방식

  • PIL Image, 파일 경로, URL, base64 문자열을 그대로 넘겨요
  • SGLang이 모든 전처리를 자동으로 처리해 줘요
  • 활용처: 빠른 프로토타이핑, 단순한 애플리케이션

2. 프로세서 출력 (Processor Output) — 커스텀 전처리용

  • HuggingFace processor로 이미지를 먼저 전처리해요
  • format: "processor_output"와 함께 프로세서 출력 dict 전체를 넘겨요
  • 활용처: 커스텀 이미지 변환, 기존 파이프라인과의 통합
  • 조건: 텍스트 프롬프트 대신 반드시 input_ids를 써야 해요

3. 사전 계산 임베딩 (Precomputed Embeddings) — 최대 성능용

  • 비전 인코더로 시각 임베딩을 미리 계산해요
  • format: "precomputed_embedding"로 임베딩을 넘겨요
  • 활용처: 같은 이미지에 대한 반복 쿼리, 캐싱, 높은 처리량 서빙
  • 성능 이점: 중복되는 비전 인코더 연산을 피해 30~50% 속도 향상

핵심 규칙: 단일 요청 안에서는 모든 이미지에 같은 형식 하나만 써야 해요. 형식을 섞으면 안 돼요.

아래 예시는 Qwen2.5-VL과 Llama 4 두 모델에 대해 세 방식을 모두 보여줘요.

Qwen2.5-VL 모델 쿼리하기

import nest_asyncio

nest_asyncio.apply()

import sglang.test.doc_patch  # noqa: F401

model_path = "Qwen/Qwen2.5-VL-3B-Instruct"
chat_template = "qwen2-vl"
example_image_url = "https://raw.githubusercontent.com/sgl-project/sglang/main/examples/assets/example_image.png"
from io import BytesIO
import requests
from PIL import Image

from sglang.srt.parser.conversation import chat_templates

image = Image.open(BytesIO(requests.get(example_image_url).content))

conv = chat_templates[chat_template].copy()
conv.append_message(conv.roles[0], f"What's shown here: {conv.image_token}?")
conv.append_message(conv.roles[1], "")
conv.image_data = [image]

print("Generated prompt text:")
print(conv.get_prompt())
print(f"\nImage size: {image.size}")
image

기본 오프라인 엔진 API 호출

from sglang import Engine

llm = Engine(model_path=model_path, chat_template=chat_template, log_level="warning")
out = llm.generate(prompt=conv.get_prompt(), image_data=[image])
print("Model response:")
print(out["text"])

프로세서 출력으로 호출하기

HuggingFace processor로 텍스트와 이미지를 전처리한 다음, processor_outputEngine.generate에 그대로 넘겨요.

from transformers import AutoProcessor

processor = AutoProcessor.from_pretrained(model_path)
processor_output = processor(
    images=[image], text=conv.get_prompt(), return_tensors="pt"
)

out = llm.generate(
    input_ids=processor_output["input_ids"][0].detach().cpu().tolist(),
    image_data=[dict(processor_output, format="processor_output")],
)
print("Response using processor output:")
print(out["text"])

사전 계산 임베딩으로 호출하기

이미지 피처를 미리 계산하면 반복되는 시각 인코딩 과정을 피할 수 있어요.

from transformers import AutoProcessor
from transformers import Qwen2_5_VLForConditionalGeneration

processor = AutoProcessor.from_pretrained(model_path)
model = Qwen2_5_VLForConditionalGeneration.from_pretrained(model_path).eval()
vision = model.model.visual.cuda()
processor_output = processor(
    images=[image], text=conv.get_prompt(), return_tensors="pt"
)

input_ids = processor_output["input_ids"][0].detach().cpu().tolist()

precomputed_embeddings = vision(
    processor_output["pixel_values"].cuda(), processor_output["image_grid_thw"].cuda()
)
precomputed_embeddings = precomputed_embeddings.pooler_output

multi_modal_item = dict(
    processor_output,
    format="precomputed_embedding",
    feature=precomputed_embeddings,
)

out = llm.generate(input_ids=input_ids, image_data=[multi_modal_item])
print("Response using precomputed embeddings:")
print(out["text"])

llm.shutdown()

Llama 4 비전 모델 쿼리하기

model_path = "meta-llama/Llama-4-Scout-17B-16E-Instruct"
chat_template = "llama-4"

from io import BytesIO
import requests
from PIL import Image

from sglang.srt.parser.conversation import chat_templates

# Download the same example image
image = Image.open(BytesIO(requests.get(example_image_url).content))

conv = chat_templates[chat_template].copy()
conv.append_message(conv.roles[0], f"What's shown here: {conv.image_token}?")
conv.append_message(conv.roles[1], "")
conv.image_data = [image]

print("Llama 4 generated prompt text:")
print(conv.get_prompt())
print(f"Image size: {image.size}")

image

Llama 4 기본 호출

Llama 4는 더 많은 계산 자원이 필요해요. 그래서 멀티-GPU 병렬(tp_size=4)과 더 큰 컨텍스트 길이로 구성돼 있어요.

llm = Engine(
    model_path=model_path,
    enable_multimodal=True,
    attention_backend="fa3",
    tp_size=4,
    context_length=65536,
)

out = llm.generate(prompt=conv.get_prompt(), image_data=[image])
print("Llama 4 response:")
print(out["text"])

프로세서 출력으로 호출하기

HuggingFace processor로 데이터를 전처리하면 추론 중 계산 오버헤드를 줄일 수 있어요.

from transformers import AutoProcessor

processor = AutoProcessor.from_pretrained(model_path)
processor_output = processor(
    images=[image], text=conv.get_prompt(), return_tensors="pt"
)

out = llm.generate(
    input_ids=processor_output["input_ids"][0].detach().cpu().tolist(),
    image_data=[dict(processor_output, format="processor_output")],
)
print("Response using processor output:")
print(out)

사전 계산 임베딩으로 호출하기

from transformers import AutoProcessor
from transformers import Llama4ForConditionalGeneration

processor = AutoProcessor.from_pretrained(model_path)
model = Llama4ForConditionalGeneration.from_pretrained(
    model_path, torch_dtype="auto"
).eval()

vision = model.vision_model.cuda()
multi_modal_projector = model.multi_modal_projector.cuda()

print(f'Image pixel values shape: {processor_output["pixel_values"].shape}')
input_ids = processor_output["input_ids"][0].detach().cpu().tolist()

# Process image through vision encoder
image_outputs = vision(
    processor_output["pixel_values"].to("cuda"),
    aspect_ratio_ids=processor_output["aspect_ratio_ids"].to("cuda"),
    aspect_ratio_mask=processor_output["aspect_ratio_mask"].to("cuda"),
    output_hidden_states=False
)
image_features = image_outputs.last_hidden_state

# Flatten image features and pass through multimodal projector
vision_flat = image_features.view(-1, image_features.size(-1))
precomputed_embeddings = multi_modal_projector(vision_flat)

# Build precomputed embedding data item
mm_item = dict(
    processor_output,
    format="precomputed_embedding",
    feature=precomputed_embeddings
)

# Use precomputed embeddings for efficient inference
out = llm.generate(input_ids=input_ids, image_data=[mm_item])
print("Llama 4 precomputed embedding response:")
print(out["text"])

더 알아보기 (Learn more)