Add Qwen vision loading and Metal inference

This commit is contained in:
Georg Bauer committed 2026-09-11 18:32:41 +02:00
1 parent 48c2f751b4
commit e32954ac1b
30 files changed
+1687 -100

No files matched your search

+48 -24
View File
@@ -14,8 +14,8 @@ pub(crate) fn download_managed_artifact(
cancel: &AtomicBool,
verified_bytes: &AtomicU64,
) -> Result<DownloadOutcome, String> {
if id == ManagedArtifactId::Qwen38FlashNext {
return download_qwen(models_path, cancel, verified_bytes);
if id.is_qwen() {
return download_qwen(id, models_path, cancel, verified_bytes);
}
download_artifact_with_cancel(
id.model(),
@@ -32,8 +32,8 @@ pub(crate) fn validate_managed_artifact(
cancel: &AtomicBool,
verified_bytes: &AtomicU64,
) -> Result<DownloadOutcome, String> {
if id == ManagedArtifactId::Qwen38FlashNext {
return validate_qwen(models_path, cancel, verified_bytes);
if id.is_qwen() {
return validate_qwen(id, models_path, cancel, verified_bytes);
}
let model = id.model();
let artifact = id.artifact();
@@ -73,8 +73,8 @@ pub(crate) fn delete_managed_artifact(
id: ManagedArtifactId,
models_path: &Path,
) -> Result<(), String> {
if id == ManagedArtifactId::Qwen38FlashNext {
return delete_qwen(models_path);
if id.is_qwen() {
return delete_qwen(id, models_path);
}
let model = id.model();
let artifact = id.artifact();
@@ -93,6 +93,7 @@ pub(crate) fn delete_managed_artifact(
}
fn download_qwen(
id: ManagedArtifactId,
models_path: &Path,
cancel: &AtomicBool,
verified_bytes: &AtomicU64,
@@ -100,7 +101,7 @@ fn download_qwen(
let root = qwen::root(models_path);
fs::create_dir_all(&root).map_err(|error| format!("{}: {error}", root.display()))?;
verified_bytes.store(0, Ordering::Relaxed);
for artifact in qwen::artifacts() {
for artifact in qwen::selected(id) {
if cancel.load(Ordering::Relaxed) {
return Ok(DownloadOutcome::Stopped);
}
@@ -145,26 +146,29 @@ fn download_qwen(
fs::write(qwen::verification_path(&root, artifact), &artifact.sha256)
.map_err(|error| error.to_string())?;
}
if let Err(error) = crate::engine::validate_qwen_artifacts(&root) {
clear_qwen_markers(&root)?;
if id == ManagedArtifactId::Qwen38FlashNext
&& let Err(error) = crate::engine::validate_qwen_artifacts(&root)
{
clear_qwen_markers(id, &root)?;
return Err(error);
}
Ok(DownloadOutcome::Complete)
}
fn validate_qwen(
id: ManagedArtifactId,
models_path: &Path,
cancel: &AtomicBool,
verified_bytes: &AtomicU64,
) -> Result<DownloadOutcome, String> {
let root = qwen::root(models_path);
verified_bytes.store(0, Ordering::Relaxed);
for artifact in qwen::artifacts() {
for artifact in qwen::selected(id) {
if !qwen::path(&root, artifact).exists() && !qwen::partial_path(&root, artifact).exists() {
return Err(format!("{} is not downloaded", artifact.path));
}
}
for artifact in qwen::artifacts() {
for artifact in qwen::selected(id) {
let destination = qwen::path(&root, artifact);
let partial = qwen::partial_path(&root, artifact);
let (source, promote) = if destination.exists() {
@@ -189,15 +193,17 @@ fn validate_qwen(
fs::write(qwen::verification_path(&root, artifact), &artifact.sha256)
.map_err(|error| error.to_string())?;
}
if let Err(error) = crate::engine::validate_qwen_artifacts(&root) {
clear_qwen_markers(&root)?;
if id == ManagedArtifactId::Qwen38FlashNext
&& let Err(error) = crate::engine::validate_qwen_artifacts(&root)
{
clear_qwen_markers(id, &root)?;
return Err(error);
}
Ok(DownloadOutcome::Complete)
}
fn clear_qwen_markers(root: &Path) -> Result<(), String> {
for artifact in qwen::artifacts() {
fn clear_qwen_markers(id: ManagedArtifactId, root: &Path) -> Result<(), String> {
for artifact in qwen::selected(id) {
let path = qwen::verification_path(root, artifact);
match fs::remove_file(&path) {
Ok(()) => {}
@@ -208,9 +214,9 @@ fn clear_qwen_markers(root: &Path) -> Result<(), String> {
Ok(())
}
fn delete_qwen(models_path: &Path) -> Result<(), String> {
fn delete_qwen(id: ManagedArtifactId, models_path: &Path) -> Result<(), String> {
let root = qwen::root(models_path);
for artifact in qwen::artifacts() {
for artifact in qwen::selected(id) {
for path in [
qwen::path(&root, artifact),
qwen::partial_path(&root, artifact),
@@ -225,7 +231,14 @@ fn delete_qwen(models_path: &Path) -> Result<(), String> {
}
match fs::remove_dir(&root) {
Ok(()) => Ok(()),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(error)
if matches!(
error.kind(),
std::io::ErrorKind::NotFound | std::io::ErrorKind::DirectoryNotEmpty
) =>
{
Ok(())
}
Err(error) => Err(format!("{}: {error}", root.display())),
}
}
@@ -480,7 +493,7 @@ mod tests {
);
assert!(ModelChoice::from_id("unknown").is_none());
assert_eq!(MODEL_CHOICES.len(), 5);
assert_eq!(MANAGED_ARTIFACTS.len(), 7);
assert_eq!(MANAGED_ARTIFACTS.len(), 8);
assert_eq!(
ModelChoice::from_id("qwen3.8-flash-next"),
Some(ModelChoice::Qwen38FlashNext)
@@ -600,20 +613,31 @@ mod tests {
))
.unwrap();
assert!(
validate_qwen(&models, &AtomicBool::new(false), &AtomicU64::new(0))
.unwrap_err()
.contains("mtp.safetensors is not downloaded")
validate_qwen(
ManagedArtifactId::Qwen38FlashNext,
&models,
&AtomicBool::new(false),
&AtomicU64::new(0)
)
.unwrap_err()
.contains("mtp.safetensors is not downloaded")
);
fs::hard_link(source.join("mtp.safetensors"), root.join("mtp.safetensors")).unwrap();
let verified = AtomicU64::new(0);
assert_eq!(
validate_qwen(&models, &AtomicBool::new(false), &verified).unwrap(),
validate_qwen(
ManagedArtifactId::Qwen38FlashNext,
&models,
&AtomicBool::new(false),
&verified
)
.unwrap(),
DownloadOutcome::Complete
);
assert_eq!(verified.load(Ordering::Relaxed), qwen::total_bytes());
assert!(qwen::is_installed(&root));
assert!(crate::engine::validate_qwen_artifacts(&root).is_ok());
delete_qwen(&models).unwrap();
delete_qwen(ManagedArtifactId::Qwen38FlashNext, &models).unwrap();
assert!(!root.exists());
fs::remove_dir_all(models).unwrap();
}