burn_backend/backend/ops/modules/base.rs
1use super::{attention, conv, ctc, linear, pool};
2use crate::ops::unfold::{create_unfolding_weight, unfold4d_using_conv2d};
3use crate::tensor::{BoolTensor, FloatTensor, IntTensor};
4use crate::{Backend, Scalar, TensorMetadata};
5#[allow(deprecated)]
6pub use burn_std::ops::{
7 AttentionModuleOptions, AvgPoolOptions, CausalAlignment, ConvOptions, ConvTransposeOptions,
8 CoordinateTransformMode, CubicOptions, DeformConvOptions, GridSampleOptions,
9 GridSamplePaddingMode, InterpolateMode, InterpolateOptions, MaxPoolOptions,
10 NearestRoundingMode, PadMode, PaddedConvOptions, UnfoldOptions,
11};
12use burn_std::{IndexingUpdateOp, IntDType, Shape};
13
14/// Gradients computed during the backward pass for each input of
15/// [attention_with_logsumexp](ModuleOps::attention_with_logsumexp).
16#[derive(new)]
17pub struct AttentionBackward<B: Backend> {
18 /// Query gradient, `[batch_size, num_heads, seq_len_q, head_dim]`.
19 pub query_grad: FloatTensor<B>,
20
21 /// Key gradient, `[batch_size, num_kv_heads, seq_len_k, head_dim]`: every query head of a
22 /// group summed into its key head.
23 pub key_grad: FloatTensor<B>,
24
25 /// Value gradient, `[batch_size, num_kv_heads, seq_len_k, val_dim]`.
26 pub value_grad: FloatTensor<B>,
27}
28
29/// Gradient computed during the backward pass for each tensor used by [conv2d](ModuleOps::conv2d).
30#[derive(new)]
31pub struct Conv2dBackward<B: Backend> {
32 /// Gradient.
33 pub x_grad: FloatTensor<B>,
34
35 /// Weights gradient.
36 pub weights_grad: FloatTensor<B>,
37
38 /// Bias gradient.
39 pub bias_grad: Option<FloatTensor<B>>,
40}
41
42/// Gradient computed during the backward pass for each tensor used by [deform_conv2d](ModuleOps::deform_conv2d).
43#[derive(new)]
44pub struct DeformConv2dBackward<B: Backend> {
45 /// Gradient.
46 pub x_grad: FloatTensor<B>,
47
48 /// Offset gradient.
49 pub offset_grad: FloatTensor<B>,
50
51 /// Weights gradient.
52 pub weight_grad: FloatTensor<B>,
53
54 /// Mask gradient.
55 pub mask_grad: Option<FloatTensor<B>>,
56
57 /// Bias gradient.
58 pub bias_grad: Option<FloatTensor<B>>,
59}
60
61/// Gradient computed during the backward pass for each tensor used by [conv3d](ModuleOps::conv3d).
62#[derive(new)]
63pub struct Conv3dBackward<B: Backend> {
64 /// Gradient.
65 pub x_grad: FloatTensor<B>,
66
67 /// Weights gradient.
68 pub weights_grad: FloatTensor<B>,
69
70 /// Bias gradient.
71 pub bias_grad: Option<FloatTensor<B>>,
72}
73
74/// Gradient computed during the backward pass for each tensor used by [max_pool1d](ModuleOps::max_pool1d).
75#[derive(new)]
76pub struct MaxPool1dBackward<B: Backend> {
77 /// Gradient.
78 pub x_grad: FloatTensor<B>,
79}
80
81/// Results from [max_pool1d](ModuleOps::max_pool1d_with_indices).
82#[derive(new)]
83pub struct MaxPool1dWithIndices<B: Backend> {
84 /// The output tensor.
85 pub output: FloatTensor<B>,
86
87 /// The indices tensor.
88 pub indices: IntTensor<B>,
89}
90
91/// Gradient computed during the backward pass for each tensor used by [max_pool2d](ModuleOps::max_pool2d).
92#[derive(new)]
93pub struct MaxPool2dBackward<B: Backend> {
94 /// Gradient.
95 pub x_grad: FloatTensor<B>,
96}
97
98/// Results from [batch_norm_train](ModuleOps::batch_norm_train).
99#[derive(new)]
100pub struct BatchNormTrain<B: Backend> {
101 /// The normalized input.
102 pub output: FloatTensor<B>,
103
104 /// The batch mean per channel, `[channels]`.
105 pub mean: FloatTensor<B>,
106
107 /// The biased batch variance per channel, `[channels]`.
108 pub variance: FloatTensor<B>,
109}
110
111/// Gradient computed during the backward pass for each tensor used by
112/// [batch_norm_train](ModuleOps::batch_norm_train).
113#[derive(new)]
114pub struct BatchNormTrainBackward<B: Backend> {
115 /// Gradient of the input.
116 pub x_grad: FloatTensor<B>,
117
118 /// Gradient of `gamma`, `[channels]`.
119 pub gamma_grad: FloatTensor<B>,
120
121 /// Gradient of `beta`, `[channels]`.
122 pub beta_grad: FloatTensor<B>,
123}
124
125/// Results from [max_pool2d](ModuleOps::max_pool2d_with_indices).
126#[derive(new)]
127pub struct MaxPool2dWithIndices<B: Backend> {
128 /// The output tensor.
129 pub output: FloatTensor<B>,
130
131 /// The indices tensor.
132 pub indices: IntTensor<B>,
133}
134
135/// Gradient computed during the backward pass for each tensor used by [max_pool3d](ModuleOps::max_pool3d).
136#[derive(new, Debug, Clone)]
137pub struct MaxPool3dBackward<B: Backend> {
138 /// Gradient.
139 pub x_grad: FloatTensor<B>,
140}
141
142/// Results from [max_pool3d](ModuleOps::max_pool3d_with_indices).
143#[derive(new, Debug, Clone)]
144pub struct MaxPool3dWithIndices<B: Backend> {
145 /// The output tensor.
146 pub output: FloatTensor<B>,
147
148 /// The indices tensor.
149 pub indices: IntTensor<B>,
150}
151
152/// Gradient computed during the backward pass for each tensor used by [interpolate](ModuleOps::interpolate).
153#[derive(new)]
154pub struct InterpolateBackward<B: Backend> {
155 /// Gradient.
156 pub x_grad: FloatTensor<B>,
157}
158
159/// Module operations trait.
160pub trait ModuleOps<B: Backend> {
161 /// Applies batch normalization using explicitly supplied channel statistics.
162 ///
163 /// The input has shape `[batch, channels, ...]`; all other tensors have
164 /// shape `[channels]`.
165 ///
166 /// This operation doesn't calculate or update statistics. Callers may
167 /// supply running statistics for inference or batch statistics calculated
168 /// by a training path.
169 fn batch_norm(
170 x: FloatTensor<B>,
171 gamma: FloatTensor<B>,
172 beta: FloatTensor<B>,
173 mean: FloatTensor<B>,
174 variance: FloatTensor<B>,
175 epsilon: f64,
176 ) -> FloatTensor<B> {
177 let rank = x.shape().num_dims();
178 let channels = x.shape()[1];
179 let mut dimensions = alloc::vec![1; rank];
180 dimensions[1] = channels;
181 let shape = Shape::from(dimensions);
182 let gamma = B::float_reshape(gamma, shape.clone());
183 let beta = B::float_reshape(beta, shape.clone());
184 let mean = B::float_reshape(mean, shape.clone());
185 let variance = B::float_reshape(variance, shape);
186 let std = B::float_sqrt(B::float_add_scalar(variance, Scalar::Float(epsilon)));
187 let normalized = B::float_div(B::float_sub(x, mean), std);
188 B::float_add(B::float_mul(normalized, gamma), beta)
189 }
190
191 /// Applies batch normalization with the statistics of the batch itself,
192 /// and returns them.
193 ///
194 /// The input has shape `[batch, channels, ...]`; `gamma` and `beta` have
195 /// shape `[channels]`, as do the returned mean and biased variance — what
196 /// a training path feeds its running statistics.
197 ///
198 /// An autodiff backend differentiates this as one operation, through
199 /// [batch_norm_train_backward](ModuleOps::batch_norm_train_backward),
200 /// rather than through the reductions and broadcasts that computing the
201 /// statistics out of tensor operations would record.
202 fn batch_norm_train(
203 x: FloatTensor<B>,
204 gamma: FloatTensor<B>,
205 beta: FloatTensor<B>,
206 epsilon: f64,
207 ) -> BatchNormTrain<B> {
208 let shape = x.shape();
209 let channels = shape[1];
210 let samples_per_channel = shape.num_elements() / channels;
211 let flattened = Shape::new([channels, samples_per_channel]);
212 let mut per_channel = alloc::vec![1; shape.num_dims()];
213 per_channel[1] = channels;
214
215 // Use mean directly to avoid overflowing intermediate sums in f16.
216 let mean = B::float_mean_dim(
217 B::float_reshape(B::float_swap_dims(x.clone(), 0, 1), flattened.clone()),
218 1,
219 );
220 let mean = B::float_reshape(mean, Shape::from(per_channel));
221 let centered = B::float_sub(x.clone(), mean.clone());
222 let variance = B::float_mean_dim(
223 B::float_reshape(
224 B::float_swap_dims(B::float_mul(centered.clone(), centered), 0, 1),
225 flattened,
226 ),
227 1,
228 );
229 let mean = B::float_reshape(mean, Shape::new([channels]));
230 let variance = B::float_reshape(variance, Shape::new([channels]));
231 let output = B::batch_norm(x, gamma, beta, mean.clone(), variance.clone(), epsilon);
232
233 BatchNormTrain::new(output, mean, variance)
234 }
235
236 /// Gradients of [batch_norm_train](ModuleOps::batch_norm_train) with
237 /// respect to its input, `gamma` and `beta`, given the gradient of its
238 /// output. `mean` and `variance` are the statistics it returned.
239 fn batch_norm_train_backward(
240 x: FloatTensor<B>,
241 gamma: FloatTensor<B>,
242 mean: FloatTensor<B>,
243 variance: FloatTensor<B>,
244 epsilon: f64,
245 output_grad: FloatTensor<B>,
246 ) -> BatchNormTrainBackward<B> {
247 let shape = x.shape();
248 let rank = shape.num_dims();
249 let channels = shape[1];
250 let flattened = Shape::new([channels, shape.num_elements() / channels]);
251 let mut per_channel = alloc::vec![1; rank];
252 per_channel[1] = channels;
253 let per_channel = Shape::from(per_channel);
254
255 let inv_std = B::float_reshape(
256 B::float_recip(B::float_sqrt(B::float_add_scalar(
257 variance,
258 Scalar::Float(epsilon),
259 ))),
260 per_channel.clone(),
261 );
262 let normalized = B::float_mul(
263 B::float_sub(x, B::float_reshape(mean, per_channel.clone())),
264 inv_std.clone(),
265 );
266
267 let output_grad_flat = B::float_reshape(
268 B::float_swap_dims(output_grad.clone(), 0, 1),
269 flattened.clone(),
270 );
271 let normalized_grad_flat = B::float_reshape(
272 B::float_swap_dims(B::float_mul(output_grad.clone(), normalized.clone()), 0, 1),
273 flattened,
274 );
275 let beta_grad = B::float_sum_dim(output_grad_flat.clone(), 1);
276 let gamma_grad = B::float_sum_dim(normalized_grad_flat.clone(), 1);
277
278 // Compute means independently: parameter-gradient sums can overflow in f16.
279 let mean_grad =
280 B::float_reshape(B::float_mean_dim(output_grad_flat, 1), per_channel.clone());
281 let mean_normalized_grad = B::float_reshape(
282 B::float_mean_dim(normalized_grad_flat, 1),
283 per_channel.clone(),
284 );
285 let centred_grad = B::float_sub(output_grad, mean_grad);
286 let projected = B::float_mul(normalized, mean_normalized_grad);
287 let scale = B::float_mul(B::float_reshape(gamma, per_channel), inv_std);
288 let x_grad = B::float_mul(scale, B::float_sub(centred_grad, projected));
289
290 BatchNormTrainBackward::new(
291 x_grad,
292 B::float_reshape(gamma_grad, Shape::new([channels])),
293 B::float_reshape(beta_grad, Shape::new([channels])),
294 )
295 }
296
297 /// Embedding operation.
298 ///
299 /// # Arguments
300 ///
301 /// * `weights` - The embedding weights.
302 /// * `indices` - The indices tensor.
303 ///
304 /// # Returns
305 ///
306 /// The output tensor.
307 fn embedding(weights: FloatTensor<B>, indices: IntTensor<B>) -> FloatTensor<B> {
308 let [batch_size, seq_length] = indices.shape().dims();
309 let [_, d_model] = weights.shape().dims();
310
311 let indices = B::int_reshape(indices, Shape::new([batch_size * seq_length]));
312 let output = B::float_select(weights, 0, indices);
313
314 B::float_reshape(output, Shape::new([batch_size, seq_length, d_model]))
315 }
316
317 /// Embedding backward operation.
318 ///
319 /// # Arguments
320 ///
321 /// * `weights` - The embedding weights.
322 /// * `output_grad` - The output gradient.
323 /// * `indices` - The indices tensor.
324 ///
325 /// # Returns
326 ///
327 /// The gradient.
328 fn embedding_backward(
329 weights: FloatTensor<B>,
330 output_grad: FloatTensor<B>,
331 indices: IntTensor<B>,
332 ) -> FloatTensor<B> {
333 let [batch_size, seq_length] = indices.shape().dims();
334 let [n_embeddings, d_model] = weights.shape().dims();
335 let device = weights.device();
336 let dtype = output_grad.dtype();
337
338 let indices = B::int_reshape(indices, Shape::new([batch_size * seq_length]));
339 let output_grad =
340 B::float_reshape(output_grad, Shape::new([batch_size * seq_length, d_model]));
341 let grad = B::float_zeros(Shape::new([n_embeddings, d_model]), &device, dtype.into());
342
343 B::float_select_assign(grad, 0, indices, output_grad, IndexingUpdateOp::Add)
344 }
345
346 /// Linear transformation.
347 ///
348 /// # Shapes
349 ///
350 /// x: `[..., d_input]`,
351 /// weight: `[d_input, d_output]`,
352 /// bias: `[d_output]`,
353 fn linear(
354 x: FloatTensor<B>,
355 weight: FloatTensor<B>,
356 bias: Option<FloatTensor<B>>,
357 ) -> FloatTensor<B> {
358 linear::linear::<B>(x, weight, bias)
359 }
360 /// Backward pass for [linear](ModuleOps::linear), returning the gradient for `x`.
361 fn linear_x_backward(weight: FloatTensor<B>, output_grad: FloatTensor<B>) -> FloatTensor<B> {
362 linear::linear_x_backward::<B>(weight, output_grad)
363 }
364 /// Backward pass for [linear](ModuleOps::linear), returning the gradient for `weight`.
365 fn linear_weight_backward(x: FloatTensor<B>, output_grad: FloatTensor<B>) -> FloatTensor<B> {
366 linear::linear_weight_backward::<B>(x, output_grad)
367 }
368 /// Backward pass for [linear](ModuleOps::linear), returning the gradient for `bias`.
369 fn linear_bias_backward(output_grad: FloatTensor<B>) -> FloatTensor<B> {
370 linear::linear_bias_backward::<B>(output_grad)
371 }
372
373 /// One dimensional convolution.
374 ///
375 /// # Shapes
376 ///
377 /// x: `[batch_size, channels_in, length]`,
378 /// weight: `[channels_out, channels_in, kernel_size]`,
379 /// bias: `[channels_out]`,
380 fn conv1d(
381 x: FloatTensor<B>,
382 weight: FloatTensor<B>,
383 bias: Option<FloatTensor<B>>,
384 options: ConvOptions<1>,
385 ) -> FloatTensor<B> {
386 conv::conv1d_from_conv2d::<B>(x, weight, bias, options)
387 }
388 /// Backward pass for the [conv1d](ModuleOps::conv1d) operation, returning the gradient for `x`.
389 fn conv1d_x_backward(
390 x: FloatTensor<B>,
391 weight: FloatTensor<B>,
392 output_grad: FloatTensor<B>,
393 options: ConvOptions<1>,
394 ) -> FloatTensor<B> {
395 conv::conv1d_x_backward::<B>(x, weight, output_grad, options)
396 }
397 /// Backward pass for the [conv1d](ModuleOps::conv1d) operation, returning the gradient for `weight`.
398 fn conv1d_weight_backward(
399 x: FloatTensor<B>,
400 weight: FloatTensor<B>,
401 output_grad: FloatTensor<B>,
402 options: ConvOptions<1>,
403 ) -> FloatTensor<B> {
404 conv::conv1d_weight_backward::<B>(x, weight, output_grad, options)
405 }
406 /// Backward pass for the [conv1d](ModuleOps::conv1d) operation, returning the gradient for `bias`.
407 fn conv1d_bias_backward(
408 x: FloatTensor<B>,
409 bias: FloatTensor<B>,
410 output_grad: FloatTensor<B>,
411 ) -> FloatTensor<B> {
412 conv::conv1d_bias_backward::<B>(x, bias, output_grad)
413 }
414 /// Two dimensional convolution.
415 ///
416 /// # Shapes
417 ///
418 /// x: `[batch_size, channels_in, height, width]`,
419 /// weight: `[channels_out, channels_in, kernel_size_1, kernel_size_2]`,
420 /// bias: `[channels_out]`,
421 fn conv2d(
422 x: FloatTensor<B>,
423 weight: FloatTensor<B>,
424 bias: Option<FloatTensor<B>>,
425 options: ConvOptions<2>,
426 ) -> FloatTensor<B>;
427 /// Backward pass for the [conv2d](ModuleOps::conv2d) operation, returning the gradient for `x`.
428 fn conv2d_x_backward(
429 x: FloatTensor<B>,
430 weight: FloatTensor<B>,
431 output_grad: FloatTensor<B>,
432 options: ConvOptions<2>,
433 ) -> FloatTensor<B> {
434 conv::conv2d_x_backward::<B>(x, weight, output_grad, options)
435 }
436 /// Backward pass for the [conv2d](ModuleOps::conv2d) operation, returning the gradient for `weight`.
437 fn conv2d_weight_backward(
438 x: FloatTensor<B>,
439 weight: FloatTensor<B>,
440 output_grad: FloatTensor<B>,
441 options: ConvOptions<2>,
442 ) -> FloatTensor<B> {
443 conv::conv2d_weight_backward::<B>(x, weight, output_grad, options)
444 }
445 /// Backward pass for the [conv2d](ModuleOps::conv2d) operation, returning the gradient for `bias`.
446 fn conv2d_bias_backward(
447 x: FloatTensor<B>,
448 bias: FloatTensor<B>,
449 output_grad: FloatTensor<B>,
450 ) -> FloatTensor<B> {
451 conv::conv2d_bias_backward::<B>(x, bias, output_grad)
452 }
453
454 /// Two dimensional deformable convolution.
455 ///
456 /// # Shapes
457 ///
458 /// x: `[batch_size, channels_in, height, width]`,
459 /// weight: `[channels_out, channels_in, kernel_size_1, kernel_size_2]`,
460 /// bias: `[channels_out]`,
461 fn deform_conv2d(
462 x: FloatTensor<B>,
463 offset: FloatTensor<B>,
464 weight: FloatTensor<B>,
465 mask: Option<FloatTensor<B>>,
466 bias: Option<FloatTensor<B>>,
467 options: DeformConvOptions<2>,
468 ) -> FloatTensor<B>;
469 /// Backward pass for the [deform_conv2d](ModuleOps::deform_conv2d) operation.
470 fn deform_conv2d_backward(
471 x: FloatTensor<B>,
472 offset: FloatTensor<B>,
473 weight: FloatTensor<B>,
474 mask: Option<FloatTensor<B>>,
475 bias: Option<FloatTensor<B>>,
476 output_grad: FloatTensor<B>,
477 options: DeformConvOptions<2>,
478 ) -> DeformConv2dBackward<B>;
479
480 /// Three dimensional convolution.
481 ///
482 /// # Shapes
483 ///
484 /// x: `[batch_size, channels_in, depth, height, width]`,
485 /// weight: `[channels_out, channels_in, kernel_size_1, kernel_size_2, kernel_size_3]`,
486 /// bias: `[channels_out]`,
487 fn conv3d(
488 x: FloatTensor<B>,
489 weight: FloatTensor<B>,
490 bias: Option<FloatTensor<B>>,
491 options: ConvOptions<3>,
492 ) -> FloatTensor<B>;
493 /// Backward pass for the [conv3d](ModuleOps::conv3d) operation, returning the gradient for `x`.
494 fn conv3d_x_backward(
495 x: FloatTensor<B>,
496 weight: FloatTensor<B>,
497 output_grad: FloatTensor<B>,
498 options: ConvOptions<3>,
499 ) -> FloatTensor<B> {
500 conv::conv3d_x_backward::<B>(x, weight, output_grad, options)
501 }
502 /// Backward pass for the [conv3d](ModuleOps::conv3d) operation, returning the gradient for `weight`.
503 fn conv3d_weight_backward(
504 x: FloatTensor<B>,
505 weight: FloatTensor<B>,
506 output_grad: FloatTensor<B>,
507 options: ConvOptions<3>,
508 ) -> FloatTensor<B> {
509 conv::conv3d_weight_backward::<B>(x, weight, output_grad, options)
510 }
511 /// Backward pass for the [conv3d](ModuleOps::conv3d) operation, returning the gradient for `bias`.
512 fn conv3d_bias_backward(
513 x: FloatTensor<B>,
514 bias: FloatTensor<B>,
515 output_grad: FloatTensor<B>,
516 ) -> FloatTensor<B> {
517 conv::conv3d_bias_backward::<B>(x, bias, output_grad)
518 }
519 /// One dimensional transposed convolution.
520 ///
521 /// # Shapes
522 ///
523 /// x: `[batch_size, channels_in, length]`,
524 /// weight: `[channels_in, channels_out, length]`,
525 /// bias: `[channels_out]`,
526 fn conv_transpose1d(
527 x: FloatTensor<B>,
528 weight: FloatTensor<B>,
529 bias: Option<FloatTensor<B>>,
530 options: ConvTransposeOptions<1>,
531 ) -> FloatTensor<B> {
532 conv::conv_transpose1d_from_conv_transpose2d::<B>(x, weight, bias, options)
533 }
534 /// Backward pass for the [conv transpose 1d](ModuleOps::conv_transpose1d) operation, returning the gradient for `x`.
535 fn conv_transpose1d_x_backward(
536 weight: FloatTensor<B>,
537 output_grad: FloatTensor<B>,
538 options: ConvTransposeOptions<1>,
539 ) -> FloatTensor<B> {
540 conv::conv_transpose1d_x_backward::<B>(weight, output_grad, options)
541 }
542 /// Backward pass for the [conv transpose 1d](ModuleOps::conv_transpose1d) operation, returning the gradient for `weight`.
543 fn conv_transpose1d_weight_backward(
544 x: FloatTensor<B>,
545 weight: FloatTensor<B>,
546 output_grad: FloatTensor<B>,
547 options: ConvTransposeOptions<1>,
548 ) -> FloatTensor<B> {
549 conv::conv_transpose1d_weight_backward::<B>(x, weight, output_grad, options)
550 }
551 /// Backward pass for the [conv transpose 1d](ModuleOps::conv_transpose1d) operation, returning the gradient for `bias`.
552 fn conv_transpose1d_bias_backward(
553 x: FloatTensor<B>,
554 bias: FloatTensor<B>,
555 output_grad: FloatTensor<B>,
556 ) -> FloatTensor<B> {
557 conv::conv_transpose1d_bias_backward::<B>(x, bias, output_grad)
558 }
559
560 /// Two dimensional transposed convolution.
561 ///
562 /// # Shapes
563 ///
564 /// x: `[batch_size, channels_in, height, width]`,
565 /// weight: `[channels_in, channels_out, kernel_size_1, kernel_size_2]`,
566 /// bias: `[channels_out]`,
567 fn conv_transpose2d(
568 x: FloatTensor<B>,
569 weight: FloatTensor<B>,
570 bias: Option<FloatTensor<B>>,
571 options: ConvTransposeOptions<2>,
572 ) -> FloatTensor<B>;
573 /// Backward pass for the [conv transpose 2d](ModuleOps::conv_transpose2d) operation, returning the gradient for `x`.
574 fn conv_transpose2d_x_backward(
575 weight: FloatTensor<B>,
576 output_grad: FloatTensor<B>,
577 options: ConvTransposeOptions<2>,
578 ) -> FloatTensor<B> {
579 conv::conv_transpose2d_x_backward::<B>(weight, output_grad, options)
580 }
581 /// Backward pass for the [conv transpose 2d](ModuleOps::conv_transpose2d) operation, returning the gradient for `weight`.
582 fn conv_transpose2d_weight_backward(
583 x: FloatTensor<B>,
584 weight: FloatTensor<B>,
585 output_grad: FloatTensor<B>,
586 options: ConvTransposeOptions<2>,
587 ) -> FloatTensor<B> {
588 conv::conv_transpose2d_weight_backward::<B>(x, weight, output_grad, options)
589 }
590 /// Backward pass for the [conv transpose 2d](ModuleOps::conv_transpose2d) operation, returning the gradient for `bias`.
591 fn conv_transpose2d_bias_backward(
592 x: FloatTensor<B>,
593 bias: FloatTensor<B>,
594 output_grad: FloatTensor<B>,
595 ) -> FloatTensor<B> {
596 conv::conv_transpose2d_bias_backward::<B>(x, bias, output_grad)
597 }
598
599 /// Three dimensional transposed convolution.
600 ///
601 /// # Shapes
602 ///
603 /// x: `[batch_size, channels_in, height, width]`,
604 /// weight: `[channels_in, channels_out, kernel_size_1, kernel_size_2, kernel_size_3]`,
605 /// bias: `[channels_out]`,
606 fn conv_transpose3d(
607 x: FloatTensor<B>,
608 weight: FloatTensor<B>,
609 bias: Option<FloatTensor<B>>,
610 options: ConvTransposeOptions<3>,
611 ) -> FloatTensor<B>;
612 /// Backward pass for the [conv transpose 3d](ModuleOps::conv_transpose3d) operation, returning the gradient for `x`.
613 fn conv_transpose3d_x_backward(
614 weight: FloatTensor<B>,
615 output_grad: FloatTensor<B>,
616 options: ConvTransposeOptions<3>,
617 ) -> FloatTensor<B> {
618 conv::conv_transpose3d_x_backward::<B>(weight, output_grad, options)
619 }
620 /// Backward pass for the [conv transpose 3d](ModuleOps::conv_transpose3d) operation, returning the gradient for `weight`.
621 fn conv_transpose3d_weight_backward(
622 x: FloatTensor<B>,
623 weight: FloatTensor<B>,
624 output_grad: FloatTensor<B>,
625 options: ConvTransposeOptions<3>,
626 ) -> FloatTensor<B> {
627 conv::conv_transpose3d_weight_backward::<B>(x, weight, output_grad, options)
628 }
629 /// Backward pass for the [conv transpose 3d](ModuleOps::conv_transpose3d) operation, returning the gradient for `bias`.
630 fn conv_transpose3d_bias_backward(
631 x: FloatTensor<B>,
632 bias: FloatTensor<B>,
633 output_grad: FloatTensor<B>,
634 ) -> FloatTensor<B> {
635 conv::conv_transpose3d_bias_backward::<B>(x, bias, output_grad)
636 }
637
638 /// Four-dimensional unfolding.
639 ///
640 /// # Shapes
641 ///
642 /// * x: ``[batch_size, channels_in, height, width]``,
643 /// * returns: ``[batch_size, channels_in * kernel_size_1 * kernel_size_2, number of blocks]``,
644 fn unfold4d(
645 x: FloatTensor<B>,
646 kernel_size: [usize; 2],
647 options: UnfoldOptions,
648 ) -> FloatTensor<B> {
649 if options.padding == [0, 0] && options.dilation == [1, 1] {
650 let blocks = B::float_unfold(x, 2, kernel_size[0], options.stride[0]);
651 let blocks = B::float_unfold(blocks, 3, kernel_size[1], options.stride[1]);
652
653 // batch, channels, h_blocks, w_blocks, h_kern, w_kern
654
655 let blocks = B::float_permute(blocks, &[0, 1, 4, 5, 2, 3]);
656 let shape = blocks.shape();
657
658 // batch, channels, h_kern, w_kern, h_blocks, w_blocks
659
660 B::float_reshape(
661 blocks,
662 [
663 shape[0],
664 shape[1] * shape[2] * shape[3],
665 shape[4] * shape[5],
666 ]
667 .into(),
668 )
669 } else {
670 unfold4d_using_conv2d::<B>(x, kernel_size, options)
671 }
672 }
673
674 /// Four dimensional fold (`col2im`), the adjoint of [unfold4d](ModuleOps::unfold4d).
675 ///
676 /// Composes [conv_transpose2d](ModuleOps::conv_transpose2d) with the same one-hot channel mapping
677 /// [unfold4d](ModuleOps::unfold4d) uses, so backends inherit a correct (and differentiable)
678 /// implementation for free and may override it with a custom one.
679 ///
680 /// # Shapes
681 ///
682 /// x: `[batch_size, channels * kernel_size_0 * kernel_size_1, num_blocks]`,
683 /// output: `[batch_size, channels, output_size_0, output_size_1]`
684 fn fold4d(
685 x: FloatTensor<B>,
686 output_size: [usize; 2],
687 kernel_size: [usize; 2],
688 options: UnfoldOptions,
689 ) -> FloatTensor<B> {
690 let [batch_size, channels_col, num_blocks] = x.shape().dims();
691 let [kernel_height, kernel_width] = kernel_size;
692 let [output_height, output_width] = output_size;
693 let [stride_height, stride_width] = options.stride;
694 let [padding_height, padding_width] = options.padding;
695 let [dilation_height, dilation_width] = options.dilation;
696
697 let kernel_elems = kernel_height * kernel_width;
698 assert_eq!(
699 channels_col % kernel_elems,
700 0,
701 "fold4d: input channels ({channels_col}) must be divisible by the kernel size product ({kernel_elems})"
702 );
703 let channels = channels_col / kernel_elems;
704
705 // Number of sliding blocks along each spatial dimension (the unfold output grid).
706 let blocks_height =
707 (output_height + 2 * padding_height - dilation_height * (kernel_height - 1) - 1)
708 / stride_height
709 + 1;
710 let blocks_width =
711 (output_width + 2 * padding_width - dilation_width * (kernel_width - 1) - 1)
712 / stride_width
713 + 1;
714 assert_eq!(
715 num_blocks,
716 blocks_height * blocks_width,
717 "fold4d: number of blocks ({num_blocks}) does not match the expected grid ({blocks_height} x {blocks_width}) for the given output size and options"
718 );
719
720 // Fold is the adjoint of unfold and uses the same one-hot channel mapping. Grouping by
721 // channel removes the otherwise unused cross-channel weights.
722 let groups = channels.max(1);
723 let weight =
724 create_unfolding_weight::<B>(channels, kernel_size, groups, &x.device(), x.dtype());
725
726 // Reshape the columns into the spatial grid of blocks, then scatter-add them back.
727 let x = B::float_reshape(
728 x,
729 Shape::new([batch_size, channels_col, blocks_height, blocks_width]),
730 );
731
732 // `padding_out` recovers the exact requested output size (always `< stride`).
733 let padding_out = [
734 (output_height + 2 * padding_height - dilation_height * (kernel_height - 1) - 1)
735 % stride_height,
736 (output_width + 2 * padding_width - dilation_width * (kernel_width - 1) - 1)
737 % stride_width,
738 ];
739
740 B::conv_transpose2d(
741 x,
742 weight,
743 None,
744 ConvTransposeOptions::new(
745 options.stride,
746 options.padding,
747 padding_out,
748 options.dilation,
749 groups,
750 ),
751 )
752 }
753
754 /// One dimensional avg pooling.
755 ///
756 /// # Shapes
757 ///
758 /// x: [batch_size, channels, length],
759 fn avg_pool1d(
760 x: FloatTensor<B>,
761 kernel_size: usize,
762 stride: usize,
763 padding: usize,
764 dilation: usize,
765 count_include_pad: bool,
766 ceil_mode: bool,
767 ) -> FloatTensor<B> {
768 pool::avg_pool1d_from_2d::<B>(
769 x,
770 kernel_size,
771 stride,
772 padding,
773 dilation,
774 count_include_pad,
775 ceil_mode,
776 )
777 }
778 /// Backward pass for the [avg pooling 1d](ModuleOps::avg_pool1d) operation.
779 #[allow(clippy::too_many_arguments)]
780 fn avg_pool1d_backward(
781 x: FloatTensor<B>,
782 grad: FloatTensor<B>,
783 kernel_size: usize,
784 stride: usize,
785 padding: usize,
786 dilation: usize,
787 count_include_pad: bool,
788 ceil_mode: bool,
789 ) -> FloatTensor<B> {
790 pool::avg_pool1d_backward_from_2d::<B>(
791 x,
792 grad,
793 kernel_size,
794 stride,
795 padding,
796 dilation,
797 count_include_pad,
798 ceil_mode,
799 )
800 }
801 /// Two dimensional avg pooling.
802 ///
803 /// # Shapes
804 ///
805 /// x: [batch_size, channels, height, width],
806 fn avg_pool2d(
807 x: FloatTensor<B>,
808 kernel_size: [usize; 2],
809 stride: [usize; 2],
810 padding: [usize; 2],
811 dilation: [usize; 2],
812 count_include_pad: bool,
813 ceil_mode: bool,
814 ) -> FloatTensor<B>;
815 /// Backward pass for the [avg pooling 2d](ModuleOps::avg_pool2d) operation.
816 #[allow(clippy::too_many_arguments)]
817 fn avg_pool2d_backward(
818 x: FloatTensor<B>,
819 grad: FloatTensor<B>,
820 kernel_size: [usize; 2],
821 stride: [usize; 2],
822 padding: [usize; 2],
823 dilation: [usize; 2],
824 count_include_pad: bool,
825 ceil_mode: bool,
826 ) -> FloatTensor<B>;
827 /// Three dimensional avg pooling.
828 ///
829 /// # Shapes
830 ///
831 /// x: [batch_size, channels, depth, height, width],
832 fn avg_pool3d(
833 x: FloatTensor<B>,
834 kernel_size: [usize; 3],
835 stride: [usize; 3],
836 padding: [usize; 3],
837 dilation: [usize; 3],
838 count_include_pad: bool,
839 ceil_mode: bool,
840 ) -> FloatTensor<B>;
841 /// Backward pass for the [avg pooling 3d](ModuleOps::avg_pool3d) operation.
842 #[allow(clippy::too_many_arguments)]
843 fn avg_pool3d_backward(
844 x: FloatTensor<B>,
845 grad: FloatTensor<B>,
846 kernel_size: [usize; 3],
847 stride: [usize; 3],
848 padding: [usize; 3],
849 dilation: [usize; 3],
850 count_include_pad: bool,
851 ceil_mode: bool,
852 ) -> FloatTensor<B>;
853 /// Two dimensional adaptive avg pooling.
854 ///
855 /// # Shapes
856 ///
857 /// x: [batch_size, channels, height, width],
858 fn adaptive_avg_pool2d(x: FloatTensor<B>, output_size: [usize; 2]) -> FloatTensor<B>;
859 /// Backward pass for the [adaptive avg pooling 2d](ModuleOps::adaptive_avg_pool2d) operation.
860 fn adaptive_avg_pool2d_backward(x: FloatTensor<B>, grad: FloatTensor<B>) -> FloatTensor<B>;
861 /// Three dimensional adaptive avg pooling.
862 ///
863 /// # Shapes
864 ///
865 /// x: [batch_size, channels, depth, height, width],
866 fn adaptive_avg_pool3d(x: FloatTensor<B>, output_size: [usize; 3]) -> FloatTensor<B>;
867 /// Backward pass for the [adaptive avg pooling 3d](ModuleOps::adaptive_avg_pool3d) operation.
868 fn adaptive_avg_pool3d_backward(x: FloatTensor<B>, grad: FloatTensor<B>) -> FloatTensor<B>;
869 /// One dimensional adaptive avg pooling.
870 ///
871 /// # Shapes
872 ///
873 /// x: [batch_size, channels, length],
874 fn adaptive_avg_pool1d(x: FloatTensor<B>, output_size: usize) -> FloatTensor<B> {
875 pool::adaptive_avg_pool1d_from_2d::<B>(x, output_size)
876 }
877 /// Backward pass for the [adaptive avg pooling 1d](ModuleOps::adaptive_avg_pool1d) operation.
878 fn adaptive_avg_pool1d_backward(x: FloatTensor<B>, grad: FloatTensor<B>) -> FloatTensor<B> {
879 pool::adaptive_avg_pool1d_backward_from_2d::<B>(x, grad)
880 }
881 /// One dimensional max pooling.
882 ///
883 /// # Shapes
884 ///
885 /// x: [batch_size, channels, length],
886 fn max_pool1d(
887 x: FloatTensor<B>,
888 kernel_size: usize,
889 stride: usize,
890 padding: usize,
891 dilation: usize,
892 ceil_mode: bool,
893 ) -> FloatTensor<B> {
894 pool::max_pool1d_from_2d::<B>(x, kernel_size, stride, padding, dilation, ceil_mode)
895 }
896
897 /// One dimensional max pooling with indices.
898 ///
899 /// # Shapes
900 ///
901 /// x: [batch_size, channels, height, width],
902 fn max_pool1d_with_indices(
903 x: FloatTensor<B>,
904 kernel_size: usize,
905 stride: usize,
906 padding: usize,
907 dilation: usize,
908 ceil_mode: bool,
909 indices_dtype: IntDType,
910 ) -> MaxPool1dWithIndices<B> {
911 pool::max_pool1d_with_indices_from_2d::<B>(
912 x,
913 kernel_size,
914 stride,
915 padding,
916 dilation,
917 ceil_mode,
918 indices_dtype,
919 )
920 }
921 /// Backward pass for the [max pooling 1d](ModuleOps::max_pool1d_with_indices) operation.
922 #[allow(clippy::too_many_arguments)]
923 fn max_pool1d_with_indices_backward(
924 x: FloatTensor<B>,
925 kernel_size: usize,
926 stride: usize,
927 padding: usize,
928 dilation: usize,
929 ceil_mode: bool,
930 output_grad: FloatTensor<B>,
931 indices: IntTensor<B>,
932 ) -> MaxPool1dBackward<B> {
933 pool::max_pool1d_with_indices_backward_from_2d::<B>(
934 x,
935 kernel_size,
936 stride,
937 padding,
938 dilation,
939 ceil_mode,
940 output_grad,
941 indices,
942 )
943 }
944
945 /// Two dimensional max pooling.
946 ///
947 /// # Shapes
948 ///
949 /// x: [batch_size, channels, height, width],
950 fn max_pool2d(
951 x: FloatTensor<B>,
952 kernel_size: [usize; 2],
953 stride: [usize; 2],
954 padding: [usize; 2],
955 dilation: [usize; 2],
956 ceil_mode: bool,
957 ) -> FloatTensor<B>;
958
959 /// Two dimensional max pooling with indices.
960 ///
961 /// # Shapes
962 ///
963 /// x: [batch_size, channels, height, width],
964 fn max_pool2d_with_indices(
965 x: FloatTensor<B>,
966 kernel_size: [usize; 2],
967 stride: [usize; 2],
968 padding: [usize; 2],
969 dilation: [usize; 2],
970 ceil_mode: bool,
971 indices_dtype: IntDType,
972 ) -> MaxPool2dWithIndices<B>;
973 /// Backward pass for the [max pooling 2d](ModuleOps::max_pool2d_with_indices) operation.
974 #[allow(clippy::too_many_arguments)]
975 fn max_pool2d_with_indices_backward(
976 x: FloatTensor<B>,
977 kernel_size: [usize; 2],
978 stride: [usize; 2],
979 padding: [usize; 2],
980 dilation: [usize; 2],
981 ceil_mode: bool,
982 output_grad: FloatTensor<B>,
983 indices: IntTensor<B>,
984 ) -> MaxPool2dBackward<B>;
985
986 /// Three dimensional max pooling.
987 ///
988 /// # Shapes
989 ///
990 /// x: [batch_size, channels, depth, height, width],
991 fn max_pool3d(
992 x: FloatTensor<B>,
993 kernel_size: [usize; 3],
994 stride: [usize; 3],
995 padding: [usize; 3],
996 dilation: [usize; 3],
997 ceil_mode: bool,
998 ) -> FloatTensor<B>;
999
1000 /// Three dimensional max pooling with indices.
1001 ///
1002 /// # Shapes
1003 ///
1004 /// x: [batch_size, channels, depth, height, width],
1005 fn max_pool3d_with_indices(
1006 x: FloatTensor<B>,
1007 kernel_size: [usize; 3],
1008 stride: [usize; 3],
1009 padding: [usize; 3],
1010 dilation: [usize; 3],
1011 ceil_mode: bool,
1012 indices_dtype: IntDType,
1013 ) -> MaxPool3dWithIndices<B>;
1014 /// Backward pass for the [max pooling 3d](ModuleOps::max_pool3d_with_indices) operation.
1015 #[allow(clippy::too_many_arguments)]
1016 fn max_pool3d_with_indices_backward(
1017 x: FloatTensor<B>,
1018 kernel_size: [usize; 3],
1019 stride: [usize; 3],
1020 padding: [usize; 3],
1021 dilation: [usize; 3],
1022 ceil_mode: bool,
1023 output_grad: FloatTensor<B>,
1024 indices: IntTensor<B>,
1025 ) -> MaxPool3dBackward<B>;
1026
1027 /// Down/up samples the input.
1028 ///
1029 /// # Shapes
1030 ///
1031 /// x: `[batch_size, channels, height, width]`,
1032 fn interpolate(
1033 x: FloatTensor<B>,
1034 output_size: [usize; 2],
1035 options: InterpolateOptions,
1036 ) -> FloatTensor<B>;
1037
1038 /// Backward pass for the [interpolate](ModuleOps::interpolate) operation.
1039 fn interpolate_backward(
1040 x: FloatTensor<B>,
1041 grad: FloatTensor<B>,
1042 output_size: [usize; 2],
1043 options: InterpolateOptions,
1044 ) -> FloatTensor<B>;
1045
1046 /// Computes scaled dot-product attention: softmax(QKᵗ * scale) · V,
1047 /// where scale defaults to 1/sqrt(head_dim). Optionally applies masking,
1048 /// additive bias, causal masking, and softcap to the attention scores.
1049 ///
1050 /// The scores are computed in this order, which every backend must follow:
1051 /// 1. `s = Q·Kᵗ · scale`
1052 /// 2. `s = softcap · tanh(s / softcap)` if `options.softcap` is set
1053 /// 3. `s = -inf` where `mask` is `true` or the causal mask hides the key
1054 /// 4. `s = s + attn_bias`
1055 /// 5. `p = softmax(s)` over keys; a row with every key hidden yields `p = 0`
1056 /// 6. `out = p · V`
1057 ///
1058 /// Softcap comes before the masks so that hidden keys stay at `-inf`.
1059 ///
1060 /// # Arguments
1061 /// - `query`: Query tensor of shape `[batch_size, num_heads, seq_len_q, head_dim]`
1062 /// - `key`: Key tensor of shape `[batch_size, num_kv_heads, seq_len_k, head_dim]`
1063 /// - `value`: Value tensor of shape `[batch_size, num_kv_heads, seq_len_k, val_dim]`
1064 ///
1065 /// `num_heads` must be a multiple of `num_kv_heads` (grouped-query attention;
1066 /// `num_kv_heads == 1` is multi-query attention). Query head `h` attends with K/V
1067 /// head `h / (num_heads / num_kv_heads)`, the PyTorch and ONNX convention.
1068 /// - `mask`: Optional boolean mask of shape `[batch_size, num_heads, seq_len_q, seq_len_k]`,
1069 /// where `true` indicates positions to mask (i.e. set to -inf before softmax).
1070 /// - `attn_bias`: Optional float tensor of shape `[batch_size, num_heads, seq_len_q, seq_len_k]`
1071 /// added to the attention scores before softmax (e.g. ALiBi, relative position biases).
1072 /// - `options`: Additional attention options (custom scale, softcap, causal masking
1073 /// and its [alignment](crate::ops::CausalAlignment)).
1074 ///
1075 /// # Returns
1076 /// A tensor of shape `[batch_size, num_heads, seq_len_q, val_dim]`
1077 /// representing the attended context per head.
1078 ///
1079 /// # Note
1080 /// This implementation does not support dropout and is intended for inference or
1081 /// use cases where dropout is not needed.
1082 fn attention(
1083 query: FloatTensor<B>,
1084 key: FloatTensor<B>,
1085 value: FloatTensor<B>,
1086 mask: Option<BoolTensor<B>>,
1087 attn_bias: Option<FloatTensor<B>>,
1088 options: AttentionModuleOptions,
1089 ) -> FloatTensor<B>;
1090
1091 /// Returns `true` if this backend implements
1092 /// [attention_backward](ModuleOps::attention_backward) natively.
1093 ///
1094 /// Autodiff queries this flag, for an attention with no mask and no additive bias, to decide
1095 /// between two paths:
1096 /// - `true`: the forward pass is [attention_with_logsumexp](ModuleOps::attention_with_logsumexp)
1097 /// and the backward pass `attention_backward`, which reads the probabilities back from the
1098 /// logsumexp instead of keeping the scores.
1099 /// - `false`: autodiff differentiates the decomposed
1100 /// [attention_fallback](attention::attention_fallback).
1101 ///
1102 /// Backends that override `attention_backward` must also override this to return `true`.
1103 fn has_attention_backward() -> bool {
1104 false
1105 }
1106
1107 /// [attention](ModuleOps::attention) with no mask and no additive bias, and each query's
1108 /// logsumexp of its scores, `[batch_size, num_heads, seq_len_q]` in `f32` whatever the
1109 /// operands' dtype: the forward pass [attention_backward](ModuleOps::attention_backward) reads
1110 /// back, where `exp(score − logsumexp)` is where its precision is lost.
1111 ///
1112 /// The default composes it from tensor operations
1113 /// ([attention::attention_with_logsumexp_default]).
1114 fn attention_with_logsumexp(
1115 query: FloatTensor<B>,
1116 key: FloatTensor<B>,
1117 value: FloatTensor<B>,
1118 options: AttentionModuleOptions,
1119 ) -> (FloatTensor<B>, FloatTensor<B>) {
1120 attention::attention_with_logsumexp_default::<B>(query, key, value, options)
1121 }
1122
1123 /// Backward pass for [attention_with_logsumexp](ModuleOps::attention_with_logsumexp): the
1124 /// gradients against the query, the key and the value, from `out_grad`, the loss's gradient
1125 /// against the output, and the forward's `out` and `logsumexp`.
1126 ///
1127 /// Autodiff only calls it when [has_attention_backward](ModuleOps::has_attention_backward)
1128 /// returns `true`. The default composes the gradients from tensor operations
1129 /// ([attention::attention_backward_default]).
1130 fn attention_backward(
1131 query: FloatTensor<B>,
1132 key: FloatTensor<B>,
1133 value: FloatTensor<B>,
1134 out: FloatTensor<B>,
1135 logsumexp: FloatTensor<B>,
1136 out_grad: FloatTensor<B>,
1137 options: AttentionModuleOptions,
1138 ) -> AttentionBackward<B> {
1139 attention::attention_backward_default::<B>(
1140 query, key, value, out, logsumexp, out_grad, options,
1141 )
1142 }
1143
1144 /// Applies Layer Normalization over the last dimension of the input tensor.
1145 ///
1146 /// Computes `(x - mean) / sqrt(var + epsilon) * gamma + beta`, where `mean` and
1147 /// (biased) `var` are reduced over the last axis.
1148 ///
1149 /// # Arguments
1150 ///
1151 /// * `tensor` - Input tensor of shape `[..., d_model]`.
1152 /// * `gamma` - Scale tensor of shape `[d_model]`.
1153 /// * `beta` - Optional bias tensor of shape `[d_model]`.
1154 /// * `epsilon` - Numerical stability term added to the variance before the square root.
1155 ///
1156 /// # Returns
1157 ///
1158 /// A tensor with the same shape as `tensor`.
1159 fn layer_norm(
1160 tensor: FloatTensor<B>,
1161 gamma: FloatTensor<B>,
1162 beta: Option<FloatTensor<B>>,
1163 epsilon: f64,
1164 ) -> FloatTensor<B> {
1165 let shape = tensor.shape();
1166 let rank = shape.num_dims();
1167 let last_dim = rank - 1;
1168 let d_model = shape[last_dim];
1169
1170 let mean = B::float_mean_dim(tensor.clone(), last_dim);
1171 let centered = B::float_sub(tensor, mean);
1172 let var = B::float_mean_dim(B::float_mul(centered.clone(), centered.clone()), last_dim);
1173 let denom = B::float_sqrt(B::float_add_scalar(var, epsilon.into()));
1174 let normalized = B::float_div(centered, denom);
1175
1176 let broadcast_dims: alloc::vec::Vec<usize> = (0..rank)
1177 .map(|i| if i == last_dim { d_model } else { 1 })
1178 .collect();
1179 let gamma_b = B::float_reshape(gamma, Shape::from(broadcast_dims.clone()));
1180 let scaled = B::float_mul(normalized, gamma_b);
1181
1182 match beta {
1183 Some(beta) => {
1184 let beta_b = B::float_reshape(beta, Shape::from(broadcast_dims));
1185 B::float_add(scaled, beta_b)
1186 }
1187 None => scaled,
1188 }
1189 }
1190
1191 /// Computes the Connectionist Temporal Classification (CTC) loss.
1192 ///
1193 /// Sums over all valid alignments between the input and target sequences
1194 /// using the forward (alpha) algorithm.
1195 ///
1196 /// # Arguments
1197 ///
1198 /// * `log_probs` - Log-probabilities of shape `[T, N, C]`
1199 /// * `targets` - Target label indices of shape `[N, S]`
1200 /// * `input_lengths` - Actual input sequence lengths per batch element `[N]`
1201 /// * `target_lengths` - Actual target lengths per batch element `[N]`
1202 /// * `blank` - Index of the blank label
1203 ///
1204 /// # Returns
1205 ///
1206 /// Per-sample loss of shape `[N]`
1207 fn ctc_loss(
1208 log_probs: FloatTensor<B>,
1209 targets: IntTensor<B>,
1210 input_lengths: IntTensor<B>,
1211 target_lengths: IntTensor<B>,
1212 blank: usize,
1213 ) -> FloatTensor<B> {
1214 ctc::ctc_loss_default::<B>(log_probs, targets, input_lengths, target_lengths, blank)
1215 }
1216
1217 /// Returns `true` if this backend implements [ctc_loss_backward](ModuleOps::ctc_loss_backward)
1218 /// natively.
1219 ///
1220 /// Autodiff queries this flag to decide between two paths:
1221 /// - `true`: use the backend's [ctc_loss](ModuleOps::ctc_loss) and
1222 /// [ctc_loss_backward](ModuleOps::ctc_loss_backward) directly.
1223 /// - `false`: call [ctc::ctc_loss_default] for the forward pass; autodiff
1224 /// then differentiates through the decomposed tensor ops.
1225 ///
1226 /// Backends that override `ctc_loss_backward` must also override this to
1227 /// return `true`.
1228 fn has_ctc_loss_backward() -> bool {
1229 false
1230 }
1231
1232 /// Backward pass for [ctc_loss](ModuleOps::ctc_loss): gradient w.r.t. `log_probs`.
1233 ///
1234 /// Autodiff only calls it when [has_ctc_loss_backward](ModuleOps::has_ctc_loss_backward)
1235 /// returns `true`. Backends without a native implementation should leave
1236 /// both methods at their defaults; the gradient is computed automatically by
1237 /// autodiff against the decomposed [ctc::ctc_loss_default] forward.
1238 ///
1239 /// The default composes the gradient from tensor operations
1240 /// ([ctc::ctc_loss_backward_default]). It serves callers that hold no autodiff
1241 /// graph for this backend, such as the interpreter behind a router, whose
1242 /// client asked for the gradient as one operation.
1243 ///
1244 /// # Arguments
1245 ///
1246 /// * `log_probs` - Log-probabilities of shape `[T, N, C]`
1247 /// * `targets` - Target label indices of shape `[N, S]`
1248 /// * `input_lengths` - Actual input sequence lengths per batch element `[N]`
1249 /// * `target_lengths` - Actual target lengths per batch element `[N]`
1250 /// * `grad_loss` - Upstream gradient w.r.t. the per-sample loss `[N]`
1251 /// * `blank` - Index of the blank label
1252 ///
1253 /// # Returns
1254 ///
1255 /// Gradient w.r.t. `log_probs` of shape `[T, N, C]`
1256 fn ctc_loss_backward(
1257 log_probs: FloatTensor<B>,
1258 targets: IntTensor<B>,
1259 input_lengths: IntTensor<B>,
1260 target_lengths: IntTensor<B>,
1261 grad_loss: FloatTensor<B>,
1262 blank: usize,
1263 ) -> FloatTensor<B> {
1264 ctc::ctc_loss_backward_default::<B>(
1265 log_probs,
1266 targets,
1267 input_lengths,
1268 target_lengths,
1269 grad_loss,
1270 blank,
1271 )
1272 }
1273}