Integrate Qwen3.8 model intake
This commit is contained in:
1 parent
4c83ac0360
commit
3773cfda2e
15 files changed
+1805
-35
No files matched your search
+203
-2
@@ -14,6 +14,9 @@ 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);
|
||||
}
|
||||
download_artifact_with_cancel(
|
||||
id.model(),
|
||||
id.artifact(),
|
||||
@@ -29,6 +32,9 @@ 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);
|
||||
}
|
||||
let model = id.model();
|
||||
let artifact = id.artifact();
|
||||
let destination = artifact.path(model, models_path);
|
||||
@@ -67,6 +73,9 @@ pub(crate) fn delete_managed_artifact(
|
||||
id: ManagedArtifactId,
|
||||
models_path: &Path,
|
||||
) -> Result<(), String> {
|
||||
if id == ManagedArtifactId::Qwen38FlashNext {
|
||||
return delete_qwen(models_path);
|
||||
}
|
||||
let model = id.model();
|
||||
let artifact = id.artifact();
|
||||
for path in [
|
||||
@@ -83,6 +92,144 @@ pub(crate) fn delete_managed_artifact(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn download_qwen(
|
||||
models_path: &Path,
|
||||
cancel: &AtomicBool,
|
||||
verified_bytes: &AtomicU64,
|
||||
) -> Result<DownloadOutcome, String> {
|
||||
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() {
|
||||
if cancel.load(Ordering::Relaxed) {
|
||||
return Ok(DownloadOutcome::Stopped);
|
||||
}
|
||||
if qwen::artifact_is_installed(&root, artifact) {
|
||||
verified_bytes.fetch_add(artifact.size, Ordering::Relaxed);
|
||||
continue;
|
||||
}
|
||||
let destination = qwen::path(&root, artifact);
|
||||
let partial = qwen::partial_path(&root, artifact);
|
||||
let source = if destination.exists() {
|
||||
destination.clone()
|
||||
} else {
|
||||
if partial.metadata().map_or(0, |metadata| metadata.len()) > artifact.size {
|
||||
File::create(&partial)
|
||||
.map_err(|error| format!("{}: {error}", partial.display()))?;
|
||||
}
|
||||
if partial.metadata().map_or(0, |metadata| metadata.len()) != artifact.size
|
||||
&& download_url_to_partial(&qwen::url(artifact), &partial, cancel)?
|
||||
== DownloadOutcome::Stopped
|
||||
{
|
||||
return Ok(DownloadOutcome::Stopped);
|
||||
}
|
||||
partial.clone()
|
||||
};
|
||||
match qwen::verify(&source, artifact, cancel, verified_bytes) {
|
||||
Ok(false) => return Ok(DownloadOutcome::Stopped),
|
||||
Ok(true) => {}
|
||||
Err(error) => {
|
||||
let _ = fs::remove_file(qwen::verification_path(&root, artifact));
|
||||
if source == partial {
|
||||
fs::remove_file(&partial).map_err(|remove_error| {
|
||||
format!("{error}; could not remove partial file: {remove_error}")
|
||||
})?;
|
||||
}
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
if source == partial {
|
||||
fs::rename(&partial, &destination)
|
||||
.map_err(|error| format!("{}: {error}", destination.display()))?;
|
||||
}
|
||||
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)?;
|
||||
return Err(error);
|
||||
}
|
||||
Ok(DownloadOutcome::Complete)
|
||||
}
|
||||
|
||||
fn validate_qwen(
|
||||
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() {
|
||||
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() {
|
||||
let destination = qwen::path(&root, artifact);
|
||||
let partial = qwen::partial_path(&root, artifact);
|
||||
let (source, promote) = if destination.exists() {
|
||||
(destination.clone(), false)
|
||||
} else if partial.exists() {
|
||||
(partial.clone(), true)
|
||||
} else {
|
||||
return Err(format!("{} is not downloaded", artifact.path));
|
||||
};
|
||||
match qwen::verify(&source, artifact, cancel, verified_bytes) {
|
||||
Ok(false) => return Ok(DownloadOutcome::Stopped),
|
||||
Ok(true) => {}
|
||||
Err(error) => {
|
||||
let _ = fs::remove_file(qwen::verification_path(&root, artifact));
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
if promote {
|
||||
fs::rename(&partial, &destination)
|
||||
.map_err(|error| format!("{}: {error}", destination.display()))?;
|
||||
}
|
||||
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)?;
|
||||
return Err(error);
|
||||
}
|
||||
Ok(DownloadOutcome::Complete)
|
||||
}
|
||||
|
||||
fn clear_qwen_markers(root: &Path) -> Result<(), String> {
|
||||
for artifact in qwen::artifacts() {
|
||||
let path = qwen::verification_path(root, artifact);
|
||||
match fs::remove_file(&path) {
|
||||
Ok(()) => {}
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
|
||||
Err(error) => return Err(format!("{}: {error}", path.display())),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_qwen(models_path: &Path) -> Result<(), String> {
|
||||
let root = qwen::root(models_path);
|
||||
for artifact in qwen::artifacts() {
|
||||
for path in [
|
||||
qwen::path(&root, artifact),
|
||||
qwen::partial_path(&root, artifact),
|
||||
qwen::verification_path(&root, artifact),
|
||||
] {
|
||||
match fs::remove_file(&path) {
|
||||
Ok(()) => {}
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
|
||||
Err(error) => return Err(format!("{}: {error}", path.display())),
|
||||
}
|
||||
}
|
||||
}
|
||||
match fs::remove_dir(&root) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
|
||||
Err(error) => Err(format!("{}: {error}", root.display())),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn download_artifact(
|
||||
model: ModelChoice,
|
||||
@@ -332,8 +479,12 @@ mod tests {
|
||||
Some(ModelChoice::DeepSeekV4Flash0731)
|
||||
);
|
||||
assert!(ModelChoice::from_id("unknown").is_none());
|
||||
assert_eq!(MODEL_CHOICES.len(), 4);
|
||||
assert_eq!(MANAGED_ARTIFACTS.len(), 6);
|
||||
assert_eq!(MODEL_CHOICES.len(), 5);
|
||||
assert_eq!(MANAGED_ARTIFACTS.len(), 7);
|
||||
assert_eq!(
|
||||
ModelChoice::from_id("qwen3.8-flash-next"),
|
||||
Some(ModelChoice::Qwen38FlashNext)
|
||||
);
|
||||
assert_eq!(ModelChoice::Glm52.main_artifact().size, 211_075_856_448);
|
||||
assert_eq!(
|
||||
ModelChoice::DeepSeekV4Flash0731.main_artifact().size,
|
||||
@@ -342,6 +493,10 @@ mod tests {
|
||||
assert_eq!(ModelChoice::DeepSeekV4Flash0731.artifacts(true).count(), 2);
|
||||
assert_eq!(ModelChoice::Glm52.artifacts(true).count(), 1);
|
||||
assert_eq!(ModelChoice::Glm53Flash.artifacts(true).count(), 2);
|
||||
assert_eq!(
|
||||
engine_artifacts(ModelChoice::Qwen38FlashNext, false, Path::new("/models")).model,
|
||||
Path::new("/models/qwen3.8-flash-next")
|
||||
);
|
||||
|
||||
let id = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
@@ -417,6 +572,52 @@ mod tests {
|
||||
fs::remove_dir_all(models_path).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires DS4_QWEN38_ARTIFACTS to point at the pinned 105 GB source"]
|
||||
fn qwen_set_verifies_reloads_and_removes_as_one_managed_artifact() {
|
||||
let source = std::env::var_os("DS4_QWEN38_ARTIFACTS")
|
||||
.map(std::path::PathBuf::from)
|
||||
.expect("DS4_QWEN38_ARTIFACTS is set");
|
||||
let models = std::env::temp_dir().join(format!(
|
||||
"ds4-qwen-managed-{}-{}",
|
||||
std::process::id(),
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos()
|
||||
));
|
||||
let root = qwen::root(&models);
|
||||
fs::create_dir_all(&root).unwrap();
|
||||
for artifact in qwen::artifacts() {
|
||||
fs::hard_link(source.join(&artifact.path), qwen::path(&root, artifact)).unwrap();
|
||||
}
|
||||
fs::remove_file(qwen::path(
|
||||
&root,
|
||||
qwen::artifacts()
|
||||
.iter()
|
||||
.find(|artifact| artifact.role == "mtp")
|
||||
.unwrap(),
|
||||
))
|
||||
.unwrap();
|
||||
assert!(
|
||||
validate_qwen(&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(),
|
||||
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();
|
||||
assert!(!root.exists());
|
||||
fs::remove_dir_all(models).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verification_reports_bytes_read() {
|
||||
let path = std::env::temp_dir().join(format!(
|
||||
|
||||
Reference in new issue
Block a user