--- a/src/engine/metal/qwen_mtplx/owner.rs +++ b/src/engine/metal/qwen_mtplx/owner.rs @@ -1068,3 +1068,7 @@ #[cfg(test)] #[path = "issue106_product.rs"] mod issue106_product; + +#[cfg(test)] +#[path = "issue107_product.rs"] +mod issue107_product; --- /dev/null +++ b/src/engine/metal/qwen_mtplx/issue107_product.rs @@ -0,0 +1,89 @@ +//! Long multi-image request and cache compatibility through the product owner. +use super::*; +use crate::settings::{ + GenerationPreferences, ReasoningMode, RuntimePreferences, SpeculativePreferences, + effective_settings, +}; +use base64::Engine as _; + +#[test] +#[ignore = "installed model plus vision tower; serial external memory supervisor required"] +fn issue107_vision_product() { + let root = std::path::PathBuf::from(std::env::var_os("DS4_QWEN38_ARTIFACTS").unwrap()); + let image = std::fs::read(std::env::var_os("DS4_QWEN_VISION_IMAGE").unwrap()).unwrap(); + let directory = std::path::PathBuf::from(std::env::var_os("DS4_ISSUE107_DIR").unwrap()); + std::fs::create_dir_all(&directory).unwrap(); + let settings = effective_settings( + crate::model::ModelChoice::Qwen38FlashNext, + &GenerationPreferences { + context_tokens: 8192, + max_generated_tokens: 1, + system_prompt: String::new(), + temperature: Some(0.), + top_p: Some(0.95), + min_p: Some(0.), + seed: Some(1), + reasoning_mode: ReasoningMode::Low, + }, + &RuntimePreferences { + speculative: SpeculativePreferences { + glm_mtp: true, + ..Default::default() + }, + ..Default::default() + }, + root.parent().unwrap(), + ) + .unwrap(); + let marker = crate::engine::vision_data_marker(&format!( + "data:image/png;base64,{}", + base64::engine::general_purpose::STANDARD.encode(&image) + )); + let messages = [ChatTurn { + user: true, + tool: false, + system: false, + skip_previous_eos: false, + reasoning: None, + reasoning_complete: true, + content: format!("Describe these images{marker}{marker}"), + }]; + let mut owner = QwenExecutor::open(&settings.engine).unwrap(); + owner.policy.store_min_suffix = Some(0); + let metrics = Metrics::new(&directory.join("metrics")); + let (out, complete) = owner + .generate( + &messages, + &settings.turn, + &AtomicBool::new(false), + &metrics, + |p, _, _| { + eprintln!("#107 prefill {p}"); + }, + |_, _| {}, + ) + .unwrap(); + assert!(complete); + assert!(out.prompt_tokens > 2048); + assert_eq!(out.cached_tokens, 0); + let saved = owner.live.current.as_ref().unwrap(); + let logits = owner.execution.values_f32(&saved.logits).unwrap(); + std::fs::write( + directory.join("long-final.f32"), + logits + .iter() + .flat_map(|v| v.to_le_bytes()) + .collect::>(), + ) + .unwrap(); + std::fs::write(directory.join("long-input.json"),serde_json::to_vec(&serde_json::json!({"ids":saved.token_ids,"images":owner.live.current_images,"prompt_tokens":out.prompt_tokens})).unwrap()).unwrap(); + owner + .save_checkpoint(&directory.join("long.bin"), [107; 32], &mut |_| {}) + .unwrap(); + println!( + "{}", + serde_json::json!({"case":"vision_long","prompt_tokens":out.prompt_tokens,"cached_tokens":out.cached_tokens, + "tokens":owner.live.current.as_ref().unwrap().token_ids,"text":out.message.content,"reasoning":out.message.reasoning, + "logits_sha256":crate::engine::metal::qwen_mtplx_tests::hex_digest(&logits.iter().flat_map(|v|v.to_le_bytes()).collect::>())}) + ); +}