1use crate::error::{Result, TrustformersError};
2use crate::{AutoModel, AutoTokenizer};
3use serde::{Deserialize, Serialize};
4use std::sync::{Arc, Mutex, OnceLock};
5use std::time::Instant;
6use trustformers_core::cache::{CacheConfig, InferenceCache};
7use trustformers_models::GenerativeModel;
8
9struct CpuSampler {
20 system: sysinfo::System,
21 refreshed_at: Instant,
22}
23
24static CPU_SAMPLER: OnceLock<Mutex<CpuSampler>> = OnceLock::new();
25
26pub(crate) fn sampled_cpu_utilization() -> f32 {
37 let cell = CPU_SAMPLER.get_or_init(|| {
38 let mut system = sysinfo::System::new();
39 system.refresh_cpu_usage();
40 Mutex::new(CpuSampler {
41 system,
42 refreshed_at: Instant::now(),
43 })
44 });
45
46 let Ok(mut guard) = cell.lock() else {
47 return 0.0;
50 };
51
52 if guard.refreshed_at.elapsed() >= sysinfo::MINIMUM_CPU_UPDATE_INTERVAL {
53 guard.system.refresh_cpu_usage();
54 guard.refreshed_at = Instant::now();
55 }
56 guard.system.global_cpu_usage()
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61pub enum PipelineInput {
62 Text(String),
64 Tokens(Vec<u32>),
66 BatchText(Vec<String>),
68 BatchTokens(Vec<Vec<u32>>),
70}
71
72pub mod adaptive_batching;
73pub mod adaptive_inference;
74pub mod advanced_caching;
75pub mod advanced_rag;
76pub mod audio_classification;
77pub mod audio_generation;
78pub mod code_generation;
79pub mod composition;
80#[cfg(feature = "async")]
81pub mod conversational;
82pub mod coreml_backend;
83pub mod custom_backend;
84pub mod depth_estimation;
85pub mod document_classification;
86pub mod document_understanding;
87pub mod dynamic_batching;
88pub mod early_exit;
89pub mod ensemble;
90pub mod feature_extraction;
91pub mod fill_mask;
92pub mod image_classification;
93pub mod image_segmentation;
94pub mod image_to_text;
95pub mod jit_compilation;
96pub mod mamba2_pipeline;
97pub mod mask_generation;
98pub mod media;
99pub mod metal_backend;
100pub mod mixture_of_depths;
101pub mod multi_doc_summarization;
102pub mod multimodal;
103pub mod object_detection;
104pub mod onnx_backend;
105pub mod openvino_backend;
106pub mod optical_flow;
107pub mod pose_estimation;
108pub mod question_answering;
109pub mod rag;
110pub mod speculative_decoding;
111pub mod speech_recognition;
112pub mod speech_to_text;
113pub mod streaming;
114pub mod summarization;
115pub mod table_question_answering;
116pub mod tensorrt_backend;
117pub mod text_classification;
118pub mod text_generation;
119pub mod text_to_image;
120pub mod text_to_speech;
121pub mod token_classification;
122pub mod translation;
123pub mod translation_enhanced;
124pub mod video_classification;
125pub mod visual_grounding;
126pub mod visual_question_answering;
127pub mod zero_shot_audio_classification;
128
129pub use adaptive_batching::{
130 AdaptiveBatchConfig, AdaptiveBatchOptimizer, BatchComparison, BatchSizeStats,
131 PerformanceReport, PerformanceSample,
132};
133pub use adaptive_inference::{
134 create_adaptive_inference_pipeline, create_balanced_adaptive_pipeline,
135 create_energy_efficient_pipeline, create_latency_optimized_pipeline,
136 create_memory_efficient_pipeline, AdaptationDecision, AdaptiveInferenceConfig,
137 AdaptiveInferenceEngine, AdaptiveInferenceResult, ConditionalStrategy, InputAnalysis,
138 LayerAnalysis, PerformanceMetrics, PrecisionMode, ResourceAllocation, ResourceStrategy,
139};
140pub use advanced_caching::{
141 AccessPattern, AdvancedCacheConfig, AdvancedLRUCache, CacheEntry, CachePriority, CacheStats,
142 PipelineCacheKeyBuilder,
143};
144pub use composition::{
145 compose_pipelines, ComposedPipeline, OutputConverter, PipelineChain, PipelineComposer,
146 TextConverter,
147};
148#[cfg(feature = "async")]
149pub use conversational::{
150 ConversationMode, ConversationRole, ConversationState, ConversationStats, ConversationTurn,
151 ConversationalConfig, ConversationalInput, ConversationalOutput, ConversationalPipeline,
152 GenerationStats, PersonaConfig, SafetyFilter,
153};
154pub use custom_backend::{
155 create_backend, create_custom_backend_pipeline, create_custom_text_classification_pipeline,
156 create_custom_text_generation_pipeline, get_backend, list_available_backends,
157 list_available_factories, register_backend_factory, BackendCapabilities, BackendConfig,
158 BackendFactory, BackendHealth, BackendMetrics, BackendModel, BackendRegistry, BackendTensor,
159 CustomBackend, CustomBackendPipeline, DataType, FactoryInfo, HealthStatus, MemoryConfig,
160 MemoryLayout, MemoryStats, MemoryUsage, ModelMetadata, ModelPerformanceStats,
161 OptimizationLevel, PerformanceConfig, PerformanceIndicators, QuantizationMode,
162 TensorConstraints, TensorSpec, GLOBAL_BACKEND_REGISTRY,
163};
164pub use document_understanding::{
165 BoundingBox as DocumentBoundingBox,
166 DocumentEntity,
167 DocumentMetadata,
168 DocumentUnderstandingConfig,
169 DocumentUnderstandingInput,
170 DocumentUnderstandingOutput,
171 DocumentUnderstandingPipeline,
172 KeyValuePair,
173 OCRResult,
174 Table,
175 TextBlock,
176 TextBlockType, };
178pub use dynamic_batching::{
179 BatchRequest,
180 BatchingStats,
181 DynamicBatchPipeline,
182 DynamicBatcher,
183 DynamicBatchingConfig,
184 RequestPriority,
186};
187pub use early_exit::{
188 create_adaptive_early_exit, create_budget_constrained_early_exit,
189 create_confidence_based_early_exit, create_early_exit_pipeline, EarlyExitConfig,
190 EarlyExitPipeline, EarlyExitPredictor, EarlyExitResult, ExitPoint, ExitStrategy, LayerOutput,
191};
192pub use ensemble::{
193 create_adaptive_voting_ensemble, create_cascade_ensemble, create_classification_ensemble,
194 create_consensus_ensemble, create_dynamic_ensemble, create_dynamic_routing_ensemble,
195 create_efficient_ensemble, create_ensemble_pipeline, create_generation_ensemble,
196 create_high_performance_ensemble, create_qa_ensemble, create_quality_latency_ensemble,
197 create_resource_aware_ensemble, create_uncertainty_ensemble, CascadeResult, EnsembleConfig,
198 EnsembleModel, EnsemblePipeline, EnsemblePrediction, EnsembleStrategy, InputCharacteristics,
199 ModelSelectionInfo, ModelSelectionStrategy, ModelWeight,
200};
201pub use fill_mask::FillMaskPipeline;
202#[cfg(feature = "vision")]
203pub use image_to_text::{ImageToTextInput, ImageToTextOutput, ImageToTextPipeline};
204pub use jit_compilation::{
205 AnomalyDetector, AnomalySeverity, AnomalyType, CompilationPriority, CompilationStrategy,
206 CompilationThresholds, CompiledPipeline, DataLayout, ExecutionPercentiles, ExecutionStats,
207 OptimizationHints, OptimizationType, PerformanceAnomaly,
208 PerformanceSample as JitPerformanceSample, PerformanceTracker, PerformanceTrend,
209 PipelineJitCompiler, PipelineJitConfig, PipelinePerformanceMetrics, TargetHardware,
210 ThermalMetrics, TrendDirection,
211};
212pub use mamba2_pipeline::{
213 create_high_performance_mamba2_pipeline, create_memory_efficient_mamba2_pipeline,
214 create_ultra_long_sequence_mamba2_pipeline, ChunkingStrategy, HardwareStrategy, Mamba2Config,
215 Mamba2Output, Mamba2PerformanceMetrics, Mamba2Pipeline, MemoryOptimization,
216};
217pub use multimodal::{
218 multimodal_pipeline, AttentionConfig, AttentionWeights, ClassificationResult,
219 FusionStrategy as MultiModalFusionStrategy, ModalityFeatures, MultiModalConfig,
220 MultiModalInput, MultiModalOutput, MultiModalPipeline, ProcessingMetadata,
221};
222pub use onnx_backend::{
223 onnx_text_classification_pipeline, onnx_text_generation_pipeline, ONNXBackendConfig,
224 ONNXBasePipeline, ONNXModel, ONNXPipelineManager, ONNXPipelineOptions,
225 ONNXTextClassificationPipeline, ONNXTextGenerationPipeline, ONNXTokenizer,
226};
227pub use openvino_backend::{
228 openvino_text_classification_pipeline, openvino_text_generation_pipeline, ExecutionPriority,
229 OpenVINOBackendConfig, OpenVINOBasePipeline, OpenVINOModel, OpenVINOPipelineManager,
230 OpenVINOPipelineOptions, OpenVINOTextClassificationPipeline, OpenVINOTextGenerationPipeline,
231 OpenVINOTokenizer, PerformanceHint,
232};
233pub use tensorrt_backend::{
234 tensorrt_text_classification_pipeline, tensorrt_text_generation_pipeline,
235 TensorRTBackendConfig, TensorRTBasePipeline, TensorRTModel, TensorRTPipelineManager,
236 TensorRTPipelineOptions, TensorRTTextClassificationPipeline, TensorRTTextGenerationPipeline,
237 TensorRTTokenizer,
238};
239
240pub use code_generation::{
244 CodeGenerationConfig, CodeGenerationError, CodeGenerationInput, CodeGenerationOutput,
245 CodeGenerationPipeline, ExtractionInfo, IndentStyle, StopReason,
246};
247pub use mask_generation::{
248 BoxPrompt, GeneratedMask, MaskGenerationError, MaskGenerationPipeline, MaskGenerationResult,
249 MaskPrompt, PointLabel, PointPrompt,
250};
251pub use multi_doc_summarization::{
252 CitationFormat, DocumentMetadata as MultiDocDocumentMetadata, ExtractedKeyPoint, InputDocument,
253 KeyPointCategory, MultiDocConfig, MultiDocSummarizationPipeline, MultiDocSummaryOutput,
254 SummarizationError, SummarizationStrategy,
255};
256pub use optical_flow::{FlowError, FlowField, FlowPyramid, FlowVector, OpticalFlowPipeline};
257pub use pose_estimation::{
258 coco_skeleton, CocoKeypoint, Keypoint, PersonPose, PoseEstimationError, PoseEstimationPipeline,
259 PoseEstimationResult, SkeletonEdge,
260};
261pub use question_answering::QuestionAnsweringPipeline;
262pub use rag::{
263 Bm25Retriever, Document, DocumentChunk, RagConfig, RagError, RagPipeline, RagResult,
264 RetrievalResult, RetrievalStrategy, TfIdfRetriever,
265};
266#[cfg(feature = "audio")]
267pub use speech_to_text::{
268 AudioInput, SpeechTask, SpeechToTextConfig, SpeechToTextOutput, SpeechToTextPipeline,
269};
270pub use streaming::{
271 AdaptiveBatcher, AggregatorConfig, BackpressureController, PartialResult,
272 PartialResultAggregator, PriorityItem, RealTimeConfig, RealTimeProcessor, RealTimeStats,
273 StreamConfig, StreamProcessor, StreamResult, StreamResultStream, StreamStats,
274 StreamTransformer, StreamingPipeline,
275};
276pub use summarization::SummarizationPipeline;
277pub use text_classification::TextClassificationPipeline;
278pub use text_generation::TextGenerationPipeline;
279pub use text_to_speech::{
280 AudioFormat, EmphasisInfo, EmphasisType, PauseInfo, PauseType, PhonemeTimings, ProsodyInfo,
281 ProsodyMarker, ProsodyType, TextToSpeechConfig, TextToSpeechInput, TextToSpeechOutput,
282 TextToSpeechPipeline,
283};
284pub use token_classification::TokenClassificationPipeline;
285pub use translation::TranslationPipeline;
286pub use translation_enhanced::{
287 DetectionResult, EnhancedTranslationPipeline, Formality, Language, LanguageDetector, Script,
288 TranslationError, TranslationRequest, TranslationResult,
289};
290#[cfg(feature = "vision")]
291pub use visual_question_answering::{
292 AnswerCandidate, AnswerGenerationStrategy, AttentionVisualization, BoundingBox, DetectedObject,
293 FusionStrategy, ImageFeatures, ImageInput, ReasoningStep, ReasoningStepType,
294 VisualQuestionAnsweringConfig, VisualQuestionAnsweringInput, VisualQuestionAnsweringOutput,
295 VisualQuestionAnsweringPipeline,
296};
297
298pub trait Pipeline: Send + Sync {
300 type Input;
301 type Output;
302
303 fn __call__(&self, inputs: Self::Input) -> Result<Self::Output>;
305
306 fn batch(&self, inputs: Vec<Self::Input>) -> Result<Vec<Self::Output>> {
308 inputs.into_iter().map(|input| self.__call__(input)).collect()
309 }
310
311 fn adaptive_batch(
313 &self,
314 inputs: Vec<Self::Input>,
315 config: Option<DynamicBatchingConfig>,
316 ) -> Result<Vec<Self::Output>>
317 where
318 Self::Input: Clone,
319 {
320 let config = config.unwrap_or_default();
321
322 let optimal_batch_size = std::cmp::min(inputs.len(), config.max_batch_size);
325
326 if inputs.len() <= optimal_batch_size {
327 self.batch(inputs)
328 } else {
329 let mut results = Vec::with_capacity(inputs.len());
331 for chunk in inputs.chunks(optimal_batch_size) {
332 let chunk_results = self.batch(chunk.to_vec())?;
333 results.extend(chunk_results);
334 }
335 Ok(results)
336 }
337 }
338
339 fn create_stream_processor(
341 &self,
342 config: StreamConfig,
343 ) -> StreamProcessor<Self::Input, Self::Output, String>
344 where
345 Self: Clone + 'static,
346 Self::Input: Send + Sync + 'static,
347 Self::Output: Send + Sync + 'static,
348 {
349 StreamProcessor::<Self::Input, Self::Output, String>::new_from_pipeline(
352 self.clone(),
353 config,
354 )
355 }
356
357 fn create_realtime_processor(
359 &self,
360 _config: RealTimeConfig,
361 ) -> Result<RealTimeProcessor<Self::Input, Self::Output, String>>
362 where
363 Self: Clone + 'static,
364 Self::Input: Send + Sync + 'static,
365 Self::Output: Send + Sync + 'static,
366 {
367 Err(TrustformersError::feature_unavailable(
374 "Real-time processor not implemented for this pipeline".to_string(),
375 "real_time_processing".to_string(),
376 ))
377 }
378}
379
380#[cfg(feature = "async")]
382#[async_trait::async_trait]
383pub trait AsyncPipeline: Send + Sync {
384 type Input: Send + Sync;
385 type Output: Send;
386
387 async fn __call_async__(&self, input: Self::Input) -> Result<Self::Output>;
389
390 async fn batch_async(&self, inputs: Vec<Self::Input>) -> Result<Vec<Self::Output>> {
392 use futures::future::join_all;
393
394 let futures = inputs.into_iter().map(|input| self.__call_async__(input));
395
396 let results = join_all(futures).await;
397 results.into_iter().collect()
398 }
399
400 async fn adaptive_batch_async(
402 &self,
403 inputs: Vec<Self::Input>,
404 config: Option<DynamicBatchingConfig>,
405 ) -> Result<Vec<Self::Output>>
406 where
407 Self::Input: Clone,
408 {
409 let config = config.unwrap_or_default();
410 let optimal_batch_size = std::cmp::min(inputs.len(), config.max_batch_size);
411
412 if inputs.len() <= optimal_batch_size {
413 self.batch_async(inputs).await
414 } else {
415 let mut results = Vec::with_capacity(inputs.len());
417 for chunk in inputs.chunks(optimal_batch_size) {
418 let chunk_results = self.batch_async(chunk.to_vec()).await?;
419 results.extend(chunk_results);
420 }
421 Ok(results)
422 }
423 }
424
425 fn create_async_batcher(&self, config: DynamicBatchingConfig) -> DynamicBatcher<Self::Input>
427 where
428 Self::Input: Clone + Send + Sync + 'static,
429 {
430 DynamicBatcher::new(config)
431 }
432}
433
434#[derive(Clone, Debug)]
436pub struct PipelineOptions {
437 pub model: Option<String>,
438 pub tokenizer: Option<String>,
439 pub device: Option<Device>,
440 pub batch_size: Option<usize>,
441 pub max_length: Option<usize>,
442 pub truncation: bool,
443 pub padding: PaddingStrategy,
444 pub num_threads: Option<usize>,
445 pub cache_config: Option<CacheConfig>,
446 pub backend: Option<Backend>,
447 pub onnx_config: Option<ONNXBackendConfig>,
448 pub tensorrt_config: Option<TensorRTBackendConfig>,
449 pub streaming: bool,
450}
451
452#[derive(Clone, Debug)]
454pub enum Backend {
455 Native,
457 ONNX { model_path: std::path::PathBuf },
459 TensorRT { model_path: std::path::PathBuf },
461}
462
463impl Default for PipelineOptions {
464 fn default() -> Self {
465 Self {
466 model: None,
467 tokenizer: None,
468 device: None,
469 batch_size: Some(1),
470 max_length: Some(512),
471 truncation: true,
472 padding: PaddingStrategy::Longest,
473 num_threads: None,
474 cache_config: Some(CacheConfig::default()),
475 backend: Some(Backend::Native),
476 onnx_config: None,
477 tensorrt_config: None,
478 streaming: false,
479 }
480 }
481}
482
483#[derive(Clone, Debug)]
485pub enum PaddingStrategy {
486 None,
488 Longest,
490 MaxLength(usize),
492}
493
494#[derive(Clone, Debug, Serialize, Deserialize)]
496pub enum Device {
497 Cpu,
498 Gpu(usize),
499}
500
501pub fn pipeline(
503 task: &str,
504 model: Option<&str>,
505 options: Option<PipelineOptions>,
506) -> Result<Box<dyn Pipeline<Input = String, Output = PipelineOutput>>> {
507 let opts = options.unwrap_or_default();
508 let model_name = model.or(opts.model.as_deref());
509
510 match task {
511 "sentiment-analysis" | "text-classification" => {
512 let model_name = model_name.unwrap_or("bert-base-uncased");
513 let model = AutoModel::from_pretrained(model_name)?;
514 let tokenizer = AutoTokenizer::from_pretrained(model_name)?;
515
516 Ok(Box::new(TextClassificationPipeline::new(model, tokenizer)?))
517 },
518 "text-generation" => {
519 let model_name = model_name.unwrap_or("gpt2");
520 let model = AutoModel::from_pretrained(model_name)?;
521 let tokenizer = AutoTokenizer::from_pretrained(model_name)?;
522
523 Ok(Box::new(TextGenerationPipeline::new(model, tokenizer)?))
524 },
525 "ner" | "token-classification" => {
526 let model_name = model_name.unwrap_or("bert-base-cased");
527 let model = AutoModel::from_pretrained(model_name)?;
528 let tokenizer = AutoTokenizer::from_pretrained(model_name)?;
529
530 Ok(Box::new(TokenClassificationPipeline::new(
531 model, tokenizer,
532 )?))
533 },
534 "question-answering" => {
535 let model_name = model_name.unwrap_or("bert-base-cased");
536 let model = AutoModel::from_pretrained(model_name)?;
537 let tokenizer = AutoTokenizer::from_pretrained(model_name)?;
538
539 Ok(Box::new(QuestionAnsweringPipeline::new(model, tokenizer)?))
540 },
541 "fill-mask" => {
542 let model_name = model_name.unwrap_or("bert-base-uncased");
543 let model = AutoModel::from_pretrained(model_name)?;
544 let tokenizer = AutoTokenizer::from_pretrained(model_name)?;
545
546 Ok(Box::new(FillMaskPipeline::new(model, tokenizer)?))
547 },
548 "summarization" => {
549 let model_name = model_name.unwrap_or("t5-small");
550 let model = AutoModel::from_pretrained(model_name)?;
551 let tokenizer = AutoTokenizer::from_pretrained(model_name)?;
552
553 Ok(Box::new(SummarizationPipeline::new(model, tokenizer)?))
554 },
555 "translation" => {
556 let model_name = model_name.unwrap_or("t5-small");
557 let model = AutoModel::from_pretrained(model_name)?;
558 let tokenizer = AutoTokenizer::from_pretrained(model_name)?;
559
560 Ok(Box::new(TranslationPipeline::new(model, tokenizer)?))
561 },
562 #[cfg(feature = "vision")]
579 "visual-question-answering" | "vqa" => {
580 let model_name = model_name.unwrap_or("dandelin/vilt-b32-finetuned-vqa");
581 let model = AutoModel::from_pretrained(model_name)?;
582 let tokenizer = AutoTokenizer::from_pretrained(model_name)?;
583
584 let pipeline = VisualQuestionAnsweringPipeline::new(model, tokenizer)?;
585 Ok(Box::new(VisualQuestionAnsweringPipelineWrapper(pipeline)))
586 },
587 "document-understanding" | "document-ai" => {
588 let model_name = model_name.unwrap_or("microsoft/layoutlmv3-base");
589 let model = AutoModel::from_pretrained(model_name)?;
590 let tokenizer = AutoTokenizer::from_pretrained(model_name)?;
591
592 let pipeline = DocumentUnderstandingPipeline::new(model, tokenizer)?;
593 Ok(Box::new(DocumentUnderstandingPipelineWrapper(pipeline)))
594 },
595 "multimodal" | "multi-modal" => {
596 let model_name = model_name.unwrap_or("openai/clip-vit-base-patch32");
597 let model = AutoModel::from_pretrained(model_name)?;
598 let tokenizer = AutoTokenizer::from_pretrained(model_name)?;
599
600 let pipeline = MultiModalPipeline::new(model, tokenizer)?;
601 Ok(Box::new(MultiModalPipelineWrapper(pipeline)))
602 },
603#[cfg(feature = "async")]
604 "conversational" | "chat" | "dialogue" => {
605 let model_name = model_name.unwrap_or("microsoft/DialoGPT-medium");
606 let model = AutoModel::from_pretrained(model_name)?;
607 let tokenizer = AutoTokenizer::from_pretrained(model_name)?;
608
609 let pipeline = ConversationalPipeline::new(model, tokenizer)?;
610 Ok(Box::new(ConversationalPipelineWrapper(pipeline)))
611 },
612 "adaptive-inference" | "adaptive" => {
613 let model_name = model_name.unwrap_or("bert-base-uncased");
614 let model = AutoModel::from_pretrained(model_name)?;
615 let tokenizer = AutoTokenizer::from_pretrained(model_name)?;
616
617 let base_pipeline = TextClassificationPipeline::new(model, tokenizer)?;
619
620 let adaptive_config = AdaptiveInferenceConfig::default();
622 let adaptive_pipeline = create_adaptive_inference_pipeline(base_pipeline, adaptive_config);
623
624 Ok(Box::new(AdaptivePipelineWrapper::new(adaptive_pipeline)))
625 },
626 _ => Err(TrustformersError::invalid_input_simple(format!(
627 "Unknown pipeline task: {}. For image-to-text pipelines, use image_to_text_pipeline() function instead. For speech-to-text pipelines, use speech_to_text_pipeline() function instead. For text-to-speech pipelines, use text_to_speech_pipeline() function instead. For visual question answering pipelines, use visual_question_answering_pipeline() function instead. For document understanding pipelines, use document_understanding_pipeline() function instead. For multimodal pipelines, use multimodal_pipeline() function instead. For conversational pipelines, use conversational_pipeline() function instead.",
628 task
629 ))),
630 }
631}
632
633pub fn enhanced_pipeline(
635 task: &str,
636 model: Option<&str>,
637 options: Option<PipelineOptions>,
638) -> Result<Box<dyn Pipeline<Input = String, Output = PipelineOutput>>> {
639 let opts = options.unwrap_or_default();
640
641 match opts.backend.as_ref().unwrap_or(&Backend::Native) {
642 Backend::Native => {
643 pipeline(task, model, Some(opts))
645 },
646 Backend::ONNX { model_path } => create_onnx_pipeline(task, model_path, &opts),
647 Backend::TensorRT { model_path } => create_tensorrt_pipeline(task, model_path, &opts),
648 }
649}
650
651fn create_onnx_pipeline(
653 task: &str,
654 model_path: &std::path::Path,
655 opts: &PipelineOptions,
656) -> Result<Box<dyn Pipeline<Input = String, Output = PipelineOutput>>> {
657 use crate::AutoTokenizer;
658
659 let tokenizer_name = opts
661 .tokenizer
662 .as_deref()
663 .or(opts.model.as_deref())
664 .unwrap_or("bert-base-uncased");
665 let tokenizer = AutoTokenizer::from_pretrained(tokenizer_name)?;
666
667 let onnx_config = if let Some(config) = &opts.onnx_config {
669 config.clone()
670 } else {
671 match opts.device.as_ref().unwrap_or(&Device::Cpu) {
672 Device::Cpu => ONNXBackendConfig::cpu_optimized(model_path.to_path_buf()),
673 Device::Gpu(device_id) => {
674 ONNXBackendConfig::gpu_optimized(model_path.to_path_buf(), Some(*device_id as i32))
675 },
676 }
677 };
678
679 match task {
680 "sentiment-analysis" | "text-classification" => {
681 let pipeline =
682 onnx_text_classification_pipeline(model_path, tokenizer, Some(onnx_config))?;
683 Ok(Box::new(OnnxPipelineWrapper::Classification(pipeline)))
684 },
685 "text-generation" => {
686 let mut pipeline =
687 onnx_text_generation_pipeline(model_path, tokenizer, Some(onnx_config))?;
688
689 if let Some(max_length) = opts.max_length {
690 pipeline = pipeline.with_max_new_tokens(max_length);
691 }
692
693 Ok(Box::new(OnnxPipelineWrapper::Generation(pipeline)))
694 },
695 _ => Err(TrustformersError::invalid_input_simple(format!(
696 "ONNX backend not yet implemented for task: {}",
697 task
698 ))),
699 }
700}
701
702fn create_tensorrt_pipeline(
704 task: &str,
705 model_path: &std::path::Path,
706 opts: &PipelineOptions,
707) -> Result<Box<dyn Pipeline<Input = String, Output = PipelineOutput>>> {
708 use crate::AutoTokenizer;
709
710 let tokenizer_name = opts
712 .tokenizer
713 .as_deref()
714 .or(opts.model.as_deref())
715 .unwrap_or("bert-base-uncased");
716 let tokenizer = AutoTokenizer::from_pretrained(tokenizer_name)?;
717
718 let tensorrt_config = if let Some(config) = &opts.tensorrt_config {
720 config.clone()
721 } else {
722 match opts.device.as_ref().unwrap_or(&Device::Cpu) {
723 Device::Cpu => {
724 TensorRTBackendConfig::latency_optimized(model_path.to_path_buf())
726 },
727 Device::Gpu(device_id) => {
728 let mut config = TensorRTBackendConfig::latency_optimized(model_path.to_path_buf());
729 config.device_id = *device_id as i32;
730 config
731 },
732 }
733 };
734
735 match task {
736 "sentiment-analysis" | "text-classification" => {
737 let pipeline = tensorrt_text_classification_pipeline(
738 model_path,
739 tokenizer,
740 Some(tensorrt_config),
741 )?;
742 Ok(Box::new(TensorRTPipelineWrapper::Classification(pipeline)))
743 },
744 "text-generation" => {
745 let mut pipeline =
746 tensorrt_text_generation_pipeline(model_path, tokenizer, Some(tensorrt_config))?;
747
748 if let Some(max_length) = opts.max_length {
749 pipeline = pipeline.with_max_new_tokens(max_length);
750 }
751
752 Ok(Box::new(TensorRTPipelineWrapper::Generation(pipeline)))
753 },
754 _ => Err(TrustformersError::invalid_input_simple(format!(
755 "TensorRT backend not yet implemented for task: {}",
756 task
757 ))),
758 }
759}
760
761enum OnnxPipelineWrapper<T: crate::core::traits::Tokenizer + Clone> {
763 Classification(ONNXTextClassificationPipeline<T>),
764 Generation(ONNXTextGenerationPipeline<T>),
765}
766
767impl<T: crate::core::traits::Tokenizer + Clone> Pipeline for OnnxPipelineWrapper<T> {
768 type Input = String;
769 type Output = PipelineOutput;
770
771 fn __call__(&self, input: Self::Input) -> Result<Self::Output> {
772 match self {
773 OnnxPipelineWrapper::Classification(pipeline) => pipeline.__call__(input),
774 OnnxPipelineWrapper::Generation(pipeline) => pipeline.__call__(input),
775 }
776 }
777}
778
779enum TensorRTPipelineWrapper<T: crate::core::traits::Tokenizer + Clone> {
781 Classification(TensorRTTextClassificationPipeline<T>),
782 Generation(TensorRTTextGenerationPipeline<T>),
783}
784
785impl<T: crate::core::traits::Tokenizer + Clone> Pipeline for TensorRTPipelineWrapper<T> {
786 type Input = String;
787 type Output = PipelineOutput;
788
789 fn __call__(&self, input: Self::Input) -> Result<Self::Output> {
790 match self {
791 TensorRTPipelineWrapper::Classification(pipeline) => pipeline.__call__(input),
792 TensorRTPipelineWrapper::Generation(pipeline) => pipeline.__call__(input),
793 }
794 }
795}
796
797#[allow(
808 dead_code,
809 reason = "field exists only to satisfy the wrapper's constructor; __call__ \
810 always errors before it could be read (Input=String cannot become DocumentUnderstandingInput)"
811)]
812pub struct DocumentUnderstandingPipelineWrapper<M, T>(DocumentUnderstandingPipeline<M, T>);
813
814impl<M, T> Pipeline for DocumentUnderstandingPipelineWrapper<M, T>
815where
816 M: crate::core::traits::Model + Clone,
817 T: crate::core::traits::Tokenizer + Clone,
818{
819 type Input = String;
820 type Output = PipelineOutput;
821
822 fn __call__(&self, _input: Self::Input) -> Result<Self::Output> {
823 Err(TrustformersError::invalid_input_simple(
831 "DocumentUnderstanding pipeline requires DocumentUnderstandingInput with image data, not string input".to_string()
832 ))
833 }
834}
835
836#[cfg(feature = "vision")]
847pub struct VisualQuestionAnsweringPipelineWrapper<M, T>(VisualQuestionAnsweringPipeline<M, T>)
848where
849 M: crate::core::traits::Model + Clone + Send + Sync + 'static,
850 T: crate::core::traits::Tokenizer + Clone + Send + Sync + 'static;
851
852#[cfg(feature = "vision")]
853impl<M, T> std::ops::Deref for VisualQuestionAnsweringPipelineWrapper<M, T>
854where
855 M: crate::core::traits::Model + Clone + Send + Sync + 'static,
856 T: crate::core::traits::Tokenizer + Clone + Send + Sync + 'static,
857{
858 type Target = VisualQuestionAnsweringPipeline<M, T>;
859
860 fn deref(&self) -> &Self::Target {
861 &self.0
862 }
863}
864
865#[cfg(feature = "vision")]
866impl<M, T> Pipeline for VisualQuestionAnsweringPipelineWrapper<M, T>
867where
868 M: crate::core::traits::Model + Clone + Send + Sync + 'static,
869 T: crate::core::traits::Tokenizer + Clone + Send + Sync + 'static,
870{
871 type Input = String;
872 type Output = PipelineOutput;
873
874 fn __call__(&self, _input: Self::Input) -> Result<Self::Output> {
878 Err(TrustformersError::invalid_input_simple(
879 "VisualQuestionAnswering pipeline requires VisualQuestionAnsweringInput with image and question data, not string input".to_string()
880 ))
881 }
882}
883
884pub struct MultiModalPipelineWrapper<M, T>(MultiModalPipeline<M, T>);
886
887impl<M, T> Pipeline for MultiModalPipelineWrapper<M, T>
888where
889 M: crate::core::traits::Model + Clone + 'static,
890 T: crate::core::traits::Tokenizer + Clone + 'static,
891{
892 type Input = String;
893 type Output = PipelineOutput;
894
895 fn __call__(&self, input: Self::Input) -> Result<Self::Output> {
896 use std::collections::HashMap;
897 let output = self.0.__call__(MultiModalInput {
898 text: Some(input),
899 image: None,
900 audio: None,
901 video: None,
902 metadata: HashMap::new(),
903 modality_weights: None,
904 })?;
905 Ok(PipelineOutput::MultiModal(output))
906 }
907}
908
909#[cfg(feature = "async")]
910pub struct ConversationalPipelineWrapper<M, T>(ConversationalPipeline<M, T>);
912
913#[cfg(feature = "async")]
914impl<M, T> Pipeline for ConversationalPipelineWrapper<M, T>
915where
916 M: crate::core::traits::Model + GenerativeModel + Clone + Send + Sync,
917 T: crate::core::traits::Tokenizer + Clone,
918{
919 type Input = String;
920 type Output = PipelineOutput;
921
922 fn __call__(&self, input: Self::Input) -> Result<Self::Output> {
923 let output = self.0.__call__(ConversationalInput {
924 message: input,
925 conversation_id: None,
926 context: None,
927 config_override: None,
928 })?;
929 Ok(PipelineOutput::Conversational(output))
930 }
931}
932
933pub struct AdaptivePipelineWrapper<P> {
935 engine: AdaptiveInferenceEngine<P>,
936}
937
938impl<P> AdaptivePipelineWrapper<P> {
939 pub fn new(engine: AdaptiveInferenceEngine<P>) -> Self {
940 Self { engine }
941 }
942}
943
944impl<P> Pipeline for AdaptivePipelineWrapper<P>
945where
946 P: Pipeline<Output = PipelineOutput> + Clone,
947 P::Input: Clone + 'static,
953{
954 type Input = P::Input;
955 type Output = PipelineOutput;
956
957 fn __call__(&self, input: Self::Input) -> Result<Self::Output> {
958 let mut engine = self.engine.clone();
965 let result = engine.adaptive_inference(input)?;
966 Ok(result.prediction)
967 }
968}
969
970#[cfg(feature = "vision")]
971pub fn image_to_text_pipeline(
973 model: Option<&str>,
974 options: Option<PipelineOptions>,
975) -> Result<ImageToTextPipeline> {
976 let opts = options.unwrap_or_default();
977 let model_name = model.or(opts.model.as_deref()).unwrap_or("blip-image-captioning-base");
978
979 let model = AutoModel::from_pretrained(model_name)?;
980 let tokenizer = AutoTokenizer::from_pretrained(model_name)?;
981
982 let mut pipeline = ImageToTextPipeline::new(model, tokenizer)?;
983
984 if let Some(max_length) = opts.max_length {
986 pipeline = pipeline.with_max_new_tokens(max_length);
987 }
988
989 match opts.device {
991 Some(Device::Cpu) => {
992 },
994 Some(Device::Gpu(_)) => {
995 },
997 None => {
998 },
1000 }
1001
1002 Ok(pipeline)
1003}
1004
1005#[cfg(feature = "audio")]
1006pub fn speech_to_text_pipeline(
1008 model: Option<&str>,
1009 options: Option<PipelineOptions>,
1010) -> Result<SpeechToTextPipeline> {
1011 let opts = options.unwrap_or_default();
1012 let model_name = model.or(opts.model.as_deref()).unwrap_or("openai/whisper-base");
1013
1014 let model = AutoModel::from_pretrained(model_name)?;
1015 let tokenizer = AutoTokenizer::from_pretrained(model_name)?;
1016
1017 let mut pipeline = SpeechToTextPipeline::new(model, tokenizer)?;
1018
1019 let mut config = SpeechToTextConfig::default();
1021
1022 if let Some(max_length) = opts.max_length {
1023 config.max_duration = Some(max_length as f64); }
1025
1026 pipeline = pipeline.with_config(config);
1027
1028 match opts.device {
1030 Some(Device::Cpu) => {
1031 },
1033 Some(Device::Gpu(_)) => {
1034 },
1036 None => {
1037 },
1039 }
1040
1041 Ok(pipeline)
1042}
1043
1044#[cfg(feature = "audio")]
1045pub fn text_to_speech_pipeline(
1047 model: Option<&str>,
1048 options: Option<PipelineOptions>,
1049) -> Result<TextToSpeechPipeline<crate::AutoModel, crate::AutoTokenizer>> {
1050 let opts = options.unwrap_or_default();
1051 let model_name = model.or(opts.model.as_deref()).unwrap_or("microsoft/speecht5_tts");
1052
1053 let model = crate::AutoModel::from_pretrained(model_name)?;
1054 let tokenizer = crate::AutoTokenizer::from_pretrained(model_name)?;
1055
1056 let mut pipeline = TextToSpeechPipeline::new(model, tokenizer)?;
1057
1058 let mut config = TextToSpeechConfig::default();
1060
1061 if let Some(max_length) = opts.max_length {
1062 config.max_duration = Some(max_length as f64); }
1064
1065 pipeline = pipeline.with_config(config);
1066
1067 match opts.device {
1069 Some(Device::Cpu) => {
1070 },
1072 Some(Device::Gpu(_)) => {
1073 },
1075 None => {
1076 },
1078 }
1079
1080 Ok(pipeline)
1081}
1082
1083#[cfg(feature = "vision")]
1084pub fn visual_question_answering_pipeline(
1086 model: Option<&str>,
1087 options: Option<PipelineOptions>,
1088) -> Result<VisualQuestionAnsweringPipeline<crate::AutoModel, crate::AutoTokenizer>> {
1089 let opts = options.unwrap_or_default();
1090 let model_name = model.or(opts.model.as_deref()).unwrap_or("dandelin/vilt-b32-finetuned-vqa");
1091
1092 let model = crate::AutoModel::from_pretrained(model_name)?;
1093 let tokenizer = crate::AutoTokenizer::from_pretrained(model_name)?;
1094
1095 let mut pipeline = VisualQuestionAnsweringPipeline::new(model, tokenizer)?;
1096
1097 let mut config = VisualQuestionAnsweringConfig::default();
1099
1100 if let Some(max_length) = opts.max_length {
1101 config.max_question_length = max_length;
1102 }
1103
1104 if let Some(batch_size) = opts.batch_size {
1105 config.top_k_answers = batch_size;
1106 }
1107
1108 pipeline = pipeline.with_config(config);
1109
1110 match opts.device {
1112 Some(Device::Cpu) => {
1113 },
1115 Some(Device::Gpu(_)) => {
1116 },
1118 None => {
1119 },
1121 }
1122
1123 Ok(pipeline)
1124}
1125
1126pub fn document_understanding_pipeline(
1128 model: Option<&str>,
1129 options: Option<PipelineOptions>,
1130) -> Result<DocumentUnderstandingPipeline<crate::AutoModel, crate::AutoTokenizer>> {
1131 let opts = options.unwrap_or_default();
1132 let model_name = model.or(opts.model.as_deref()).unwrap_or("microsoft/layoutlmv3-base");
1133
1134 let model = crate::AutoModel::from_pretrained(model_name)?;
1135 let tokenizer = crate::AutoTokenizer::from_pretrained(model_name)?;
1136
1137 let mut pipeline = DocumentUnderstandingPipeline::new(model, tokenizer)?;
1138
1139 let mut config = DocumentUnderstandingConfig::default();
1141
1142 if let Some(max_length) = opts.max_length {
1143 config.max_length = max_length;
1144 }
1145
1146 if let Some(batch_size) = opts.batch_size {
1147 config.confidence_threshold = (batch_size as f32) / 100.0; }
1149
1150 pipeline = pipeline.with_config(config);
1151
1152 match opts.device {
1154 Some(Device::Cpu) => {
1155 },
1157 Some(Device::Gpu(_)) => {
1158 },
1160 None => {
1161 },
1163 }
1164
1165 Ok(pipeline)
1166}
1167
1168#[derive(Debug, Clone, Serialize, Deserialize)]
1170pub enum PipelineOutput {
1171 Classification(Vec<ClassificationOutput>),
1173 Generation(GenerationOutput),
1175 TokenClassification(Vec<TokenClassificationOutput>),
1177 QuestionAnswering(QuestionAnsweringOutput),
1179 FillMask(Vec<FillMaskOutput>),
1181 Summarization(String),
1183 Translation(String),
1185 #[cfg(feature = "vision")]
1187 ImageToText(ImageToTextOutput),
1188 #[cfg(feature = "audio")]
1190 SpeechToText(SpeechToTextOutput),
1191 #[cfg(feature = "audio")]
1193 TextToSpeech(TextToSpeechOutput),
1194 #[cfg(feature = "vision")]
1196 VisualQuestionAnswering(VisualQuestionAnsweringOutput),
1197 DocumentUnderstanding(DocumentUnderstandingOutput),
1199 MultiModal(MultiModalOutput),
1201 #[cfg(feature = "async")]
1203 Conversational(ConversationalOutput),
1204 AdvancedRAG(advanced_rag::AdvancedRAGOutput),
1206 MixtureOfDepths(mixture_of_depths::MoDExecutionResult),
1208 SpeculativeDecoding(speculative_decoding::SpeculativeDecodingResult),
1210 Mamba2(mamba2_pipeline::Mamba2Output),
1212 Text(String),
1214}
1215
1216#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1217pub struct ClassificationOutput {
1218 pub label: String,
1219 pub score: f32,
1220}
1221
1222#[derive(Debug, Clone, Serialize, Deserialize)]
1223pub struct GenerationOutput {
1224 pub generated_text: String,
1225 pub sequences: Option<Vec<Vec<u32>>>,
1226 pub scores: Option<Vec<f32>>,
1227}
1228
1229#[derive(Debug, Clone, Serialize, Deserialize)]
1230pub struct TokenClassificationOutput {
1231 pub entity: String,
1232 pub score: f32,
1233 pub index: usize,
1234 pub word: String,
1235 pub start: usize,
1236 pub end: usize,
1237}
1238
1239#[derive(Debug, Clone, Serialize, Deserialize)]
1240pub struct QuestionAnsweringOutput {
1241 pub answer: String,
1242 pub score: f32,
1243 pub start: usize,
1244 pub end: usize,
1245}
1246
1247#[derive(Debug, Clone, Serialize, Deserialize)]
1248pub struct FillMaskOutput {
1249 pub sequence: String,
1250 pub score: f32,
1251 pub token: u32,
1252 pub token_str: String,
1253}
1254
1255#[derive(Clone)]
1257pub struct BasePipeline<M, T> {
1258 pub model: Arc<M>,
1259 pub tokenizer: Arc<T>,
1260 pub device: Device,
1261 pub batch_size: usize,
1262 pub max_length: usize,
1263 pub truncation: bool,
1264 pub padding: PaddingStrategy,
1265 pub cache: Option<Arc<InferenceCache>>,
1266 pub advanced_cache: Option<Arc<AdvancedLRUCache<String>>>,
1267 pub cache_key_builder: PipelineCacheKeyBuilder,
1268}
1269
1270impl<M, T> BasePipeline<M, T> {
1271 pub fn new(model: M, tokenizer: T) -> Self {
1272 Self {
1273 model: Arc::new(model),
1274 tokenizer: Arc::new(tokenizer),
1275 device: Device::Cpu,
1276 batch_size: 1,
1277 max_length: 512,
1278 truncation: true,
1279 padding: PaddingStrategy::Longest,
1280 cache: None,
1281 advanced_cache: None,
1282 cache_key_builder: PipelineCacheKeyBuilder::new(),
1283 }
1284 }
1285
1286 pub fn to_device(mut self, device: Device) -> Self {
1287 self.device = device;
1288 self
1289 }
1290
1291 pub fn with_batch_size(mut self, batch_size: usize) -> Self {
1292 self.batch_size = batch_size;
1293 self
1294 }
1295
1296 pub fn with_max_length(mut self, max_length: usize) -> Self {
1297 self.max_length = max_length;
1298 self
1299 }
1300
1301 pub fn with_padding(mut self, padding: PaddingStrategy) -> Self {
1302 self.padding = padding;
1303 self
1304 }
1305
1306 pub fn with_cache(mut self, cache_config: CacheConfig) -> Self {
1307 self.cache = Some(Arc::new(InferenceCache::new(cache_config)));
1308 self
1309 }
1310
1311 pub fn with_existing_cache(mut self, cache: Arc<InferenceCache>) -> Self {
1312 self.cache = Some(cache);
1313 self
1314 }
1315
1316 pub fn get_cache(&self) -> Option<Arc<InferenceCache>> {
1317 self.cache.clone()
1318 }
1319
1320 pub fn create_dynamic_config(&self) -> DynamicBatchingConfig {
1322 DynamicBatchingConfig {
1323 initial_batch_size: self.batch_size,
1324 max_batch_size: std::cmp::max(self.batch_size * 4, 32),
1325 min_batch_size: 1,
1326 target_latency_ms: match self.device {
1327 Device::Gpu(_) => 50, Device::Cpu => 100, },
1330 max_wait_time_ms: 25,
1331 throughput_threshold: 10.0,
1332 performance_window_size: 10,
1333 adjustment_factor: 1.2,
1334 }
1335 }
1336
1337 pub fn get_optimal_batch_size(&self, input_count: usize, estimated_memory_mb: f64) -> usize {
1339 let base_size = self.batch_size;
1340
1341 let memory_factor = if estimated_memory_mb > 1000.0 {
1343 0.8 } else if estimated_memory_mb < 100.0 {
1345 1.5 } else {
1347 1.0
1348 };
1349
1350 let device_factor = match self.device {
1352 Device::Gpu(_) => 2.0, Device::Cpu => 1.0,
1354 };
1355
1356 let calculated_size = (base_size as f64 * memory_factor * device_factor) as usize;
1357
1358 std::cmp::min(calculated_size, input_count)
1360 }
1361
1362 pub fn create_adaptive_config(&self) -> AdaptiveBatchConfig {
1364 AdaptiveBatchConfig {
1365 min_batch_size: 1,
1366 max_batch_size: std::cmp::max(self.batch_size * 8, 64),
1367 samples_per_size: 10,
1368 warmup_iterations: 3,
1369 target_latency_percentile: 95.0,
1370 target_latency_ms: match self.device {
1371 Device::Gpu(_) => 50.0, Device::Cpu => 100.0, },
1374 throughput_weight: 0.4,
1375 latency_weight: 0.4,
1376 memory_weight: 0.2,
1377 reevaluation_interval_secs: 300,
1378 }
1379 }
1380
1381 pub fn create_adaptive_optimizer(&self) -> AdaptiveBatchOptimizer {
1383 let config = self.create_adaptive_config();
1384 AdaptiveBatchOptimizer::new(config)
1385 }
1386
1387 pub fn create_performance_sample(
1402 &self,
1403 batch_size: usize,
1404 latency_ms: f64,
1405 throughput_rps: f64,
1406 memory_usage_mb: f64,
1407 ) -> PerformanceSample {
1408 PerformanceSample {
1409 batch_size,
1410 latency_ms,
1411 throughput_rps,
1412 memory_usage_mb,
1413 gpu_memory_mb: 0.0,
1414 cpu_utilization: sampled_cpu_utilization(),
1415 gpu_utilization: 0.0,
1416 timestamp: std::time::SystemTime::now(),
1417 }
1418 }
1419
1420 pub fn with_advanced_cache(mut self, config: AdvancedCacheConfig) -> Self {
1422 self.advanced_cache = Some(Arc::new(AdvancedLRUCache::new(config)));
1423 self
1424 }
1425
1426 pub fn with_existing_advanced_cache(mut self, cache: Arc<AdvancedLRUCache<String>>) -> Self {
1428 self.advanced_cache = Some(cache);
1429 self
1430 }
1431
1432 pub fn create_advanced_cache_config(&self) -> AdvancedCacheConfig {
1434 AdvancedCacheConfig {
1435 max_entries: match self.device {
1436 Device::Gpu(_) => 50000, Device::Cpu => 10000, },
1439 max_memory_bytes: match self.device {
1440 Device::Gpu(_) => 2 * 1024 * 1024 * 1024, Device::Cpu => 1024 * 1024 * 1024, },
1443 ttl_seconds: 3600, cleanup_interval_seconds: 300, lru_eviction_threshold: 0.8,
1446 smart_eviction_threshold: 0.9,
1447 enable_hit_rate_tracking: true,
1448 enable_memory_pressure_monitoring: true,
1449 enable_access_pattern_analysis: true,
1450 }
1451 }
1452
1453 pub fn cache_get(&self, input: &str, model_id: &str, config_hash: u64) -> Option<String> {
1455 if let Some(cache) = &self.advanced_cache {
1456 let key = self.cache_key_builder.build_key(&input, model_id, config_hash);
1457 cache.get(&key)
1458 } else {
1459 None
1460 }
1461 }
1462
1463 pub fn cache_put(
1465 &self,
1466 input: &str,
1467 model_id: &str,
1468 config_hash: u64,
1469 output: String,
1470 memory_size: u64,
1471 priority: CachePriority,
1472 tags: std::collections::HashSet<String>,
1473 ttl: Option<std::time::Duration>,
1474 ) -> Result<()> {
1475 if let Some(cache) = &self.advanced_cache {
1476 let key = self.cache_key_builder.build_key(&input, model_id, config_hash);
1477 cache.insert(key, output, memory_size, priority, tags, ttl)?;
1478 }
1479 Ok(())
1480 }
1481
1482 pub fn get_cache_stats(&self) -> Option<CacheStats> {
1484 self.advanced_cache.as_ref().map(|cache| cache.get_stats())
1485 }
1486
1487 pub fn clear_cache_by_tag(&self, tag: &str) -> usize {
1489 if let Some(cache) = &self.advanced_cache {
1490 cache.remove_by_tag(tag)
1491 } else {
1492 0
1493 }
1494 }
1495
1496 pub fn get_cache_size_info(&self) -> Option<(usize, u64)> {
1498 self.advanced_cache.as_ref().map(|cache| cache.size_info())
1499 }
1500}
1501
1502#[cfg(test)]
1503mod performance_sample_tests {
1504 use super::*;
1505
1506 #[test]
1516 fn test_cpu_utilization_is_measured_not_hardcoded_seven_tenths() {
1517 let reading = sampled_cpu_utilization();
1523 assert!(
1524 (reading - 0.7).abs() > 1e-6,
1525 "cpu_utilization must be a real measurement, not the old hardcoded 0.7 placeholder \
1526 (got exactly 0.7, which is suspicious)"
1527 );
1528 }
1529
1530 #[test]
1531 fn test_cpu_utilization_is_a_valid_percentage() {
1532 let reading = sampled_cpu_utilization();
1538 assert!(
1539 (0.0..=100.0).contains(&reading),
1540 "cpu_utilization must be a plausible percentage reading, got {reading}"
1541 );
1542 }
1543
1544 #[test]
1545 fn test_cpu_utilization_repeated_calls_do_not_block() {
1546 let start = Instant::now();
1550 for _ in 0..1000 {
1551 let _ = sampled_cpu_utilization();
1552 }
1553 let elapsed = start.elapsed();
1554 assert!(
1555 elapsed < sysinfo::MINIMUM_CPU_UPDATE_INTERVAL,
1556 "1000 calls to sampled_cpu_utilization() must not block for anywhere near the \
1557 refresh interval; took {elapsed:?}"
1558 );
1559 }
1560
1561 #[test]
1562 fn test_create_performance_sample_cpu_field_is_not_placeholder() {
1563 let base = BasePipeline::<(), ()>::new((), ());
1564 let sample = base.create_performance_sample(4, 50.0, 20.0, 256.0);
1565 assert!(
1566 (sample.cpu_utilization - 0.7).abs() > 1e-6,
1567 "create_performance_sample's cpu_utilization must not be the old hardcoded 0.7"
1568 );
1569 }
1570
1571 #[test]
1572 fn test_create_performance_sample_gpu_fields_are_honest_zero_not_fabricated() {
1573 let base = BasePipeline::<(), ()>::new((), ());
1581 let small = base.create_performance_sample(4, 50.0, 20.0, 100.0);
1582 let large = base.create_performance_sample(4, 50.0, 20.0, 100_000.0);
1583 assert_eq!(small.gpu_memory_mb, 0.0);
1584 assert_eq!(large.gpu_memory_mb, 0.0);
1585 assert_eq!(
1586 small.gpu_memory_mb, large.gpu_memory_mb,
1587 "gpu_memory_mb must not scale with memory_usage_mb (no real GPU memory query exists)"
1588 );
1589 assert_eq!(small.gpu_utilization, 0.0);
1590 assert_eq!(large.gpu_utilization, 0.0);
1591 }
1592
1593 #[test]
1594 fn test_create_performance_sample_gpu_fields_honest_zero_even_on_gpu_device() {
1595 let mut base = BasePipeline::<(), ()>::new((), ());
1599 base.device = Device::Gpu(0);
1600 let sample = base.create_performance_sample(4, 50.0, 20.0, 100.0);
1601 assert_eq!(
1602 sample.gpu_utilization, 0.0,
1603 "gpu_utilization must be honestly 0.0 even for a GPU device, since no GPU telemetry \
1604 source is wired into this workspace"
1605 );
1606 }
1607
1608 #[test]
1609 fn test_create_performance_sample_preserves_caller_supplied_fields() {
1610 let base = BasePipeline::<(), ()>::new((), ());
1611 let sample = base.create_performance_sample(16, 123.5, 45.6, 789.0);
1612 assert_eq!(sample.batch_size, 16);
1613 assert!((sample.latency_ms - 123.5).abs() < 1e-9);
1614 assert!((sample.throughput_rps - 45.6).abs() < 1e-9);
1615 assert!((sample.memory_usage_mb - 789.0).abs() < 1e-9);
1616 }
1617}