[model-gateway] add qwen2_vl model image processor and tests (#14374)
This commit is contained in:
@@ -41,11 +41,11 @@ MODELS = {
|
||||
"processor_class": "LlavaNextImageProcessor",
|
||||
"description": "Multi-crop anyres processing",
|
||||
},
|
||||
# Future models:
|
||||
# "qwen2_vl": {
|
||||
# "model_id": "Qwen/Qwen2-VL-7B-Instruct",
|
||||
# "processor_class": "Qwen2VLImageProcessor",
|
||||
# },
|
||||
"qwen2_vl": {
|
||||
"model_id": "Qwen/Qwen2-VL-7B-Instruct",
|
||||
"processor_class": "Qwen2VLImageProcessor",
|
||||
"description": "Dynamic resolution with smart resize",
|
||||
},
|
||||
}
|
||||
|
||||
# Default test images
|
||||
@@ -189,6 +189,77 @@ def generate_golden_llava_next(image_path: str, output_dir: str) -> dict:
|
||||
return result
|
||||
|
||||
|
||||
def generate_golden_qwen2_vl(image_path: str, output_dir: str) -> dict:
|
||||
"""Generate golden output for Qwen2-VL.
|
||||
|
||||
Qwen2-VL uses dynamic resolution with smart resize:
|
||||
1. Smart resize to fit within min/max pixel bounds
|
||||
2. Align dimensions to (patch_size * merge_size) boundary
|
||||
3. Normalize with CLIP mean/std
|
||||
4. Returns image_grid_thw for position encoding
|
||||
|
||||
Default parameters:
|
||||
- patch_size: 14
|
||||
- merge_size: 2
|
||||
- min_pixels: 256 * 28 * 28 = 200,704
|
||||
- max_pixels: 1280 * 28 * 28 = 1,003,520
|
||||
- temporal_patch_size: 2
|
||||
"""
|
||||
try:
|
||||
from transformers import Qwen2VLImageProcessor
|
||||
except ImportError:
|
||||
print("Qwen2VLImageProcessor not available, skipping qwen2_vl")
|
||||
return None
|
||||
|
||||
processor = Qwen2VLImageProcessor.from_pretrained("Qwen/Qwen2-VL-7B-Instruct")
|
||||
image = Image.open(image_path).convert("RGB")
|
||||
original_size = image.size
|
||||
|
||||
# Process image
|
||||
outputs = processor(images=image, return_tensors="np")
|
||||
pixel_values = outputs["pixel_values"]
|
||||
image_grid_thw = outputs.get("image_grid_thw")
|
||||
|
||||
# Get config values for token calculation
|
||||
patch_size = processor.patch_size
|
||||
merge_size = processor.merge_size
|
||||
temporal_patch_size = getattr(processor, "temporal_patch_size", 2)
|
||||
min_pixels = processor.min_pixels
|
||||
max_pixels = processor.max_pixels
|
||||
|
||||
# Calculate number of tokens
|
||||
# tokens = (T * H * W) / merge_size²
|
||||
if image_grid_thw is not None:
|
||||
# image_grid_thw has shape [batch, 3] with [T, H, W]
|
||||
grid_thw = image_grid_thw[0] # First (and only) image
|
||||
num_tokens = int(np.prod(grid_thw) / (merge_size**2))
|
||||
else:
|
||||
num_tokens = None
|
||||
|
||||
result = {
|
||||
"pixel_values": pixel_values,
|
||||
"original_size": original_size,
|
||||
"processor_config": processor.to_dict(),
|
||||
}
|
||||
|
||||
if image_grid_thw is not None:
|
||||
result["image_grid_thw"] = np.array(image_grid_thw)
|
||||
|
||||
if num_tokens is not None:
|
||||
result["num_tokens"] = num_tokens
|
||||
|
||||
# Add debug info
|
||||
result["config_info"] = {
|
||||
"patch_size": patch_size,
|
||||
"merge_size": merge_size,
|
||||
"temporal_patch_size": temporal_patch_size,
|
||||
"min_pixels": min_pixels,
|
||||
"max_pixels": max_pixels,
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def save_golden(model_key: str, image_name: str, data: dict, output_dir: str):
|
||||
"""Save golden output to files."""
|
||||
model_dir = Path(output_dir) / model_key
|
||||
@@ -220,6 +291,7 @@ def generate_for_model(model_key: str, image_paths: list, output_dir: str):
|
||||
"llava": generate_golden_llava,
|
||||
"llava_pad": generate_golden_llava_pad,
|
||||
"llava_next": generate_golden_llava_next,
|
||||
"qwen2_vl": generate_golden_qwen2_vl,
|
||||
}.get(model_key)
|
||||
|
||||
if generator_fn is None:
|
||||
|
||||
Reference in New Issue
Block a user