[model-gateway] add phi4 vision image processor (#14430)

This commit is contained in:
Simo Lin
2025-12-04 07:40:33 -08:00
committed by GitHub
parent 441420e149
commit 11d33c0e8f
8 changed files with 1120 additions and 56 deletions
@@ -56,6 +56,11 @@ MODELS = {
"processor_class": "Phi3VImageProcessor",
"description": "Dynamic HD transform with 336x336 tiles",
},
"phi4_vision": {
"model_id": "microsoft/Phi-4-multimodal-instruct",
"processor_class": "Phi4MMImageProcessor",
"description": "Dynamic HD transform with 448x448 tiles and SiGLIP encoder",
},
}
# Default test images
@@ -419,6 +424,62 @@ def generate_golden_phi3_vision(image_path: str, output_dir: str) -> dict:
return result
def generate_golden_phi4_vision(image_path: str, output_dir: str) -> dict:
"""Generate golden output for Phi4-Vision (Phi-4-multimodal).
Phi4-Vision uses Dynamic HD transform similar to Phi3 but with:
- Base resolution: 448 (vs 336 in Phi3)
- Normalization: [0.5, 0.5, 0.5] mean/std (vs CLIP in Phi3)
- Default dynamic_hd: 36 (vs 16 num_crops in Phi3)
- Uses SiGLIP vision encoder (vs CLIP in Phi3)
- Has per-crop attention masks
Token count formula:
256 + 1 + mask_sum + mask_col0_sum + 16
Note: Phi4 uses 'input_image_embeds' key instead of 'pixel_values'
"""
from transformers import AutoProcessor
processor = AutoProcessor.from_pretrained(
"microsoft/Phi-4-multimodal-instruct", trust_remote_code=True
)
image = Image.open(image_path).convert("RGB")
original_size = image.size
# Process image using the image processor directly
outputs = processor.image_processor(images=image, return_tensors="np")
# Phi4 uses 'input_image_embeds' instead of 'pixel_values'
pixel_values = outputs.get("input_image_embeds")
pixel_attention_mask = outputs.get("image_attention_mask")
image_sizes = outputs.get("image_sizes")
num_img_tokens = outputs.get("num_img_tokens")
result = {
"pixel_values": pixel_values,
"original_size": original_size,
"processor_config": processor.image_processor.to_dict(),
}
if pixel_attention_mask is not None:
result["pixel_attention_mask"] = np.array(pixel_attention_mask)
if image_sizes is not None:
result["image_sizes"] = np.array(image_sizes)
if num_img_tokens is not None:
result["num_img_tokens"] = np.array(num_img_tokens)
# Add debug info
result["config_info"] = {
"dynamic_hd": getattr(processor.image_processor, "dynamic_hd", 36),
"base_resolution": 448,
}
return result
def generate_for_model(model_key: str, image_paths: list, output_dir: str):
"""Generate golden outputs for a specific model."""
print(f"\nGenerating golden outputs for {model_key}...")
@@ -430,6 +491,7 @@ def generate_for_model(model_key: str, image_paths: list, output_dir: str):
"qwen2_vl": generate_golden_qwen2_vl,
"qwen3_vl": generate_golden_qwen3_vl,
"phi3_vision": generate_golden_phi3_vision,
"phi4_vision": generate_golden_phi4_vision,
}.get(model_key)
if generator_fn is None: