1use std::borrow::Cow;
4
5use rand::rng;
6use rand_distr::{Distribution, Normal, Uniform};
7
8use super::kernels::{elementwise as ew, indexing, matmul, nn, reduce};
9use super::{Cpu, CpuBoolStorage, CpuFloatStorage, CpuIntStorage, int_ids_as_usize, usize_to_int_storage};
10use crate::dtype::{BoolDType, FloatDType, IntDType};
11use crate::{
12 BinaryOp, CmpOp, DType, Device, Error, FloatOps, Layout, Result, Shape, dispatch_float, dispatch_float_raw, dispatch_float2,
13 dispatch_float2_raw,
14};
15
16fn build(n: usize, dtype: FloatDType, f32v: impl Fn() -> f32, f64v: impl Fn() -> f64) -> CpuFloatStorage {
18 match dtype {
19 FloatDType::F32 => CpuFloatStorage::F32((0..n).map(|_| f32v()).collect()),
20 FloatDType::F64 => CpuFloatStorage::F64((0..n).map(|_| f64v()).collect()),
21 }
22}
23
24impl FloatOps<Cpu> for Cpu {
25 fn f_zeros(shape: &Shape, _device: &Cpu, dtype: FloatDType) -> Result<<Cpu as Device>::FloatStorage> {
26 Ok(build(shape.element_count(), dtype, || 0.0, || 0.0))
27 }
28
29 fn f_ones(shape: &Shape, _device: &Cpu, dtype: FloatDType) -> Result<<Cpu as Device>::FloatStorage> {
30 Ok(build(shape.element_count(), dtype, || 1.0, || 1.0))
31 }
32
33 fn f_full(shape: &Shape, value: f64, _device: &Cpu, dtype: FloatDType) -> Result<<Cpu as Device>::FloatStorage> {
34 Ok(build(shape.element_count(), dtype, || value as f32, || value))
35 }
36
37 fn f_from_f64<'a>(data: impl Into<Cow<'a, [f64]>>, _device: &Cpu) -> Result<<Cpu as Device>::FloatStorage> {
38 let data = data.into();
39 Ok(match data {
40 Cow::Owned(v) => CpuFloatStorage::F64(v),
41 Cow::Borrowed(s) => CpuFloatStorage::F64(s.to_vec()),
42 })
43 }
44
45 fn f_from_f32<'a>(data: impl Into<Cow<'a, [f32]>>, _device: &Cpu) -> Result<<Cpu as Device>::FloatStorage> {
46 let data = data.into();
47 Ok(match data {
48 Cow::Owned(v) => CpuFloatStorage::F32(v),
49 Cow::Borrowed(s) => CpuFloatStorage::F32(s.to_vec()),
50 })
51 }
52
53 fn f_from_bytes<'a>(
54 bytes: impl Into<Cow<'a, [u8]>>,
55 _shape: &Shape,
56 _device: &Cpu,
57 dtype: FloatDType,
58 ) -> Result<<Cpu as Device>::FloatStorage> {
59 let bytes = bytes.into();
60 Ok(match dtype {
61 FloatDType::F32 => {
62 let v: Vec<f32> = bytes.chunks_exact(4).map(|c| f32::from_le_bytes(c.try_into().unwrap())).collect();
63 CpuFloatStorage::F32(v)
64 }
65 FloatDType::F64 => {
66 let v: Vec<f64> = bytes.chunks_exact(8).map(|c| f64::from_le_bytes(c.try_into().unwrap())).collect();
67 CpuFloatStorage::F64(v)
68 }
69 })
70 }
71
72 fn f_rand_uniform(shape: &Shape, lo: f64, hi: f64, _device: &Cpu, dtype: FloatDType) -> Result<<Cpu as Device>::FloatStorage> {
73 let n = shape.element_count();
74 let mut r = rng();
75 let s = match dtype {
76 FloatDType::F64 => {
77 let u = Uniform::new(lo, hi).map_err(|e| Error::Rand(e.to_string()))?;
78 CpuFloatStorage::F64((0..n).map(|_| u.sample(&mut r)).collect())
79 }
80 FloatDType::F32 => {
81 let u = Uniform::new(lo as f32, hi as f32).map_err(|e| Error::Rand(e.to_string()))?;
82 CpuFloatStorage::F32((0..n).map(|_| u.sample(&mut r)).collect())
83 }
84 };
85 Ok(s)
86 }
87
88 fn f_rand_normal(shape: &Shape, mean: f64, std: f64, _device: &Cpu, dtype: FloatDType) -> Result<<Cpu as Device>::FloatStorage> {
89 let n = shape.element_count();
90 let mut r = rng();
91 let s = match dtype {
92 FloatDType::F64 => {
93 let d = Normal::new(mean, std).map_err(|e| Error::Rand(e.to_string()))?;
94 CpuFloatStorage::F64((0..n).map(|_| d.sample(&mut r)).collect())
95 }
96 FloatDType::F32 => {
97 let d = Normal::new(mean as f32, std as f32).map_err(|e| Error::Rand(e.to_string()))?;
98 CpuFloatStorage::F32((0..n).map(|_| d.sample(&mut r)).collect())
99 }
100 };
101 Ok(s)
102 }
103
104 fn f_contiguous(x: &<Cpu as Device>::FloatStorage, l: &Layout) -> Result<<Cpu as Device>::FloatStorage> {
105 Ok(dispatch_float!(x, |d| super::kernels::iter::gather(d, l)))
106 }
107
108 fn f_cast_float(x: &CpuFloatStorage, layout: &Layout, to: FloatDType) -> Result<CpuFloatStorage> {
109 let s = match to {
110 FloatDType::F32 => CpuFloatStorage::F32(dispatch_float_raw!(x, |d| layout.storage_indices().map(|i| d[i] as f32).collect())),
111 FloatDType::F64 => CpuFloatStorage::F64(dispatch_float_raw!(x, |d| layout.storage_indices().map(|i| d[i] as f64).collect())),
112 };
113 Ok(s)
114 }
115
116 fn f_cast_int(x: &CpuFloatStorage, layout: &Layout, to: IntDType) -> Result<CpuIntStorage> {
117 let s = match to {
118 IntDType::I32 => CpuIntStorage::I32(dispatch_float_raw!(x, |d| layout.storage_indices().map(|i| d[i] as i32).collect())),
119 IntDType::U32 => CpuIntStorage::U32(dispatch_float_raw!(x, |d| layout.storage_indices().map(|i| d[i] as u32).collect())),
120 IntDType::U8 => CpuIntStorage::U8(dispatch_float_raw!(x, |d| layout.storage_indices().map(|i| d[i] as u8).collect())),
121 };
122 Ok(s)
123 }
124
125 fn f_cast_bool(x: &CpuFloatStorage, layout: &Layout, _to: BoolDType) -> Result<CpuBoolStorage> {
126 Ok(CpuBoolStorage(dispatch_float_raw!(x, |d| layout.storage_indices().map(|i| d[i] != 0.).collect())))
127 }
128
129 fn f_to_vec(x: &<Cpu as Device>::FloatStorage, layout: &Layout) -> Result<Vec<f64>> {
130 Ok(match x {
131 CpuFloatStorage::F32(d) => layout.storage_indices().map(|i| d[i] as f64).collect(),
132 CpuFloatStorage::F64(d) => layout.storage_indices().map(|i| d[i]).collect(),
133 })
134 }
135
136 fn f_to_bytes<'a>(x: &'a <Cpu as Device>::FloatStorage, layout: &Layout) -> Result<Cow<'a, [u8]>> {
137 if layout.is_contiguous() {
138 Ok(match x {
139 CpuFloatStorage::F32(d) => Cow::Borrowed(bytemuck::cast_slice(d)),
140 CpuFloatStorage::F64(d) => Cow::Borrowed(bytemuck::cast_slice(d)),
141 })
142 } else {
143 let contig = Self::f_contiguous(x, layout)?;
144 Ok(match contig {
145 CpuFloatStorage::F32(d) => Cow::Owned(bytemuck::cast_slice(&d).to_vec()),
146 CpuFloatStorage::F64(d) => Cow::Owned(bytemuck::cast_slice(&d).to_vec()),
147 })
148 }
149 }
150
151 fn f_binary(
152 lhs: &<Cpu as Device>::FloatStorage,
153 lhs_l: &Layout,
154 rhs: &<Cpu as Device>::FloatStorage,
155 rhs_l: &Layout,
156 op: BinaryOp,
157 ) -> Result<<Cpu as Device>::FloatStorage> {
158 dispatch_float2!(lhs, rhs, "binary", |a, b| ew::num_binary(a, lhs_l, b, rhs_l, op))
159 }
160
161 fn f_binary_scalar(
162 lhs: &<Cpu as Device>::FloatStorage,
163 lhs_l: &Layout,
164 rhs: f64,
165 op: BinaryOp,
166 ) -> Result<<Cpu as Device>::FloatStorage> {
167 Ok(match lhs {
168 CpuFloatStorage::F32(d) => CpuFloatStorage::F32(ew::num_binary_scalar(d, lhs_l, rhs as f32, op)),
169 CpuFloatStorage::F64(d) => CpuFloatStorage::F64(ew::num_binary_scalar(d, lhs_l, rhs, op)),
170 })
171 }
172
173 fn f_binary_scalar_(dst: &mut <Cpu as Device>::FloatStorage, dst_l: &Layout, rhs: f64, op: BinaryOp) -> Result<()> {
174 match dst {
175 CpuFloatStorage::F32(d) => {
176 ew::binary_scalar_(d, dst_l, rhs as f32, binary_fn_f32(op));
177 Ok(())
178 }
179 CpuFloatStorage::F64(d) => {
180 ew::binary_scalar_(d, dst_l, rhs, binary_fn_f64(op));
181 Ok(())
182 }
183 }
184 }
185
186 fn f_binary_scalar_lhs(scalar: f64, rhs: &CpuFloatStorage, rhs_l: &Layout, op: BinaryOp) -> Result<CpuFloatStorage> {
187 Ok(match rhs {
188 CpuFloatStorage::F32(d) => CpuFloatStorage::F32(ew::num_scalar_binary(scalar as f32, d, rhs_l, op)),
189 CpuFloatStorage::F64(d) => CpuFloatStorage::F64(ew::num_scalar_binary(scalar, d, rhs_l, op)),
190 })
191 }
192
193 fn f_unary(x: &<Cpu as Device>::FloatStorage, l: &Layout, op: crate::UnaryOp<f64>) -> Result<<Cpu as Device>::FloatStorage> {
194 match x {
195 CpuFloatStorage::F32(d) => Ok(CpuFloatStorage::F32(match op {
196 crate::UnaryOp::Neg => ew::unary(d, l, |v: f32| -v),
197 crate::UnaryOp::Abs => ew::unary(d, l, |v: f32| v.abs()),
198 crate::UnaryOp::Sign => ew::unary(d, l, |v: f32| v.signum()),
199 crate::UnaryOp::Affine(mul, add) => ew::unary(d, l, |v: f32| v * mul as f32 + add as f32),
200 crate::UnaryOp::Pow(exp) => ew::unary(d, l, |v: f32| v.powf(exp as f32)),
201 crate::UnaryOp::Clamp(min, max) => {
202 let lo = min.map(|v| v as f32);
203 let hi = max.map(|v| v as f32);
204 ew::unary(d, l, |v: f32| {
205 let mut val = v;
206 if let Some(lo) = lo {
207 val = lo.max(val);
208 }
209 if let Some(hi) = hi {
210 val = hi.min(val);
211 }
212 val
213 })
214 }
215 })),
216 CpuFloatStorage::F64(d) => Ok(CpuFloatStorage::F64(match op {
217 crate::UnaryOp::Neg => ew::unary(d, l, |v: f64| -v),
218 crate::UnaryOp::Abs => ew::unary(d, l, |v: f64| v.abs()),
219 crate::UnaryOp::Sign => ew::unary(d, l, |v: f64| v.signum()),
220 crate::UnaryOp::Affine(mul, add) => ew::unary(d, l, |v: f64| v * mul + add),
221 crate::UnaryOp::Pow(exp) => ew::unary(d, l, |v: f64| v.powf(exp)),
222 crate::UnaryOp::Clamp(min, max) => ew::unary(d, l, |v: f64| {
223 let mut val = v;
224 if let Some(lo) = min {
225 val = lo.max(val);
226 }
227 if let Some(hi) = max {
228 val = hi.min(val);
229 }
230 val
231 }),
232 })),
233 }
234 }
235
236 fn f_float_unary(x: &<Cpu as Device>::FloatStorage, l: &Layout, op: crate::FloatUnaryOp) -> Result<<Cpu as Device>::FloatStorage> {
237 Ok(dispatch_float!(x, |d| ew::float_unary(d, l, op)))
238 }
239
240 fn f_unary_(dst: &mut <Cpu as Device>::FloatStorage, dst_l: &Layout, op: crate::UnaryOp<f64>) -> Result<()> {
241 match dst {
242 CpuFloatStorage::F32(d) => Ok(match op {
243 crate::UnaryOp::Neg => ew::unary_(d, dst_l, |v: f32| -v),
244 crate::UnaryOp::Abs => ew::unary_(d, dst_l, |v: f32| v.abs()),
245 crate::UnaryOp::Sign => ew::unary_(d, dst_l, |v: f32| v.signum()),
246 crate::UnaryOp::Affine(mul, add) => ew::unary_(d, dst_l, |v: f32| v * mul as f32 + add as f32),
247 crate::UnaryOp::Pow(exp) => ew::unary_(d, dst_l, |v: f32| v.powf(exp as f32)),
248 crate::UnaryOp::Clamp(min, max) => {
249 let lo = min.map(|v| v as f32);
250 let hi = max.map(|v| v as f32);
251 ew::unary_(d, dst_l, |v: f32| {
252 let mut val = v;
253 if let Some(lo) = lo {
254 val = lo.max(val);
255 }
256 if let Some(hi) = hi {
257 val = hi.min(val);
258 }
259 val
260 })
261 }
262 }),
263 CpuFloatStorage::F64(d) => Ok(match op {
264 crate::UnaryOp::Neg => ew::unary_(d, dst_l, |v: f64| -v),
265 crate::UnaryOp::Abs => ew::unary_(d, dst_l, |v: f64| v.abs()),
266 crate::UnaryOp::Sign => ew::unary_(d, dst_l, |v: f64| v.signum()),
267 crate::UnaryOp::Affine(mul, add) => ew::unary_(d, dst_l, |v: f64| v * mul + add),
268 crate::UnaryOp::Pow(exp) => ew::unary_(d, dst_l, |v: f64| v.powf(exp)),
269 crate::UnaryOp::Clamp(min, max) => ew::unary_(d, dst_l, |v: f64| {
270 let mut val = v;
271 if let Some(lo) = min {
272 val = lo.max(val);
273 }
274 if let Some(hi) = max {
275 val = hi.min(val);
276 }
277 val
278 }),
279 }),
280 }
281 }
282
283 fn f_float_unary_(dst: &mut <Cpu as Device>::FloatStorage, dst_l: &Layout, op: crate::FloatUnaryOp) -> Result<()> {
284 use super::kernels::element::CpuFloat;
285 match dst {
286 CpuFloatStorage::F32(d) => {
287 match op {
288 crate::FloatUnaryOp::Exp => ew::unary_(d, dst_l, |v: f32| v.exp()),
289 crate::FloatUnaryOp::Ln => ew::unary_(d, dst_l, |v: f32| v.ln()),
290 crate::FloatUnaryOp::Sin => ew::unary_(d, dst_l, |v: f32| v.sin()),
291 crate::FloatUnaryOp::Cos => ew::unary_(d, dst_l, |v: f32| v.cos()),
292 crate::FloatUnaryOp::Tanh => ew::unary_(d, dst_l, |v: f32| v.tanh()),
293 crate::FloatUnaryOp::Sqr => ew::unary_(d, dst_l, |v: f32| v.sqr()),
294 crate::FloatUnaryOp::Sqrt => ew::unary_(d, dst_l, |v: f32| v.sqrt()),
295 crate::FloatUnaryOp::Recip => ew::unary_(d, dst_l, |v: f32| v.recip()),
296 crate::FloatUnaryOp::Gelu => ew::unary_(d, dst_l, |v: f32| v.gelu()),
297 crate::FloatUnaryOp::GeluErf => ew::unary_(d, dst_l, |v: f32| v.gelu_erf()),
298 crate::FloatUnaryOp::Erf => ew::unary_(d, dst_l, |v: f32| CpuFloat::erf(v)),
299 crate::FloatUnaryOp::Relu => ew::unary_(d, dst_l, |v: f32| v.relu()),
300 crate::FloatUnaryOp::Silu => ew::unary_(d, dst_l, |v: f32| v.silu()),
301 crate::FloatUnaryOp::Sigmoid => ew::unary_(d, dst_l, |v: f32| v.sigmoid()),
302 crate::FloatUnaryOp::Floor => ew::unary_(d, dst_l, |v: f32| v.floor()),
303 crate::FloatUnaryOp::Ceil => ew::unary_(d, dst_l, |v: f32| v.ceil()),
304 crate::FloatUnaryOp::Round => ew::unary_(d, dst_l, |v: f32| v.round()),
305 crate::FloatUnaryOp::LeakyRelu(a) => ew::unary_(d, dst_l, |v: f32| v.leaky_relu(a as f32)),
306 }
307 Ok(())
308 }
309 CpuFloatStorage::F64(d) => {
310 match op {
311 crate::FloatUnaryOp::Exp => ew::unary_(d, dst_l, |v: f64| v.exp()),
312 crate::FloatUnaryOp::Ln => ew::unary_(d, dst_l, |v: f64| v.ln()),
313 crate::FloatUnaryOp::Sin => ew::unary_(d, dst_l, |v: f64| v.sin()),
314 crate::FloatUnaryOp::Cos => ew::unary_(d, dst_l, |v: f64| v.cos()),
315 crate::FloatUnaryOp::Tanh => ew::unary_(d, dst_l, |v: f64| v.tanh()),
316 crate::FloatUnaryOp::Sqr => ew::unary_(d, dst_l, |v: f64| v.sqr()),
317 crate::FloatUnaryOp::Sqrt => ew::unary_(d, dst_l, |v: f64| v.sqrt()),
318 crate::FloatUnaryOp::Recip => ew::unary_(d, dst_l, |v: f64| v.recip()),
319 crate::FloatUnaryOp::Gelu => ew::unary_(d, dst_l, |v: f64| v.gelu()),
320 crate::FloatUnaryOp::GeluErf => ew::unary_(d, dst_l, |v: f64| v.gelu_erf()),
321 crate::FloatUnaryOp::Erf => ew::unary_(d, dst_l, |v: f64| CpuFloat::erf(v)),
322 crate::FloatUnaryOp::Relu => ew::unary_(d, dst_l, |v: f64| v.relu()),
323 crate::FloatUnaryOp::Silu => ew::unary_(d, dst_l, |v: f64| v.silu()),
324 crate::FloatUnaryOp::Sigmoid => ew::unary_(d, dst_l, |v: f64| v.sigmoid()),
325 crate::FloatUnaryOp::Floor => ew::unary_(d, dst_l, |v: f64| v.floor()),
326 crate::FloatUnaryOp::Ceil => ew::unary_(d, dst_l, |v: f64| v.ceil()),
327 crate::FloatUnaryOp::Round => ew::unary_(d, dst_l, |v: f64| v.round()),
328 crate::FloatUnaryOp::LeakyRelu(a) => ew::unary_(d, dst_l, |v: f64| v.leaky_relu(a)),
329 }
330 Ok(())
331 }
332 }
333 }
334
335 fn f_cmp(
336 lhs: &<Cpu as Device>::FloatStorage,
337 lhs_l: &Layout,
338 rhs: &<Cpu as Device>::FloatStorage,
339 rhs_l: &Layout,
340 op: CmpOp,
341 ) -> Result<<Cpu as Device>::BoolStorage> {
342 let v = dispatch_float2_raw!(lhs, rhs, "cmp", |a, b| ew::num_cmp(a, lhs_l, b, rhs_l, op))?;
343 Ok(CpuBoolStorage(v))
344 }
345
346 fn f_cmp_scalar(lhs: &<Cpu as Device>::FloatStorage, lhs_l: &Layout, rhs: f64, op: CmpOp) -> Result<<Cpu as Device>::BoolStorage> {
347 match lhs {
348 CpuFloatStorage::F32(d) => Ok(CpuBoolStorage(ew::cmp_scalar(d, lhs_l, rhs as f32, op))),
349 CpuFloatStorage::F64(d) => Ok(CpuBoolStorage(ew::cmp_scalar(d, lhs_l, rhs, op))),
350 }
351 }
352
353 fn f_reduce(
354 x: &<Cpu as Device>::FloatStorage,
355 l: &Layout,
356 dims: &[usize],
357 keepdim: bool,
358 op: crate::ReduceOp,
359 ) -> Result<(<Cpu as Device>::FloatStorage, Shape)> {
360 let reducer = reduce::Reducer::from(op);
361 match x {
362 CpuFloatStorage::F32(d) => {
363 let (v, s) = reduce::reduce_dims(d, l, dims, keepdim, reducer)?;
364 Ok((CpuFloatStorage::F32(v), s))
365 }
366 CpuFloatStorage::F64(d) => {
367 let (v, s) = reduce::reduce_dims(d, l, dims, keepdim, reducer)?;
368 Ok((CpuFloatStorage::F64(v), s))
369 }
370 }
371 }
372
373 fn f_arg_reduce(
374 x: &<Cpu as Device>::FloatStorage,
375 l: &Layout,
376 dim: usize,
377 keepdim: bool,
378 take_max: bool,
379 ) -> Result<(<Cpu as Device>::IntStorage, Shape)> {
380 let (idx, shape) = dispatch_float_raw!(x, |d| reduce::arg_reduce(d, l, dim, keepdim, take_max))?;
381 Ok((usize_to_int_storage(&idx, DType::U32), shape))
382 }
383
384 fn f_matmul(
385 lhs: &<Cpu as Device>::FloatStorage,
386 lhs_l: &Layout,
387 rhs: &<Cpu as Device>::FloatStorage,
388 rhs_l: &Layout,
389 ) -> Result<(<Cpu as Device>::FloatStorage, Shape)> {
390 match (lhs, rhs) {
391 (CpuFloatStorage::F32(a), CpuFloatStorage::F32(b)) => {
392 let (v, s) = matmul::matmul(a, lhs_l, b, rhs_l)?;
393 Ok((CpuFloatStorage::F32(v), s))
394 }
395 (CpuFloatStorage::F64(a), CpuFloatStorage::F64(b)) => {
396 let (v, s) = matmul::matmul(a, lhs_l, b, rhs_l)?;
397 Ok((CpuFloatStorage::F64(v), s))
398 }
399 (l, r) => Err(Error::DTypeMismatch { lhs: l.dtype(), rhs: r.dtype(), op: "matmul" }),
400 }
401 }
402
403 fn f_add_matmul_(
404 dst: &mut <Cpu as Device>::FloatStorage,
405 dst_l: &Layout,
406 lhs: &<Cpu as Device>::FloatStorage,
407 lhs_l: &Layout,
408 rhs: &<Cpu as Device>::FloatStorage,
409 rhs_l: &Layout,
410 ) -> Result<()> {
411 match (dst, lhs, rhs) {
413 (CpuFloatStorage::F32(d), CpuFloatStorage::F32(l), CpuFloatStorage::F32(r)) => matmul::add_matmul(d, dst_l, l, lhs_l, r, rhs_l),
414 (CpuFloatStorage::F64(d), CpuFloatStorage::F64(l), CpuFloatStorage::F64(r)) => matmul::add_matmul(d, dst_l, l, lhs_l, r, rhs_l),
415 (_d, l, r) => Err(Error::DTypeMismatch { lhs: l.dtype(), rhs: r.dtype(), op: "f_add_matmul_" }),
416 }
417 }
418
419 fn f_binary_(
420 dst: &mut <Cpu as Device>::FloatStorage,
421 dst_l: &Layout,
422 src: &<Cpu as Device>::FloatStorage,
423 src_l: &Layout,
424 op: BinaryOp,
425 ) -> Result<()> {
426 match (dst, src) {
427 (CpuFloatStorage::F32(d), CpuFloatStorage::F32(s)) => {
428 ew::binary_(d, dst_l, s, src_l, binary_fn_f32(op));
429 Ok(())
430 }
431 (CpuFloatStorage::F64(d), CpuFloatStorage::F64(s)) => {
432 ew::binary_(d, dst_l, s, src_l, binary_fn_f64(op));
433 Ok(())
434 }
435 (d, s) => Err(Error::DTypeMismatch { lhs: d.dtype(), rhs: s.dtype(), op: "in-place binary" }),
436 }
437 }
438
439 fn f_index_select(
440 x: &<Cpu as Device>::FloatStorage,
441 x_l: &Layout,
442 idx: &<Cpu as Device>::IntStorage,
443 idx_l: &Layout,
444 dim: usize,
445 ) -> Result<(<Cpu as Device>::FloatStorage, Shape)> {
446 let ids = int_ids_as_usize(idx, idx_l);
447 match x {
448 CpuFloatStorage::F32(d) => {
449 let (v, dims) = indexing::index_select(d, x_l, &ids, idx_l, dim)?;
450 Ok((CpuFloatStorage::F32(v), Shape::from(dims)))
451 }
452 CpuFloatStorage::F64(d) => {
453 let (v, dims) = indexing::index_select(d, x_l, &ids, idx_l, dim)?;
454 Ok((CpuFloatStorage::F64(v), Shape::from(dims)))
455 }
456 }
457 }
458
459 fn f_gather(
460 x: &<Cpu as Device>::FloatStorage,
461 x_l: &Layout,
462 idx: &<Cpu as Device>::IntStorage,
463 idx_l: &Layout,
464 dim: usize,
465 ) -> Result<(<Cpu as Device>::FloatStorage, Shape)> {
466 let ids = int_ids_as_usize(idx, idx_l);
467 match x {
468 CpuFloatStorage::F32(d) => {
469 let (v, dims) = indexing::gather(d, x_l, &ids, idx_l, dim)?;
470 Ok((CpuFloatStorage::F32(v), Shape::from(dims)))
471 }
472 CpuFloatStorage::F64(d) => {
473 let (v, dims) = indexing::gather(d, x_l, &ids, idx_l, dim)?;
474 Ok((CpuFloatStorage::F64(v), Shape::from(dims)))
475 }
476 }
477 }
478
479 fn f_index_add(
480 init: &<Cpu as Device>::FloatStorage,
481 init_l: &Layout,
482 idx: &<Cpu as Device>::IntStorage,
483 idx_l: &Layout,
484 src: &<Cpu as Device>::FloatStorage,
485 _src_l: &Layout,
486 dim: usize,
487 ) -> Result<<Cpu as Device>::FloatStorage> {
488 let ids = int_ids_as_usize(idx, idx_l);
489 dispatch_float2!(init, src, "index-add", |a, b| indexing::index_add(a, init_l, &ids, idx_l, b, dim)?)
490 }
491
492 fn f_scatter_add(
493 init: &<Cpu as Device>::FloatStorage,
494 init_l: &Layout,
495 idx: &<Cpu as Device>::IntStorage,
496 idx_l: &Layout,
497 src: &<Cpu as Device>::FloatStorage,
498 _src_l: &Layout,
499 dim: usize,
500 ) -> Result<<Cpu as Device>::FloatStorage> {
501 let ids = int_ids_as_usize(idx, idx_l);
502 dispatch_float2!(init, src, "scatter-add", |a, b| indexing::scatter_add(a, init_l, &ids, idx_l, b, dim)?)
503 }
504
505 fn f_cat(srcs: &[(&<Cpu as Device>::FloatStorage, &Layout)], dim: usize) -> Result<(<Cpu as Device>::FloatStorage, Shape)> {
506 if srcs.is_empty() {
507 return Err(Error::OpRequiresAtLeastOneTensor { op: "cat" });
508 }
509 let dt = srcs[0].0.dtype();
511 for (s, _) in srcs {
512 if s.dtype() != dt {
513 return Err(Error::DTypeMismatch { lhs: dt, rhs: s.dtype(), op: "cat" });
514 }
515 }
516 match dt {
517 DType::F32 => {
518 let views: Vec<(&[f32], &Layout)> = srcs.iter().map(|(s, l)| (as_f32(s), *l)).collect();
519 let (v, shape) = super::kernels::shape::cat(&views, dim)?;
520 Ok((CpuFloatStorage::F32(v), shape))
521 }
522 _ => {
523 let views: Vec<(&[f64], &Layout)> = srcs.iter().map(|(s, l)| (as_f64(s), *l)).collect();
524 let (v, shape) = super::kernels::shape::cat(&views, dim)?;
525 Ok((CpuFloatStorage::F64(v), shape))
526 }
527 }
528 }
529
530 fn f_softmax(x: &<Cpu as Device>::FloatStorage, l: &Layout, dim: usize) -> Result<<Cpu as Device>::FloatStorage> {
531 match x {
532 CpuFloatStorage::F32(d) => Ok(CpuFloatStorage::F32(nn::softmax(d, l, dim)?)),
533 CpuFloatStorage::F64(d) => Ok(CpuFloatStorage::F64(nn::softmax(d, l, dim)?)),
534 }
535 }
536
537 fn f_rms_norm(
538 x: &<Cpu as Device>::FloatStorage,
539 x_l: &Layout,
540 weight: &<Cpu as Device>::FloatStorage,
541 weight_l: &Layout,
542 eps: f64,
543 ) -> Result<<Cpu as Device>::FloatStorage> {
544 match (x, weight) {
545 (CpuFloatStorage::F32(d), CpuFloatStorage::F32(w)) => Ok(CpuFloatStorage::F32(nn::rms_norm(d, x_l, w, weight_l, eps as f32)?)),
546 (CpuFloatStorage::F64(d), CpuFloatStorage::F64(w)) => Ok(CpuFloatStorage::F64(nn::rms_norm(d, x_l, w, weight_l, eps)?)),
547 (l, r) => Err(Error::DTypeMismatch { lhs: l.dtype(), rhs: r.dtype(), op: "rms_norm" }),
548 }
549 }
550
551 fn f_pick(
552 mask: &<Cpu as Device>::BoolStorage,
553 mask_l: &Layout,
554 on_true: &<Cpu as Device>::FloatStorage,
555 true_l: &Layout,
556 on_false: &<Cpu as Device>::FloatStorage,
557 false_l: &Layout,
558 ) -> Result<<Cpu as Device>::FloatStorage> {
559 let m: Vec<bool> = mask_l.storage_indices().map(|i| mask.0[i]).collect();
560 match (on_true, on_false) {
561 (CpuFloatStorage::F32(t), CpuFloatStorage::F32(f)) => {
562 let tv = super::kernels::iter::gather(t, true_l);
563 let fv = super::kernels::iter::gather(f, false_l);
564 Ok(CpuFloatStorage::F32(m.iter().enumerate().map(|(i, &c)| if c { tv[i] } else { fv[i] }).collect()))
565 }
566 (CpuFloatStorage::F64(t), CpuFloatStorage::F64(f)) => {
567 let tv = super::kernels::iter::gather(t, true_l);
568 let fv = super::kernels::iter::gather(f, false_l);
569 Ok(CpuFloatStorage::F64(m.iter().enumerate().map(|(i, &c)| if c { tv[i] } else { fv[i] }).collect()))
570 }
571 (l, r) => Err(Error::DTypeMismatch { lhs: l.dtype(), rhs: r.dtype(), op: "pick" }),
572 }
573 }
574
575 fn f_pick_true(
576 mask: &<Cpu as Device>::BoolStorage,
577 mask_l: &Layout,
578 value: f64,
579 on_false: &<Cpu as Device>::FloatStorage,
580 false_l: &Layout,
581 ) -> Result<<Cpu as Device>::FloatStorage> {
582 let m: Vec<bool> = mask_l.storage_indices().map(|i| mask.0[i]).collect();
583 match on_false {
584 CpuFloatStorage::F32(f) => {
585 let fv = super::kernels::iter::gather(f, false_l);
586 let val = value as f32;
587 Ok(CpuFloatStorage::F32(m.iter().enumerate().map(|(i, &c)| if c { val } else { fv[i] }).collect()))
588 }
589 CpuFloatStorage::F64(f) => {
590 let fv = super::kernels::iter::gather(f, false_l);
591 Ok(CpuFloatStorage::F64(m.iter().enumerate().map(|(i, &c)| if c { value } else { fv[i] }).collect()))
592 }
593 }
594 }
595
596 fn f_pick_false(
597 mask: &<Cpu as Device>::BoolStorage,
598 mask_l: &Layout,
599 on_true: &<Cpu as Device>::FloatStorage,
600 true_l: &Layout,
601 value: f64,
602 ) -> Result<<Cpu as Device>::FloatStorage> {
603 let m: Vec<bool> = mask_l.storage_indices().map(|i| mask.0[i]).collect();
604 match on_true {
605 CpuFloatStorage::F32(t) => {
606 let tv = super::kernels::iter::gather(t, true_l);
607 let val = value as f32;
608 Ok(CpuFloatStorage::F32(m.iter().enumerate().map(|(i, &c)| if c { tv[i] } else { val }).collect()))
609 }
610 CpuFloatStorage::F64(t) => {
611 let tv = super::kernels::iter::gather(t, true_l);
612 Ok(CpuFloatStorage::F64(m.iter().enumerate().map(|(i, &c)| if c { tv[i] } else { value }).collect()))
613 }
614 }
615 }
616
617 fn f_allclose(a: &CpuFloatStorage, a_l: &Layout, b: &CpuFloatStorage, b_l: &Layout, rtol: f64, atol: f64) -> Result<bool> {
618 match (a, b) {
619 (CpuFloatStorage::F32(av), CpuFloatStorage::F32(bv)) => {
620 let rtol = rtol as f32;
621 let atol = atol as f32;
622 Ok(a_l.storage_indices().zip(b_l.storage_indices()).all(|(ai, bi)| {
623 let diff = (av[ai] - bv[bi]).abs();
624 diff <= atol + rtol * bv[bi].abs()
625 }))
626 }
627 (CpuFloatStorage::F64(av), CpuFloatStorage::F64(bv)) => Ok(a_l.storage_indices().zip(b_l.storage_indices()).all(|(ai, bi)| {
628 let diff = (av[ai] - bv[bi]).abs();
629 diff <= atol + rtol * bv[bi].abs()
630 })),
631 _ => return Err(crate::Error::DTypeMismatch { lhs: a.dtype(), rhs: b.dtype(), op: "allclose" }),
632 }
633 }
634}
635
636fn as_f32(s: &CpuFloatStorage) -> &[f32] {
637 match s {
638 CpuFloatStorage::F32(d) => d,
639 _ => unreachable!("dtype checked by caller"),
640 }
641}
642
643fn as_f64(s: &CpuFloatStorage) -> &[f64] {
644 match s {
645 CpuFloatStorage::F64(d) => d,
646 _ => unreachable!("dtype checked by caller"),
647 }
648}
649
650fn binary_fn_f32(op: BinaryOp) -> fn(f32, f32) -> f32 {
651 use super::kernels::element::CpuNum;
652 match op {
653 BinaryOp::Add => |a, b| a + b,
654 BinaryOp::Sub => |a, b| a - b,
655 BinaryOp::Mul => |a, b| a * b,
656 BinaryOp::Div => |a, b| a / b,
657 BinaryOp::Maximum => CpuNum::maximum,
658 BinaryOp::Minimum => CpuNum::minimum,
659 }
660}
661
662fn binary_fn_f64(op: BinaryOp) -> fn(f64, f64) -> f64 {
663 use super::kernels::element::CpuNum;
664 match op {
665 BinaryOp::Add => |a, b| a + b,
666 BinaryOp::Sub => |a, b| a - b,
667 BinaryOp::Mul => |a, b| a * b,
668 BinaryOp::Div => |a, b| a / b,
669 BinaryOp::Maximum => CpuNum::maximum,
670 BinaryOp::Minimum => CpuNum::minimum,
671 }
672}