Skip to content
9 changes: 9 additions & 0 deletions docs/design/cuda_graphs_multimodal.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ Models opt-in to encoder CUDA Graphs by implementing the [SupportsEncoderCudaGra

| Architecture | Models | CG for Image | CG for Video |
| ------------ | ------ | ------------ | ------------ |
| `Llama4ForConditionalGeneration` | `Llama 4` | ✅︎ | - |
| `Qwen2VLForConditionalGeneration` | `Qwen2-VL` | ✅︎ | ✅︎ |
| `Qwen2_5_VLForConditionalGeneration` | `Qwen2.5-VL` | ✅︎ | ✅︎ |
| `Qwen3VLForConditionalGeneration` | `Qwen3-VL` | ✅︎ | ✅︎ |
Expand Down Expand Up @@ -116,6 +117,14 @@ vllm serve Qwen/Qwen3-VL-32B \
--compilation-config '{"cudagraph_mm_encoder": true}'
```

For `Llama 4` (image only):

```bash
vllm serve meta-llama/Llama-4-Scout-17B-16E-Instruct \
--limit-mm-per-prompt '{"image": 1}' \
--compilation-config '{"cudagraph_mm_encoder": true}'
```

With explicit budgets:

```bash
Expand Down
1 change: 1 addition & 0 deletions examples/generate/multimodal/vision_language_offline.py
Original file line number Diff line number Diff line change
Expand Up @@ -2554,6 +2554,7 @@ def run_tarsier2(questions: list[str], modality: str) -> ModelRequestData:


MODELS_SUPPORT_VIT_CUDA_GRAPH = [
"llama4",
"qwen2_5_vl",
"qwen3_vl",
"qwen3_vl_moe",
Expand Down
2 changes: 1 addition & 1 deletion tests/models/multimodal/generation/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ def _granite4_vision_vllm_to_hf_output(vllm_output, model):
dtype="bfloat16",
auto_cls=AutoModelForImageTextToText,
tensor_parallel_size=4,
marks=multi_gpu_marks(num_gpus=4),
marks=[pytest.mark.core_model, *multi_gpu_marks(num_gpus=4)],
Comment thread
This conversation was marked as resolved.
Outdated
),
"llava_next": VLMTestInfo(
models=["llava-hf/llava-v1.6-mistral-7b-hf"],
Expand Down
20 changes: 19 additions & 1 deletion tests/models/multimodal/generation/test_vit_cudagraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
from vllm.platforms import current_platform

from ....conftest import IMAGE_ASSETS, VIDEO_ASSETS
from ...utils import dummy_hf_overrides

from ....utils import create_new_process_for_each_test, multi_gpu_marks, dummy_hf_overrides
from .vlm_utils.builders import sample_frames_with_video_metadata


Expand Down Expand Up @@ -51,6 +52,23 @@ def step3_vl_chat_template(content: str) -> str:


MODEL_CONFIGS: dict[str, VitCudagraphTestConfig] = {
"llama4": VitCudagraphTestConfig(
model="meta-llama/Llama-4-Scout-17B-16E-Instruct",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this 17B model would cause OOM issues in the CI.

FYI, #43082.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea... this is tricky. llama4 cg test is probably too heavy. I can shrink the config more but it looks like the only options are moving the llama4 cg testing to a different hardware config or not testing llama4.

what r ur thoughts?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use dummy weights to test this model, because we only test basic functionality here. You can refer to Step3-VL below to reduce num layers in config.

modalities=["image"],
image_prompt=(
"<|begin_of_text|><|header_start|>user<|header_end|>\n\n"
"<|image|>What is in this image?<|eot|>"
"<|header_start|>assistant<|header_end|>\n\n"
),
Comment thread
This conversation was marked as resolved.
max_model_len=4096,
max_tokens=32,
max_num_seqs=2,
vllm_runner_kwargs={
"distributed_executor_backend": "mp",
"tensor_parallel_size": 4,
},
marks=[pytest.mark.core_model, *multi_gpu_marks(num_gpus=4)],
),
"qwen2_5_vl": VitCudagraphTestConfig(
model="Qwen/Qwen2.5-VL-3B-Instruct",
image_prompt=qwen_vl_chat_template(
Expand Down
112 changes: 112 additions & 0 deletions tests/models/multimodal/processing/test_mllama4.py
Comment thread
This conversation was marked as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Tests for mllama's multimodal preprocessing and profiling."""

from types import SimpleNamespace

import pytest
import torch
from torch import prod
from transformers import Llama4Config

from vllm.model_executor.models.mllama4 import Llama4ForConditionalGeneration
from vllm.multimodal import MULTIMODAL_REGISTRY

from ...utils import build_model_context
Expand Down Expand Up @@ -53,3 +57,111 @@ def test_profiling(model_id: str, max_model_len: int):
assert total_tokens == sum(
placeholder.length for placeholder in mm_inputs["mm_placeholders"]["image"]
)


class VisionModelStub(torch.nn.Module):
def forward(self, pixel_values: torch.Tensor) -> torch.Tensor:
return pixel_values.unsqueeze(-1)


class ProjectorStub(torch.nn.Module):
def forward(self, vision_embeddings: torch.Tensor) -> torch.Tensor:
return vision_embeddings + 1


def make_test_model() -> Llama4ForConditionalGeneration:
model = object.__new__(Llama4ForConditionalGeneration)
torch.nn.Module.__init__(model)
model.config = SimpleNamespace(
vision_config=SimpleNamespace(
image_size=8,
patch_size=4,
pixel_shuffle_ratio=1.0,
num_channels=3,
),
text_config=SimpleNamespace(hidden_size=1),
)
model.vllm_config = SimpleNamespace(
model_config=SimpleNamespace(max_model_len=20),
)
model.vision_model = VisionModelStub()
model.multi_modal_projector = ProjectorStub()
model.use_data_parallel = False
return model


def test_encoder_cudagraph_metadata():
model = make_test_model()
patches_per_chunk = model.get_image_patches_per_chunk()
mm_kwargs = {
"patches_per_image": torch.tensor([2, 3], dtype=torch.int32),
}
vllm_config = SimpleNamespace(
scheduler_config=SimpleNamespace(max_num_batched_tokens=32),
)

config = model.get_encoder_cudagraph_config()

assert config.modalities == ["image"]
assert config.input_key_by_modality == {"image": "pixel_values"}
assert config.buffer_keys == []
assert config.out_hidden_size == 1
assert patches_per_chunk == 4
assert model.get_encoder_cudagraph_budget_range(vllm_config) == (
patches_per_chunk,
20,
)
assert model.get_encoder_cudagraph_num_items(mm_kwargs) == 2
assert model.get_encoder_cudagraph_per_item_input_sizes(mm_kwargs) == [2, 3]
assert model.get_encoder_cudagraph_per_item_output_tokens(mm_kwargs) == [
2 * patches_per_chunk,
3 * patches_per_chunk,
]


def test_select_encoder_cudagraph_items():
model = make_test_model()
mm_kwargs = {
"pixel_values": torch.tensor([[0.0], [1.0], [2.0], [3.0], [4.0]]),
"patches_per_image": torch.tensor([2, 3], dtype=torch.int32),
}

selected = model.select_encoder_cudagraph_items(mm_kwargs, [1])
empty = model.select_encoder_cudagraph_items(mm_kwargs, [])

assert torch.equal(selected["pixel_values"], torch.tensor([[2.0], [3.0], [4.0]]))
assert torch.equal(
selected["patches_per_image"], torch.tensor([3], dtype=torch.int32)
)
assert empty["pixel_values"].shape == (0, 1)
assert empty["patches_per_image"].shape == (0,)


def test_prepare_encoder_cudagraph_capture_inputs_rounds_up():
model = make_test_model()
patches_per_chunk = model.get_image_patches_per_chunk()

capture_inputs = model.prepare_encoder_cudagraph_capture_inputs(
token_budget=patches_per_chunk + 1,
max_batch_size=2,
max_frames_per_batch=0,
device=torch.device("cpu"),
dtype=torch.float32,
)

assert capture_inputs.mm_kwargs["pixel_values"].shape == (2, 3, 8, 8)
assert capture_inputs.buffers == {}


def test_encoder_cudagraph_forward_matches_eager():
model = make_test_model()
mm_kwargs = {
"pixel_values": torch.tensor([[1.0], [2.0]]),
}

eager = model.encoder_eager_forward(mm_kwargs)
cg = model.encoder_cudagraph_forward(mm_kwargs, buffers={})

expected = torch.tensor([[2.0], [3.0]])
assert torch.equal(eager, expected)
assert torch.equal(cg, expected)
Loading
Loading