| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | import os |
| 5 | |
| 6 | import pytest |
| 7 | |
| 8 | from vllm.assets.video import VideoAsset |
| 9 | from vllm.config import ModelConfig |
| 10 | from vllm.multimodal import MULTIMODAL_REGISTRY |
| 11 | |
| 12 | from ....conftest import ImageTestAssets |
| 13 | from ...utils import build_model_context |
| 14 | |
| 15 | MODEL_ID = "nvidia/Cosmos3-Edge" |
| 16 | IMAGE_PLACEHOLDER = "<|vision_start|><|image_pad|><|vision_end|>" |
| 17 | VIDEO_PLACEHOLDER = "<|vision_start|><|video_pad|><|vision_end|>" |
| 18 | LOCAL_MODEL_PATH = os.getenv("COSMOS3_EDGE_MODEL_PATH") |
| 19 | |
| 20 | |
| 21 | @pytest.fixture(scope="module") |
| 22 | def processor(): |
| 23 | if LOCAL_MODEL_PATH: |
| 24 | model_config = ModelConfig( |
| 25 | LOCAL_MODEL_PATH, |
| 26 | tokenizer=LOCAL_MODEL_PATH, |
| 27 | max_model_len=4096, |
| 28 | limit_mm_per_prompt={"image": 2, "video": 1}, |
| 29 | ) |
| 30 | else: |
| 31 | ctx = build_model_context( |
| 32 | MODEL_ID, |
| 33 | limit_mm_per_prompt={"image": 2, "video": 1}, |
| 34 | ) |
| 35 | model_config = ctx.model_config |
| 36 | |
| 37 | return MULTIMODAL_REGISTRY.create_processor(model_config) |
| 38 | |
| 39 | |
| 40 | def _assert_image_outputs(processor, processed, num_images: int) -> None: |
| 41 | mm_data = processed["mm_kwargs"].get_data() |
| 42 | grid_thw = mm_data["image_grid_thw"] |
| 43 | pixel_values = mm_data["pixel_values"] |
| 44 | |
| 45 | assert grid_thw.shape == (num_images, 3) |
| 46 | assert pixel_values.shape[0] == int(grid_thw.prod(dim=-1).sum()) |
| 47 | |
| 48 | merge_size = processor.info.get_hf_config().vision_config.spatial_merge_size |
| 49 | expected_tokens = (grid_thw.prod(dim=-1) // merge_size**2).tolist() |
| 50 | image_placeholders = processed["mm_placeholders"]["image"] |
| 51 | |
| 52 | assert len(image_placeholders) == num_images |
| 53 | assert [placeholder.length for placeholder in image_placeholders] == ( |
| 54 | expected_tokens |
| 55 | ) |
| 56 | |
| 57 | image_token_id = processor.info.get_hf_processor().image_token_id |
| 58 | assert processed["prompt_token_ids"].count(image_token_id) == sum(expected_tokens) |
| 59 | |
| 60 | |
| 61 | def _assert_video_outputs(processor, processed) -> None: |
| 62 | mm_data = processed["mm_kwargs"].get_data() |
| 63 | grid_thw = mm_data["video_grid_thw"] |
| 64 | pixel_values = mm_data["pixel_values_videos"] |
| 65 | |
| 66 | assert grid_thw.shape == (1, 3) |
| 67 | assert pixel_values.shape[0] == int(grid_thw.prod()) |
| 68 | assert len(processed["mm_placeholders"]["video"]) == 1 |
| 69 | |
| 70 | merge_size = processor.info.get_hf_config().vision_config.spatial_merge_size |
| 71 | expected_tokens = int(grid_thw.prod()) // merge_size**2 |
| 72 | video_token_id = processor.info.get_hf_config().video_token_id |
| 73 | prompt_token_ids = processed["prompt_token_ids"] |
| 74 | assert prompt_token_ids.count(video_token_id) == expected_tokens |
| 75 | |
| 76 | hf_processor = processor.info.get_hf_processor() |
| 77 | expected_frame_wrappers = int(grid_thw[:, 0].sum()) |
| 78 | assert ( |
| 79 | prompt_token_ids.count(hf_processor.vision_start_token_id) |
| 80 | == expected_frame_wrappers |
| 81 | ) |
| 82 | assert ( |
| 83 | prompt_token_ids.count(hf_processor.vision_end_token_id) |
| 84 | == expected_frame_wrappers |
| 85 | ) |
| 86 | |
| 87 | |
| 88 | @pytest.mark.parametrize("num_images", [1, 2]) |
| 89 | def test_process_images( |
| 90 | processor, |
| 91 | image_assets: ImageTestAssets, |
| 92 | num_images: int, |
| 93 | ) -> None: |
| 94 | images = [asset.pil_image for asset in image_assets[:num_images]] |
| 95 | processed = processor( |
| 96 | IMAGE_PLACEHOLDER * num_images, |
| 97 | mm_items=processor.info.parse_mm_data({"image": images}), |
| 98 | hf_processor_mm_kwargs={}, |
| 99 | ) |
| 100 | |
| 101 | _assert_image_outputs(processor, processed, num_images) |
| 102 | |
| 103 | |
| 104 | def test_process_video(processor) -> None: |
| 105 | video_asset = VideoAsset(name="baby_reading", num_frames=8) |
| 106 | video = (video_asset.np_ndarrays, video_asset.metadata) |
| 107 | processed = processor( |
| 108 | VIDEO_PLACEHOLDER, |
| 109 | mm_items=processor.info.parse_mm_data({"video": [video]}), |
| 110 | hf_processor_mm_kwargs={}, |
| 111 | ) |
| 112 | |
| 113 | _assert_video_outputs(processor, processed) |