Skip to main content

trustformers/pipeline/
mod.rs

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
9/// Cached-and-rate-limited host CPU utilization sampler.
10///
11/// `sysinfo::System::global_cpu_usage()` is meaningful only relative to a
12/// prior refresh at least `sysinfo::MINIMUM_CPU_UPDATE_INTERVAL` earlier
13/// (see `sysinfo`'s own docs); refreshing on every call would either block
14/// the caller for that interval or (if not slept on) report a near-zero
15/// delta every time. This sampler instead keeps one process-wide `System`
16/// behind a `Mutex`, refreshes it opportunistically whenever the interval
17/// has actually elapsed, and reuses the last real reading in between --
18/// never fabricating a value, only reusing the most recent measured one.
19struct CpuSampler {
20    system: sysinfo::System,
21    refreshed_at: Instant,
22}
23
24static CPU_SAMPLER: OnceLock<Mutex<CpuSampler>> = OnceLock::new();
25
26/// Real host CPU utilization, in percent (`0.0..=100.0` per core, averaged
27/// across logical CPUs), sampled through `sysinfo`.
28///
29/// This never blocks: if the minimum refresh interval has not elapsed since
30/// the last measurement, it returns that measurement rather than sleeping
31/// or reporting an artificial 0%. The very first call after process start
32/// reflects whatever `sysinfo::System::new()` had available before any
33/// refresh (typically `0.0`, since CPU usage is delta-based), which is
34/// honest -- there is no measurement to report yet, so `0.0` is correct
35/// rather than a placeholder.
36pub(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        // A poisoned mutex means a prior holder panicked mid-refresh; there
48        // is no sound value to return here other than "unmeasured".
49        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/// Common input format for pipelines
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub enum PipelineInput {
62    /// Text input
63    Text(String),
64    /// Token input
65    Tokens(Vec<u32>),
66    /// Batch text input
67    BatchText(Vec<String>),
68    /// Batch token input
69    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, // document_understanding_pipeline, // Removed to avoid duplicate with function definition
177};
178pub use dynamic_batching::{
179    BatchRequest,
180    BatchingStats,
181    DynamicBatchPipeline,
182    DynamicBatcher,
183    DynamicBatchingConfig,
184    // PerformanceMetrics, // Removed duplicate import (already imported above)
185    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
240// Export enhanced pipeline factory and backend types
241// Backend is defined in this module, so no need to import
242// pub use enhanced_pipeline; // Commented out to avoid duplicate definition with function below
243pub 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
298/// Base trait for all pipelines
299pub trait Pipeline: Send + Sync {
300    type Input;
301    type Output;
302
303    /// Main entry point for pipeline processing
304    fn __call__(&self, inputs: Self::Input) -> Result<Self::Output>;
305
306    /// Process multiple inputs in a batch
307    fn batch(&self, inputs: Vec<Self::Input>) -> Result<Vec<Self::Output>> {
308        inputs.into_iter().map(|input| self.__call__(input)).collect()
309    }
310
311    /// Process multiple inputs with adaptive batch sizing
312    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        // For now, implement a simple adaptive strategy
323        // In a real implementation, this would use performance monitoring
324        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            // Process in chunks
330            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    /// Create a streaming processor with enhanced capabilities
340    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        // This default implementation assumes the pipeline also implements StreamingPipeline
350        // Implementers can override this method for custom behavior
351        StreamProcessor::<Self::Input, Self::Output, String>::new_from_pipeline(
352            self.clone(),
353            config,
354        )
355    }
356
357    /// Create a real-time processor for low-latency scenarios
358    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        // Default `Pipeline` trait method: a plain `Pipeline` only exposes a
368        // whole-model `__call__`, so there is no low-latency streaming path
369        // to wire `_config` into here. Types that implement `StreamingPipeline`
370        // should override this method with `RealTimeProcessor::new` and use
371        // the config; this default correctly reports the capability as
372        // unavailable rather than fabricating a processor.
373        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/// Async pipeline trait for concurrent processing
381#[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 processing of single input
388    async fn __call_async__(&self, input: Self::Input) -> Result<Self::Output>;
389
390    /// Async batch processing with concurrent execution
391    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 adaptive batch processing with dynamic sizing
401    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            // Process in chunks concurrently but with controlled parallelism
416            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    /// Create a dynamic batcher for this async pipeline
426    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/// Options for pipeline creation
435#[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/// Backend specification for pipeline execution
453#[derive(Clone, Debug)]
454pub enum Backend {
455    /// Native TrustformeRS backend
456    Native,
457    /// ONNX Runtime backend
458    ONNX { model_path: std::path::PathBuf },
459    /// TensorRT backend
460    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/// Padding strategy for batch processing
484#[derive(Clone, Debug)]
485pub enum PaddingStrategy {
486    /// No padding
487    None,
488    /// Pad to the longest sequence in the batch
489    Longest,
490    /// Pad to a fixed length
491    MaxLength(usize),
492}
493
494/// Device specification for pipeline execution
495#[derive(Clone, Debug, Serialize, Deserialize)]
496pub enum Device {
497    Cpu,
498    Gpu(usize),
499}
500
501/// Factory function to create pipelines
502pub 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        // Note: Speech pipelines use different Input/Output types than the generic pipeline
563        // They should be instantiated directly rather than through this function
564        // #[cfg(feature = "audio")]
565        // "automatic-speech-recognition" | "speech-to-text" => {
566        //     let model_name = model_name.unwrap_or("openai/whisper-base");
567        //     let model = AutoModel::from_pretrained(model_name)?;
568        //     let tokenizer = AutoTokenizer::from_pretrained(model_name)?;
569        //     Ok(Box::new(SpeechToTextPipeline::new(model, tokenizer)?))
570        // },
571        // #[cfg(feature = "audio")]
572        // "text-to-speech" | "tts" => {
573        //     let model_name = model_name.unwrap_or("microsoft/speecht5_tts");
574        //     let model = AutoModel::from_pretrained(model_name)?;
575        //     let tokenizer = AutoTokenizer::from_pretrained(model_name)?;
576        //     Ok(Box::new(TextToSpeechPipeline::new(model, tokenizer)?))
577        // },
578        #[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            // Create a base text classification pipeline
618            let base_pipeline = TextClassificationPipeline::new(model, tokenizer)?;
619
620            // Wrap with adaptive inference
621            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
633/// Enhanced pipeline factory that supports both native and ONNX backends
634pub 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            // Use existing native pipeline factory
644            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
651/// Create ONNX-backed pipeline
652fn 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    // Load tokenizer (still use native tokenizer)
660    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    // Create ONNX config based on options
668    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
702/// Create TensorRT-backed pipeline
703fn 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    // Load tokenizer (still use native tokenizer)
711    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    // Create TensorRT config based on options
719    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                // TensorRT typically runs on GPU, but we can create a CPU-fallback config
725                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
761/// Wrapper to make ONNX pipelines compatible with the unified Pipeline trait
762enum 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
779/// Wrapper to make TensorRT pipelines compatible with the unified Pipeline trait
780enum 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/// Wrapper to make DocumentUnderstanding pipeline compatible with the unified Pipeline trait
798///
799/// The wrapped pipeline is intentionally never read through this type: see
800/// `Pipeline::__call__` below for why `Input = String` can never actually
801/// drive `DocumentUnderstandingPipeline` (which needs image bytes). The
802/// field still has to exist so `pipeline("document-understanding", ...)` has
803/// somewhere to put the constructed pipeline and this type remains
804/// `Pipeline`-shaped; reason it is unread, rather than deleting it, is the
805/// correct fix here (deleting it would remove the ability to construct this
806/// wrapper at all from `pipeline::mod::pipeline`).
807#[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        // This wrapper exists only so `DocumentUnderstandingPipeline` can be
824        // named through the unified `Pipeline<Input = String>` trait object
825        // returned by `pipeline()`. It cannot actually run: the wrapped
826        // pipeline needs `DocumentUnderstandingInput` (image bytes plus
827        // metadata), which a plain `String` cannot carry, so every call
828        // reports that and directs the caller to
829        // `document_understanding_pipeline()` for the real typed API.
830        Err(TrustformersError::invalid_input_simple(
831            "DocumentUnderstanding pipeline requires DocumentUnderstandingInput with image data, not string input".to_string()
832        ))
833    }
834}
835
836/// Wrapper to make VisualQuestionAnswering pipeline compatible with the unified Pipeline trait
837///
838/// `Pipeline::__call__` below can never actually drive the wrapped pipeline:
839/// VQA needs `VisualQuestionAnsweringInput` (image bytes plus a question),
840/// which a plain `String` cannot carry, so every call through the unified
841/// `Pipeline<Input = String>` trait object reports that and directs the
842/// caller to `visual_question_answering_pipeline()` for the real typed API.
843/// The wrapped pipeline is still reachable, though: `Deref` exposes it to
844/// any caller holding the concrete wrapper type (rather than the
845/// `Box<dyn Pipeline>` returned by `pipeline()`).
846#[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    // `_input`: required by the `Pipeline` trait signature, but VQA needs
875    // both image and question data that a `String` cannot carry (see the
876    // struct doc above), so it is never actually consulted.
877    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
884/// Wrapper to make MultiModal pipeline compatible with the unified Pipeline trait
885pub 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")]
910/// Wrapper to make Conversational pipeline compatible with the unified Pipeline trait
911pub 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
933/// Wrapper to make adaptive inference pipelines compatible with the unified Pipeline trait
934pub 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    // `'static` is required because `AdaptiveInferenceEngine::adaptive_inference`
948    // downcasts the input via `std::any::Any` (see
949    // `pipeline::adaptive_inference::as_text`) to recognise textual input
950    // for real, content-derived analysis -- `Any::downcast_ref` itself
951    // requires `'static`.
952    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        // The engine accumulates adaptation history / calibration data
959        // across calls, so each `__call__` clones the wrapper's engine
960        // rather than mutating shared state through a `&self` receiver;
961        // callers that want the accumulated history should drive
962        // `AdaptiveInferenceEngine` directly instead of through this
963        // `Pipeline`-trait wrapper.
964        let mut engine = self.engine.clone();
965        let result = engine.adaptive_inference(input)?;
966        Ok(result.prediction)
967    }
968}
969
970#[cfg(feature = "vision")]
971/// Factory function specifically for image-to-text pipelines
972pub 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    // Apply options
985    if let Some(max_length) = opts.max_length {
986        pipeline = pipeline.with_max_new_tokens(max_length);
987    }
988
989    // Configure device if specified
990    match opts.device {
991        Some(Device::Cpu) => {
992            // Already default
993        },
994        Some(Device::Gpu(_)) => {
995            // Would configure GPU device in real implementation
996        },
997        None => {
998            // Use default (CPU)
999        },
1000    }
1001
1002    Ok(pipeline)
1003}
1004
1005#[cfg(feature = "audio")]
1006/// Factory function specifically for speech-to-text pipelines
1007pub 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    // Apply options
1020    let mut config = SpeechToTextConfig::default();
1021
1022    if let Some(max_length) = opts.max_length {
1023        config.max_duration = Some(max_length as f64); // Convert to seconds
1024    }
1025
1026    pipeline = pipeline.with_config(config);
1027
1028    // Configure device if specified
1029    match opts.device {
1030        Some(Device::Cpu) => {
1031            // Already default
1032        },
1033        Some(Device::Gpu(_)) => {
1034            // Would configure GPU device in real implementation
1035        },
1036        None => {
1037            // Use default (CPU)
1038        },
1039    }
1040
1041    Ok(pipeline)
1042}
1043
1044#[cfg(feature = "audio")]
1045/// Factory function specifically for text-to-speech pipelines
1046pub 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    // Apply configuration from options
1059    let mut config = TextToSpeechConfig::default();
1060
1061    if let Some(max_length) = opts.max_length {
1062        config.max_duration = Some(max_length as f64); // Convert to seconds
1063    }
1064
1065    pipeline = pipeline.with_config(config);
1066
1067    // Configure device if specified
1068    match opts.device {
1069        Some(Device::Cpu) => {
1070            // Already default
1071        },
1072        Some(Device::Gpu(_)) => {
1073            // Would configure GPU device in real implementation
1074        },
1075        None => {
1076            // Use default (CPU)
1077        },
1078    }
1079
1080    Ok(pipeline)
1081}
1082
1083#[cfg(feature = "vision")]
1084/// Factory function specifically for visual question answering pipelines
1085pub 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    // Apply configuration from options
1098    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    // Configure device if specified
1111    match opts.device {
1112        Some(Device::Cpu) => {
1113            // Already default
1114        },
1115        Some(Device::Gpu(_)) => {
1116            // Would configure GPU device in real implementation
1117        },
1118        None => {
1119            // Use default (CPU)
1120        },
1121    }
1122
1123    Ok(pipeline)
1124}
1125
1126/// Factory function specifically for document understanding pipelines
1127pub 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    // Apply configuration from options
1140    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; // Use batch_size as confidence threshold
1148    }
1149
1150    pipeline = pipeline.with_config(config);
1151
1152    // Configure device if specified
1153    match opts.device {
1154        Some(Device::Cpu) => {
1155            // Already default
1156        },
1157        Some(Device::Gpu(_)) => {
1158            // Would configure GPU device in real implementation
1159        },
1160        None => {
1161            // Use default (CPU)
1162        },
1163    }
1164
1165    Ok(pipeline)
1166}
1167
1168/// Common output format for pipelines
1169#[derive(Debug, Clone, Serialize, Deserialize)]
1170pub enum PipelineOutput {
1171    /// Classification output with labels and scores
1172    Classification(Vec<ClassificationOutput>),
1173    /// Text generation output
1174    Generation(GenerationOutput),
1175    /// Token classification output (NER)
1176    TokenClassification(Vec<TokenClassificationOutput>),
1177    /// Question answering output
1178    QuestionAnswering(QuestionAnsweringOutput),
1179    /// Fill mask output
1180    FillMask(Vec<FillMaskOutput>),
1181    /// Summarization output
1182    Summarization(String),
1183    /// Translation output
1184    Translation(String),
1185    /// Image-to-text output
1186    #[cfg(feature = "vision")]
1187    ImageToText(ImageToTextOutput),
1188    /// Speech-to-text output
1189    #[cfg(feature = "audio")]
1190    SpeechToText(SpeechToTextOutput),
1191    /// Text-to-speech output
1192    #[cfg(feature = "audio")]
1193    TextToSpeech(TextToSpeechOutput),
1194    /// Visual question answering output
1195    #[cfg(feature = "vision")]
1196    VisualQuestionAnswering(VisualQuestionAnsweringOutput),
1197    /// Document understanding output
1198    DocumentUnderstanding(DocumentUnderstandingOutput),
1199    /// Multi-modal output
1200    MultiModal(MultiModalOutput),
1201    /// Conversational output
1202    #[cfg(feature = "async")]
1203    Conversational(ConversationalOutput),
1204    /// Advanced RAG output
1205    AdvancedRAG(advanced_rag::AdvancedRAGOutput),
1206    /// Mixture of Depths output
1207    MixtureOfDepths(mixture_of_depths::MoDExecutionResult),
1208    /// Speculative Decoding output
1209    SpeculativeDecoding(speculative_decoding::SpeculativeDecodingResult),
1210    /// Mamba-2 State Space Model output
1211    Mamba2(mamba2_pipeline::Mamba2Output),
1212    /// Simple text output
1213    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/// Base struct for pipelines that need a model and tokenizer
1256#[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    /// Create a dynamic batching configuration based on pipeline settings
1321    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, // Lower latency target for GPU
1328                Device::Cpu => 100,   // Higher latency target for CPU
1329            },
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    /// Get optimal batch size based on input characteristics
1338    pub fn get_optimal_batch_size(&self, input_count: usize, estimated_memory_mb: f64) -> usize {
1339        let base_size = self.batch_size;
1340
1341        // Adjust based on available memory (simplified calculation)
1342        let memory_factor = if estimated_memory_mb > 1000.0 {
1343            0.8 // Reduce batch size for large memory usage
1344        } else if estimated_memory_mb < 100.0 {
1345            1.5 // Increase batch size for small memory usage
1346        } else {
1347            1.0
1348        };
1349
1350        // Adjust based on device capabilities
1351        let device_factor = match self.device {
1352            Device::Gpu(_) => 2.0, // GPU can handle larger batches
1353            Device::Cpu => 1.0,
1354        };
1355
1356        let calculated_size = (base_size as f64 * memory_factor * device_factor) as usize;
1357
1358        // Ensure we don't exceed input count
1359        std::cmp::min(calculated_size, input_count)
1360    }
1361
1362    /// Create an adaptive batch configuration based on pipeline settings
1363    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, // Lower latency target for GPU
1372                Device::Cpu => 100.0,   // Higher latency target for CPU
1373            },
1374            throughput_weight: 0.4,
1375            latency_weight: 0.4,
1376            memory_weight: 0.2,
1377            reevaluation_interval_secs: 300,
1378        }
1379    }
1380
1381    /// Create an adaptive batch optimizer for this pipeline
1382    pub fn create_adaptive_optimizer(&self) -> AdaptiveBatchOptimizer {
1383        let config = self.create_adaptive_config();
1384        AdaptiveBatchOptimizer::new(config)
1385    }
1386
1387    /// Helper method to create a performance sample
1388    ///
1389    /// `batch_size`, `latency_ms`, `throughput_rps`, and `memory_usage_mb`
1390    /// are the caller's own measurements of the batch that was just run.
1391    /// `cpu_utilization` is a real host reading taken here via `sysinfo`
1392    /// (see `sampled_cpu_utilization`). `gpu_utilization` and
1393    /// `gpu_memory_mb` are honestly `0.0` ("not measured") on every device:
1394    /// no GPU telemetry source (NVML/rocm-smi/IOKit/Metal performance
1395    /// counters/...) is wired into this workspace, so inventing a plausible
1396    /// non-zero number here (the previous behavior derived a fake GPU
1397    /// utilization from the enum variant and a fake GPU memory figure as a
1398    /// fixed fraction of host memory) would feed the adaptive batch
1399    /// optimizer's `memory_weight`/objective on numbers that do not
1400    /// correspond to anything the hardware actually reported.
1401    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    /// Enable advanced caching with configuration
1421    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    /// Enable advanced caching with existing cache
1427    pub fn with_existing_advanced_cache(mut self, cache: Arc<AdvancedLRUCache<String>>) -> Self {
1428        self.advanced_cache = Some(cache);
1429        self
1430    }
1431
1432    /// Create default advanced cache configuration
1433    pub fn create_advanced_cache_config(&self) -> AdvancedCacheConfig {
1434        AdvancedCacheConfig {
1435            max_entries: match self.device {
1436                Device::Gpu(_) => 50000, // GPU can handle more entries
1437                Device::Cpu => 10000,    // CPU more conservative
1438            },
1439            max_memory_bytes: match self.device {
1440                Device::Gpu(_) => 2 * 1024 * 1024 * 1024, // 2GB for GPU
1441                Device::Cpu => 1024 * 1024 * 1024,        // 1GB for CPU
1442            },
1443            ttl_seconds: 3600,             // 1 hour
1444            cleanup_interval_seconds: 300, // 5 minutes
1445            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    /// Get cache from advanced cache
1454    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    /// Put value in advanced cache
1464    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    /// Get cache statistics
1483    pub fn get_cache_stats(&self) -> Option<CacheStats> {
1484        self.advanced_cache.as_ref().map(|cache| cache.get_stats())
1485    }
1486
1487    /// Clear cache entries by tag
1488    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    /// Get cache size information
1497    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    // -------------------------------------------------------------------
1507    // Regression coverage for the fabricated `PerformanceSample` fields:
1508    // `cpu_utilization` used to be a hardcoded `0.7` and `gpu_memory_mb`
1509    // used to be `memory_usage_mb * 0.8` regardless of any real
1510    // measurement. These tests fail against that old behavior because they
1511    // assert real (measured) properties instead of the specific constants
1512    // the old code emitted.
1513    // -------------------------------------------------------------------
1514
1515    #[test]
1516    fn test_cpu_utilization_is_measured_not_hardcoded_seven_tenths() {
1517        // The old code always returned exactly 0.7. A real sysinfo reading
1518        // on any machine actually running this test suite will essentially
1519        // never land on exactly 0.7 (a float with no reason to be that
1520        // specific value), so this is a meaningful (if probabilistic)
1521        // regression check, not just a range check.
1522        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        // `System::global_cpu_usage()` is documented to average across
1533        // logical CPUs and stay within `0.0..=100.0`; this just guards
1534        // against a wildly nonsensical reading (e.g. NaN or a stray
1535        // per-core-summed value) rather than pinning an exact figure that
1536        // would be brittle across CI hardware.
1537        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        // Calling this in a tight loop must not incur
1547        // MINIMUM_CPU_UPDATE_INTERVAL of blocking per call -- it should
1548        // reuse the cached reading between refreshes.
1549        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        // Regression test: the old code set `gpu_memory_mb: memory_usage_mb
1574        // * 0.8` and `gpu_utilization` to a fixed 0.8/0.0 purely from the
1575        // `Device` enum, neither of which reflects any real GPU telemetry
1576        // (none is wired into this workspace). Both fields must now be an
1577        // honest 0.0 ("not measured"), and in particular gpu_memory_mb must
1578        // NOT track memory_usage_mb at all -- doubling the input must not
1579        // double the output the way the old `* 0.8` formula would.
1580        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        // The old code special-cased `Device::Gpu(_)` to fabricate
1596        // `gpu_utilization: 0.8`. With no real telemetry wired up, the
1597        // device variant alone cannot justify a non-zero reading.
1598        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}