Compare commits

..

No commits in common. "58e2be795ecbbc96051919b10d9ab8aede8b2c55" and "e17a4dd9d032ecc48c4d1f50804bda660d7179cd" have entirely different histories.

4 changed files with 0 additions and 270 deletions

View File

@ -1,100 +0,0 @@
# Windows Linker Issue - C Runtime Mismatch
## Problem Description
The project currently fails to link on Windows due to a C runtime library mismatch:
```
error LNK2038: discordance détectée pour 'RuntimeLibrary' :
la valeur 'MD_DynamicRelease' ne correspond pas à la valeur 'MT_StaticRelease'
```
## Root Cause
- **ONNX Runtime** (ort crate): Compiled with **dynamic C runtime** (MD_DynamicRelease)
- **esaxx-rs** (dependency of tokenizers crate): Compiled with **static C runtime** (MT_StaticRelease)
These two libraries cannot coexist in the same binary due to incompatible C runtime libraries.
## What Works
✅ Code compiles successfully - all Rust code is correct
✅ NPU detection and ONNX session creation work
✅ Model downloading infrastructure works
✅ Inference logic is properly implemented
❌ Final executable cannot be linked due to C runtime mismatch
## Attempted Solutions
1. **Custom .cargo/config.toml with rustflags** - Failed
- Tried `/NODEFAULTLIB:libcmt.lib /NODEFAULTLIB:libcpmt.lib`
- Tried `/DEFAULTLIB:msvcrt.lib`
- Resulted in missing C runtime symbols
2. **RUSTFLAGS environment variable** - Failed
- Tried `-C target-feature=+crt-static`
- Same runtime mismatch persists
3. **Feature flags to disable inference** - Partial success
- Would require disabling the entire inference module
- Defeats the purpose of the implementation
## Possible Solutions
### Option 1: Wait for upstream fix
- File issue with `tokenizers` or `esaxx-rs` to provide dynamic runtime builds
- Or file issue with `ort` to provide static runtime builds
### Option 2: Use alternative tokenizer
- Implement custom BPE tokenizer without esaxx-rs dependency
- Use `tiktoken-rs` or `rust-tokenizers` (check runtime compatibility)
- Use Python tokenizer via FFI/subprocess
### Option 3: Separate inference service
- Move ONNX inference to separate process
- Communicate via HTTP/IPC
- Avoids mixing incompatible libraries in same binary
### Option 4: Use pre-tokenized inputs
- Tokenize text externally (Python script)
- Load pre-tokenized tensors in Rust
- Bypass tokenizers crate entirely
### Option 5: Different ONNX Runtime backend
- Try `tract` instead of `ort` (pure Rust, no C++ dependencies)
- May lose DirectML/NPU acceleration
## Current Status
**Code Status**: ✅ Complete and correct
**Build Status**: ❌ Blocked by linker
**Commit**: Inference implementation committed (e528b10)
## Implementation Summary
Despite the linker issue, the following was successfully implemented:
- `src/ai/inference.rs`: Complete ONNX inference pipeline
- OnnxClassifier struct with NPU support
- Tokenization (padding/truncation)
- Inference with DirectML acceleration
- Classification with softmax probabilities
- RefCell pattern for session management
- `src/ai/models.rs`: Added distilbert_tokenizer() config
- `src/ai/mod.rs`: Exported OnnxClassifier
All code compiles successfully. Only the final linking step fails.
## Next Steps
1. Research alternative tokenizer libraries with dynamic runtime
2. Consider implementing Option 3 (separate service) for quick resolution
3. Monitor upstream issues for long-term fix
---
📝 Document created: 2025-10-16
🤖 Generated with Claude Code

View File

