主要改了 vendor/esaxx-rs/build.rs:只在 crt-static 目标下才启用 static_crt(true),用来修复 Windows 下常见的 MSVC 运行库冲突
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
use std::path::Path;
|
||||
|
||||
use aha::models::{
|
||||
ArtifactKind, LoadSpec, ModelPaths, WhichModel, qwen3_reranker::generate::Qwen3RerankerModel,
|
||||
};
|
||||
use anyhow::{Result, anyhow};
|
||||
|
||||
const QWEN3_RERANKER_ONNX_DIR: &str = r"D:\model_download\Qwen3-Reranker-0.6B-ONNX";
|
||||
const QWEN3_RERANKER_SAFETENSORS_DIR: &str = r"D:\model_download\Qwen3-Reranker-0.6B";
|
||||
const QWEN3_RERANKER_GGUF_DIR: &str = r"D:\model_download\Qwen3-Reranker-0.6B-Q8_0-GGUF";
|
||||
|
||||
fn require_existing_dir(path: &str) -> Result<()> {
|
||||
let dir = Path::new(path);
|
||||
if !dir.exists() {
|
||||
return Err(anyhow!("model dir not found: {}", path));
|
||||
}
|
||||
if !dir.is_dir() {
|
||||
return Err(anyhow!("path is not a directory: {}", path));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn first_file_with_extension(dir: &str, extension: &str) -> Result<String> {
|
||||
require_existing_dir(dir)?;
|
||||
let mut files = std::fs::read_dir(dir)?
|
||||
.flatten()
|
||||
.map(|entry| entry.path())
|
||||
.filter(|path| {
|
||||
path.is_file()
|
||||
&& path
|
||||
.extension()
|
||||
.is_some_and(|ext| ext.eq_ignore_ascii_case(extension))
|
||||
})
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.collect::<Vec<_>>();
|
||||
files.sort();
|
||||
files
|
||||
.into_iter()
|
||||
.next()
|
||||
.ok_or_else(|| anyhow!("no .{} file found in {}", extension, dir))
|
||||
}
|
||||
|
||||
fn top_score_index(scores: &[f32]) -> Option<usize> {
|
||||
scores
|
||||
.iter()
|
||||
.enumerate()
|
||||
.max_by(|lhs, rhs| lhs.1.total_cmp(rhs.1))
|
||||
.map(|(idx, _)| idx)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qwen3_reranker_onnx_init_from_spec_can_rerank() -> Result<()> {
|
||||
if let Err(err) = aha::models::common::onnx::ensure_ort_dylib_path() {
|
||||
println!("skip reranker onnx test: {err}");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Err(err) = require_existing_dir(QWEN3_RERANKER_ONNX_DIR) {
|
||||
println!("skip reranker onnx test: {err}");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let spec = LoadSpec {
|
||||
model: WhichModel::Qwen3Reranker0_6B,
|
||||
artifact: ArtifactKind::Onnx,
|
||||
paths: ModelPaths {
|
||||
onnx_path: Some(QWEN3_RERANKER_ONNX_DIR.to_string()),
|
||||
// Intentionally left None to validate tokenizer auto-fallback from *-ONNX to sibling dir.
|
||||
tokenizer_dir: None,
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
|
||||
let mut model = Qwen3RerankerModel::init_from_spec(&spec, None, None)?;
|
||||
let docs = vec![
|
||||
"Rust async requests are commonly built with reqwest and tokio.".to_string(),
|
||||
"Paris is the capital of France.".to_string(),
|
||||
];
|
||||
let scores = model.rerank("How to make async HTTP calls in Rust?", &docs)?;
|
||||
assert_eq!(scores.len(), docs.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qwen3_reranker_safetensors_init_from_spec_can_rerank() -> Result<()> {
|
||||
if let Err(err) = require_existing_dir(QWEN3_RERANKER_SAFETENSORS_DIR) {
|
||||
println!("skip reranker safetensors test: {err}");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let spec = LoadSpec {
|
||||
model: WhichModel::Qwen3Reranker0_6B,
|
||||
artifact: ArtifactKind::Safetensors,
|
||||
paths: ModelPaths {
|
||||
weight_dir: Some(QWEN3_RERANKER_SAFETENSORS_DIR.to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
|
||||
let mut model = Qwen3RerankerModel::init_from_spec(&spec, None, None)?;
|
||||
let docs = vec![
|
||||
"Rust async requests are commonly built with reqwest and tokio.".to_string(),
|
||||
"Paris is the capital of France.".to_string(),
|
||||
];
|
||||
let scores = model.rerank("How to make async HTTP calls in Rust?", &docs)?;
|
||||
assert_eq!(scores.len(), docs.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qwen3_reranker_load_spec_accepts_gguf() {
|
||||
let spec = LoadSpec {
|
||||
model: WhichModel::Qwen3Reranker0_6B,
|
||||
artifact: ArtifactKind::Gguf,
|
||||
paths: ModelPaths {
|
||||
gguf_path: Some("D:/model_download/Qwen3-Reranker-0.6B-GGUF/model.gguf".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
spec.validate()
|
||||
.expect("qwen3 reranker should accept gguf artifact");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qwen3_reranker_init_from_spec_gguf_can_rerank() -> Result<()> {
|
||||
let gguf_path = match first_file_with_extension(QWEN3_RERANKER_GGUF_DIR, "gguf") {
|
||||
Ok(path) => path,
|
||||
Err(err) => {
|
||||
println!("skip reranker gguf test: {err}");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let spec = LoadSpec {
|
||||
model: WhichModel::Qwen3Reranker0_6B,
|
||||
artifact: ArtifactKind::Gguf,
|
||||
paths: ModelPaths {
|
||||
gguf_path: Some(gguf_path),
|
||||
tokenizer_dir: Some(QWEN3_RERANKER_GGUF_DIR.to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
let mut model = Qwen3RerankerModel::init_from_spec(&spec, None, None)?;
|
||||
let docs = vec![
|
||||
"Rust async requests are commonly built with reqwest and tokio.".to_string(),
|
||||
"Paris is the capital of France.".to_string(),
|
||||
];
|
||||
let scores = model.rerank("How to make async HTTP calls in Rust?", &docs)?;
|
||||
assert_eq!(scores.len(), docs.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qwen3_reranker_top_doc_consistent_across_formats() -> Result<()> {
|
||||
if let Err(err) = require_existing_dir(QWEN3_RERANKER_SAFETENSORS_DIR) {
|
||||
println!("skip reranker consistency test (safetensors): {err}");
|
||||
return Ok(());
|
||||
}
|
||||
if let Err(err) = require_existing_dir(QWEN3_RERANKER_ONNX_DIR) {
|
||||
println!("skip reranker consistency test (onnx): {err}");
|
||||
return Ok(());
|
||||
}
|
||||
if let Err(err) = aha::models::common::onnx::ensure_ort_dylib_path() {
|
||||
println!("skip reranker consistency test (onnxruntime): {err}");
|
||||
return Ok(());
|
||||
}
|
||||
let gguf_path = match first_file_with_extension(QWEN3_RERANKER_GGUF_DIR, "gguf") {
|
||||
Ok(path) => path,
|
||||
Err(err) => {
|
||||
println!("skip reranker consistency test (gguf): {err}");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let query = "How to make async HTTP calls in Rust?";
|
||||
let docs = vec![
|
||||
"Rust async requests are commonly built with reqwest and tokio.".to_string(),
|
||||
"Paris is the capital of France.".to_string(),
|
||||
"Database index tuning can improve SQL query speed.".to_string(),
|
||||
];
|
||||
|
||||
let safetensors_spec = LoadSpec {
|
||||
model: WhichModel::Qwen3Reranker0_6B,
|
||||
artifact: ArtifactKind::Safetensors,
|
||||
paths: ModelPaths {
|
||||
weight_dir: Some(QWEN3_RERANKER_SAFETENSORS_DIR.to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
let onnx_spec = LoadSpec {
|
||||
model: WhichModel::Qwen3Reranker0_6B,
|
||||
artifact: ArtifactKind::Onnx,
|
||||
paths: ModelPaths {
|
||||
onnx_path: Some(QWEN3_RERANKER_ONNX_DIR.to_string()),
|
||||
tokenizer_dir: None,
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
let gguf_spec = LoadSpec {
|
||||
model: WhichModel::Qwen3Reranker0_6B,
|
||||
artifact: ArtifactKind::Gguf,
|
||||
paths: ModelPaths {
|
||||
gguf_path: Some(gguf_path),
|
||||
tokenizer_dir: Some(QWEN3_RERANKER_GGUF_DIR.to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
|
||||
let mut safetensors_model = Qwen3RerankerModel::init_from_spec(&safetensors_spec, None, None)?;
|
||||
let mut onnx_model = Qwen3RerankerModel::init_from_spec(&onnx_spec, None, None)?;
|
||||
let mut gguf_model = Qwen3RerankerModel::init_from_spec(&gguf_spec, None, None)?;
|
||||
|
||||
let safetensors_scores = safetensors_model.rerank(query, &docs)?;
|
||||
let onnx_scores = onnx_model.rerank(query, &docs)?;
|
||||
let gguf_scores = gguf_model.rerank(query, &docs)?;
|
||||
|
||||
let safetensors_top = top_score_index(&safetensors_scores)
|
||||
.ok_or_else(|| anyhow!("safetensors scores are empty"))?;
|
||||
let onnx_top = top_score_index(&onnx_scores).ok_or_else(|| anyhow!("onnx scores are empty"))?;
|
||||
let gguf_top = top_score_index(&gguf_scores).ok_or_else(|| anyhow!("gguf scores are empty"))?;
|
||||
|
||||
assert_eq!(
|
||||
safetensors_top, 0,
|
||||
"expected safetensors top doc to be rust doc"
|
||||
);
|
||||
assert_eq!(onnx_top, 0, "expected onnx top doc to be rust doc");
|
||||
assert_eq!(gguf_top, 0, "expected gguf top doc to be rust doc");
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user