diff --git a/src/registry/qwen3_vl.rs b/src/registry/qwen3_vl.rs index c0a76cc..b214eff 100644 --- a/src/registry/qwen3_vl.rs +++ b/src/registry/qwen3_vl.rs @@ -137,7 +137,8 @@ impl ModelProcessorSpec for Qwen3VLVisionSpec { let is_qwen3_5 = id.contains("qwen3.5") || id.contains("qwen3.6") || model_type.is_some_and(|mt| mt == "qwen3_5" || mt == "qwen3_5_moe"); - is_qwen3_vl || is_qwen3_5 + let is_qwen4_exp = model_type.is_some_and(|mt| mt == "qwen4_exp"); + is_qwen3_vl || is_qwen3_5 || is_qwen4_exp } fn placeholder_token(&self, metadata: &ModelMetadata) -> RegistryResult { @@ -503,4 +504,23 @@ mod tests { .expect("should match qwen3.5 alias"); assert_eq!(spec.name(), "qwen3_vl"); } + + #[test] + fn qwen4_exp_matches_alias_via_model_type() { + let tokenizer = TestTokenizer::new(&[("<|image_pad|>", 151655)]); + let config = json!({ + "model_type": "qwen4_exp", + "image_token_id": 151655, + }); + let metadata = ModelMetadata { + model_id: "custom-model", + tokenizer: &tokenizer, + config: &config, + }; + let registry = ModelRegistry::new(); + let spec = registry + .lookup(&metadata) + .expect("should match qwen4_exp alias"); + assert_eq!(spec.name(), "qwen3_vl"); + } } diff --git a/src/vision/processor.rs b/src/vision/processor.rs index 7573e31..ab31522 100644 --- a/src/vision/processor.rs +++ b/src/vision/processor.rs @@ -203,6 +203,7 @@ impl VisionProcessorRegistry { /// - `qwen2.5-vl` -> Qwen2VLProcessor (same preprocessing as Qwen2-VL) /// - `qwen3-vl` -> Qwen3VLProcessor (patch_size=16, [0.5,0.5,0.5] normalization) /// - `qwen3.5` / `qwen3_5` -> Qwen3VLProcessor (Qwen3.5 reuses Qwen3-VL preprocessing) + /// - `qwen4_exp` / `qwen4-exp` -> Qwen3VLProcessor (same vision tower as Qwen3.5) /// - `phi-3-vision` -> Phi3VisionProcessor (HD transform with 336x336 tiles) pub fn with_defaults() -> Self { let mut registry = Self::new(); @@ -272,6 +273,16 @@ impl VisionProcessorRegistry { Box::new(super::processors::Qwen3VLProcessor::new()), ); + // Qwen4-Exp: same vision tower as Qwen3.5 + registry.register( + "qwen4_exp", + Box::new(super::processors::Qwen3VLProcessor::new()), + ); + registry.register( + "qwen4-exp", + Box::new(super::processors::Qwen3VLProcessor::new()), + ); + // Register Qwen2-VL (matches Qwen/Qwen2-VL-*, etc.) registry.register( "qwen2-vl", @@ -443,4 +454,14 @@ mod tests { .expect("phi3 processor by model_type"); assert_eq!(processor.model_name(), "phi3-vision"); } + + #[test] + fn test_registry_find_qwen4_exp_model_type_fallback() { + let registry = VisionProcessorRegistry::with_defaults(); + + let processor = registry + .find("custom-model", Some("qwen4_exp")) + .expect("qwen3 processor by qwen4_exp model_type"); + assert_eq!(processor.model_name(), "qwen3-vl"); + } }