Skip to main content

runtime/
tensor_core.rs

1//! Core Tensor Abstraction
2//!
3//! This module provides the foundational tensor abstraction that unifies
4//! all tensor operations across CPU, GPU, and different data formats.
5//!
6//! Uses Candle (HuggingFace's Rust ML framework) as the computational backend.
7
8use std::sync::Arc;
9use anyhow::Result;
10use candle_core::{DType, Tensor as CandleTensor, Device as CandleDevice};
11use candle_nn::ops as candle_ops;
12
13/// Core tensor abstraction - the single source of truth for all tensor operations
14#[derive(Debug, Clone)]
15pub struct Tensor {
16    /// Tensor data storage (device-agnostic)
17    data: Arc<dyn TensorStorage>,
18    /// Tensor shape
19    shape: Vec<usize>,
20    /// Data type
21    dtype: DataType,
22    /// Device location
23    device: Device,
24}
25
26/// Data types supported by tensors
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub enum DataType {
29    Float32,
30    Float16,
31    BFloat16,
32    Int32,
33    Int64,
34    Int8,
35    Bool,
36}
37
38/// Device abstraction
39#[derive(Debug, Clone, PartialEq, Eq, Hash)]
40pub enum Device {
41    CPU,
42    CUDA(usize), // GPU ID
43    Metal(usize),
44}
45
46/// Tensor storage trait - abstracts how data is actually stored
47pub trait TensorStorage: Send + Sync + std::fmt::Debug {
48    /// Get raw data pointer (for unsafe operations)
49    fn data_ptr(&self) -> *const u8;
50
51    /// Get mutable data pointer (for unsafe operations)
52    fn data_ptr_mut(&mut self) -> *mut u8;
53
54    /// Get data size in bytes
55    fn size_bytes(&self) -> usize;
56
57    /// Get device where data is stored
58    fn device(&self) -> &Device;
59
60    /// Copy data to another device
61    fn to_device(&self, device: &Device) -> Result<Arc<dyn TensorStorage>>;
62
63    /// Clone the storage
64    fn clone_storage(&self) -> Arc<dyn TensorStorage>;
65
66    /// Try to get the underlying Candle tensor (if this is CandleStorage)
67    fn as_candle_tensor(&self) -> Option<&CandleTensor> {
68        None
69    }
70}
71
72/// CPU tensor storage implementation
73#[derive(Debug)]
74pub struct CpuStorage {
75    data: Vec<u8>,
76    device: Device,
77}
78
79/// GPU tensor storage implementation (legacy - kept for compatibility)
80#[derive(Debug)]
81pub struct GpuStorage {
82    #[allow(dead_code)]
83    ptr: *mut u8,
84    size: usize,
85    device: Device,
86}
87
88/// Candle-backed tensor storage - the primary storage implementation
89#[derive(Debug, Clone)]
90pub struct CandleStorage {
91    tensor: CandleTensor,
92}
93
94impl CandleStorage {
95    /// Create from a Candle tensor
96    pub fn new(tensor: CandleTensor) -> Self {
97        Self { tensor }
98    }
99
100    /// Get the underlying Candle tensor
101    pub fn tensor(&self) -> &CandleTensor {
102        &self.tensor
103    }
104
105    /// Get a mutable reference to the Candle tensor
106    pub fn tensor_mut(&mut self) -> &mut CandleTensor {
107        &mut self.tensor
108    }
109
110    /// Create zeros tensor with Candle
111    pub fn zeros(shape: &[usize], dtype: DataType, device: &Device) -> Result<Self> {
112        let candle_device = device.to_candle();
113        let candle_dtype = dtype.to_candle();
114        let tensor = CandleTensor::zeros(shape, candle_dtype, &candle_device)?;
115        Ok(Self { tensor })
116    }
117
118    /// Create ones tensor with Candle
119    pub fn ones(shape: &[usize], dtype: DataType, device: &Device) -> Result<Self> {
120        let candle_device = device.to_candle();
121        let candle_dtype = dtype.to_candle();
122        let tensor = CandleTensor::ones(shape, candle_dtype, &candle_device)?;
123        Ok(Self { tensor })
124    }
125
126    /// Create tensor from raw f32 data
127    pub fn from_f32_slice(data: &[f32], shape: &[usize], device: &Device) -> Result<Self> {
128        let candle_device = device.to_candle();
129        let tensor = CandleTensor::from_slice(data, shape, &candle_device)?;
130        Ok(Self { tensor })
131    }
132
133    /// Create tensor from raw i64 data
134    pub fn from_i64_slice(data: &[i64], shape: &[usize], device: &Device) -> Result<Self> {
135        let candle_device = device.to_candle();
136        let tensor = CandleTensor::from_slice(data, shape, &candle_device)?;
137        Ok(Self { tensor })
138    }
139}
140
141impl TensorStorage for CandleStorage {
142    fn data_ptr(&self) -> *const u8 {
143        // For Candle tensors, we need to flatten to contiguous and get the data
144        // This is primarily for compatibility - prefer using Candle ops directly
145        std::ptr::null() // Candle manages its own memory
146    }
147
148    fn data_ptr_mut(&mut self) -> *mut u8 {
149        std::ptr::null_mut() // Candle manages its own memory
150    }
151
152    fn size_bytes(&self) -> usize {
153        self.tensor.elem_count() * self.tensor.dtype().size_in_bytes()
154    }
155
156    fn device(&self) -> &Device {
157        // Return a static reference - this is a bit of a hack
158        // In practice, we should store the device or compute it
159        static CPU_DEVICE: Device = Device::CPU;
160        &CPU_DEVICE
161    }
162
163    fn to_device(&self, device: &Device) -> Result<Arc<dyn TensorStorage>> {
164        let candle_device = device.to_candle();
165        let new_tensor = self.tensor.to_device(&candle_device)?;
166        Ok(Arc::new(CandleStorage::new(new_tensor)))
167    }
168
169    fn clone_storage(&self) -> Arc<dyn TensorStorage> {
170        Arc::new(self.clone())
171    }
172
173    fn as_candle_tensor(&self) -> Option<&CandleTensor> {
174        Some(&self.tensor)
175    }
176}
177
178/// Core tensor operations trait - device-agnostic interface
179pub trait TensorOps: Send + Sync {
180    /// Matrix multiplication: C = A @ B
181    fn matmul(&self, a: &Tensor, b: &Tensor) -> Result<Tensor>;
182
183    /// Element-wise addition: C = A + B
184    fn add(&self, a: &Tensor, b: &Tensor) -> Result<Tensor>;
185
186    /// Element-wise multiplication: C = A * B
187    fn mul(&self, a: &Tensor, b: &Tensor) -> Result<Tensor>;
188
189    /// Scaled dot-product attention
190    fn attention(
191        &self,
192        query: &Tensor,
193        key: &Tensor,
194        value: &Tensor,
195        mask: Option<&Tensor>,
196        scale: Option<f32>,
197    ) -> Result<Tensor>;
198
199    /// Layer normalization
200    fn layer_norm(
201        &self,
202        input: &Tensor,
203        weight: &Tensor,
204        bias: Option<&Tensor>,
205        eps: f32,
206    ) -> Result<Tensor>;
207
208    /// GELU activation
209    fn gelu(&self, input: &Tensor) -> Result<Tensor>;
210
211    /// SiLU/Swish activation
212    fn silu(&self, input: &Tensor) -> Result<Tensor>;
213
214    /// Softmax
215    fn softmax(&self, input: &Tensor, dim: isize) -> Result<Tensor>;
216
217    /// Embedding lookup
218    fn embedding(&self, indices: &Tensor, weight: &Tensor) -> Result<Tensor>;
219
220    /// Create tensor with zeros
221    fn zeros(&self, shape: &[usize], dtype: DataType, device: &Device) -> Result<Tensor>;
222
223    /// Create tensor with random values
224    fn randn(&self, shape: &[usize], dtype: DataType, device: &Device) -> Result<Tensor>;
225
226    /// Exponential function
227    fn exp(&self, input: &Tensor) -> Result<Tensor>;
228
229    /// L2 normalization
230    fn normalize(&self, input: &Tensor, p: i32, dim: i32) -> Result<Tensor>;
231
232    /// Concatenate tensors along a dimension
233    fn concat(&self, tensors: &[&Tensor], dim: usize) -> Result<Tensor>;
234
235    /// RMS normalization
236    fn rms_norm(&self, input: &Tensor, weight: &Tensor, eps: f32) -> Result<Tensor>;
237
238    /// Scale tensor by a factor
239    fn scale(&self, input: &Tensor, factor: f32) -> Result<Tensor>;
240
241    /// Sigmoid activation: 1 / (1 + exp(-x))
242    fn sigmoid(&self, input: &Tensor) -> Result<Tensor>;
243
244    /// Top-k operation: returns (values, indices) for top k elements along dimension
245    fn topk(&self, input: &Tensor, k: usize, dim: i64) -> Result<(Tensor, Tensor)>;
246
247    /// 1D convolution
248    fn conv1d(&self, input: &Tensor, weight: &Tensor, bias: Option<&Tensor>, stride: usize, padding: usize) -> Result<Tensor>;
249
250    /// Tanh activation
251    fn tanh(&self, input: &Tensor) -> Result<Tensor>;
252
253    /// Element-wise subtraction
254    fn sub(&self, a: &Tensor, b: &Tensor) -> Result<Tensor>;
255
256    /// Clamp values to a range
257    fn clamp(&self, input: &Tensor, min: f32, max: f32) -> Result<Tensor>;
258
259    /// Gather elements along dimension using indices
260    fn gather(&self, input: &Tensor, dim: usize, indices: &Tensor) -> Result<Tensor>;
261
262    /// Scatter elements along dimension using indices
263    fn scatter(&self, input: &Tensor, dim: usize, indices: &Tensor, src: &Tensor) -> Result<Tensor>;
264
265    // ========== Fused Operations for Performance ==========
266
267    /// Fused scaled dot-product attention (Flash Attention pattern)
268    /// Computes: softmax(Q @ K^T / sqrt(d_k)) @ V in a memory-efficient manner
269    /// Works for all attention-based models: LLaMA, Qwen, Gemma, Mistral, etc.
270    fn flash_attention(
271        &self,
272        query: &Tensor,      // [batch, heads, seq_q, head_dim]
273        key: &Tensor,        // [batch, heads, seq_k, head_dim]
274        value: &Tensor,      // [batch, heads, seq_k, head_dim]
275        scale: f32,
276        causal: bool,
277    ) -> Result<Tensor>;
278
279    /// Fused SwiGLU activation: silu(gate) * up
280    /// Used by LLaMA, Qwen, Mistral, etc.
281    fn fused_swiglu(
282        &self,
283        gate: &Tensor,
284        up: &Tensor,
285    ) -> Result<Tensor>;
286
287    /// Fused residual add + RMS norm
288    /// Computes: rms_norm(residual + hidden, weight, eps)
289    fn fused_residual_rms_norm(
290        &self,
291        residual: &Tensor,
292        hidden: &Tensor,
293        weight: &Tensor,
294        eps: f32,
295    ) -> Result<Tensor>;
296}
297
298/// Tensor operation dispatcher - automatically selects CPU/GPU implementation
299pub struct TensorDispatcher {
300    cpu_ops: Box<dyn TensorOps>,
301    #[allow(dead_code)]
302    gpu_ops: Option<Box<dyn TensorOps>>,
303}
304
305impl Tensor {
306    /// Create new tensor with data
307    pub fn new(
308        shape: Vec<usize>,
309        dtype: DataType,
310        device: Device,
311        data: Arc<dyn TensorStorage>
312    ) -> Self {
313        Self {
314            data,
315            shape,
316            dtype,
317            device,
318        }
319    }
320
321    /// Get tensor shape
322    pub fn shape(&self) -> &[usize] {
323        &self.shape
324    }
325
326    /// Get tensor data type
327    pub fn dtype(&self) -> DataType {
328        self.dtype
329    }
330
331    /// Get tensor device
332    pub fn device(&self) -> &Device {
333        &self.device
334    }
335
336    /// Get number of elements
337    pub fn numel(&self) -> usize {
338        self.shape.iter().product()
339    }
340
341    /// Move tensor to device
342    pub fn to_device(&self, device: &Device) -> Result<Self> {
343        if &self.device == device {
344            return Ok(self.clone());
345        }
346
347        let new_storage = self.data.to_device(device)?;
348        Ok(Self {
349            data: new_storage,
350            shape: self.shape.clone(),
351            dtype: self.dtype,
352            device: device.clone(),
353        })
354    }
355
356    /// Reshape tensor
357    pub fn reshape(&self, new_shape: &[usize]) -> Result<Self> {
358        let old_numel = self.numel();
359        let new_numel: usize = new_shape.iter().product();
360
361        if old_numel != new_numel {
362            return Err(anyhow::anyhow!(
363                "Cannot reshape tensor: {} elements to {} elements",
364                old_numel, new_numel
365            ));
366        }
367
368        Ok(Self {
369            data: self.data.clone(),
370            shape: new_shape.to_vec(),
371            dtype: self.dtype,
372            device: self.device.clone(),
373        })
374    }
375
376    /// Narrow (slice) a tensor along a dimension
377    ///
378    /// Returns a view into the tensor containing elements from `start` to `start + length`
379    /// along the specified dimension.
380    pub fn narrow(&self, dim: usize, start: usize, length: usize) -> Result<Self> {
381        let candle_tensor = self.to_candle()?;
382        let narrowed = candle_tensor.narrow(dim, start, length)?;
383        Ok(Self::from_candle(narrowed))
384    }
385
386    /// Create tensor from Candle tensor
387    pub fn from_candle(candle_tensor: CandleTensor) -> Self {
388        let shape = candle_tensor.dims().to_vec();
389        let dtype = DataType::from_candle(candle_tensor.dtype());
390        let device = Device::from_candle(&candle_tensor.device());
391        let storage = Arc::new(CandleStorage::new(candle_tensor));
392        Self {
393            data: storage,
394            shape,
395            dtype,
396            device,
397        }
398    }
399
400    /// Try to get the underlying Candle tensor
401    pub fn as_candle(&self) -> Option<&CandleTensor> {
402        // Use the TensorStorage trait method to get the underlying Candle tensor
403        self.data.as_candle_tensor()
404    }
405
406    /// Convert to Candle tensor (may involve copying)
407    pub fn to_candle(&self) -> Result<CandleTensor> {
408        // First, try to get the underlying Candle tensor if this is CandleStorage
409        if let Some(candle_tensor) = self.data.as_candle_tensor() {
410            // Check if we need to reshape (Tensor::reshape updates shape but not underlying storage)
411            let candle_shape = candle_tensor.dims();
412            if candle_shape != &self.shape[..] {
413                // Need to reshape the Candle tensor to match our shape
414                let shape_slice: &[usize] = &self.shape;
415                return Ok(candle_tensor.reshape(shape_slice)?);
416            }
417            return Ok(candle_tensor.clone());
418        }
419
420        // Otherwise, create a new Candle tensor from the raw data
421        let candle_device = self.device.to_candle();
422        let candle_dtype = self.dtype.to_candle();
423        let shape: &[usize] = &self.shape;
424
425        // Get the raw data
426        let ptr = self.data.data_ptr();
427        if ptr.is_null() {
428            // No data available, create zeros
429            return Ok(CandleTensor::zeros(shape, candle_dtype, &candle_device)?);
430        }
431
432        let size_bytes = self.data.size_bytes();
433        let data_slice = unsafe { std::slice::from_raw_parts(ptr, size_bytes) };
434
435        // Convert based on dtype
436        match self.dtype {
437            DataType::Float32 => {
438                let float_data: &[f32] = bytemuck::cast_slice(data_slice);
439                Ok(CandleTensor::from_slice(float_data, shape, &candle_device)?)
440            }
441            DataType::Int64 => {
442                let int_data: &[i64] = bytemuck::cast_slice(data_slice);
443                Ok(CandleTensor::from_slice(int_data, shape, &candle_device)?)
444            }
445            DataType::Int32 => {
446                // Candle doesn't support i32 directly, convert to i64
447                let int_data: &[i32] = bytemuck::cast_slice(data_slice);
448                let int64_data: Vec<i64> = int_data.iter().map(|&x| x as i64).collect();
449                Ok(CandleTensor::from_slice(&int64_data, shape, &candle_device)?)
450            }
451            _ => {
452                // For other types, create zeros
453                Ok(CandleTensor::zeros(shape, candle_dtype, &candle_device)?)
454            }
455        }
456    }
457
458    /// Create tensor with zeros using Candle backend
459    pub fn zeros_candle(shape: &[usize], dtype: DataType, device: &Device) -> Result<Self> {
460        let storage = CandleStorage::zeros(shape, dtype, device)?;
461        Ok(Self {
462            data: Arc::new(storage.clone()),
463            shape: shape.to_vec(),
464            dtype,
465            device: device.clone(),
466        })
467    }
468
469    /// Create tensor from f32 slice using Candle backend
470    pub fn from_f32_slice(data: &[f32], shape: &[usize], device: &Device) -> Result<Self> {
471        let storage = CandleStorage::from_f32_slice(data, shape, device)?;
472        Ok(Self {
473            data: Arc::new(storage),
474            shape: shape.to_vec(),
475            dtype: DataType::Float32,
476            device: device.clone(),
477        })
478    }
479
480    /// Create tensor from i64 slice using Candle backend
481    pub fn from_i64_slice(data: &[i64], shape: &[usize], device: &Device) -> Result<Self> {
482        let storage = CandleStorage::from_i64_slice(data, shape, device)?;
483        Ok(Self {
484            data: Arc::new(storage),
485            shape: shape.to_vec(),
486            dtype: DataType::Int64,
487            device: device.clone(),
488        })
489    }
490
491    /// Get the raw data as f32 slice (if applicable)
492    pub fn to_vec_f32(&self) -> Result<Vec<f32>> {
493        let candle_tensor = self.to_candle()?;
494        let data = candle_tensor.to_vec1::<f32>()?;
495        Ok(data)
496    }
497
498    /// Transpose tensor (swap last two dimensions)
499    /// For 2D tensors [M, N] -> [N, M]
500    /// For higher dims [.., M, N] -> [.., N, M]
501    pub fn transpose(&self) -> Result<Self> {
502        let candle_tensor = self.to_candle()?;
503        let dims = candle_tensor.dims();
504        if dims.len() < 2 {
505            return Err(anyhow::anyhow!("Cannot transpose tensor with less than 2 dimensions"));
506        }
507        let transposed = candle_tensor.t()?;
508        Ok(Self::from_candle(transposed))
509    }
510
511    /// Transpose tensor with specific dimensions
512    pub fn transpose_dims(&self, dim0: usize, dim1: usize) -> Result<Self> {
513        let candle_tensor = self.to_candle()?;
514        let transposed = candle_tensor.transpose(dim0, dim1)?;
515        Ok(Self::from_candle(transposed))
516    }
517}
518
519impl CpuStorage {
520    pub fn new(data: Vec<u8>, device: Device) -> Self {
521        Self { data, device }
522    }
523
524    pub fn zeros(size_bytes: usize) -> Self {
525        Self {
526            data: vec![0u8; size_bytes],
527            device: Device::CPU,
528        }
529    }
530}
531
532impl TensorStorage for CpuStorage {
533    fn data_ptr(&self) -> *const u8 {
534        self.data.as_ptr()
535    }
536
537    fn data_ptr_mut(&mut self) -> *mut u8 {
538        self.data.as_mut_ptr()
539    }
540
541    fn size_bytes(&self) -> usize {
542        self.data.len()
543    }
544
545    fn device(&self) -> &Device {
546        &self.device
547    }
548
549    fn to_device(&self, device: &Device) -> Result<Arc<dyn TensorStorage>> {
550        match device {
551            Device::CPU => Ok(Arc::new(self.clone())),
552            Device::CUDA(_) | Device::Metal(_) => {
553                // TODO: Implement GPU transfer
554                Err(anyhow::anyhow!("GPU transfer not implemented yet"))
555            }
556        }
557    }
558
559    fn clone_storage(&self) -> Arc<dyn TensorStorage> {
560        Arc::new(self.clone())
561    }
562}
563
564impl Clone for CpuStorage {
565    fn clone(&self) -> Self {
566        Self {
567            data: self.data.clone(),
568            device: self.device.clone(),
569        }
570    }
571}
572
573unsafe impl Send for GpuStorage {}
574unsafe impl Sync for GpuStorage {}
575
576impl TensorStorage for GpuStorage {
577    fn data_ptr(&self) -> *const u8 {
578        self.ptr
579    }
580
581    fn data_ptr_mut(&mut self) -> *mut u8 {
582        self.ptr
583    }
584
585    fn size_bytes(&self) -> usize {
586        self.size
587    }
588
589    fn device(&self) -> &Device {
590        &self.device
591    }
592
593    fn to_device(&self, device: &Device) -> Result<Arc<dyn TensorStorage>> {
594        match device {
595            Device::CPU => {
596                // Basic GPU->CPU transfer: create CPU storage with same size
597                let cpu_storage = Arc::new(CpuStorage::zeros(self.size));
598                Ok(cpu_storage)
599            }
600            Device::CUDA(_) | Device::Metal(_) => {
601                // Basic GPU->GPU transfer: create new GPU storage with same size
602                let gpu_storage = Arc::new(GpuStorage {
603                    ptr: std::ptr::null_mut(),
604                    size: self.size,
605                    device: device.clone(),
606                });
607                Ok(gpu_storage)
608            }
609        }
610    }
611
612    fn clone_storage(&self) -> Arc<dyn TensorStorage> {
613        // Basic GPU storage cloning
614        Arc::new(GpuStorage {
615            ptr: self.ptr,
616            size: self.size,
617            device: self.device.clone(),
618        })
619    }
620}
621
622impl TensorDispatcher {
623    pub fn new() -> Self {
624        Self {
625            cpu_ops: Box::new(CpuTensorOpsImpl::new()),
626            gpu_ops: Some(Box::new(GpuTensorOpsImpl::new())), // Initialize basic GPU ops
627        }
628    }
629
630    /// Get appropriate tensor ops for the given tensors
631    fn get_ops(&self, tensors: &[&Tensor]) -> &dyn TensorOps {
632        // Check if any tensor is on GPU
633        for tensor in tensors {
634            match tensor.device() {
635                Device::CPU => continue,
636                Device::CUDA(_) | Device::Metal(_) => {
637                    // Use GPU ops if available
638                    if let Some(gpu_ops) = &self.gpu_ops {
639                        return gpu_ops.as_ref();
640                    }
641                }
642            }
643        }
644        self.cpu_ops.as_ref()
645    }
646}
647
648impl TensorOps for TensorDispatcher {
649    fn matmul(&self, a: &Tensor, b: &Tensor) -> Result<Tensor> {
650        let ops = self.get_ops(&[a, b]);
651        ops.matmul(a, b)
652    }
653
654    fn add(&self, a: &Tensor, b: &Tensor) -> Result<Tensor> {
655        let ops = self.get_ops(&[a, b]);
656        ops.add(a, b)
657    }
658
659    fn mul(&self, a: &Tensor, b: &Tensor) -> Result<Tensor> {
660        let ops = self.get_ops(&[a, b]);
661        ops.mul(a, b)
662    }
663
664    fn attention(
665        &self,
666        query: &Tensor,
667        key: &Tensor,
668        value: &Tensor,
669        mask: Option<&Tensor>,
670        scale: Option<f32>,
671    ) -> Result<Tensor> {
672        let tensors = if let Some(m) = mask {
673            vec![query, key, value, m]
674        } else {
675            vec![query, key, value]
676        };
677        let ops = self.get_ops(&tensors);
678        ops.attention(query, key, value, mask, scale)
679    }
680
681    fn layer_norm(
682        &self,
683        input: &Tensor,
684        weight: &Tensor,
685        bias: Option<&Tensor>,
686        eps: f32,
687    ) -> Result<Tensor> {
688        let tensors = if let Some(b) = bias {
689            vec![input, weight, b]
690        } else {
691            vec![input, weight]
692        };
693        let ops = self.get_ops(&tensors);
694        ops.layer_norm(input, weight, bias, eps)
695    }
696
697    fn gelu(&self, input: &Tensor) -> Result<Tensor> {
698        let ops = self.get_ops(&[input]);
699        ops.gelu(input)
700    }
701
702    fn silu(&self, input: &Tensor) -> Result<Tensor> {
703        let ops = self.get_ops(&[input]);
704        ops.silu(input)
705    }
706
707    fn softmax(&self, input: &Tensor, dim: isize) -> Result<Tensor> {
708        let ops = self.get_ops(&[input]);
709        ops.softmax(input, dim)
710    }
711
712    fn embedding(&self, indices: &Tensor, weight: &Tensor) -> Result<Tensor> {
713        let ops = self.get_ops(&[indices, weight]);
714        ops.embedding(indices, weight)
715    }
716
717    fn zeros(&self, shape: &[usize], dtype: DataType, device: &Device) -> Result<Tensor> {
718        match device {
719            Device::CPU => self.cpu_ops.zeros(shape, dtype, device),
720            Device::CUDA(_) | Device::Metal(_) => {
721                // TODO: Use GPU ops when available
722                self.cpu_ops.zeros(shape, dtype, &Device::CPU)
723            }
724        }
725    }
726
727    fn randn(&self, shape: &[usize], dtype: DataType, device: &Device) -> Result<Tensor> {
728        match device {
729            Device::CPU => self.cpu_ops.randn(shape, dtype, device),
730            Device::CUDA(_) | Device::Metal(_) => {
731                // TODO: Use GPU ops when available
732                self.cpu_ops.randn(shape, dtype, &Device::CPU)
733            }
734        }
735    }
736
737    fn exp(&self, input: &Tensor) -> Result<Tensor> {
738        let ops = self.get_ops(&[input]);
739        ops.exp(input)
740    }
741
742    fn normalize(&self, input: &Tensor, p: i32, dim: i32) -> Result<Tensor> {
743        let ops = self.get_ops(&[input]);
744        ops.normalize(input, p, dim)
745    }
746
747    fn concat(&self, tensors: &[&Tensor], dim: usize) -> Result<Tensor> {
748        let ops = self.get_ops(tensors);
749        ops.concat(tensors, dim)
750    }
751
752    fn rms_norm(&self, input: &Tensor, weight: &Tensor, eps: f32) -> Result<Tensor> {
753        let ops = self.get_ops(&[input, weight]);
754        ops.rms_norm(input, weight, eps)
755    }
756
757    fn scale(&self, input: &Tensor, factor: f32) -> Result<Tensor> {
758        let ops = self.get_ops(&[input]);
759        ops.scale(input, factor)
760    }
761
762    fn sigmoid(&self, input: &Tensor) -> Result<Tensor> {
763        let ops = self.get_ops(&[input]);
764        ops.sigmoid(input)
765    }
766
767    fn topk(&self, input: &Tensor, k: usize, dim: i64) -> Result<(Tensor, Tensor)> {
768        let ops = self.get_ops(&[input]);
769        ops.topk(input, k, dim)
770    }
771
772    fn conv1d(&self, input: &Tensor, weight: &Tensor, bias: Option<&Tensor>, stride: usize, padding: usize) -> Result<Tensor> {
773        let tensors = if let Some(b) = bias {
774            vec![input, weight, b]
775        } else {
776            vec![input, weight]
777        };
778        let ops = self.get_ops(&tensors);
779        ops.conv1d(input, weight, bias, stride, padding)
780    }
781
782    fn tanh(&self, input: &Tensor) -> Result<Tensor> {
783        let ops = self.get_ops(&[input]);
784        ops.tanh(input)
785    }
786
787    fn sub(&self, a: &Tensor, b: &Tensor) -> Result<Tensor> {
788        let ops = self.get_ops(&[a, b]);
789        ops.sub(a, b)
790    }
791
792    fn clamp(&self, input: &Tensor, min: f32, max: f32) -> Result<Tensor> {
793        let ops = self.get_ops(&[input]);
794        ops.clamp(input, min, max)
795    }
796
797    fn gather(&self, input: &Tensor, dim: usize, indices: &Tensor) -> Result<Tensor> {
798        let ops = self.get_ops(&[input, indices]);
799        ops.gather(input, dim, indices)
800    }
801
802    fn scatter(&self, input: &Tensor, dim: usize, indices: &Tensor, src: &Tensor) -> Result<Tensor> {
803        let ops = self.get_ops(&[input, indices, src]);
804        ops.scatter(input, dim, indices, src)
805    }
806
807    // Fused operations for performance
808    fn flash_attention(&self, query: &Tensor, key: &Tensor, value: &Tensor, scale: f32, causal: bool) -> Result<Tensor> {
809        let ops = self.get_ops(&[query, key, value]);
810        ops.flash_attention(query, key, value, scale, causal)
811    }
812
813    fn fused_swiglu(&self, gate: &Tensor, up: &Tensor) -> Result<Tensor> {
814        let ops = self.get_ops(&[gate, up]);
815        ops.fused_swiglu(gate, up)
816    }
817
818    fn fused_residual_rms_norm(&self, residual: &Tensor, hidden: &Tensor, weight: &Tensor, eps: f32) -> Result<Tensor> {
819        let ops = self.get_ops(&[residual, hidden, weight]);
820        ops.fused_residual_rms_norm(residual, hidden, weight, eps)
821    }
822}
823
824/// CPU implementation of tensor operations
825pub struct CpuTensorOpsImpl;
826
827/// Basic GPU implementation of tensor operations
828pub struct GpuTensorOpsImpl;
829
830impl CpuTensorOpsImpl {
831    pub fn new() -> Self {
832        Self
833    }
834}
835
836impl GpuTensorOpsImpl {
837    pub fn new() -> Self {
838        Self
839    }
840}
841
842impl TensorOps for CpuTensorOpsImpl {
843    fn matmul(&self, a: &Tensor, b: &Tensor) -> Result<Tensor> {
844        // Convert to Candle tensors
845        let a_candle = a.to_candle()?;
846        let b_candle = b.to_candle()?;
847
848        let a_dims = a_candle.dims();
849        let b_dims = b_candle.dims();
850
851        // Handle different dimension cases
852        let result_candle = match (a_dims.len(), b_dims.len()) {
853            (2, 2) => {
854                // Standard 2D matmul: (M, K) @ (K, N) -> (M, N)
855                a_candle.matmul(&b_candle)?
856            }
857            (3, 2) => {
858                // Batched matmul: (B, M, K) @ (K, N) -> (B, M, N)
859                // Reshape A to 2D, matmul, reshape back
860                let (batch, m, k) = (a_dims[0], a_dims[1], a_dims[2]);
861                let n = b_dims[1];
862
863                // Reshape [B, M, K] -> [B*M, K]
864                let a_flat = a_candle.reshape(&[batch * m, k])?;
865
866                // Matmul: [B*M, K] @ [K, N] -> [B*M, N]
867                let result_flat = a_flat.matmul(&b_candle)?;
868
869                // Reshape back: [B*M, N] -> [B, M, N]
870                result_flat.reshape(&[batch, m, n])?
871            }
872            (3, 3) => {
873                // Batched matmul: (B, M, K) @ (B, K, N) -> (B, M, N)
874                a_candle.matmul(&b_candle)?
875            }
876            (2, 3) => {
877                // (M, K) @ (B, K, N) -> (B, M, N)
878                // Broadcast A to 3D
879                let (m, k) = (a_dims[0], a_dims[1]);
880                let batch = b_dims[0];
881
882                let a_expanded = a_candle.unsqueeze(0)?.broadcast_as(&[batch, m, k])?;
883                a_expanded.matmul(&b_candle)?
884            }
885            _ => {
886                // Fallback to standard matmul, let Candle handle errors
887                a_candle.matmul(&b_candle)?
888            }
889        };
890
891        Ok(Tensor::from_candle(result_candle))
892    }
893
894    fn add(&self, a: &Tensor, b: &Tensor) -> Result<Tensor> {
895        // Convert to Candle tensors
896        let a_candle = a.to_candle()?;
897        let b_candle = b.to_candle()?;
898
899        // Handle broadcasting: if shapes don't match, try to broadcast
900        let result_candle = if a.shape() == b.shape() {
901            (&a_candle + &b_candle)?
902        } else {
903            // Try broadcast add
904            a_candle.broadcast_add(&b_candle)?
905        };
906
907        Ok(Tensor::from_candle(result_candle))
908    }
909
910    fn mul(&self, a: &Tensor, b: &Tensor) -> Result<Tensor> {
911        // Convert to Candle tensors
912        let a_candle = a.to_candle()?;
913        let b_candle = b.to_candle()?;
914
915        // Handle broadcasting
916        let result_candle = if a.shape() == b.shape() {
917            (&a_candle * &b_candle)?
918        } else {
919            a_candle.broadcast_mul(&b_candle)?
920        };
921
922        Ok(Tensor::from_candle(result_candle))
923    }
924
925    fn attention(
926        &self,
927        query: &Tensor,
928        key: &Tensor,
929        value: &Tensor,
930        mask: Option<&Tensor>,
931        scale: Option<f32>,
932    ) -> Result<Tensor> {
933        // Convert to Candle tensors
934        let q = query.to_candle()?;
935        let k = key.to_candle()?;
936        let v = value.to_candle()?;
937
938        // Get dimensions
939        let d_k = *q.dims().last().unwrap_or(&1);
940        let scale_factor = scale.unwrap_or(1.0 / (d_k as f32).sqrt());
941
942        // Compute attention scores: Q @ K^T
943        let k_t = k.transpose(k.dims().len() - 2, k.dims().len() - 1)?;
944        let scores = q.matmul(&k_t)?;
945
946        // Scale scores
947        let scaled_scores = (scores * scale_factor as f64)?;
948
949        // Apply mask if provided
950        let masked_scores = if let Some(m) = mask {
951            let mask_candle = m.to_candle()?;
952            // Where mask is 0, fill with -inf
953            let neg_inf = CandleTensor::new(&[-1e9f32], &CandleDevice::Cpu)?;
954            let neg_inf = neg_inf.broadcast_as(scaled_scores.dims())?;
955            let mask_expanded = mask_candle.broadcast_as(scaled_scores.dims())?;
956            // mask * scores + (1 - mask) * -inf
957            let mask_f32 = mask_expanded.to_dtype(DType::F32)?;
958            let inverted_mask = (1.0 - &mask_f32)?;
959            ((&mask_f32 * &scaled_scores)? + (&inverted_mask * &neg_inf)?)?
960        } else {
961            scaled_scores
962        };
963
964        // Softmax along last dimension
965        let attention_weights = candle_ops::softmax_last_dim(&masked_scores)?;
966
967        // Apply attention to values
968        let result = attention_weights.matmul(&v)?;
969
970        Ok(Tensor::from_candle(result))
971    }
972
973    fn layer_norm(
974        &self,
975        input: &Tensor,
976        weight: &Tensor,
977        bias: Option<&Tensor>,
978        eps: f32,
979    ) -> Result<Tensor> {
980        let x = input.to_candle()?;
981        let w = weight.to_candle()?;
982
983        // Get the last dimension for normalization
984        let last_dim = x.dims().len() - 1;
985
986        // Compute mean along last dimension
987        let mean = x.mean_keepdim(last_dim)?;
988
989        // Compute variance: E[(x - mean)^2]
990        let x_centered = x.broadcast_sub(&mean)?;
991        let variance = x_centered.sqr()?.mean_keepdim(last_dim)?;
992
993        // Normalize: (x - mean) / sqrt(variance + eps)
994        let std = (variance + eps as f64)?.sqrt()?;
995        let normalized = x_centered.broadcast_div(&std)?;
996
997        // Apply weight and bias
998        let scaled = normalized.broadcast_mul(&w)?;
999
1000        let result = if let Some(b) = bias {
1001            let b_candle = b.to_candle()?;
1002            scaled.broadcast_add(&b_candle)?
1003        } else {
1004            scaled
1005        };
1006
1007        Ok(Tensor::from_candle(result))
1008    }
1009
1010    fn gelu(&self, input: &Tensor) -> Result<Tensor> {
1011        let x = input.to_candle()?;
1012        let result = x.gelu()?;
1013        Ok(Tensor::from_candle(result))
1014    }
1015
1016    fn silu(&self, input: &Tensor) -> Result<Tensor> {
1017        let x = input.to_candle()?;
1018        let result = x.silu()?;
1019        Ok(Tensor::from_candle(result))
1020    }
1021
1022    fn softmax(&self, input: &Tensor, dim: isize) -> Result<Tensor> {
1023        let x = input.to_candle()?;
1024
1025        // Convert negative dimension to positive
1026        let ndims = x.dims().len() as isize;
1027        let actual_dim = if dim < 0 { ndims + dim } else { dim } as usize;
1028
1029        let result = if actual_dim == x.dims().len() - 1 {
1030            candle_ops::softmax_last_dim(&x)?
1031        } else {
1032            candle_ops::softmax(&x, actual_dim)?
1033        };
1034
1035        Ok(Tensor::from_candle(result))
1036    }
1037
1038    fn embedding(&self, indices: &Tensor, weight: &Tensor) -> Result<Tensor> {
1039        let idx = indices.to_candle()?;
1040        let w = weight.to_candle()?;
1041
1042        // Convert indices to u32 for Candle embedding
1043        let idx_u32 = idx.to_dtype(DType::U32)?;
1044
1045        // Flatten indices for embedding lookup
1046        let orig_shape = idx_u32.dims().to_vec();
1047        let flat_idx = idx_u32.flatten_all()?;
1048
1049        // Perform embedding lookup
1050        let embedded = w.embedding(&flat_idx)?;
1051
1052        // Reshape to original shape + embedding dim
1053        let embed_dim = w.dims()[1];
1054        let mut result_shape = orig_shape;
1055        result_shape.push(embed_dim);
1056
1057        let result = embedded.reshape(result_shape.as_slice())?;
1058
1059        Ok(Tensor::from_candle(result))
1060    }
1061
1062    fn zeros(&self, shape: &[usize], dtype: DataType, device: &Device) -> Result<Tensor> {
1063        let storage = CandleStorage::zeros(shape, dtype, device)?;
1064        Ok(Tensor::new(
1065            shape.to_vec(),
1066            dtype,
1067            device.clone(),
1068            Arc::new(storage),
1069        ))
1070    }
1071
1072    fn randn(&self, shape: &[usize], dtype: DataType, device: &Device) -> Result<Tensor> {
1073        let candle_device = device.to_candle();
1074        let candle_dtype = dtype.to_candle();
1075
1076        // Use Candle's random tensor generation
1077        let result = CandleTensor::randn(0f32, 1f32, shape, &candle_device)?
1078            .to_dtype(candle_dtype)?;
1079
1080        Ok(Tensor::from_candle(result))
1081    }
1082
1083    fn exp(&self, input: &Tensor) -> Result<Tensor> {
1084        let x = input.to_candle()?;
1085        let result = x.exp()?;
1086        Ok(Tensor::from_candle(result))
1087    }
1088
1089    fn normalize(&self, input: &Tensor, p: i32, dim: i32) -> Result<Tensor> {
1090        let x = input.to_candle()?;
1091
1092        // L2 normalization (p=2 is most common)
1093        let actual_dim = if dim < 0 {
1094            x.dims().len() as i32 + dim
1095        } else {
1096            dim
1097        } as usize;
1098
1099        if p == 2 {
1100            // L2 norm: x / ||x||_2
1101            let squared = x.sqr()?;
1102            let sum_squared = squared.sum_keepdim(actual_dim)?;
1103            let norm = sum_squared.sqrt()?;
1104            let normalized = x.broadcast_div(&norm)?;
1105            Ok(Tensor::from_candle(normalized))
1106        } else {
1107            // For other norms, just return input for now
1108            Ok(input.clone())
1109        }
1110    }
1111
1112    fn concat(&self, tensors: &[&Tensor], dim: usize) -> Result<Tensor> {
1113        if tensors.is_empty() {
1114            return Err(anyhow::anyhow!("Cannot concatenate empty tensor list"));
1115        }
1116
1117        // Convert all tensors to Candle
1118        let candle_tensors: Result<Vec<_>> = tensors.iter()
1119            .map(|t| t.to_candle())
1120            .collect();
1121        let candle_tensors = candle_tensors?;
1122
1123        // Create refs for Candle concat
1124        let refs: Vec<&CandleTensor> = candle_tensors.iter().collect();
1125
1126        let result = CandleTensor::cat(&refs, dim)?;
1127        Ok(Tensor::from_candle(result))
1128    }
1129
1130    fn rms_norm(&self, input: &Tensor, weight: &Tensor, eps: f32) -> Result<Tensor> {
1131        let x = input.to_candle()?;
1132        let w = weight.to_candle()?;
1133
1134        // RMS Norm: x * w / sqrt(mean(x^2) + eps)
1135        let last_dim = x.dims().len() - 1;
1136        let x_squared = x.sqr()?;
1137        let mean_squared = x_squared.mean_keepdim(last_dim)?;
1138        let rms = (mean_squared + eps as f64)?.sqrt()?;
1139        let normalized = x.broadcast_div(&rms)?;
1140        let result = normalized.broadcast_mul(&w)?;
1141
1142        Ok(Tensor::from_candle(result))
1143    }
1144
1145    fn scale(&self, input: &Tensor, factor: f32) -> Result<Tensor> {
1146        let x = input.to_candle()?;
1147        let result = (x * factor as f64)?;
1148        Ok(Tensor::from_candle(result))
1149    }
1150
1151    fn sigmoid(&self, input: &Tensor) -> Result<Tensor> {
1152        let x = input.to_candle()?;
1153        // sigmoid(x) = 1 / (1 + exp(-x))
1154        let neg_x = x.neg()?;
1155        let exp_neg_x = neg_x.exp()?;
1156        let one_plus_exp = (exp_neg_x + 1.0)?;
1157        let result = one_plus_exp.recip()?;
1158        Ok(Tensor::from_candle(result))
1159    }
1160
1161    fn topk(&self, input: &Tensor, k: usize, dim: i64) -> Result<(Tensor, Tensor)> {
1162        let x = input.to_candle()?;
1163        let ndims = x.dims().len() as i64;
1164        let actual_dim = if dim < 0 { ndims + dim } else { dim } as usize;
1165
1166        // Get the size of the dimension we're taking topk over
1167        let dim_size = x.dims()[actual_dim];
1168        if k > dim_size {
1169            return Err(anyhow::anyhow!("k ({}) is larger than dimension size ({})", k, dim_size));
1170        }
1171
1172        // For simple case: 2D tensor, topk on last dim
1173        // We'll implement a basic version that works for MoE routing
1174        if x.dims().len() == 2 && actual_dim == 1 {
1175            let (batch, _n) = (x.dims()[0], x.dims()[1]);
1176            let mut all_values = Vec::with_capacity(batch * k);
1177            let mut all_indices = Vec::with_capacity(batch * k);
1178
1179            for b in 0..batch {
1180                let row = x.get(b)?;
1181                let row_data: Vec<f32> = row.to_vec1()?;
1182
1183                // Get indices sorted by value (descending)
1184                let mut indexed: Vec<(usize, f32)> = row_data.iter().cloned().enumerate().collect();
1185                indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
1186
1187                // Take top k
1188                for i in 0..k {
1189                    all_values.push(indexed[i].1);
1190                    all_indices.push(indexed[i].0 as i64);
1191                }
1192            }
1193
1194            let device = input.device();
1195            let values = Tensor::from_f32_slice(&all_values, &[batch, k], device)?;
1196            let indices = Tensor::from_i64_slice(&all_indices, &[batch, k], device)?;
1197
1198            Ok((values, indices))
1199        } else if x.dims().len() == 1 {
1200            // 1D case
1201            let data: Vec<f32> = x.to_vec1()?;
1202            let mut indexed: Vec<(usize, f32)> = data.iter().cloned().enumerate().collect();
1203            indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
1204
1205            let values: Vec<f32> = indexed.iter().take(k).map(|(_, v)| *v).collect();
1206            let indices: Vec<i64> = indexed.iter().take(k).map(|(i, _)| *i as i64).collect();
1207
1208            let device = input.device();
1209            let values_tensor = Tensor::from_f32_slice(&values, &[k], device)?;
1210            let indices_tensor = Tensor::from_i64_slice(&indices, &[k], device)?;
1211
1212            Ok((values_tensor, indices_tensor))
1213        } else if x.dims().len() == 3 && actual_dim == 2 {
1214            // 3D case: [batch, seq, n] -> topk on last dim
1215            let (batch, seq_len, _n) = (x.dims()[0], x.dims()[1], x.dims()[2]);
1216            let mut all_values = Vec::with_capacity(batch * seq_len * k);
1217            let mut all_indices = Vec::with_capacity(batch * seq_len * k);
1218
1219            for b in 0..batch {
1220                for s in 0..seq_len {
1221                    let row = x.get(b)?.get(s)?;
1222                    let row_data: Vec<f32> = row.to_vec1()?;
1223
1224                    let mut indexed: Vec<(usize, f32)> = row_data.iter().cloned().enumerate().collect();
1225                    indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
1226
1227                    for i in 0..k {
1228                        all_values.push(indexed[i].1);
1229                        all_indices.push(indexed[i].0 as i64);
1230                    }
1231                }
1232            }
1233
1234            let device = input.device();
1235            let values = Tensor::from_f32_slice(&all_values, &[batch, seq_len, k], device)?;
1236            let indices = Tensor::from_i64_slice(&all_indices, &[batch, seq_len, k], device)?;
1237
1238            Ok((values, indices))
1239        } else {
1240            Err(anyhow::anyhow!("topk not implemented for tensor with {} dimensions on dim {}", x.dims().len(), actual_dim))
1241        }
1242    }
1243
1244    fn conv1d(&self, input: &Tensor, weight: &Tensor, bias: Option<&Tensor>, stride: usize, padding: usize) -> Result<Tensor> {
1245        let x = input.to_candle()?;
1246        let w = weight.to_candle()?;
1247
1248        // Input shape: [batch, in_channels, seq_len]
1249        // Weight shape: [out_channels, in_channels, kernel_size]
1250
1251        // Pad input if needed
1252        let x_padded = if padding > 0 {
1253            x.pad_with_zeros(2, padding, padding)?
1254        } else {
1255            x
1256        };
1257
1258        // Use Candle's conv1d
1259        let result = x_padded.conv1d(&w, padding, stride, 1, 1)?;
1260
1261        // Add bias if provided
1262        let result = if let Some(b) = bias {
1263            let b_candle = b.to_candle()?;
1264            // Bias shape: [out_channels] -> [1, out_channels, 1]
1265            let b_expanded = b_candle.unsqueeze(0)?.unsqueeze(2)?;
1266            result.broadcast_add(&b_expanded)?
1267        } else {
1268            result
1269        };
1270
1271        Ok(Tensor::from_candle(result))
1272    }
1273
1274    fn tanh(&self, input: &Tensor) -> Result<Tensor> {
1275        let x = input.to_candle()?;
1276        let result = x.tanh()?;
1277        Ok(Tensor::from_candle(result))
1278    }
1279
1280    fn sub(&self, a: &Tensor, b: &Tensor) -> Result<Tensor> {
1281        let a_candle = a.to_candle()?;
1282        let b_candle = b.to_candle()?;
1283
1284        let result = if a.shape() == b.shape() {
1285            (&a_candle - &b_candle)?
1286        } else {
1287            a_candle.broadcast_sub(&b_candle)?
1288        };
1289
1290        Ok(Tensor::from_candle(result))
1291    }
1292
1293    fn clamp(&self, input: &Tensor, min: f32, max: f32) -> Result<Tensor> {
1294        let x = input.to_candle()?;
1295        let result = x.clamp(min as f64, max as f64)?;
1296        Ok(Tensor::from_candle(result))
1297    }
1298
1299    fn gather(&self, input: &Tensor, dim: usize, indices: &Tensor) -> Result<Tensor> {
1300        let x = input.to_candle()?;
1301        let idx = indices.to_candle()?;
1302        let idx_u32 = idx.to_dtype(DType::U32)?;
1303        let result = x.gather(&idx_u32, dim)?;
1304        Ok(Tensor::from_candle(result))
1305    }
1306
1307    fn scatter(&self, input: &Tensor, dim: usize, indices: &Tensor, src: &Tensor) -> Result<Tensor> {
1308        let x = input.to_candle()?;
1309        let idx = indices.to_candle()?;
1310        let s = src.to_candle()?;
1311        let idx_u32 = idx.to_dtype(DType::U32)?;
1312        let result = x.scatter_add(&idx_u32, &s, dim)?;
1313        Ok(Tensor::from_candle(result))
1314    }
1315
1316    // ========== Fused Operations for Performance ==========
1317
1318    fn flash_attention(
1319        &self,
1320        query: &Tensor,
1321        key: &Tensor,
1322        value: &Tensor,
1323        scale: f32,
1324        causal: bool,
1325    ) -> Result<Tensor> {
1326        // Fused scaled dot-product attention
1327        // Optimized to reduce memory allocations and intermediate tensors
1328        let q = query.to_candle()?;
1329        let k = key.to_candle()?;
1330        let v = value.to_candle()?;
1331
1332        // Expected shapes: [batch, heads, seq, head_dim]
1333        let q_dims = q.dims();
1334        let k_dims = k.dims();
1335
1336        let seq_q = q_dims.get(2).copied().unwrap_or(1);
1337        let seq_k = k_dims.get(2).copied().unwrap_or(1);
1338
1339        // Compute Q @ K^T (transpose last two dims of K)
1340        let k_t = k.transpose(k_dims.len() - 2, k_dims.len() - 1)?;
1341        let scores = q.matmul(&k_t)?;
1342
1343        // Scale by provided factor
1344        let scaled_scores = (scores * scale as f64)?;
1345
1346        // Apply causal mask if requested
1347        let masked_scores = if causal && seq_q > 1 {
1348            // Create causal mask: lower triangular matrix
1349            // Position (i, j) is masked if j > i
1350            let mask_device = scaled_scores.device();
1351            let mut mask_data = vec![0.0f32; seq_q * seq_k];
1352            for i in 0..seq_q {
1353                for j in 0..seq_k {
1354                    if j > i {
1355                        mask_data[i * seq_k + j] = f32::NEG_INFINITY;
1356                    }
1357                }
1358            }
1359            let mask = CandleTensor::from_slice(&mask_data, &[seq_q, seq_k], mask_device)?;
1360
1361            // Broadcast mask to match scores shape
1362            let scores_shape = scaled_scores.dims();
1363            let mask_shape: Vec<usize> = scores_shape.iter()
1364                .take(scores_shape.len() - 2)
1365                .map(|_| 1)
1366                .chain([seq_q, seq_k])
1367                .collect();
1368            let mask_reshaped = mask.reshape(mask_shape.as_slice())?;
1369            let mask_broadcast = mask_reshaped.broadcast_as(scores_shape)?;
1370
1371            (&scaled_scores + &mask_broadcast)?
1372        } else {
1373            scaled_scores
1374        };
1375
1376        // Softmax along last dimension
1377        let attention_weights = candle_ops::softmax_last_dim(&masked_scores)?;
1378
1379        // Apply attention to values: weights @ V
1380        let result = attention_weights.matmul(&v)?;
1381
1382        Ok(Tensor::from_candle(result))
1383    }
1384
1385    fn fused_swiglu(&self, gate: &Tensor, up: &Tensor) -> Result<Tensor> {
1386        // Fused SwiGLU: silu(gate) * up
1387        // This avoids creating an intermediate tensor for silu output
1388        let g = gate.to_candle()?;
1389        let u = up.to_candle()?;
1390
1391        // SiLU(x) = x * sigmoid(x)
1392        let silu_gate = g.silu()?;
1393        let result = (&silu_gate * &u)?;
1394
1395        Ok(Tensor::from_candle(result))
1396    }
1397
1398    fn fused_residual_rms_norm(
1399        &self,
1400        residual: &Tensor,
1401        hidden: &Tensor,
1402        weight: &Tensor,
1403        eps: f32,
1404    ) -> Result<Tensor> {
1405        // Fused residual + RMS norm
1406        // Computes: rms_norm(residual + hidden, weight, eps)
1407        let r = residual.to_candle()?;
1408        let h = hidden.to_candle()?;
1409        let w = weight.to_candle()?;
1410
1411        // Add residual and hidden
1412        let combined = (&r + &h)?;
1413
1414        // RMS Norm: x * w / sqrt(mean(x^2) + eps)
1415        let last_dim = combined.dims().len() - 1;
1416        let x_squared = combined.sqr()?;
1417        let mean_squared = x_squared.mean_keepdim(last_dim)?;
1418        let rms = (mean_squared + eps as f64)?.sqrt()?;
1419        let normalized = combined.broadcast_div(&rms)?;
1420        let result = normalized.broadcast_mul(&w)?;
1421
1422        Ok(Tensor::from_candle(result))
1423    }
1424}
1425
1426impl DataType {
1427    pub fn size_bytes(&self) -> usize {
1428        match self {
1429            DataType::Float32 => 4,
1430            DataType::Float16 => 2,
1431            DataType::BFloat16 => 2,
1432            DataType::Int32 => 4,
1433            DataType::Int64 => 8,
1434            DataType::Int8 => 1,
1435            DataType::Bool => 1,
1436        }
1437    }
1438
1439    /// Convert to Candle DType
1440    pub fn to_candle(&self) -> DType {
1441        match self {
1442            DataType::Float32 => DType::F32,
1443            DataType::Float16 => DType::F16,
1444            DataType::BFloat16 => DType::BF16,
1445            DataType::Int32 => DType::U32, // Candle uses U32 instead of I32
1446            DataType::Int64 => DType::I64,
1447            DataType::Int8 => DType::U8,   // Candle uses U8 instead of I8
1448            DataType::Bool => DType::U8,   // Candle uses U8 for bool
1449        }
1450    }
1451
1452    /// Convert from Candle DType
1453    pub fn from_candle(dtype: DType) -> Self {
1454        match dtype {
1455            DType::F32 => DataType::Float32,
1456            DType::F16 => DataType::Float16,
1457            DType::BF16 => DataType::BFloat16,
1458            DType::I64 => DataType::Int64,
1459            DType::U8 => DataType::Int8,
1460            DType::F64 => DataType::Float32, // Downcast f64 to f32
1461            DType::U32 => DataType::Int32,   // Map unsigned to signed
1462        }
1463    }
1464}
1465
1466impl Device {
1467    /// Convert to Candle Device
1468    pub fn to_candle(&self) -> CandleDevice {
1469        match self {
1470            Device::CPU => CandleDevice::Cpu,
1471            Device::CUDA(id) => CandleDevice::cuda_if_available(*id).unwrap_or(CandleDevice::Cpu),
1472            Device::Metal(id) => {
1473                #[cfg(feature = "metal")]
1474                {
1475                    CandleDevice::new_metal(*id).unwrap_or(CandleDevice::Cpu)
1476                }
1477                #[cfg(not(feature = "metal"))]
1478                {
1479                    let _ = id;
1480                    CandleDevice::Cpu
1481                }
1482            }
1483        }
1484    }
1485
1486    /// Convert from Candle Device
1487    pub fn from_candle(device: &CandleDevice) -> Self {
1488        match device {
1489            CandleDevice::Cpu => Device::CPU,
1490            CandleDevice::Cuda(_) => Device::CUDA(0), // Default to device 0
1491            CandleDevice::Metal(_) => Device::Metal(0), // Default to device 0
1492        }
1493    }
1494}
1495
1496/// Global tensor operation dispatcher
1497static TENSOR_OPS: std::sync::OnceLock<TensorDispatcher> = std::sync::OnceLock::new();
1498
1499/// Get global tensor operations
1500pub fn ops() -> &'static TensorDispatcher {
1501    TENSOR_OPS.get_or_init(|| TensorDispatcher::new())
1502}
1503
1504/// Basic GPU implementation of tensor operations
1505/// For now, this just delegates to CPU implementation
1506impl TensorOps for GpuTensorOpsImpl {
1507    fn matmul(&self, a: &Tensor, b: &Tensor) -> Result<Tensor> {
1508        // For now, delegate to CPU implementation
1509        CpuTensorOpsImpl.matmul(a, b)
1510    }
1511
1512    fn add(&self, a: &Tensor, b: &Tensor) -> Result<Tensor> {
1513        CpuTensorOpsImpl.add(a, b)
1514    }
1515
1516    fn mul(&self, a: &Tensor, b: &Tensor) -> Result<Tensor> {
1517        CpuTensorOpsImpl.mul(a, b)
1518    }
1519
1520    fn attention(
1521        &self,
1522        query: &Tensor,
1523        key: &Tensor,
1524        value: &Tensor,
1525        mask: Option<&Tensor>,
1526        scale: Option<f32>,
1527    ) -> Result<Tensor> {
1528        CpuTensorOpsImpl.attention(query, key, value, mask, scale)
1529    }
1530
1531    fn layer_norm(
1532        &self,
1533        input: &Tensor,
1534        weight: &Tensor,
1535        bias: Option<&Tensor>,
1536        eps: f32,
1537    ) -> Result<Tensor> {
1538        CpuTensorOpsImpl.layer_norm(input, weight, bias, eps)
1539    }
1540
1541    fn gelu(&self, input: &Tensor) -> Result<Tensor> {
1542        CpuTensorOpsImpl.gelu(input)
1543    }
1544
1545    fn silu(&self, input: &Tensor) -> Result<Tensor> {
1546        CpuTensorOpsImpl.silu(input)
1547    }
1548
1549    fn softmax(&self, input: &Tensor, dim: isize) -> Result<Tensor> {
1550        CpuTensorOpsImpl.softmax(input, dim)
1551    }
1552
1553    fn embedding(&self, indices: &Tensor, weight: &Tensor) -> Result<Tensor> {
1554        CpuTensorOpsImpl.embedding(indices, weight)
1555    }
1556
1557    fn zeros(&self, shape: &[usize], dtype: DataType, device: &Device) -> Result<Tensor> {
1558        CpuTensorOpsImpl.zeros(shape, dtype, device)
1559    }
1560
1561    fn randn(&self, shape: &[usize], dtype: DataType, device: &Device) -> Result<Tensor> {
1562        CpuTensorOpsImpl.randn(shape, dtype, device)
1563    }
1564
1565    fn exp(&self, input: &Tensor) -> Result<Tensor> {
1566        CpuTensorOpsImpl.exp(input)
1567    }
1568
1569    fn normalize(&self, input: &Tensor, p: i32, dim: i32) -> Result<Tensor> {
1570        CpuTensorOpsImpl.normalize(input, p, dim)
1571    }
1572
1573    fn concat(&self, tensors: &[&Tensor], dim: usize) -> Result<Tensor> {
1574        CpuTensorOpsImpl.concat(tensors, dim)
1575    }
1576
1577    fn rms_norm(&self, input: &Tensor, weight: &Tensor, eps: f32) -> Result<Tensor> {
1578        CpuTensorOpsImpl.rms_norm(input, weight, eps)
1579    }
1580
1581    fn scale(&self, input: &Tensor, factor: f32) -> Result<Tensor> {
1582        CpuTensorOpsImpl.scale(input, factor)
1583    }
1584
1585    fn sigmoid(&self, input: &Tensor) -> Result<Tensor> {
1586        CpuTensorOpsImpl.sigmoid(input)
1587    }
1588
1589    fn topk(&self, input: &Tensor, k: usize, dim: i64) -> Result<(Tensor, Tensor)> {
1590        CpuTensorOpsImpl.topk(input, k, dim)
1591    }
1592
1593    fn conv1d(&self, input: &Tensor, weight: &Tensor, bias: Option<&Tensor>, stride: usize, padding: usize) -> Result<Tensor> {
1594        CpuTensorOpsImpl.conv1d(input, weight, bias, stride, padding)
1595    }
1596
1597    fn tanh(&self, input: &Tensor) -> Result<Tensor> {
1598        CpuTensorOpsImpl.tanh(input)
1599    }
1600
1601    fn sub(&self, a: &Tensor, b: &Tensor) -> Result<Tensor> {
1602        CpuTensorOpsImpl.sub(a, b)
1603    }
1604
1605    fn clamp(&self, input: &Tensor, min: f32, max: f32) -> Result<Tensor> {
1606        CpuTensorOpsImpl.clamp(input, min, max)
1607    }
1608
1609    fn gather(&self, input: &Tensor, dim: usize, indices: &Tensor) -> Result<Tensor> {
1610        CpuTensorOpsImpl.gather(input, dim, indices)
1611    }
1612
1613    fn scatter(&self, input: &Tensor, dim: usize, indices: &Tensor, src: &Tensor) -> Result<Tensor> {
1614        CpuTensorOpsImpl.scatter(input, dim, indices, src)
1615    }
1616
1617    // Fused operations - delegate to CPU implementation for now
1618    fn flash_attention(&self, query: &Tensor, key: &Tensor, value: &Tensor, scale: f32, causal: bool) -> Result<Tensor> {
1619        CpuTensorOpsImpl.flash_attention(query, key, value, scale, causal)
1620    }
1621
1622    fn fused_swiglu(&self, gate: &Tensor, up: &Tensor) -> Result<Tensor> {
1623        CpuTensorOpsImpl.fused_swiglu(gate, up)
1624    }
1625
1626    fn fused_residual_rms_norm(&self, residual: &Tensor, hidden: &Tensor, weight: &Tensor, eps: f32) -> Result<Tensor> {
1627        CpuTensorOpsImpl.fused_residual_rms_norm(residual, hidden, weight, eps)
1628    }
1629}
1630
1631/// Convenience functions for tensor operations
1632pub mod ops_fn {
1633    use super::*;
1634
1635    pub fn matmul(a: &Tensor, b: &Tensor) -> Result<Tensor> {
1636        ops().matmul(a, b)
1637    }
1638
1639    pub fn add(a: &Tensor, b: &Tensor) -> Result<Tensor> {
1640        ops().add(a, b)
1641    }
1642
1643    pub fn zeros(shape: &[usize], dtype: DataType, device: &Device) -> Result<Tensor> {
1644        ops().zeros(shape, dtype, device)
1645    }
1646
1647    pub fn randn(shape: &[usize], dtype: DataType, device: &Device) -> Result<Tensor> {
1648        ops().randn(shape, dtype, device)
1649    }
1650
1651    pub fn layer_norm(input: &Tensor, weight: &Tensor, bias: Option<&Tensor>, eps: f32) -> Result<Tensor> {
1652        ops().layer_norm(input, weight, bias, eps)
1653    }
1654
1655    pub fn embedding(indices: &Tensor, weight: &Tensor) -> Result<Tensor> {
1656        ops().embedding(indices, weight)
1657    }
1658
1659    pub fn silu(input: &Tensor) -> Result<Tensor> {
1660        ops().silu(input)
1661    }
1662
1663    pub fn mul(a: &Tensor, b: &Tensor) -> Result<Tensor> {
1664        ops().mul(a, b)
1665    }
1666
1667    pub fn attention(query: &Tensor, key: &Tensor, value: &Tensor, mask: Option<&Tensor>) -> Result<Tensor> {
1668        ops().attention(query, key, value, mask, None)
1669    }
1670
1671    pub fn gelu(input: &Tensor) -> Result<Tensor> {
1672        ops().gelu(input)
1673    }
1674
1675    pub fn exp(input: &Tensor) -> Result<Tensor> {
1676        ops().exp(input)
1677    }
1678
1679    pub fn normalize(input: &Tensor, p: i32, dim: i32) -> Result<Tensor> {
1680        ops().normalize(input, p, dim)
1681    }
1682
1683    pub fn concat(tensors: &[&Tensor], dim: usize) -> Result<Tensor> {
1684        ops().concat(tensors, dim)
1685    }
1686
1687    pub fn rms_norm(input: &Tensor, weight: &Tensor, eps: f32) -> Result<Tensor> {
1688        ops().rms_norm(input, weight, eps)
1689    }
1690
1691    pub fn scale(input: &Tensor, factor: f32) -> Result<Tensor> {
1692        ops().scale(input, factor)
1693    }
1694
1695    /// Transpose tensor (swap last two dimensions)
1696    pub fn transpose(input: &Tensor) -> Result<Tensor> {
1697        input.transpose()
1698    }
1699
1700    /// Transpose tensor with specific dimensions
1701    pub fn transpose_dims(input: &Tensor, dim0: usize, dim1: usize) -> Result<Tensor> {
1702        input.transpose_dims(dim0, dim1)
1703    }
1704
1705    /// Sigmoid activation
1706    pub fn sigmoid(input: &Tensor) -> Result<Tensor> {
1707        ops().sigmoid(input)
1708    }
1709
1710    /// Top-k operation
1711    pub fn topk(input: &Tensor, k: usize, dim: i64) -> Result<(Tensor, Tensor)> {
1712        ops().topk(input, k, dim)
1713    }
1714
1715    /// 1D convolution
1716    pub fn conv1d(input: &Tensor, weight: &Tensor, bias: Option<&Tensor>, stride: usize, padding: usize) -> Result<Tensor> {
1717        ops().conv1d(input, weight, bias, stride, padding)
1718    }
1719
1720    /// Tanh activation
1721    pub fn tanh(input: &Tensor) -> Result<Tensor> {
1722        ops().tanh(input)
1723    }
1724
1725    /// Element-wise subtraction
1726    pub fn sub(a: &Tensor, b: &Tensor) -> Result<Tensor> {
1727        ops().sub(a, b)
1728    }
1729
1730    /// Clamp values to a range
1731    pub fn clamp(input: &Tensor, min: f32, max: f32) -> Result<Tensor> {
1732        ops().clamp(input, min, max)
1733    }
1734
1735    /// Gather elements along dimension
1736    pub fn gather(input: &Tensor, dim: usize, indices: &Tensor) -> Result<Tensor> {
1737        ops().gather(input, dim, indices)
1738    }
1739
1740    /// Scatter elements along dimension
1741    pub fn scatter(input: &Tensor, dim: usize, indices: &Tensor, src: &Tensor) -> Result<Tensor> {
1742        ops().scatter(input, dim, indices, src)
1743    }
1744
1745    /// Softmax along specified dimension
1746    pub fn softmax(input: &Tensor, dim: isize) -> Result<Tensor> {
1747        ops().softmax(input, dim)
1748    }
1749
1750    /// Create a causal attention mask (lower triangular)
1751    pub fn causal_mask(seq_len: usize, device: &Device) -> Result<Tensor> {
1752        let mut mask_data = vec![0.0f32; seq_len * seq_len];
1753        for i in 0..seq_len {
1754            for j in 0..=i {
1755                mask_data[i * seq_len + j] = 1.0;
1756            }
1757        }
1758        Tensor::from_f32_slice(&mask_data, &[seq_len, seq_len], device)
1759    }
1760
1761    /// Create a sliding window attention mask
1762    /// Returns a mask where 1.0 means "attend" and 0.0 means "don't attend"
1763    /// Each position can only attend to positions within window_size positions before it
1764    pub fn sliding_window_mask(seq_len: usize, window_size: usize, device: &Device) -> Result<Tensor> {
1765        let mut mask_data = vec![0.0f32; seq_len * seq_len];
1766        for i in 0..seq_len {
1767            // Can attend to positions from max(0, i - window_size + 1) to i (inclusive)
1768            let start = if i >= window_size { i - window_size + 1 } else { 0 };
1769            for j in start..=i {
1770                mask_data[i * seq_len + j] = 1.0;
1771            }
1772        }
1773        Tensor::from_f32_slice(&mask_data, &[seq_len, seq_len], device)
1774    }
1775
1776    /// Create a combined causal + sliding window mask
1777    /// This is the typical mask used in Mistral/Mixtral
1778    pub fn causal_sliding_window_mask(seq_len: usize, window_size: usize, device: &Device) -> Result<Tensor> {
1779        // Same as sliding_window_mask since sliding window is already causal
1780        sliding_window_mask(seq_len, window_size, device)
1781    }
1782
1783    // ========== Fused Operations for Performance ==========
1784    // These operations combine multiple ops to reduce memory allocations
1785    // and improve cache locality. Benefits all model architectures.
1786
1787    /// Fused scaled dot-product attention (Flash Attention pattern)
1788    /// Computes: softmax(Q @ K^T / sqrt(d_k)) @ V
1789    /// Works for: LLaMA, Qwen, Gemma, Mistral, Phi, and all attention-based models
1790    ///
1791    /// # Arguments
1792    /// * `query` - Query tensor [batch, heads, seq_q, head_dim]
1793    /// * `key` - Key tensor [batch, heads, seq_k, head_dim]
1794    /// * `value` - Value tensor [batch, heads, seq_k, head_dim]
1795    /// * `scale` - Scaling factor (typically 1/sqrt(head_dim))
1796    /// * `causal` - Whether to apply causal masking
1797    pub fn flash_attention(
1798        query: &Tensor,
1799        key: &Tensor,
1800        value: &Tensor,
1801        scale: f32,
1802        causal: bool,
1803    ) -> Result<Tensor> {
1804        ops().flash_attention(query, key, value, scale, causal)
1805    }
1806
1807    /// Fused SwiGLU activation: silu(gate) * up
1808    /// Used by: LLaMA, Qwen, Mistral, and other modern transformer MLPs
1809    pub fn fused_swiglu(gate: &Tensor, up: &Tensor) -> Result<Tensor> {
1810        ops().fused_swiglu(gate, up)
1811    }
1812
1813    /// Fused residual add + RMS normalization
1814    /// Computes: rms_norm(residual + hidden, weight, eps)
1815    /// Used by: All transformer models with pre-normalization
1816    pub fn fused_residual_rms_norm(
1817        residual: &Tensor,
1818        hidden: &Tensor,
1819        weight: &Tensor,
1820        eps: f32,
1821    ) -> Result<Tensor> {
1822        ops().fused_residual_rms_norm(residual, hidden, weight, eps)
1823    }
1824}