fix(vlm): materialize Qwen3-VL features on the vision device (#31596)
This commit is contained in:
@@ -68,7 +68,10 @@ from sglang.srt.models.utils import (
|
|||||||
WeightsMapper,
|
WeightsMapper,
|
||||||
compute_cu_seqlens_from_grid_numpy,
|
compute_cu_seqlens_from_grid_numpy,
|
||||||
)
|
)
|
||||||
from sglang.srt.multimodal.mm_utils import run_dp_sharded_mrope_vision_model
|
from sglang.srt.multimodal.mm_utils import (
|
||||||
|
materialize_multimodal_features,
|
||||||
|
run_dp_sharded_mrope_vision_model,
|
||||||
|
)
|
||||||
from sglang.srt.multimodal.vit_cuda_graph_runner import ViTCudaGraphRunner
|
from sglang.srt.multimodal.vit_cuda_graph_runner import ViTCudaGraphRunner
|
||||||
from sglang.srt.runtime_context import get_parallel, get_server_args
|
from sglang.srt.runtime_context import get_parallel, get_server_args
|
||||||
from sglang.srt.utils import (
|
from sglang.srt.utils import (
|
||||||
@@ -1330,9 +1333,10 @@ class Qwen3VLForConditionalGeneration(nn.Module):
|
|||||||
return pattern.pad_input_tokens(input_ids, mm_inputs)
|
return pattern.pad_input_tokens(input_ids, mm_inputs)
|
||||||
|
|
||||||
def get_image_feature(self, items: List[MultimodalDataItem]) -> torch.Tensor:
|
def get_image_feature(self, items: List[MultimodalDataItem]) -> torch.Tensor:
|
||||||
# in qwen-vl, last dim is the same
|
pixel_values = materialize_multimodal_features(
|
||||||
pixel_values = torch.cat([item.feature for item in items], dim=0).type(
|
[item.feature for item in items],
|
||||||
self.visual.dtype
|
device=self.visual.device,
|
||||||
|
dtype=self.visual.dtype,
|
||||||
)
|
)
|
||||||
image_grid_thw = torch.concat([item.image_grid_thw for item in items], dim=0)
|
image_grid_thw = torch.concat([item.image_grid_thw for item in items], dim=0)
|
||||||
assert pixel_values.dim() == 2, pixel_values.dim()
|
assert pixel_values.dim() == 2, pixel_values.dim()
|
||||||
@@ -1349,9 +1353,10 @@ class Qwen3VLForConditionalGeneration(nn.Module):
|
|||||||
return self.visual(pixel_values, grid_thw=image_grid_thw)
|
return self.visual(pixel_values, grid_thw=image_grid_thw)
|
||||||
|
|
||||||
def get_video_feature(self, items: List[MultimodalDataItem]) -> torch.Tensor:
|
def get_video_feature(self, items: List[MultimodalDataItem]) -> torch.Tensor:
|
||||||
# in qwen-vl, last dim is the same
|
pixel_values = materialize_multimodal_features(
|
||||||
pixel_values = torch.cat([item.feature for item in items], dim=0).type(
|
[item.feature for item in items],
|
||||||
self.visual.dtype
|
device=self.visual.device,
|
||||||
|
dtype=self.visual.dtype,
|
||||||
)
|
)
|
||||||
video_grid_thw = torch.concat([item.video_grid_thw for item in items], dim=0)
|
video_grid_thw = torch.concat([item.video_grid_thw for item in items], dim=0)
|
||||||
assert pixel_values.dim() == 2, pixel_values.dim()
|
assert pixel_values.dim() == 2, pixel_values.dim()
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
"""Regression tests for Qwen3-VL multimodal feature materialization."""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from sglang.srt.models.qwen3_vl import Qwen3VLForConditionalGeneration
|
||||||
|
from sglang.test.ci.ci_register import register_cpu_ci
|
||||||
|
from sglang.test.test_utils import CustomTestCase
|
||||||
|
|
||||||
|
register_cpu_ci(est_time=5, suite="base-a-test-cpu")
|
||||||
|
|
||||||
|
|
||||||
|
class _RecordingVisual:
|
||||||
|
device = torch.device("meta")
|
||||||
|
dtype = torch.bfloat16
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.pixel_values = None
|
||||||
|
self.grid_thw = None
|
||||||
|
|
||||||
|
def __call__(self, pixel_values, *, grid_thw):
|
||||||
|
self.pixel_values = pixel_values
|
||||||
|
self.grid_thw = grid_thw
|
||||||
|
return pixel_values
|
||||||
|
|
||||||
|
|
||||||
|
class TestQwen3VLFeatureMaterialization(CustomTestCase):
|
||||||
|
def test_image_features_are_packed_on_the_visual_device(self):
|
||||||
|
visual = _RecordingVisual()
|
||||||
|
model = SimpleNamespace(visual=visual, use_data_parallel=False)
|
||||||
|
items = [
|
||||||
|
SimpleNamespace(
|
||||||
|
feature=torch.ones(2, 3),
|
||||||
|
image_grid_thw=torch.tensor([[1, 1, 2]]),
|
||||||
|
),
|
||||||
|
SimpleNamespace(
|
||||||
|
feature=torch.ones(1, 3),
|
||||||
|
image_grid_thw=torch.tensor([[1, 1, 1]]),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
output = Qwen3VLForConditionalGeneration.get_image_feature(model, items)
|
||||||
|
|
||||||
|
self.assertIs(visual.pixel_values, output)
|
||||||
|
self.assertEqual(output.shape, (3, 3))
|
||||||
|
self.assertEqual(output.device, visual.device)
|
||||||
|
self.assertEqual(output.dtype, visual.dtype)
|
||||||
|
|
||||||
|
def test_video_features_are_packed_on_the_visual_device(self):
|
||||||
|
visual = _RecordingVisual()
|
||||||
|
model = SimpleNamespace(visual=visual, use_data_parallel=False)
|
||||||
|
items = [
|
||||||
|
SimpleNamespace(
|
||||||
|
feature=torch.ones(3, 4),
|
||||||
|
video_grid_thw=torch.tensor([[1, 1, 3]]),
|
||||||
|
),
|
||||||
|
SimpleNamespace(
|
||||||
|
feature=torch.ones(2, 4),
|
||||||
|
video_grid_thw=torch.tensor([[1, 1, 2]]),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
output = Qwen3VLForConditionalGeneration.get_video_feature(model, items)
|
||||||
|
|
||||||
|
self.assertIs(visual.pixel_values, output)
|
||||||
|
self.assertEqual(output.shape, (5, 4))
|
||||||
|
self.assertEqual(output.device, visual.device)
|
||||||
|
self.assertEqual(output.dtype, visual.dtype)
|
||||||
|
self.assertTrue(
|
||||||
|
torch.equal(visual.grid_thw, torch.tensor([[1, 1, 3], [1, 1, 2]]))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main(verbosity=2)
|
||||||
Reference in New Issue
Block a user