| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | # NOTE To avoid overloading the CI pipeline, this test script will not |
| 5 | # be triggered on CI and is primarily intended for local testing and verification. |
| 6 | |
| 7 | import vllm |
| 8 | from vllm.assets.image import ImageAsset |
| 9 | from vllm.lora.request import LoRARequest |
| 10 | |
| 11 | from ..utils import multi_gpu_test |
| 12 | |
| 13 | MODEL_PATH = "google/gemma-4-E2B-it" |
| 14 | |
| 15 | PROMPT_TEMPLATE = """<bos><|turn>user |
| 16 | <|image|>What is in the image?<turn|> |
| 17 | <|turn>model |
| 18 | """ |
| 19 | |
| 20 | TEST_IMAGES = [ |
| 21 | ImageAsset("stop_sign"), |
| 22 | ImageAsset("cherry_blossom"), |
| 23 | ] |
| 24 | |
| 25 | EXPECTED_OUTPUTS_VISION = [ |
| 26 | "A red stop sign stands prominently in the foreground.", |
| 27 | "A majestic skyscraper stands tall, partially obscured by a vibrant " |
| 28 | "canopy of cherry blossoms, against a clear blue sky.", |
| 29 | ] |
| 30 | |
| 31 | |
| 32 | def generate_and_test(llm: vllm.LLM, lora_path: str, lora_id: int) -> None: |
| 33 | prompts = [ |
| 34 | { |
| 35 | "prompt": PROMPT_TEMPLATE, |
| 36 | "multi_modal_data": {"image": asset.pil_image}, |
| 37 | } |
| 38 | for asset in TEST_IMAGES |
| 39 | ] |
| 40 | sampling_params = vllm.SamplingParams(temperature=0, max_tokens=128) |
| 41 | outputs = llm.generate( |
| 42 | prompts, |
| 43 | sampling_params, |
| 44 | lora_request=LoRARequest(str(lora_id), lora_id, lora_path), |
| 45 | ) |
| 46 | |
| 47 | generated_texts = [output.outputs[0].text.strip() for output in outputs] |
| 48 | for generated, expected in zip(generated_texts, EXPECTED_OUTPUTS_VISION): |
| 49 | assert generated.startswith(expected), ( |
| 50 | f"Generated text {generated!r} does not match expected {expected!r}" |
| 51 | ) |
| 52 | |
| 53 | |
| 54 | def test_gemma4_lora(gemma4_vision_lora_files): |
| 55 | llm = vllm.LLM( |
| 56 | MODEL_PATH, |
| 57 | max_model_len=1024, |
| 58 | enable_lora=True, |
| 59 | enforce_eager=True, |
| 60 | max_loras=4, |
| 61 | trust_remote_code=True, |
| 62 | limit_mm_per_prompt={"image": 1}, |
| 63 | mm_processor_cache_gb=0, |
| 64 | enable_tower_connector_lora=True, |
| 65 | ) |
| 66 | |
| 67 | generate_and_test(llm, gemma4_vision_lora_files, lora_id=1) |
| 68 | generate_and_test(llm, gemma4_vision_lora_files, lora_id=2) |
| 69 | |
| 70 | |
| 71 | @multi_gpu_test(num_gpus=2) |
| 72 | def test_gemma4_lora_tp2(gemma4_vision_lora_files): |
| 73 | llm = vllm.LLM( |
| 74 | MODEL_PATH, |
| 75 | max_model_len=1024, |
| 76 | enforce_eager=True, |
| 77 | enable_lora=True, |
| 78 | max_loras=4, |
| 79 | trust_remote_code=True, |
| 80 | tensor_parallel_size=2, |
| 81 | limit_mm_per_prompt={"image": 1}, |
| 82 | mm_processor_cache_gb=0, |
| 83 | enable_tower_connector_lora=True, |
| 84 | ) |
| 85 | |
| 86 | generate_and_test(llm, gemma4_vision_lora_files, lora_id=1) |
| 87 | generate_and_test(llm, gemma4_vision_lora_files, lora_id=2) |
| 88 | |
| 89 | |
| 90 | @multi_gpu_test(num_gpus=4) |
| 91 | def test_gemma4_lora_tp4(gemma4_vision_lora_files): |
| 92 | llm = vllm.LLM( |
| 93 | MODEL_PATH, |
| 94 | max_model_len=1024, |
| 95 | enforce_eager=True, |
| 96 | enable_lora=True, |
| 97 | max_loras=4, |
| 98 | trust_remote_code=True, |
| 99 | tensor_parallel_size=4, |
| 100 | limit_mm_per_prompt={"image": 1}, |
| 101 | mm_processor_cache_gb=0, |
| 102 | enable_tower_connector_lora=True, |
| 103 | ) |
| 104 | |
| 105 | generate_and_test(llm, gemma4_vision_lora_files, lora_id=1) |
| 106 | generate_and_test(llm, gemma4_vision_lora_files, lora_id=2) |