1use candle::{CpuStorage, DType, Layout, Module, Result, Shape, Tensor, D};
5use rayon::prelude::*;
6
7pub fn softmax<D: candle::shape::Dim>(xs: &Tensor, dim: D) -> Result<Tensor> {
23 let dim = dim.to_index(xs.shape(), "softmax")?;
24 let max = xs.max_keepdim(dim)?;
25 let diff = xs.broadcast_sub(&max)?;
26 let num = diff.exp()?;
27 let den = num.sum_keepdim(dim)?;
28 num.broadcast_div(&den)
29}
30
31pub fn log_softmax<D: candle::shape::Dim>(xs: &Tensor, d: D) -> Result<Tensor> {
32 let d = d.to_index(xs.shape(), "log-softmax")?;
33 let max = xs.max_keepdim(d)?;
34 let diff = xs.broadcast_sub(&max)?;
35 let sum_exp = diff.exp()?.sum_keepdim(d)?;
36 let log_sm = diff.broadcast_sub(&sum_exp.log()?)?;
37 Ok(log_sm)
38}
39
40pub fn silu(xs: &Tensor) -> Result<Tensor> {
41 xs.silu()
42}
43
44pub fn swiglu(xs: &Tensor) -> Result<Tensor> {
45 let xs = xs.chunk(2, D::Minus1)?;
46 &xs[0].silu()? * &xs[1]
47}
48
49struct Sigmoid;
50
51impl candle::CustomOp1 for Sigmoid {
52 fn name(&self) -> &'static str {
53 "sigmoid"
54 }
55
56 fn cpu_fwd(&self, storage: &CpuStorage, layout: &Layout) -> Result<(CpuStorage, Shape)> {
57 use candle::backend::BackendStorage;
58
59 fn fwd<T: num_traits::Float>(v: T) -> T {
60 (v.neg().exp() + T::one()).recip()
61 }
62
63 let storage = match storage {
65 CpuStorage::BF16(slice) => {
66 CpuStorage::BF16(candle::cpu_backend::unary_map(slice, layout, fwd))
67 }
68 CpuStorage::F16(slice) => {
69 CpuStorage::F16(candle::cpu_backend::unary_map(slice, layout, fwd))
70 }
71 CpuStorage::F32(slice) => {
72 CpuStorage::F32(candle::cpu_backend::unary_map(slice, layout, fwd))
73 }
74 CpuStorage::F64(slice) => {
75 CpuStorage::F64(candle::cpu_backend::unary_map(slice, layout, fwd))
76 }
77 _ => Err(candle::Error::UnsupportedDTypeForOp(
78 storage.dtype(),
79 self.name(),
80 ))?,
81 };
82 Ok((storage, layout.shape().clone()))
83 }
84
85 #[cfg(feature = "cuda")]
86 fn cuda_fwd(
87 &self,
88 storage: &candle::CudaStorage,
89 layout: &Layout,
90 ) -> Result<(candle::CudaStorage, Shape)> {
91 use candle::backend::BackendStorage;
92 use candle::cuda_backend::cudarc::driver::{
93 CudaSlice, DeviceRepr, LaunchConfig, PushKernelArg, ValidAsZeroBits,
94 };
95 use candle::cuda_backend::SlicePtrOrNull;
96 use candle::cuda_backend::{kernel_name, kernels, Map1, WrapErr};
97 use candle::{CudaDevice, WithDType};
98
99 struct S;
100 impl Map1 for S {
101 fn f<T: DeviceRepr + WithDType + ValidAsZeroBits>(
102 &self,
103 src: &CudaSlice<T>,
104 dev: &CudaDevice,
105 layout: &Layout,
106 ) -> Result<CudaSlice<T>> {
107 let shape = layout.shape();
108 let dims = shape.dims();
109 let el_count = shape.elem_count();
110 let cfg = LaunchConfig::for_num_elems(el_count as u32);
111 let ds = SlicePtrOrNull::params_from_layout(dev, layout)?;
112 let src = &src.slice(layout.start_offset()..);
113 let func = dev.get_or_load_func(&kernel_name::<T>("usigmoid"), &kernels::UNARY)?;
114 let out = unsafe { dev.alloc::<T>(el_count)? };
116
117 let mut builder = func.builder();
118 candle::builder_arg!(builder, el_count, dims.len());
119 ds.builder_arg(&mut builder);
120 builder.arg(src);
121 builder.arg(&out);
122 unsafe { builder.launch(cfg) }.w()?;
124 Ok(out)
125 }
126 }
127
128 let dev = storage.device();
129 let slice = S.map(&storage.slice, dev, layout)?;
130 let dst = candle::CudaStorage {
131 slice,
132 device: dev.clone(),
133 };
134 Ok((dst, layout.shape().clone()))
135 }
136
137 #[cfg(feature = "metal")]
138 fn metal_fwd(
139 &self,
140 storage: &candle::MetalStorage,
141 layout: &Layout,
142 ) -> Result<(candle::MetalStorage, Shape)> {
143 use candle::backend::BackendStorage;
144 use candle::MetalError;
145 let device = storage.device();
146 let dtype = storage.dtype();
147 let shape = layout.shape();
148 let el_count = shape.elem_count();
149 let buffer = device
150 .new_buffer_builder()
151 .with_size_for(el_count, dtype)
152 .with_label("sigmoid")
153 .build()?;
154 let encoder = device.command_encoder()?;
155 encoder.set_label("sigmoid");
156 let src = candle_metal_kernels::BufferOffset {
157 buffer: storage.buffer(),
158 offset_in_bytes: layout.start_offset() * storage.dtype().size_in_bytes(),
159 };
160
161 if layout.is_contiguous() {
162 use candle_metal_kernels::unary::contiguous;
163 let kernel_name = match dtype {
164 DType::F16 => contiguous::sigmoid::HALF,
165 DType::F32 => contiguous::sigmoid::FLOAT,
166 DType::BF16 => contiguous::sigmoid::BFLOAT,
167 dtype => {
168 candle::bail!("Metal contiguous unary sigmoid {dtype:?} not implemented")
169 }
170 };
171 candle_metal_kernels::call_unary_contiguous(
172 device.metal_device(),
173 &encoder,
174 device.kernels(),
175 kernel_name,
176 dtype.size_in_bytes(),
177 el_count,
178 src,
179 &buffer,
180 )
181 .map_err(MetalError::from)?;
182 } else {
183 use candle_metal_kernels::unary::strided;
184 let kernel_name = match dtype {
185 DType::F16 => strided::sigmoid::HALF,
186 DType::F32 => strided::sigmoid::FLOAT,
187 DType::BF16 => strided::sigmoid::BFLOAT,
188 dtype => {
189 candle::bail!("Metal strided unary sigmoid {dtype:?} not implemented")
190 }
191 };
192 let dst = candle_metal_kernels::BufferOffset::zero_offset(&buffer);
193 candle_metal_kernels::call_unary_strided(
194 device.metal_device(),
195 &encoder,
196 device.kernels(),
197 kernel_name,
198 layout.dims(),
199 src,
200 layout.stride(),
201 dst,
202 )
203 .map_err(MetalError::from)?;
204 }
205
206 let new_storage = candle::MetalStorage::new(buffer, device.clone(), el_count, dtype);
207 Ok((new_storage, layout.shape().clone()))
208 }
209
210 fn bwd(&self, _arg: &Tensor, res: &Tensor, grad_res: &Tensor) -> Result<Option<Tensor>> {
211 let d_dx_sigmoid = res.ones_like()?.sub(res)?.mul(res)?;
213 Ok(Some(grad_res.mul(&d_dx_sigmoid)?))
214 }
215}
216
217pub fn sigmoid(xs: &Tensor) -> Result<Tensor> {
218 xs.apply_op1(Sigmoid)
219}
220
221pub fn hard_sigmoid(xs: &Tensor) -> Result<Tensor> {
222 ((xs + 3.0)? / 6.0)?.clamp(0f32, 1f32)
224}
225
226pub fn mish(xs: &Tensor) -> Result<Tensor> {
227 xs * (1.0 + xs.exp()?)?.log()?.tanh()
228}
229
230pub fn leaky_relu(xs: &Tensor, negative_slope: f64) -> Result<Tensor> {
231 let zeros = xs.zeros_like()?;
232 xs.maximum(&zeros)? + xs.minimum(&zeros)? * negative_slope
233}
234
235pub fn selu(xs: &Tensor, alpha: f32, gamma: f32) -> Result<Tensor> {
236 let is_pos = xs.gt(0f32)?;
237 let alpha_t = Tensor::full(alpha, xs.dims(), xs.device())?;
238 let neg = xs.exp()?.mul(&alpha_t)?.sub(&alpha_t)?;
239 let selu = is_pos.where_cond(xs, &neg)?;
240 let gamma_t = Tensor::full(gamma, xs.dims(), xs.device())?;
241 selu.broadcast_mul(&gamma_t)
242}
243
244pub fn dropout(xs: &Tensor, drop_p: f32) -> Result<Tensor> {
245 if !(0. ..1.).contains(&drop_p) {
251 candle::bail!("dropout probability has to be in [0, 1), got {drop_p}")
252 }
253 let rand = Tensor::rand(0f32, 1f32, xs.shape(), xs.device())?;
254 let scale = 1.0 / (1.0 - drop_p as f64);
255 let drop_p = Tensor::new(drop_p, xs.device())?.broadcast_as(xs.shape())?;
256 let mask = (rand.ge(&drop_p)?.to_dtype(xs.dtype())? * scale)?;
257 xs * mask
258}
259
260#[derive(Clone, Debug)]
261pub struct Dropout {
262 drop_p: f32,
263}
264
265impl Dropout {
266 pub fn new(drop_p: f32) -> Dropout {
267 Self { drop_p }
268 }
269
270 pub fn forward(&self, xs: &Tensor, train: bool) -> Result<Tensor> {
271 if train {
272 dropout(xs, self.drop_p)
273 } else {
274 Ok(xs.clone())
275 }
276 }
277}
278
279impl candle::ModuleT for Dropout {
280 fn forward_t(&self, xs: &Tensor, train: bool) -> Result<Tensor> {
281 self.forward(xs, train)
282 }
283}
284
285struct SoftmaxLastDim;
286
287impl candle::CustomOp1 for SoftmaxLastDim {
288 fn name(&self) -> &'static str {
289 "softmax-last-dim"
290 }
291
292 fn cpu_fwd(&self, storage: &CpuStorage, layout: &Layout) -> Result<(CpuStorage, Shape)> {
293 fn softmax<T: candle::WithDType + num_traits::Float>(
294 src: &[T],
295 layout: &Layout,
296 ) -> Result<(CpuStorage, Shape)> {
297 let src = match layout.contiguous_offsets() {
298 None => candle::bail!("input has to be contiguous"),
299 Some((o1, o2)) => &src[o1..o2],
300 };
301 let el_count = layout.shape().elem_count();
302 let dims = layout.shape().dims();
303 let dim_m1 = dims[dims.len() - 1];
304 let mut dst = vec![T::zero(); el_count];
305 src.par_chunks(dim_m1)
306 .zip(dst.par_chunks_mut(dim_m1))
307 .for_each(|(src, dst)| {
308 let mut max = T::neg_infinity();
309 unsafe { T::vec_reduce_max(src.as_ptr(), &mut max, dim_m1) };
310 for (s, d) in src.iter().zip(dst.iter_mut()) {
311 *d = (*s - max).exp();
312 }
313 let mut sum_exp = T::zero();
314 unsafe { T::vec_reduce_sum(dst.as_ptr(), &mut sum_exp, dim_m1) };
315 for d in dst.iter_mut() {
316 *d /= sum_exp
317 }
318 });
319 let storage = candle::WithDType::to_cpu_storage_owned(dst);
320 Ok((storage, Shape::from_dims(dims)))
321 }
322
323 match storage {
324 CpuStorage::BF16(slice) => softmax::<half::bf16>(slice, layout),
325 CpuStorage::F16(slice) => softmax::<half::f16>(slice, layout),
326 CpuStorage::F32(slice) => softmax::<f32>(slice, layout),
327 CpuStorage::F64(slice) => softmax::<f64>(slice, layout),
328 _ => candle::bail!("unsupported dtype for softmax {:?}", storage),
329 }
330 }
331
332 #[cfg(feature = "cuda")]
333 fn cuda_fwd(
334 &self,
335 storage: &candle::CudaStorage,
336 layout: &Layout,
337 ) -> Result<(candle::CudaStorage, Shape)> {
338 use candle::cuda_backend::cudarc::driver::{
339 CudaSlice, DeviceRepr, LaunchConfig, PushKernelArg,
340 };
341 use candle::cuda_backend::{kernel_name, kernels, Map1, WrapErr};
342 use candle::{CudaDevice, WithDType};
343
344 struct S;
345 impl Map1 for S {
346 fn f<T: DeviceRepr + WithDType>(
347 &self,
348 src: &CudaSlice<T>,
349 dev: &CudaDevice,
350 layout: &Layout,
351 ) -> Result<CudaSlice<T>> {
352 let src = match layout.contiguous_offsets() {
353 None => candle::bail!("input has to be contiguous"),
354 Some((o1, o2)) => src.slice(o1..o2),
355 };
356 let el = layout.shape().elem_count();
357 let dims = layout.shape().dims();
358 let dim_m1 = dims[dims.len() - 1];
359 let (n_rows, n_cols) = (el / dim_m1, dim_m1);
360
361 let cfg = LaunchConfig {
362 grid_dim: (n_rows as u32, 1, 1),
363 block_dim: (1, 32, 1),
364 shared_mem_bytes: 0,
365 };
366 let func = dev.get_or_load_func(&kernel_name::<T>("softmax"), &kernels::REDUCE)?;
367 let dst = unsafe { dev.alloc::<T>(el)? };
369 let mut builder = func.builder();
370 builder.arg(&src);
371 builder.arg(&dst);
372 candle::builder_arg!(builder, n_cols as i32);
373 unsafe { builder.launch(cfg) }.w()?;
375 Ok(dst)
376 }
377 }
378
379 use candle::backend::BackendStorage;
380 let dev = storage.device();
381 let slice = S.map(&storage.slice, dev, layout)?;
382 let dst = candle::cuda_backend::CudaStorage {
383 slice,
384 device: dev.clone(),
385 };
386 Ok((dst, layout.shape().clone()))
387 }
388
389 #[cfg(feature = "metal")]
390 fn metal_fwd(
391 &self,
392 storage: &candle::MetalStorage,
393 layout: &Layout,
394 ) -> Result<(candle::MetalStorage, Shape)> {
395 use candle::backend::BackendStorage;
396 let device = storage.device();
397 let encoder = device.command_encoder()?;
398 encoder.set_label("softmax");
399 let kernels = device.kernels();
400 let name = match storage.dtype() {
401 DType::F32 => "softmax_f32",
402 DType::F16 => "softmax_f16",
403 DType::BF16 => "softmax_bf16",
404 dtype => candle::bail!("softmax-last-dim is not implemented for {dtype:?}"),
405 };
406
407 let n = layout.stride().len();
408 if !(layout.is_contiguous() && layout.stride()[n - 1] == 1) {
409 candle::bail!("Non contiguous softmax-last-dim is not implemented");
410 }
411
412 let last_dim = layout.dims()[layout.shape().rank() - 1];
413 let elem_count = layout.shape().elem_count();
414 let output = device
415 .new_buffer_builder()
416 .with_size_for(elem_count, storage.dtype())
417 .with_label("softmax")
418 .build()?;
419 candle_metal_kernels::call_last_softmax(
420 device.metal_device(),
421 &encoder,
422 kernels,
423 name,
424 elem_count,
425 last_dim,
426 storage.buffer(),
427 layout.start_offset() * storage.dtype().size_in_bytes(),
428 &output,
429 )
430 .map_err(candle::Error::wrap)?;
431 let newstorage =
432 candle::MetalStorage::new(output, device.clone(), elem_count, storage.dtype());
433 Ok((newstorage, layout.shape().clone()))
434 }
435}
436
437pub fn softmax_last_dim(xs: &Tensor) -> Result<Tensor> {
438 xs.apply_op1_no_bwd(&SoftmaxLastDim)
439}
440
441#[derive(Debug, Clone)]
442struct RmsNorm {
443 eps: f32,
444}
445
446impl candle::CustomOp2 for RmsNorm {
447 fn name(&self) -> &'static str {
448 "rms-norm"
449 }
450
451 fn cpu_fwd(
452 &self,
453 s1: &CpuStorage,
454 l1: &Layout,
455 s2: &CpuStorage,
456 l2: &Layout,
457 ) -> Result<(CpuStorage, Shape)> {
458 use candle::backend::BackendStorage;
459
460 let eps = self.eps;
461 fn inner<
462 T: candle::WithDType
463 + num_traits::Float
464 + num_traits::AsPrimitive<f32>
465 + num_traits::FromPrimitive,
466 >(
467 src: &[T],
468 layout: &Layout,
469 alpha: &[T],
470 alpha_layout: &Layout,
471 eps: f32,
472 ) -> Result<(CpuStorage, Shape)> {
473 let src = match layout.contiguous_offsets() {
474 None => candle::bail!("input has to be contiguous"),
475 Some((o1, o2)) => &src[o1..o2],
476 };
477 let alpha = match alpha_layout.contiguous_offsets() {
478 None => candle::bail!("alpha has to be contiguous"),
479 Some((o1, o2)) => &alpha[o1..o2],
480 };
481 let el_count = layout.shape().elem_count();
482 let dims = layout.shape().dims();
483 let dim_m1 = dims[dims.len() - 1];
484 let n_rows = el_count / dim_m1;
485 let mut dst = vec![T::zero(); el_count];
486
487 fn rms_row<
488 T: candle::WithDType
489 + num_traits::Float
490 + num_traits::AsPrimitive<f32>
491 + num_traits::FromPrimitive,
492 >(
493 src: &[T],
494 alpha: &[T],
495 n: usize,
496 eps: f32,
497 dst: &mut [T],
498 ) {
499 let sum2 = src
500 .iter()
501 .map(|&v| {
502 let v = v.as_();
503 v * v
504 })
505 .sum::<f32>();
506 let m = (sum2 / n as f32 + eps).sqrt();
507 let m = T::from_f32(m).unwrap_or_else(T::nan);
508 for ((d, s), alpha) in dst.iter_mut().zip(src.iter()).zip(alpha) {
509 *d = *s / m * *alpha
510 }
511 }
512
513 if n_rows <= 32 {
514 let n = dim_m1;
515 for row in 0..n_rows {
516 let src = &src[row * n..(row + 1) * n];
517 let dst = &mut dst[row * n..(row + 1) * n];
518 rms_row(src, alpha, n, eps, dst);
519 }
520 } else {
521 src.par_chunks(dim_m1)
522 .zip(dst.par_chunks_mut(dim_m1))
523 .for_each(|(src, dst)| {
524 let n = src.len();
525 rms_row(src, alpha, n, eps, dst);
526 });
527 }
528 let storage = candle::WithDType::to_cpu_storage_owned(dst);
529 Ok((storage, Shape::from_dims(dims)))
530 }
531
532 use CpuStorage as C;
533 match (s1, s2) {
534 (C::BF16(s1), C::BF16(s2)) => inner::<half::bf16>(s1, l1, s2, l2, eps),
535 (C::F16(s1), C::F16(s2)) => inner::<half::f16>(s1, l1, s2, l2, eps),
536 (C::F32(s1), C::F32(s2)) => inner::<f32>(s1, l1, s2, l2, eps),
537 _ => candle::bail!("unsupported dtype for rmsnorm {:?}", s1.dtype()),
538 }
539 }
540
541 #[cfg(feature = "cuda")]
542 fn cuda_fwd(
543 &self,
544 s1: &candle::CudaStorage,
545 l1: &Layout,
546 s2: &candle::CudaStorage,
547 l2: &Layout,
548 ) -> Result<(candle::CudaStorage, Shape)> {
549 use candle::cuda_backend::cudarc::driver::{
550 CudaSlice, DeviceRepr, LaunchConfig, PushKernelArg,
551 };
552 use candle::cuda_backend::{kernel_name, kernels, Map2, WrapErr};
553 use candle::{CudaDevice, WithDType};
554
555 struct S {
556 eps: f32,
557 }
558 impl Map2 for S {
559 fn f<T: DeviceRepr + WithDType>(
560 &self,
561 src: &CudaSlice<T>,
562 layout: &Layout,
563 alpha: &CudaSlice<T>,
564 alpha_layout: &Layout,
565 dev: &CudaDevice,
566 ) -> Result<CudaSlice<T>> {
567 let src = match layout.contiguous_offsets() {
568 None => candle::bail!("input has to be contiguous"),
569 Some((o1, o2)) => src.slice(o1..o2),
570 };
571 let alpha = match alpha_layout.contiguous_offsets() {
572 None => candle::bail!("alpha has to be contiguous"),
573 Some((o1, o2)) => alpha.slice(o1..o2),
574 };
575 let el = layout.shape().elem_count();
576 let dims = layout.shape().dims();
577 let dim_m1 = dims[dims.len() - 1];
578 let (n_rows, n_cols) = (el / dim_m1, dim_m1);
579
580 let block_size = if n_cols < 1024 { 32 } else { 1024 };
581 let cfg = LaunchConfig {
582 grid_dim: (n_rows as u32, 1, 1),
583 block_dim: (block_size, 1, 1),
584 shared_mem_bytes: 0,
585 };
586 let func = dev.get_or_load_func(&kernel_name::<T>("rmsnorm"), &kernels::REDUCE)?;
587 let dst = unsafe { dev.alloc::<T>(el)? };
589 let mut builder = func.builder();
590 builder.arg(&src);
591 builder.arg(&dst);
592 builder.arg(&alpha);
593 candle::builder_arg!(builder, n_cols as i32, block_size as i32, self.eps);
594 unsafe { builder.launch(cfg) }.w()?;
596 Ok(dst)
597 }
598 }
599
600 use candle::backend::BackendStorage;
601 let dev = s1.device();
602 let slice = S { eps: self.eps }.map(&s1.slice, l1, &s2.slice, l2, dev)?;
603 let dst = candle::cuda_backend::CudaStorage {
604 slice,
605 device: dev.clone(),
606 };
607 Ok((dst, l1.shape().clone()))
608 }
609
610 #[cfg(feature = "metal")]
611 fn metal_fwd(
612 &self,
613 s1: &candle::MetalStorage,
614 l1: &Layout,
615 s2: &candle::MetalStorage,
616 l2: &Layout,
617 ) -> Result<(candle::MetalStorage, Shape)> {
618 use candle::backend::BackendStorage;
619 let device = s1.device();
620 let encoder = device.command_encoder()?;
621 encoder.set_label("rmsnorm");
622 let kernels = device.kernels();
623 let name = match (s1.dtype(), s2.dtype()) {
624 (DType::F32, DType::F32) => "rmsnorm_f32",
625 (DType::F16, DType::F16) => "rmsnorm_f16",
626 (DType::BF16, DType::BF16) => "rmsnorm_bf16",
627 (dt1, dt2) => candle::bail!("rmsnorm is not implemented for {dt1:?} {dt2:?}"),
628 };
629
630 if !(l1.is_contiguous() && l2.is_contiguous()) {
631 candle::bail!("Non contiguous rmsnorm is not implemented");
632 }
633
634 let last_dim = l1.dims()[l1.shape().rank() - 1];
635 let elem_count = l1.shape().elem_count();
636 let output = device
637 .new_buffer_builder()
638 .with_size_for(elem_count, s1.dtype())
639 .with_label("rmsnorm")
640 .build()?;
641 candle_metal_kernels::call_rms_norm(
642 device.metal_device(),
643 &encoder,
644 kernels,
645 name,
646 elem_count,
647 last_dim,
648 self.eps,
649 s1.buffer(),
650 l1.start_offset() * s1.dtype().size_in_bytes(),
651 s2.buffer(),
652 l2.start_offset() * s2.dtype().size_in_bytes(),
653 &output,
654 )
655 .map_err(candle::Error::wrap)?;
656 let newstorage = candle::MetalStorage::new(output, device.clone(), elem_count, s1.dtype());
657 Ok((newstorage, l1.shape().clone()))
658 }
659}
660
661pub fn rms_norm_slow(x: &Tensor, alpha: &Tensor, eps: f32) -> Result<Tensor> {
662 let x_dtype = x.dtype();
663 let internal_dtype = match x_dtype {
664 DType::F16 | DType::BF16 => DType::F32,
665 d => d,
666 };
667 let hidden_size = x.dim(D::Minus1)?;
668 let x = x.to_dtype(internal_dtype)?;
669 let norm_x = (x.sqr()?.sum_keepdim(D::Minus1)? / hidden_size as f64)?;
670 let x_normed = x.broadcast_div(&(norm_x + eps as f64)?.sqrt()?)?;
671 x_normed.to_dtype(x_dtype)?.broadcast_mul(alpha)
672}
673
674pub fn rms_norm(xs: &Tensor, alpha: &Tensor, eps: f32) -> Result<Tensor> {
675 let hidden_size_xs = xs.dim(D::Minus1)?;
676 let hidden_size_alpha = alpha.dims1()?;
677 if hidden_size_xs != hidden_size_alpha {
678 candle::bail!(
679 "shape mismatch in rms-norm {:?} {:?}",
680 xs.shape(),
681 alpha.shape()
682 )
683 }
684 xs.apply_op2_no_bwd(alpha, &RmsNorm { eps })
685}
686
687#[derive(Debug, Clone)]
688struct LayerNorm {
689 eps: f32,
690}
691
692impl candle::CustomOp3 for LayerNorm {
693 fn name(&self) -> &'static str {
694 "layer-norm"
695 }
696
697 fn cpu_fwd(
698 &self,
699 s1: &CpuStorage,
700 l1: &Layout,
701 s2: &CpuStorage,
702 l2: &Layout,
703 s3: &CpuStorage,
704 l3: &Layout,
705 ) -> Result<(CpuStorage, Shape)> {
706 use candle::backend::BackendStorage;
707
708 let eps = self.eps;
709 fn inner<
710 T: candle::WithDType
711 + num_traits::Float
712 + num_traits::AsPrimitive<f32>
713 + num_traits::FromPrimitive,
714 >(
715 src: &[T],
716 layout: &Layout,
717 alpha: &[T],
718 alpha_layout: &Layout,
719 beta: &[T],
720 beta_layout: &Layout,
721 eps: f32,
722 ) -> Result<(CpuStorage, Shape)> {
723 let src = match layout.contiguous_offsets() {
724 None => candle::bail!("input has to be contiguous"),
725 Some((o1, o2)) => &src[o1..o2],
726 };
727 let alpha = match alpha_layout.contiguous_offsets() {
728 None => candle::bail!("alpha has to be contiguous"),
729 Some((o1, o2)) => &alpha[o1..o2],
730 };
731 let beta = match beta_layout.contiguous_offsets() {
732 None => candle::bail!("beta has to be contiguous"),
733 Some((o1, o2)) => &beta[o1..o2],
734 };
735 let el_count = layout.shape().elem_count();
736 let dims = layout.shape().dims();
737 let dim_m1 = dims[dims.len() - 1];
738 let mut dst = vec![T::zero(); el_count];
739 src.par_chunks(dim_m1)
740 .zip(dst.par_chunks_mut(dim_m1))
741 .for_each(|(src, dst)| {
742 let mut sum = 0f32;
743 let mut sum2 = 0f32;
744 for v in src {
745 let v = v.as_();
746 sum += v;
747 sum2 += v * v;
748 }
749 let mean = sum / dim_m1 as f32;
750 let var = sum2 / dim_m1 as f32 - mean * mean;
751 let inv_std = (var + eps).sqrt().recip();
752 for ((d, s), (alpha, beta)) in
753 dst.iter_mut().zip(src.iter()).zip(alpha.iter().zip(beta))
754 {
755 let alpha = alpha.as_();
756 let beta = beta.as_();
757 let d_ = (s.as_() - mean) * inv_std * alpha + beta;
758 *d = T::from_f32(d_).unwrap_or_else(T::nan);
759 }
760 });
761 let storage = candle::WithDType::to_cpu_storage_owned(dst);
762 Ok((storage, Shape::from_dims(dims)))
763 }
764
765 use CpuStorage as C;
766 match (s1, s2, s3) {
767 (C::BF16(s1), C::BF16(s2), C::BF16(s3)) => {
768 inner::<half::bf16>(s1, l1, s2, l2, s3, l3, eps)
769 }
770 (C::F16(s1), C::F16(s2), C::F16(s3)) => inner::<half::f16>(s1, l1, s2, l2, s3, l3, eps),
771 (C::F32(s1), C::F32(s2), C::F32(s3)) => inner::<f32>(s1, l1, s2, l2, s3, l3, eps),
772 _ => candle::bail!("unsupported dtype for rmsnorm {:?}", s1.dtype()),
773 }
774 }
775
776 #[cfg(feature = "cuda")]
777 fn cuda_fwd(
778 &self,
779 s1: &candle::CudaStorage,
780 l1: &Layout,
781 s2: &candle::CudaStorage,
782 l2: &Layout,
783 s3: &candle::CudaStorage,
784 l3: &Layout,
785 ) -> Result<(candle::CudaStorage, Shape)> {
786 use candle::cuda_backend::cudarc::driver::{
787 CudaSlice, DeviceRepr, LaunchConfig, PushKernelArg,
788 };
789 use candle::cuda_backend::{kernel_name, kernels, Map3, WrapErr};
790 use candle::{CudaDevice, WithDType};
791
792 struct S {
793 eps: f32,
794 }
795 impl Map3 for S {
796 fn f<T: DeviceRepr + WithDType>(
797 &self,
798 src: &CudaSlice<T>,
799 layout: &Layout,
800 alpha: &CudaSlice<T>,
801 alpha_layout: &Layout,
802 beta: &CudaSlice<T>,
803 beta_layout: &Layout,
804 dev: &CudaDevice,
805 ) -> Result<CudaSlice<T>> {
806 let src = match layout.contiguous_offsets() {
807 None => candle::bail!("input has to be contiguous"),
808 Some((o1, o2)) => src.slice(o1..o2),
809 };
810 let alpha = match alpha_layout.contiguous_offsets() {
811 None => candle::bail!("alpha has to be contiguous"),
812 Some((o1, o2)) => alpha.slice(o1..o2),
813 };
814 let beta = match beta_layout.contiguous_offsets() {
815 None => candle::bail!("beta has to be contiguous"),
816 Some((o1, o2)) => beta.slice(o1..o2),
817 };
818 let el = layout.shape().elem_count();
819 let dims = layout.shape().dims();
820 let dim_m1 = dims[dims.len() - 1];
821 let (n_rows, n_cols) = (el / dim_m1, dim_m1);
822
823 let block_size = if n_cols < 1024 { 32 } else { 1024 };
824 let cfg = LaunchConfig {
825 grid_dim: (n_rows as u32, 1, 1),
826 block_dim: (block_size, 1, 1),
827 shared_mem_bytes: 0,
828 };
829 let func =
830 dev.get_or_load_func(&kernel_name::<T>("layernorm"), &kernels::REDUCE)?;
831 let dst = unsafe { dev.alloc::<T>(el)? };
833 let mut builder = func.builder();
834 builder.arg(&src);
835 builder.arg(&dst);
836 builder.arg(&alpha);
837 builder.arg(&beta);
838 candle::builder_arg!(builder, n_cols as i32, block_size as i32, self.eps);
839 unsafe { builder.launch(cfg) }.w()?;
841 Ok(dst)
842 }
843 }
844
845 use candle::backend::BackendStorage;
846 let dev = s1.device();
847 let slice = S { eps: self.eps }.map(&s1.slice, l1, &s2.slice, l2, &s3.slice, l3, dev)?;
848 let dst = candle::cuda_backend::CudaStorage {
849 slice,
850 device: dev.clone(),
851 };
852 Ok((dst, l1.shape().clone()))
853 }
854
855 #[cfg(feature = "metal")]
856 fn metal_fwd(
857 &self,
858 s1: &candle::MetalStorage,
859 l1: &Layout,
860 s2: &candle::MetalStorage,
861 l2: &Layout,
862 s3: &candle::MetalStorage,
863 l3: &Layout,
864 ) -> Result<(candle::MetalStorage, Shape)> {
865 use candle::backend::BackendStorage;
866 let device = s1.device();
867 let encoder = device.command_encoder()?;
868 encoder.set_label("layernorm");
869 let kernels = device.kernels();
870 let name = match (s1.dtype(), s2.dtype(), s3.dtype()) {
871 (DType::F32, DType::F32, DType::F32) => "layernorm_f32",
872 (DType::F16, DType::F16, DType::F16) => "layernorm_f16",
873 (DType::BF16, DType::BF16, DType::BF16) => "layernorm_bf16",
874 (dt1, dt2, dt3) => {
875 candle::bail!("layernorm is not implemented for {dt1:?} {dt2:?} {dt3:?}")
876 }
877 };
878
879 if !(l1.is_contiguous() && l2.is_contiguous() && l3.is_contiguous()) {
880 candle::bail!("Non contiguous layernorm is not implemented");
881 }
882
883 let last_dim = l1.dims()[l1.shape().rank() - 1];
884 let elem_count = l1.shape().elem_count();
885 let output = device
886 .new_buffer_builder()
887 .with_size_for(elem_count, s1.dtype())
888 .with_label("layernorm")
889 .build()?;
890 candle_metal_kernels::call_layer_norm(
891 device.metal_device(),
892 &encoder,
893 kernels,
894 name,
895 elem_count,
896 last_dim,
897 self.eps,
898 s1.buffer(),
899 l1.start_offset() * s1.dtype().size_in_bytes(),
900 s2.buffer(),
901 l2.start_offset() * s2.dtype().size_in_bytes(),
902 s3.buffer(),
903 l3.start_offset() * s3.dtype().size_in_bytes(),
904 &output,
905 )
906 .map_err(candle::Error::wrap)?;
907 let newstorage = candle::MetalStorage::new(output, device.clone(), elem_count, s1.dtype());
908 Ok((newstorage, l1.shape().clone()))
909 }
910}
911
912pub fn layer_norm_slow(x: &Tensor, alpha: &Tensor, beta: &Tensor, eps: f32) -> Result<Tensor> {
913 let x_dtype = x.dtype();
914 let internal_dtype = match x_dtype {
915 DType::F16 | DType::BF16 => DType::F32,
916 d => d,
917 };
918 let hidden_size = x.dim(D::Minus1)?;
919 let x = x.to_dtype(internal_dtype)?;
920 let x = {
921 let mean_x = (x.sum_keepdim(D::Minus1)? / hidden_size as f64)?;
922 x.broadcast_sub(&mean_x)?
923 };
924 let norm_x = (x.sqr()?.sum_keepdim(D::Minus1)? / hidden_size as f64)?;
925 let x_normed = x.broadcast_div(&(norm_x + eps as f64)?.sqrt()?)?;
926 x_normed
927 .to_dtype(x_dtype)?
928 .broadcast_mul(alpha)?
929 .broadcast_add(beta)
930}
931
932pub fn layer_norm(xs: &Tensor, alpha: &Tensor, beta: &Tensor, eps: f32) -> Result<Tensor> {
933 let hidden_size_xs = xs.dim(D::Minus1)?;
934 let hidden_size_alpha = alpha.dims1()?;
935 let hidden_size_beta = beta.dims1()?;
936 if hidden_size_xs != hidden_size_alpha || hidden_size_xs != hidden_size_beta {
937 candle::bail!(
938 "shape mismatch in layer-norm src: {:?} alpha: {:?} beta: {:?}",
939 xs.shape(),
940 alpha.shape(),
941 beta.shape()
942 )
943 }
944 xs.apply_op3_no_bwd(alpha, beta, &LayerNorm { eps })
945}
946
947pub fn pixel_shuffle(xs: &Tensor, upscale_factor: usize) -> Result<Tensor> {
949 let (b_size, c, h, w) = xs.dims4()?;
950 let out_c = c / upscale_factor / upscale_factor;
951 xs.reshape((b_size, out_c, upscale_factor, upscale_factor, h, w))?
952 .permute((0, 1, 4, 2, 5, 3))?
953 .reshape((b_size, out_c, h * upscale_factor, w * upscale_factor))
954}
955
956pub fn pixel_unshuffle(xs: &Tensor, downscale_factor: usize) -> Result<Tensor> {
957 let (b_size, c, h, w) = xs.dims4()?;
958 let out_c = c * downscale_factor * downscale_factor;
959 xs.reshape((
960 b_size,
961 c,
962 h / downscale_factor,
963 downscale_factor,
964 w / downscale_factor,
965 downscale_factor,
966 ))?
967 .permute((0, 1, 3, 5, 2, 4))?
968 .reshape((b_size, out_c, h / downscale_factor, w / downscale_factor))
969}
970
971pub fn replication_pad2d(xs: &Tensor, pad: usize) -> Result<Tensor> {
973 match pad {
974 0 => Ok(xs.clone()),
975 1 => {
976 let (_b_size, _c, h, w) = xs.dims4()?;
977 let (first, last) = (xs.narrow(3, 0, 1)?, xs.narrow(3, w - 1, 1)?);
978 let xs = Tensor::cat(&[&first, xs, &last], 3)?;
979 let (first, last) = (xs.narrow(2, 0, 1)?, xs.narrow(2, h - 1, 1)?);
980 Tensor::cat(&[&first, &xs, &last], 2)
981 }
982 n => candle::bail!("replication-pad with a size of {n} is not supported"),
983 }
984}
985
986#[derive(Clone, Debug)]
987pub struct Identity;
988
989impl Identity {
990 pub fn new() -> Identity {
991 Self
992 }
993}
994
995impl Default for Identity {
996 fn default() -> Self {
997 Self
998 }
999}
1000
1001impl Module for Identity {
1002 fn forward(&self, xs: &Tensor) -> Result<Tensor> {
1003 Ok(xs.clone())
1004 }
1005}
1006
1007#[allow(dead_code)]
1008struct Sdpa {
1009 scale: f32,
1010 softcapping: f32,
1011 mask: Option<Tensor>,
1012 do_causal: bool,
1013}
1014
1015impl candle::CustomOp3 for Sdpa {
1016 fn name(&self) -> &'static str {
1017 "metal-sdpa"
1018 }
1019
1020 fn cpu_fwd(
1021 &self,
1022 _s1: &CpuStorage,
1023 _l1: &Layout,
1024 _s2: &CpuStorage,
1025 _l2: &Layout,
1026 _s3: &CpuStorage,
1027 _l3: &Layout,
1028 ) -> Result<(CpuStorage, Shape)> {
1029 candle::bail!("SDPA has no cpu impl")
1030 }
1031
1032 #[cfg(feature = "metal")]
1033 fn metal_fwd(
1034 &self,
1035 q: &candle::MetalStorage,
1036 q_l: &Layout,
1037 k: &candle::MetalStorage,
1038 k_l: &Layout,
1039 v: &candle::MetalStorage,
1040 v_l: &Layout,
1041 ) -> Result<(candle::MetalStorage, Shape)> {
1042 use candle::backend::BackendStorage;
1043 use candle_metal_kernels::SdpaDType;
1044
1045 let device = q.device();
1046
1047 let out_dims = vec![q_l.dim(0)?, q_l.dim(1)?, q_l.dim(2)?, v_l.dim(3)?];
1048 let elem_count: usize = out_dims.iter().product();
1049 let out_shape = Shape::from_dims(&out_dims);
1050 let out_layout = Layout::contiguous(out_shape.clone());
1051
1052 let output = device
1053 .new_buffer_builder()
1054 .with_size_for(elem_count, q.dtype())
1055 .with_label("sdpa_o")
1056 .build()?;
1057
1058 if q_l.dim(D::Minus1)? != k_l.dim(D::Minus1)? {
1060 candle::bail!("`q` and `k` last dims must match");
1061 }
1062
1063 if v_l.dim(D::Minus(3))? != k_l.dim(D::Minus(3))? {
1065 candle::bail!("`k` and `v` head dims must match");
1066 }
1067
1068 if q_l.dim(D::Minus(3))? % k_l.dim(D::Minus(3))? != 0 {
1070 candle::bail!("query `n_heads` must be a multiple of `n_kv_heads`");
1071 }
1072
1073 let k_head = k_l.dim(D::Minus1)?;
1074 let q_head = q_l.dim(D::Minus1)?;
1075 let q_seq = q_l.dim(2)?;
1076 let k_seq = k_l.dim(2)?;
1077
1078 let mut implementation_supports_use_case = q_head == k_head;
1079 let supported_head_dim = q_head == 32
1080 || q_head == 64
1081 || q_head == 72
1082 || q_head == 80
1083 || q_head == 96
1084 || q_head == 128
1085 || q_head == 256
1086 || q_head == 512;
1087
1088 let supports_sdpa_full_mask = self.mask.is_none() || q_seq <= k_seq;
1089 let supports_sdpa_full_dtype = !(q_head == 512 && q.dtype() == DType::F32);
1091 let supports_sdpa_full =
1092 q_seq > 1 && supported_head_dim && supports_sdpa_full_mask && supports_sdpa_full_dtype;
1093 let supports_sdpa_vector = q_seq == 1 && supported_head_dim && q_seq <= k_seq;
1094
1095 implementation_supports_use_case &= supports_sdpa_full || supports_sdpa_vector;
1096
1097 if !supported_head_dim {
1098 candle::bail!(
1099 "Meta SDPA does not support q head dim {q_head}: q dims {:?}, k dims {:?}, v dims {:?}.",
1100 q_l.dims(),
1101 k_l.dims(),
1102 v_l.dims()
1103 );
1104 }
1105 if !implementation_supports_use_case {
1106 candle::bail!(
1107 "Meta SDPA does not support q dims {:?}, k dims {:?}, v dims {:?}.",
1108 q_l.dims(),
1109 k_l.dims(),
1110 v_l.dims()
1111 );
1112 }
1113
1114 for t in [k.dtype(), v.dtype()] {
1115 if q.dtype() != t {
1116 candle::bail!("all q, k, v dtypes must match.");
1117 }
1118 }
1119
1120 let itype = match q.dtype() {
1121 DType::BF16 => SdpaDType::BF16,
1122 DType::F16 => SdpaDType::F16,
1123 DType::F32 => SdpaDType::F32,
1124 other => candle::bail!("unsupported sdpa type {other:?}"),
1125 };
1126
1127 let encoder = q.device().command_encoder()?;
1128 if supports_sdpa_vector {
1129 const TWO_PASS_K_THRESHOLD: usize = 1024;
1132 if k_seq >= TWO_PASS_K_THRESHOLD {
1133 let mut intermediate_shape = [
1134 &out_dims[0..out_dims.len() - 2],
1135 &[candle_metal_kernels::SDPA_2PASS_BLOCKS],
1136 &[out_dims[out_dims.len() - 1]],
1137 ]
1138 .concat();
1139 let intermediate = device
1140 .new_buffer_builder()
1141 .with_size_for(intermediate_shape.iter().product::<usize>(), DType::F32)
1142 .with_label("sdpa_2pass_intermediate")
1143 .build()?;
1144 let _ = intermediate_shape.pop().unwrap();
1145 let sums = device
1146 .new_buffer_builder()
1147 .with_size_for(intermediate_shape.iter().product::<usize>(), DType::F32)
1148 .with_label("sdpa_2pass_sums")
1149 .build()?;
1150 let maxs = device
1151 .new_buffer_builder()
1152 .with_size_for(intermediate_shape.iter().product::<usize>(), DType::F32)
1153 .with_label("sdpa_2pass_maxs")
1154 .build()?;
1155
1156 encoder.set_label("vector_attention");
1157 candle_metal_kernels::call_sdpa_vector_2pass(
1158 q.device().device(),
1159 &encoder,
1160 q.device().kernels(),
1161 q_l.start_offset() * q.dtype().size_in_bytes(),
1162 q_l.dims(),
1163 q.buffer(),
1164 k_l.start_offset() * k.dtype().size_in_bytes(),
1165 k_l.dims(),
1166 k_l.stride(),
1167 k.buffer(),
1168 v_l.start_offset() * v.dtype().size_in_bytes(),
1169 v_l.stride(),
1170 v.buffer(),
1171 &output,
1172 &intermediate,
1173 &sums,
1174 &maxs,
1175 self.scale,
1176 self.softcapping,
1177 itype,
1178 )
1179 .map_err(candle::Error::wrap)?;
1180 } else {
1181 encoder.set_label("vector_attention");
1182 candle_metal_kernels::call_sdpa_vector(
1183 q.device().device(),
1184 &encoder,
1185 q.device().kernels(),
1186 q_l.start_offset() * q.dtype().size_in_bytes(),
1187 q_l.dims(),
1188 q.buffer(),
1189 k_l.start_offset() * k.dtype().size_in_bytes(),
1190 k_l.dims(),
1191 k_l.stride(),
1192 k.buffer(),
1193 v_l.start_offset() * v.dtype().size_in_bytes(),
1194 v_l.stride(),
1195 v.buffer(),
1196 &output,
1197 self.scale,
1198 self.softcapping,
1199 itype,
1200 )
1201 .map_err(candle::Error::wrap)?;
1202 }
1203 } else if supports_sdpa_full {
1204 encoder.set_label("full_attention");
1205 if self.softcapping != 1. {
1206 candle::bail!("SDPA full requires softcapping to be disabled (1.0)");
1207 }
1208
1209 let mask_s_l = self.mask.as_ref().map(|m| m.storage_and_layout());
1210
1211 let (mask_type, mask_buffer, mask_strides) = if let Some(mask) = &self.mask {
1212 let (mask_s, mask_l) = mask_s_l.as_ref().unwrap();
1213
1214 let mask_buffer = match &**mask_s {
1215 candle::Storage::Metal(m) => m.buffer(),
1216 _ => candle::bail!("Expected metal device for mask"),
1217 };
1218
1219 let mask_type = match mask.dtype() {
1220 DType::BF16 => SdpaDType::BF16,
1221 DType::F16 => SdpaDType::F16,
1222 DType::F32 => SdpaDType::F32,
1223 other => candle::bail!("unsupported sdpa type {other:?}"),
1224 };
1225 if mask_type != itype {
1226 candle::bail!("Mask type {mask_type:?} must match q type {itype:?}");
1227 }
1228
1229 if mask_l.dims() != [q_l.dim(0)?, q_l.dim(1)?, q_l.dim(2)?, k_seq] {
1230 candle::bail!(
1231 "Mask shape must be {:?} (bs, qheads, qseq, kseq), got {:?}",
1232 [q_l.dim(0)?, q_head, q_l.dim(2)?, k_seq],
1233 mask_l.dims()
1234 );
1235 }
1236
1237 (
1238 Some(mask_type),
1239 Some(mask_buffer),
1240 Some(mask_l.stride().to_vec()),
1241 )
1242 } else {
1243 (None, None, None)
1244 };
1245
1246 candle_metal_kernels::call_sdpa_full(
1247 q.device().device(),
1248 &encoder,
1249 q.device().kernels(),
1250 q_l.start_offset() * q.dtype().size_in_bytes(),
1251 q_l.dims(),
1252 q_l.stride(),
1253 q.buffer(),
1254 k_l.start_offset() * k.dtype().size_in_bytes(),
1255 k_l.dims(),
1256 k_l.stride(),
1257 k.buffer(),
1258 v_l.start_offset() * v.dtype().size_in_bytes(),
1259 v.buffer(),
1260 v_l.stride(),
1261 mask_type,
1262 mask_buffer,
1263 mask_strides.as_deref(),
1264 &output,
1265 out_layout.stride(),
1266 self.scale,
1267 self.do_causal,
1268 itype,
1269 )
1270 .map_err(candle::Error::wrap)?;
1271 } else {
1272 candle::bail!("must be vector or full sdpa kernel");
1273 }
1274
1275 let newstorage = candle::MetalStorage::new(output, device.clone(), elem_count, q.dtype());
1276 Ok((newstorage, out_shape))
1277 }
1278}
1279
1280pub fn sdpa(
1309 q: &Tensor,
1310 k: &Tensor,
1311 v: &Tensor,
1312 mask: Option<&Tensor>,
1313 do_causal: bool,
1314 scale: f32,
1315 softcapping: f32,
1316) -> Result<Tensor> {
1317 q.apply_op3_no_bwd(
1318 k,
1319 v,
1320 &Sdpa {
1321 scale,
1322 softcapping,
1323 mask: mask.cloned(),
1324 do_causal,
1325 },
1326 )
1327}