1use oxmera_core::{Device, Error, Result, Shape};
7
8use crate::autograd::{GradFn, is_recording};
9use crate::backend::{Backend, BinaryOp, ReduceOp, UnaryOp, backend_for};
10use crate::tensor::{Tensor, ViewKind};
11
12use std::sync::Arc;
13
14fn same_device(a: &Tensor, b: &Tensor, op: &'static str) -> Result<Device> {
15 if a.device() != b.device() {
16 return Err(Error::DeviceMismatch {
17 lhs: a.device(),
18 rhs: b.device(),
19 op,
20 });
21 }
22 Ok(a.device())
23}
24
25fn record(
26 out: Tensor,
27 inputs: Vec<Tensor>,
28 vjp: impl Fn(&Tensor) -> Result<Vec<Option<Tensor>>> + Send + Sync + 'static,
29) -> Tensor {
30 if is_recording() && inputs.iter().any(Tensor::is_tracked) {
31 out.with_grad_fn(GradFn {
32 inputs,
33 vjp: Box::new(vjp),
34 })
35 } else {
36 out
37 }
38}
39
40pub(crate) fn reduce_to_shape(grad: &Tensor, shape: &Shape) -> Result<Tensor> {
43 if grad.shape() == shape {
44 return Ok(grad.clone());
45 }
46 let gdims = grad.dims().to_vec();
47 let tdims = shape.dims();
48 let lead = gdims.len() - tdims.len();
49 let mut axes: Vec<usize> = (0..lead).collect();
50 for (i, &td) in tdims.iter().enumerate() {
51 if td == 1 && gdims[lead + i] != 1 {
52 axes.push(lead + i);
53 }
54 }
55 let reduced = if axes.is_empty() {
56 grad.clone()
57 } else {
58 grad.sum_keepdim(&axes, true)?
59 };
60 reduced.reshape(shape.clone())
61}
62
63pub(crate) fn record_view(input: &Tensor, out: Tensor, kind: ViewKind) -> Tensor {
65 let in_shape = input.shape().clone();
66 record(out, vec![input.clone()], move |g| {
67 let gi = match &kind {
68 ViewKind::Reshape | ViewKind::Contiguous => g.reshape(in_shape.clone())?,
69 ViewKind::Permute(perm) => {
70 let mut inverse = vec![0usize; perm.len()];
71 for (i, &p) in perm.iter().enumerate() {
72 inverse[p] = i;
73 }
74 g.permute(&inverse)?
75 }
76 ViewKind::Narrow { dim, start, len } => {
77 let indices: Vec<i64> = (*start..start + len).map(|i| i as i64).collect();
78 let indices = Tensor::from_vec_i64(indices, Shape::from([*len]))?;
79 Tensor::zeros(in_shape.clone())
80 .to_device(g.device())?
81 .index_add(*dim, &indices, g)?
82 }
83 ViewKind::Broadcast => reduce_to_shape(g, &in_shape)?,
84 };
85 Ok(vec![Some(gi)])
86 })
87}
88
89impl Tensor {
90 fn backend(&self) -> Result<Arc<dyn Backend>> {
91 backend_for(self.device())
92 }
93
94 fn unary_op(&self, op: UnaryOp) -> Result<Tensor> {
97 let out = self.backend()?.unary(op, self)?;
98 let a = self.clone();
99 let o = out.clone();
100 Ok(record(out, vec![self.clone()], move |g| {
101 let gi = match op {
102 UnaryOp::Neg => g.neg()?,
103 UnaryOp::Exp => g.mul(&o)?,
104 UnaryOp::Ln => g.div(&a)?,
105 UnaryOp::Abs => {
106 let sign = a
107 .gt_mask(&Tensor::scalar_on(&a, 0.0)?)?
108 .sub(&Tensor::scalar_on(&a, 0.0)?.gt_mask(&a)?)?;
109 g.mul(&sign)?
110 }
111 UnaryOp::Sqrt => g.mul(&Tensor::scalar_on(&a, 0.5)?)?.div(&o)?,
112 UnaryOp::Sin => g.mul(&a.cos()?)?,
113 UnaryOp::Cos => g.mul(&a.sin()?.neg()?)?,
114 UnaryOp::Tanh => {
115 let one = Tensor::scalar_on(&a, 1.0)?;
116 g.mul(&one.sub(&o.mul(&o)?)?)?
117 }
118 UnaryOp::Relu => g.mul(&a.gt_mask(&Tensor::scalar_on(&a, 0.0)?)?)?,
119 UnaryOp::Gelu => {
120 let c = Tensor::scalar_on(&a, 0.797_884_6)?;
123 let k = Tensor::scalar_on(&a, 0.044_715)?;
124 let one = Tensor::scalar_on(&a, 1.0)?;
125 let half = Tensor::scalar_on(&a, 0.5)?;
126 let three_k = Tensor::scalar_on(&a, 3.0 * 0.044_715)?;
127 let x2 = a.mul(&a)?;
128 let u = c.mul(&a.add(&k.mul(&x2.mul(&a)?)?)?)?;
129 let t = u.tanh()?;
130 let sech2 = one.sub(&t.mul(&t)?)?;
131 let du = c.mul(&one.add(&three_k.mul(&x2)?)?)?;
132 let d = half
133 .mul(&one.add(&t)?)?
134 .add(&half.mul(&a)?.mul(&sech2)?.mul(&du)?)?;
135 g.mul(&d)?
136 }
137 UnaryOp::Sigmoid => {
138 let one = Tensor::scalar_on(&a, 1.0)?;
139 g.mul(&o)?.mul(&one.sub(&o)?)?
140 }
141 };
142 Ok(vec![Some(gi)])
143 }))
144 }
145
146 pub fn neg(&self) -> Result<Tensor> {
148 self.unary_op(UnaryOp::Neg)
149 }
150 pub fn exp(&self) -> Result<Tensor> {
152 self.unary_op(UnaryOp::Exp)
153 }
154 pub fn ln(&self) -> Result<Tensor> {
156 self.unary_op(UnaryOp::Ln)
157 }
158 pub fn abs(&self) -> Result<Tensor> {
160 self.unary_op(UnaryOp::Abs)
161 }
162 pub fn sqrt(&self) -> Result<Tensor> {
164 self.unary_op(UnaryOp::Sqrt)
165 }
166 pub fn sin(&self) -> Result<Tensor> {
168 self.unary_op(UnaryOp::Sin)
169 }
170 pub fn cos(&self) -> Result<Tensor> {
172 self.unary_op(UnaryOp::Cos)
173 }
174 pub fn tanh(&self) -> Result<Tensor> {
176 self.unary_op(UnaryOp::Tanh)
177 }
178 pub fn relu(&self) -> Result<Tensor> {
180 self.unary_op(UnaryOp::Relu)
181 }
182 pub fn gelu(&self) -> Result<Tensor> {
184 self.unary_op(UnaryOp::Gelu)
185 }
186 pub fn sigmoid(&self) -> Result<Tensor> {
188 self.unary_op(UnaryOp::Sigmoid)
189 }
190
191 pub fn scalar_on(like: &Tensor, value: f32) -> Result<Tensor> {
194 Tensor::scalar(value).to_device(like.device())
195 }
196
197 fn binary_op(&self, op: BinaryOp, rhs: &Tensor) -> Result<Tensor> {
200 let device = same_device(self, rhs, "binary")?;
201 let out = backend_for(device)?.binary(op, self, rhs)?;
202 let (a, b) = (self.clone(), rhs.clone());
203 let o = out.clone();
204 Ok(record(out, vec![self.clone(), rhs.clone()], move |g| {
205 let (ga, gb): (Option<Tensor>, Option<Tensor>) = match op {
206 BinaryOp::Add => (Some(g.clone()), Some(g.clone())),
207 BinaryOp::Sub => (Some(g.clone()), Some(g.neg()?)),
208 BinaryOp::Mul => (Some(g.mul_raw(&b)?), Some(g.mul_raw(&a)?)),
209 BinaryOp::Div => {
210 let ga = g.div_raw(&b)?;
211 let gb = g.mul_raw(&o)?.div_raw(&b)?.neg()?;
212 (Some(ga), Some(gb))
213 }
214 BinaryOp::Pow => {
215 let one = Tensor::scalar_on(&a, 1.0)?;
216 let ga = g.mul_raw(&b)?.mul_raw(&a.pow(&b.sub(&one)?)?)?;
217 let gb = g.mul_raw(&o)?.mul_raw(&a.ln()?)?;
218 (Some(ga), Some(gb))
219 }
220 BinaryOp::Maximum => {
221 let mask = a.gt_mask(&b)?;
222 let one = Tensor::scalar_on(&a, 1.0)?;
223 let ga = g.mul_raw(&mask)?;
224 let gb = g.mul_raw(&one.sub(&mask)?)?;
225 (Some(ga), Some(gb))
226 }
227 BinaryOp::Minimum => {
228 let mask = b.gt_mask(&a)?;
229 let one = Tensor::scalar_on(&a, 1.0)?;
230 let ga = g.mul_raw(&mask)?;
231 let gb = g.mul_raw(&one.sub(&mask)?)?;
232 (Some(ga), Some(gb))
233 }
234 BinaryOp::Gt | BinaryOp::Eq => (None, None),
235 };
236 let ga = match ga {
237 Some(t) => Some(reduce_to_shape(&t, a.shape())?),
238 None => None,
239 };
240 let gb = match gb {
241 Some(t) => Some(reduce_to_shape(&t, b.shape())?),
242 None => None,
243 };
244 Ok(vec![ga, gb])
245 }))
246 }
247
248 fn mul_raw(&self, rhs: &Tensor) -> Result<Tensor> {
251 let device = same_device(self, rhs, "mul")?;
252 backend_for(device)?.binary(BinaryOp::Mul, self, rhs)
253 }
254
255 fn div_raw(&self, rhs: &Tensor) -> Result<Tensor> {
256 let device = same_device(self, rhs, "div")?;
257 backend_for(device)?.binary(BinaryOp::Div, self, rhs)
258 }
259
260 pub fn add(&self, rhs: &Tensor) -> Result<Tensor> {
262 self.binary_op(BinaryOp::Add, rhs)
263 }
264 pub fn sub(&self, rhs: &Tensor) -> Result<Tensor> {
266 self.binary_op(BinaryOp::Sub, rhs)
267 }
268 pub fn mul(&self, rhs: &Tensor) -> Result<Tensor> {
270 self.binary_op(BinaryOp::Mul, rhs)
271 }
272 pub fn div(&self, rhs: &Tensor) -> Result<Tensor> {
274 self.binary_op(BinaryOp::Div, rhs)
275 }
276 pub fn pow(&self, rhs: &Tensor) -> Result<Tensor> {
278 self.binary_op(BinaryOp::Pow, rhs)
279 }
280 pub fn maximum(&self, rhs: &Tensor) -> Result<Tensor> {
282 self.binary_op(BinaryOp::Maximum, rhs)
283 }
284 pub fn minimum(&self, rhs: &Tensor) -> Result<Tensor> {
286 self.binary_op(BinaryOp::Minimum, rhs)
287 }
288 pub fn gt_mask(&self, rhs: &Tensor) -> Result<Tensor> {
290 self.binary_op(BinaryOp::Gt, rhs)
291 }
292 pub fn eq_mask(&self, rhs: &Tensor) -> Result<Tensor> {
294 self.binary_op(BinaryOp::Eq, rhs)
295 }
296
297 pub fn add_scalar(&self, s: f32) -> Result<Tensor> {
299 self.add(&Tensor::scalar_on(self, s)?)
300 }
301 pub fn mul_scalar(&self, s: f32) -> Result<Tensor> {
303 self.mul(&Tensor::scalar_on(self, s)?)
304 }
305
306 pub fn matmul(&self, rhs: &Tensor) -> Result<Tensor> {
317 let device = same_device(self, rhs, "matmul")?;
318 let out = backend_for(device)?.matmul(self, rhs)?;
319 let (a, b) = (self.clone(), rhs.clone());
320 Ok(record(out, vec![self.clone(), rhs.clone()], move |g| {
321 let ga = backend_for(g.device())?.matmul(g, &b.t()?)?;
324 let gb = backend_for(g.device())?.matmul(&a.t()?, g)?;
325 Ok(vec![
326 Some(reduce_to_shape(&ga, a.shape())?),
327 Some(reduce_to_shape(&gb, b.shape())?),
328 ])
329 }))
330 }
331
332 fn reduce_op(&self, op: ReduceOp, axes: &[usize], keepdim: bool) -> Result<Tensor> {
335 let axes = normalize_axes(axes, self.ndim(), "reduce")?;
336 let out = self.backend()?.reduce(op, self, &axes, keepdim)?;
337 let a = self.clone();
338 let o = out.clone();
339 let axes_c = axes.clone();
340 Ok(record(out, vec![self.clone()], move |g| {
341 let g_keep = if keepdim {
343 g.clone()
344 } else {
345 unsqueeze_axes(g, &axes_c)?
346 };
347 let gi = match op {
348 ReduceOp::Sum => g_keep.broadcast_to(a.shape().clone())?.contiguous()?,
349 ReduceOp::Max | ReduceOp::Min => {
350 let o_keep = if keepdim {
351 o.clone()
352 } else {
353 unsqueeze_axes(&o, &axes_c)?
354 };
355 let mask = a.eq_mask(&o_keep.broadcast_to(a.shape().clone())?)?;
356 let count = mask.sum_keepdim(&axes_c, true)?;
357 g_keep
358 .broadcast_to(a.shape().clone())?
359 .mul_raw(&mask)?
360 .div_raw(&count.broadcast_to(a.shape().clone())?.contiguous()?)?
361 }
362 };
363 Ok(vec![Some(gi)])
364 }))
365 }
366
367 pub fn sum(&self, axes: &[usize]) -> Result<Tensor> {
369 self.reduce_op(ReduceOp::Sum, axes, false)
370 }
371
372 pub fn sum_keepdim(&self, axes: &[usize], keepdim: bool) -> Result<Tensor> {
374 self.reduce_op(ReduceOp::Sum, axes, keepdim)
375 }
376
377 pub fn max(&self, axes: &[usize]) -> Result<Tensor> {
379 self.reduce_op(ReduceOp::Max, axes, false)
380 }
381
382 pub fn max_keepdim(&self, axes: &[usize], keepdim: bool) -> Result<Tensor> {
384 self.reduce_op(ReduceOp::Max, axes, keepdim)
385 }
386
387 pub fn min(&self, axes: &[usize]) -> Result<Tensor> {
389 self.reduce_op(ReduceOp::Min, axes, false)
390 }
391
392 pub fn mean(&self, axes: &[usize]) -> Result<Tensor> {
395 self.mean_keepdim(axes, false)
396 }
397
398 pub fn mean_keepdim(&self, axes: &[usize], keepdim: bool) -> Result<Tensor> {
400 let axes_n = normalize_axes(axes, self.ndim(), "mean")?;
401 let n: usize = axes_n.iter().map(|&ax| self.dims()[ax]).product();
402 self.sum_keepdim(&axes_n, keepdim)?
403 .mul_scalar(1.0 / n as f32)
404 }
405
406 pub fn argmax(&self, dim: usize, keepdim: bool) -> Result<Tensor> {
409 if dim >= self.ndim() {
410 return Err(Error::InvalidArgument {
411 op: "argmax",
412 detail: format!("dim {dim} out of range for rank {}", self.ndim()),
413 });
414 }
415 self.backend()?.argmax(self, dim, keepdim)
416 }
417
418 pub fn softmax(&self, dim: usize) -> Result<Tensor> {
420 let shifted = self.sub(&self.max_keepdim(&[dim], true)?.detach())?;
423 let e = shifted.exp()?;
424 let denom = e.sum_keepdim(&[dim], true)?;
425 e.div(&denom)
426 }
427
428 pub fn log_softmax(&self, dim: usize) -> Result<Tensor> {
430 let shifted = self.sub(&self.max_keepdim(&[dim], true)?.detach())?;
431 let lse = shifted.exp()?.sum_keepdim(&[dim], true)?.ln()?;
432 shifted.sub(&lse)
433 }
434
435 pub fn index_select(&self, dim: usize, indices: &Tensor) -> Result<Tensor> {
439 let out = dispatch_index(self, |be, t| be.index_select(t, dim, indices))?;
440 let in_shape = self.shape().clone();
441 let idx = indices.clone();
442 Ok(record(out, vec![self.clone()], move |g| {
443 let zeros = Tensor::zeros(in_shape.clone()).to_device(g.device())?;
444 Ok(vec![Some(zeros.index_add(dim, &idx, g)?)])
445 }))
446 }
447
448 pub fn index_add(&self, dim: usize, indices: &Tensor, src: &Tensor) -> Result<Tensor> {
450 let out = dispatch_index(self, |be, t| be.index_add(t, dim, indices, src))?;
451 let idx = indices.clone();
452 Ok(record(out, vec![self.clone(), src.clone()], move |g| {
453 Ok(vec![Some(g.clone()), Some(g.index_select(dim, &idx)?)])
454 }))
455 }
456
457 pub fn to_device(&self, device: Device) -> Result<Tensor> {
461 if self.device() == device {
462 return Ok(self.clone());
463 }
464 let out = match (self.device(), device) {
465 (Device::Cpu, target) => backend_for(target)?.upload(&self.contiguous_data()?)?,
466 (_, Device::Cpu) => self.backend()?.download(self)?,
467 (_, target) => {
468 let host = self.backend()?.download(self)?;
469 backend_for(target)?.upload(&host)?
470 }
471 };
472 let source = self.device();
473 Ok(record(out, vec![self.clone()], move |g| {
474 Ok(vec![Some(g.to_device(source)?)])
475 }))
476 }
477}
478
479fn unsqueeze_axes(t: &Tensor, axes: &[usize]) -> Result<Tensor> {
482 let mut out = t.clone();
483 let mut sorted = axes.to_vec();
484 sorted.sort_unstable();
485 for &ax in &sorted {
486 out = out.unsqueeze(ax)?;
487 }
488 Ok(out)
489}
490
491fn normalize_axes(axes: &[usize], ndim: usize, op: &'static str) -> Result<Vec<usize>> {
493 let mut axes: Vec<usize> = if axes.is_empty() {
494 (0..ndim).collect()
495 } else {
496 axes.to_vec()
497 };
498 axes.sort_unstable();
499 axes.dedup();
500 if let Some(&bad) = axes.iter().find(|&&a| a >= ndim) {
501 return Err(Error::InvalidArgument {
502 op,
503 detail: format!("axis {bad} out of range for rank {ndim}"),
504 });
505 }
506 Ok(axes)
507}
508
509fn dispatch_index(
512 t: &Tensor,
513 f: impl Fn(&dyn Backend, &Tensor) -> Result<Tensor>,
514) -> Result<Tensor> {
515 let backend = backend_for(t.device())?;
516 match f(backend.as_ref(), t) {
517 Err(Error::NotImplemented { .. }) if t.device() != Device::Cpu => {
518 let cpu = backend.download(t)?;
519 let cpu_backend = backend_for(Device::Cpu)?;
520 let out = f(cpu_backend.as_ref(), &cpu)?;
521 backend_for(t.device())?.upload(&out)
522 }
523 other => other,
524 }
525}