主要改了 vendor/esaxx-rs/build.rs:只在 crt-static 目标下才启用 static_crt(true),用来修复 Windows 下常见的 MSVC 运行库冲突

This commit is contained in:
273265088@qq.com
2026-03-25 16:05:00 +08:00
parent b8d8732c3d
commit a74b67c93f
60 changed files with 16216 additions and 349 deletions
+229
View File
@@ -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(())
}