@ -1,157 +0,0 @@
/// ONNX inference with NPU acceleration
use crate::ai::NpuDevice;
use crate::error::{Result, AppError};
use ndarray::Array2;
use ort::session::Session;
use ort::value::Value;
use tokenizers::Tokenizer;
/// Text classifier using ONNX model with NPU
pub struct OnnxClassifier {
session: std::cell::RefCell<Session>,
tokenizer: Tokenizer,
npu_device: NpuDevice,
max_length: usize,
}
impl OnnxClassifier {
/// Create a new ONNX classifier with NPU acceleration
pub fn new(model_path: &str, tokenizer_path: &str) -> Result<Self> {
let npu_device = NpuDevice::detect();
log::info!("Loading ONNX model: {}", model_path);
log::info!("NPU Device: {} (available: {})", npu_device.device_name(), npu_device.is_available());
// Create ONNX session with NPU if available
let session = npu_device.create_session(model_path)?;
log::info!("Loading tokenizer: {}", tokenizer_path);
let tokenizer = Tokenizer::from_file(tokenizer_path)
.map_err(|e| AppError::Analysis(format!("Failed to load tokenizer: {}", e)))?;
Ok(Self {
session: std::cell::RefCell::new(session),
tokenizer,
npu_device,
max_length: 128,
})
}
/// Check if NPU is being used
pub fn is_using_npu(&self) -> bool {
self.npu_device.is_available()
}
/// Get device information
pub fn device_info(&self) -> String {
self.npu_device.device_name().to_string()
}
/// Tokenize input text
fn tokenize(&self, text: &str) -> Result<(Vec<i64>, Vec<i64>)> {
let encoding = self.tokenizer
.encode(text, true)
.map_err(|e| AppError::Analysis(format!("Tokenization failed: {}", e)))?;
let mut input_ids: Vec<i64> = encoding.get_ids().iter().map(|&x| x as i64).collect();
let mut attention_mask: Vec<i64> = encoding.get_attention_mask().iter().map(|&x| x as i64).collect();
// Pad or truncate to max_length
if input_ids.len() > self.max_length {
input_ids.truncate(self.max_length);
attention_mask.truncate(self.max_length);
} else {
let padding = self.max_length - input_ids.len();
input_ids.extend(vec![0; padding]);
attention_mask.extend(vec![0; padding]);
}
Ok((input_ids, attention_mask))
}
/// Run inference on input text
pub fn predict(&self, text: &str) -> Result<Vec<f32>> {
// Tokenize input
let (input_ids, attention_mask) = self.tokenize(text)?;
// Convert to ndarray (batch_size=1, seq_length=max_length)
let input_ids_array = Array2::from_shape_vec(
(1, self.max_length),
input_ids,
).map_err(|e| AppError::Analysis(format!("Array creation failed: {}", e)))?;
let attention_mask_array = Array2::from_shape_vec(
(1, self.max_length),
attention_mask,
).map_err(|e| AppError::Analysis(format!("Array creation failed: {}", e)))?;
// Create ONNX values
let input_ids_value = Value::from_array(input_ids_array)
.map_err(|e| AppError::Analysis(format!("Failed to create input_ids value: {}", e)))?;
let attention_mask_value = Value::from_array(attention_mask_array)
.map_err(|e| AppError::Analysis(format!("Failed to create attention_mask value: {}", e)))?;
// Run inference
let mut session = self.session.borrow_mut();
let outputs = session
.run(ort::inputs!["input_ids" => input_ids_value, "attention_mask" => attention_mask_value])
.map_err(|e| AppError::Analysis(format!("Inference failed: {}", e)))?;
// Extract logits
let logits = outputs["logits"]
.try_extract_tensor::<f32>()
.map_err(|e| AppError::Analysis(format!("Failed to extract logits: {}", e)))?;
// Convert to Vec<f32>
let (_shape, data) = logits;
let logits_vec: Vec<f32> = data.to_vec();
Ok(logits_vec)
}
/// Classify text and return the predicted class index
pub fn classify(&self, text: &str) -> Result<usize> {
let logits = self.predict(text)?;
// Find the index of the maximum value
let predicted_class = logits
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
.map(|(idx, _)| idx)
.ok_or_else(|| AppError::Analysis("No predictions found".to_string()))?;
Ok(predicted_class)
}
/// Classify text and return probabilities for all classes
pub fn classify_with_probabilities(&self, text: &str) -> Result<Vec<f32>> {
let logits = self.predict(text)?;
// Apply softmax to get probabilities
let max_logit = logits.iter().copied().fold(f32::NEG_INFINITY, f32::max);
let exp_logits: Vec<f32> = logits.iter().map(|&x| (x - max_logit).exp()).collect();
let sum_exp: f32 = exp_logits.iter().sum();
let probabilities: Vec<f32> = exp_logits.iter().map(|&x| x / sum_exp).collect();
Ok(probabilities)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn test_classifier_creation() {
let model_path = "models/distilbert-base.onnx";
let tokenizer_path = "models/distilbert-tokenizer.json";
if Path::new(model_path).exists() && Path::new(tokenizer_path).exists() {
let classifier = OnnxClassifier::new(model_path, tokenizer_path);
assert!(classifier.is_ok());
}
}
}

View File

@ -3,10 +3,8 @@ pub mod classifier;
pub mod npu;
pub mod models;
pub mod vision;
pub mod inference;
pub use classifier::NpuClassifier;
pub use npu::NpuDevice;
pub use models::{AvailableModels, ModelConfig, ModelDownloader};
pub use vision::{ImageAnalyzer, ImageAnalysis};
pub use inference::OnnxClassifier;

View File

@ -41,17 +41,6 @@ impl AvailableModels {
}
}
/// DistilBERT Tokenizer
pub fn distilbert_tokenizer() -> ModelConfig {
ModelConfig {
name: "distilbert-tokenizer".to_string(),
url: "https://huggingface.co/Xenova/distilbert-base-uncased/resolve/main/tokenizer.json".to_string(),
filename: "distilbert-tokenizer.json".to_string(),
size_mb: 1,
description: "DistilBERT Tokenizer - Text preprocessing".to_string(),
}
}
/// MiniLM for lightweight text embeddings (Xenova repo)
pub fn minilm() -> ModelConfig {
ModelConfig {