1use float8::F8E4M3;
9use fuel_backend_contract::dyn_backend::{DynBackendDevice, DynBackendStorage};
10use fuel_cpu_kernels::erf;
11use fuel_ir::conv::{ParamsConv1D, ParamsConv2D, ParamsConvTranspose1D, ParamsConvTranspose2D};
12use fuel_ir::op::{BinaryOp, CmpOp, ReduceOp, UnaryOp};
13use fuel_ir::{
14 CpuStorage as HostBuffer, DType, DeviceLocation, Error, Layout, Result, Scalar, Shape,
15};
16use half::{bf16, f16};
17use num_traits::Float as _;
18use std::any::Any;
19use std::sync::Arc;
20
21use crate::chassis::unary::{quiet_f32, quiet_f64};
22use crate::utils::{Map1, Map1Any, Map2, Map2InPlace, Map2U8, binary_map, unary_map};
23
24#[derive(Debug, Clone)]
33pub struct CpuStorage(pub HostBuffer);
34
35pub type CpuBackendStorage = CpuStorage;
37
38impl CpuStorage {
39 pub fn into_inner(self) -> HostBuffer {
41 self.0
42 }
43
44 pub fn inner(&self) -> &HostBuffer {
46 &self.0
47 }
48}
49
50impl From<HostBuffer> for CpuStorage {
51 fn from(s: HostBuffer) -> Self {
52 Self(s)
53 }
54}
55
56impl From<CpuStorage> for HostBuffer {
57 fn from(s: CpuStorage) -> Self {
58 s.0
59 }
60}
61
62impl fuel_backend_contract::backend::HostStorage for CpuStorage {
63 fn as_host_buffer_ref(&self) -> fuel_ir::Result<fuel_ir::HostBufferRef<'_>> {
64 Ok(self.0.as_ref())
65 }
66
67 fn into_host_buffer(self) -> fuel_ir::Result<fuel_ir::HostBuffer> {
70 Ok(self.0)
71 }
72}
73
74#[derive(Debug, Clone, Copy)]
80pub struct CpuBackendDevice;
81
82impl fuel_backend_contract::backend::BackendRuntime for CpuBackendDevice {
83 fn available_bytes(&self) -> Option<u64> {
89 crate::system_memory::available_bytes()
90 }
91
92 fn total_bytes(&self) -> Option<u64> {
96 crate::system_memory::total_bytes()
97 }
98}
99
100fn downcast(s: &dyn DynBackendStorage) -> Result<&CpuStorage> {
106 s.as_any().downcast_ref::<CpuStorage>().ok_or_else(|| {
107 Error::DeviceMismatchBinaryOp {
108 lhs: DeviceLocation::Cpu,
109 rhs: s.device_dyn().location_dyn(),
110 op: "dyn_backend",
111 }
112 .bt()
113 })
114}
115
116fn downcast_mut(s: &mut dyn DynBackendStorage) -> Result<&mut CpuStorage> {
118 let loc = s.device_dyn().location_dyn();
119 s.as_any_mut().downcast_mut::<CpuStorage>().ok_or_else(|| {
120 Error::DeviceMismatchBinaryOp {
121 lhs: DeviceLocation::Cpu,
122 rhs: loc,
123 op: "dyn_backend",
124 }
125 .bt()
126 })
127}
128
129fn cpu_unary_op(s: &HostBuffer, layout: &Layout, op: UnaryOp) -> Result<HostBuffer> {
135 use UnaryOp::*;
136
137 match op {
142 Exp => float_unary(
143 s,
144 layout,
145 |v: f32| quiet_f32(v.exp()),
146 |v: f64| quiet_f64(v.exp()),
147 ),
148 Log => float_unary(
149 s,
150 layout,
151 |v: f32| quiet_f32(v.ln()),
152 |v: f64| quiet_f64(v.ln()),
153 ),
154 Sin => float_unary(
155 s,
156 layout,
157 |v: f32| quiet_f32(v.sin()),
158 |v: f64| quiet_f64(v.sin()),
159 ),
160 Cos => float_unary(
161 s,
162 layout,
163 |v: f32| quiet_f32(v.cos()),
164 |v: f64| quiet_f64(v.cos()),
165 ),
166 Tanh => float_unary(
167 s,
168 layout,
169 |v: f32| quiet_f32(v.tanh()),
170 |v: f64| quiet_f64(v.tanh()),
171 ),
172 Neg => all_unary_neg(s, layout),
173 Recip => float_unary(s, layout, |v: f32| v.recip(), |v: f64| v.recip()),
174 Sqr => all_unary_sqr(s, layout),
175 Sqrt => float_unary(
176 s,
177 layout,
178 |v: f32| quiet_f32(v.sqrt()),
179 |v: f64| quiet_f64(v.sqrt()),
180 ),
181 Abs => all_unary_abs(s, layout),
182 Relu => all_unary_relu(s, layout),
183 Floor => float_unary_identity_int(
184 s,
185 layout,
186 |v: f32| quiet_f32(v.floor()),
187 |v: f64| quiet_f64(v.floor()),
188 ),
189 Ceil => float_unary_identity_int(
190 s,
191 layout,
192 |v: f32| quiet_f32(v.ceil()),
193 |v: f64| quiet_f64(v.ceil()),
194 ),
195 Round => float_unary_identity_int(
196 s,
197 layout,
198 |v: f32| quiet_f32(v.round()),
199 |v: f64| quiet_f64(v.round()),
200 ),
201 Sign => all_unary_sign(s, layout),
202 GeluTanh => float_unary(s, layout, gelu_f32, gelu_f64),
203 Gelu => float_unary(s, layout, gelu_erf_f32, gelu_erf_f64),
204 Erf => float_unary(s, layout, erf::erf_f32, erf::erf_f64),
205 Silu => float_unary(s, layout, silu_f32, silu_f64),
206 }
207}
208
209#[allow(clippy::excessive_precision)]
210fn gelu_f32(v: f32) -> f32 {
211 0.5 * v
212 * (1.0 + f32::tanh(0.79788456080286535587989211986876373 * v * (1.0 + 0.044715 * v * v)))
213}
214
215#[allow(clippy::excessive_precision)]
216fn gelu_f64(v: f64) -> f64 {
217 0.5 * v
218 * (1.0 + f64::tanh(0.79788456080286535587989211986876373 * v * (1.0 + 0.044715 * v * v)))
219}
220
221fn gelu_erf_f32(v: f32) -> f32 {
222 (erf::erf_f32(v * std::f32::consts::FRAC_1_SQRT_2) + 1.) * 0.5 * v
223}
224
225fn gelu_erf_f64(v: f64) -> f64 {
226 (erf::erf_f64(v * std::f64::consts::FRAC_1_SQRT_2) + 1.) * 0.5 * v
227}
228
229fn silu_f32(v: f32) -> f32 {
230 v / (1.0 + (-v).exp())
231}
232
233fn silu_f64(v: f64) -> f64 {
234 v / (1.0 + (-v).exp())
235}
236
237fn float_unary(
240 s: &HostBuffer,
241 layout: &Layout,
242 f32_fn: impl Fn(f32) -> f32 + Copy,
243 f64_fn: impl Fn(f64) -> f64 + Copy,
244) -> Result<HostBuffer> {
245 match s {
246 HostBuffer::BF16(data) => {
247 let out = unary_map(data, layout, |v: bf16| bf16::from_f32(f32_fn(v.to_f32())));
248 Ok(HostBuffer::BF16(out))
249 }
250 HostBuffer::F16(data) => {
251 let out = unary_map(data, layout, |v: f16| f16::from_f32(f32_fn(v.to_f32())));
252 Ok(HostBuffer::F16(out))
253 }
254 HostBuffer::F32(data) => {
255 let out = unary_map(data, layout, f32_fn);
256 Ok(HostBuffer::F32(out))
257 }
258 HostBuffer::F64(data) => {
259 let out = unary_map(data, layout, f64_fn);
260 Ok(HostBuffer::F64(out))
261 }
262 HostBuffer::F8E4M3(data) => {
263 let out = unary_map(data, layout, |v: F8E4M3| {
264 F8E4M3::from_f32(f32_fn(v.to_f32()))
265 });
266 Ok(HostBuffer::F8E4M3(out))
267 }
268 other => Err(Error::UnsupportedDTypeForOp(other.dtype(), "unary_op").bt()),
269 }
270}
271
272fn float_unary_identity_int(
274 s: &HostBuffer,
275 layout: &Layout,
276 f32_fn: impl Fn(f32) -> f32 + Copy,
277 f64_fn: impl Fn(f64) -> f64 + Copy,
278) -> Result<HostBuffer> {
279 match s {
280 HostBuffer::U8(d) => Ok(HostBuffer::U8(unary_map(d, layout, |v| v))),
281 HostBuffer::U32(d) => Ok(HostBuffer::U32(unary_map(d, layout, |v| v))),
282 HostBuffer::I16(d) => Ok(HostBuffer::I16(unary_map(d, layout, |v| v))),
283 HostBuffer::I32(d) => Ok(HostBuffer::I32(unary_map(d, layout, |v| v))),
284 HostBuffer::I64(d) => Ok(HostBuffer::I64(unary_map(d, layout, |v| v))),
285 _ => float_unary(s, layout, f32_fn, f64_fn),
286 }
287}
288
289fn all_unary_neg(s: &HostBuffer, layout: &Layout) -> Result<HostBuffer> {
290 match s {
291 HostBuffer::BF16(d) => Ok(HostBuffer::BF16(unary_map(d, layout, |v: bf16| -v))),
292 HostBuffer::F16(d) => Ok(HostBuffer::F16(unary_map(d, layout, |v: f16| -v))),
293 HostBuffer::F32(d) => Ok(HostBuffer::F32(unary_map(d, layout, |v: f32| -v))),
294 HostBuffer::F64(d) => Ok(HostBuffer::F64(unary_map(d, layout, |v: f64| -v))),
295 HostBuffer::I16(d) => Ok(HostBuffer::I16(unary_map(d, layout, |v: i16| {
300 v.wrapping_neg()
301 }))),
302 HostBuffer::I32(d) => Ok(HostBuffer::I32(unary_map(d, layout, |v: i32| {
303 v.wrapping_neg()
304 }))),
305 HostBuffer::I64(d) => Ok(HostBuffer::I64(unary_map(d, layout, |v: i64| {
306 v.wrapping_neg()
307 }))),
308 HostBuffer::F8E4M3(d) => Ok(HostBuffer::F8E4M3(unary_map(d, layout, |v: F8E4M3| -v))),
309 other => Err(Error::UnsupportedDTypeForOp(other.dtype(), "neg").bt()),
310 }
311}
312
313fn all_unary_sqr(s: &HostBuffer, layout: &Layout) -> Result<HostBuffer> {
314 match s {
315 HostBuffer::BF16(d) => Ok(HostBuffer::BF16(unary_map(d, layout, |v: bf16| v * v))),
316 HostBuffer::F16(d) => Ok(HostBuffer::F16(unary_map(d, layout, |v: f16| v * v))),
317 HostBuffer::F32(d) => Ok(HostBuffer::F32(unary_map(d, layout, |v: f32| v * v))),
318 HostBuffer::F64(d) => Ok(HostBuffer::F64(unary_map(d, layout, |v: f64| v * v))),
319 HostBuffer::U8(d) => Ok(HostBuffer::U8(unary_map(d, layout, |v: u8| v * v))),
320 HostBuffer::U32(d) => Ok(HostBuffer::U32(unary_map(d, layout, |v: u32| v * v))),
321 HostBuffer::I16(d) => Ok(HostBuffer::I16(unary_map(d, layout, |v: i16| v * v))),
322 HostBuffer::I32(d) => Ok(HostBuffer::I32(unary_map(d, layout, |v: i32| v * v))),
323 HostBuffer::I64(d) => Ok(HostBuffer::I64(unary_map(d, layout, |v: i64| v * v))),
324 HostBuffer::F8E4M3(d) => Ok(HostBuffer::F8E4M3(unary_map(d, layout, |v: F8E4M3| v * v))),
325 other => Err(Error::UnsupportedDTypeForOp(other.dtype(), "sqr").bt()),
326 }
327}
328
329fn all_unary_abs(s: &HostBuffer, layout: &Layout) -> Result<HostBuffer> {
330 match s {
331 HostBuffer::BF16(d) => Ok(HostBuffer::BF16(unary_map(d, layout, |v: bf16| {
345 bf16::from_bits(v.to_bits() & 0x7FFF)
346 }))),
347 HostBuffer::F16(d) => Ok(HostBuffer::F16(unary_map(d, layout, |v: f16| {
348 f16::from_bits(v.to_bits() & 0x7FFF)
349 }))),
350 HostBuffer::F32(d) => Ok(HostBuffer::F32(unary_map(d, layout, |v: f32| v.abs()))),
351 HostBuffer::F64(d) => Ok(HostBuffer::F64(unary_map(d, layout, |v: f64| v.abs()))),
352 HostBuffer::U8(d) => Ok(HostBuffer::U8(unary_map(d, layout, |v: u8| v))),
353 HostBuffer::U32(d) => Ok(HostBuffer::U32(unary_map(d, layout, |v: u32| v))),
354 HostBuffer::I16(d) => Ok(HostBuffer::I16(unary_map(d, layout, |v: i16| {
358 v.wrapping_abs()
359 }))),
360 HostBuffer::I32(d) => Ok(HostBuffer::I32(unary_map(d, layout, |v: i32| {
361 v.wrapping_abs()
362 }))),
363 HostBuffer::I64(d) => Ok(HostBuffer::I64(unary_map(d, layout, |v: i64| {
364 v.wrapping_abs()
365 }))),
366 HostBuffer::F8E4M3(d) => Ok(HostBuffer::F8E4M3(unary_map(d, layout, |v: F8E4M3| {
375 v.abs()
376 }))),
377 other => Err(Error::UnsupportedDTypeForOp(other.dtype(), "abs").bt()),
378 }
379}
380
381fn all_unary_relu(s: &HostBuffer, layout: &Layout) -> Result<HostBuffer> {
387 match s {
388 HostBuffer::BF16(d) => Ok(HostBuffer::BF16(unary_map(d, layout, |v: bf16| {
389 if v.is_nan() { v } else { v.max(bf16::ZERO) }
390 }))),
391 HostBuffer::F16(d) => Ok(HostBuffer::F16(unary_map(d, layout, |v: f16| {
392 if v.is_nan() { v } else { v.max(f16::ZERO) }
393 }))),
394 HostBuffer::F32(d) => Ok(HostBuffer::F32(unary_map(d, layout, |v: f32| {
395 if v.is_nan() { v } else { v.max(0.0) }
396 }))),
397 HostBuffer::F64(d) => Ok(HostBuffer::F64(unary_map(d, layout, |v: f64| {
398 if v.is_nan() { v } else { v.max(0.0) }
399 }))),
400 HostBuffer::U8(d) => Ok(HostBuffer::U8(unary_map(d, layout, |v: u8| v))),
401 HostBuffer::U32(d) => Ok(HostBuffer::U32(unary_map(d, layout, |v: u32| v))),
402 HostBuffer::I16(d) => Ok(HostBuffer::I16(unary_map(d, layout, |v: i16| v.max(0)))),
403 HostBuffer::I32(d) => Ok(HostBuffer::I32(unary_map(d, layout, |v: i32| v.max(0)))),
404 HostBuffer::I64(d) => Ok(HostBuffer::I64(unary_map(d, layout, |v: i64| v.max(0)))),
405 HostBuffer::F8E4M3(d) => Ok(HostBuffer::F8E4M3(unary_map(d, layout, |v: F8E4M3| {
406 if v.is_nan() { v } else { v.max(F8E4M3::ZERO) }
407 }))),
408 other => Err(Error::UnsupportedDTypeForOp(other.dtype(), "relu").bt()),
409 }
410}
411
412fn all_unary_sign(s: &HostBuffer, layout: &Layout) -> Result<HostBuffer> {
413 match s {
414 HostBuffer::BF16(d) => Ok(HostBuffer::BF16(unary_map(d, layout, |v: bf16| {
415 bf16::from((v > bf16::ZERO) as i8) - bf16::from((v < bf16::ZERO) as i8)
416 }))),
417 HostBuffer::F16(d) => Ok(HostBuffer::F16(unary_map(d, layout, |v: f16| {
418 f16::from((v > f16::ZERO) as i8) - f16::from((v < f16::ZERO) as i8)
419 }))),
420 HostBuffer::F32(d) => Ok(HostBuffer::F32(unary_map(d, layout, |v: f32| {
421 f32::from(v > 0.) - f32::from(v < 0.)
422 }))),
423 HostBuffer::F64(d) => Ok(HostBuffer::F64(unary_map(d, layout, |v: f64| {
424 f64::from(v > 0.) - f64::from(v < 0.)
425 }))),
426 HostBuffer::I16(d) => Ok(HostBuffer::I16(unary_map(d, layout, |v: i16| v.signum()))),
427 HostBuffer::I32(d) => Ok(HostBuffer::I32(unary_map(d, layout, |v: i32| v.signum()))),
428 HostBuffer::I64(d) => Ok(HostBuffer::I64(unary_map(d, layout, |v: i64| v.signum()))),
429 HostBuffer::F8E4M3(d) => Ok(HostBuffer::F8E4M3(unary_map(d, layout, |v: F8E4M3| {
430 let f = v.to_f32();
431 F8E4M3::from_f32(f32::from(f > 0.) - f32::from(f < 0.))
432 }))),
433 other => Err(Error::UnsupportedDTypeForOp(other.dtype(), "sign").bt()),
434 }
435}
436
437fn cpu_binary_op(
439 lhs: &HostBuffer,
440 rhs: &HostBuffer,
441 lhs_l: &Layout,
442 rhs_l: &Layout,
443 op: BinaryOp,
444) -> Result<HostBuffer> {
445 use BinaryOp::*;
446 match op {
447 Add => all_binary(
452 lhs,
453 rhs,
454 lhs_l,
455 rhs_l,
456 |a, b| a + b,
457 |a: i64, b: i64| a.wrapping_add(b),
458 "add",
459 ),
460 Sub => all_binary(
461 lhs,
462 rhs,
463 lhs_l,
464 rhs_l,
465 |a, b| a - b,
466 |a: i64, b: i64| a.wrapping_sub(b),
467 "sub",
468 ),
469 Mul => all_binary(
470 lhs,
471 rhs,
472 lhs_l,
473 rhs_l,
474 |a, b| a * b,
475 |a: i64, b: i64| a.wrapping_mul(b),
476 "mul",
477 ),
478 Div => all_binary(
485 lhs,
486 rhs,
487 lhs_l,
488 rhs_l,
489 |a, b| a / b,
490 |a: i64, b: i64| (a as f64 / b as f64) as i64,
491 "div",
492 ),
493 Maximum => all_binary(
501 lhs,
502 rhs,
503 lhs_l,
504 rhs_l,
505 |a, b| {
506 if a.is_nan() {
510 a
511 } else if b.is_nan() || a < b {
512 b
513 } else {
514 a
515 }
516 },
517 |a: i64, b: i64| a.max(b),
521 "maximum",
522 ),
523 Minimum => all_binary(
524 lhs,
525 rhs,
526 lhs_l,
527 rhs_l,
528 |a, b| {
529 if a.is_nan() {
533 a
534 } else if b.is_nan() || a > b {
535 b
536 } else {
537 a
538 }
539 },
540 |a: i64, b: i64| a.min(b),
541 "minimum",
542 ),
543 }
544}
545
546fn all_binary<F, G>(
547 lhs: &HostBuffer,
548 rhs: &HostBuffer,
549 lhs_l: &Layout,
550 rhs_l: &Layout,
551 f: F,
552 fi: G,
553 op_name: &'static str,
554) -> Result<HostBuffer>
555where
556 F: Fn(f64, f64) -> f64 + Copy,
557 G: Fn(i64, i64) -> i64 + Copy,
567{
568 macro_rules! dispatch_pair {
570 ($lhs_v:ident, $rhs_v:ident, $variant:ident, $conv_fn:expr, $result_fn:expr) => {{
571 let out = binary_map(lhs_l, rhs_l, $lhs_v, $rhs_v, |a, b| {
572 let result = f($conv_fn(a), $conv_fn(b));
573 $result_fn(result)
574 });
575 Ok(HostBuffer::$variant(out))
576 }};
577 }
578
579 match (lhs, rhs) {
580 (HostBuffer::BF16(a), HostBuffer::BF16(b)) => {
581 dispatch_pair!(a, b, BF16, |v: bf16| v.to_f64(), |v: f64| bf16::from_f64(v))
582 }
583 (HostBuffer::F16(a), HostBuffer::F16(b)) => {
584 dispatch_pair!(a, b, F16, |v: f16| v.to_f64(), |v: f64| f16::from_f64(v))
585 }
586 (HostBuffer::F32(a), HostBuffer::F32(b)) => {
587 let out = binary_map(lhs_l, rhs_l, a, b, |a, b| f(a as f64, b as f64) as f32);
588 Ok(HostBuffer::F32(out))
589 }
590 (HostBuffer::F64(a), HostBuffer::F64(b)) => {
591 let out = binary_map(lhs_l, rhs_l, a, b, &f);
592 Ok(HostBuffer::F64(out))
593 }
594 (HostBuffer::U8(a), HostBuffer::U8(b)) => {
595 let out = binary_map(lhs_l, rhs_l, a, b, |a, b| fi(a as i64, b as i64) as u8);
596 Ok(HostBuffer::U8(out))
597 }
598 (HostBuffer::U32(a), HostBuffer::U32(b)) => {
599 let out = binary_map(lhs_l, rhs_l, a, b, |a, b| fi(a as i64, b as i64) as u32);
600 Ok(HostBuffer::U32(out))
601 }
602 (HostBuffer::I16(a), HostBuffer::I16(b)) => {
603 let out = binary_map(lhs_l, rhs_l, a, b, |a, b| fi(a as i64, b as i64) as i16);
604 Ok(HostBuffer::I16(out))
605 }
606 (HostBuffer::I32(a), HostBuffer::I32(b)) => {
607 let out = binary_map(lhs_l, rhs_l, a, b, |a, b| fi(a as i64, b as i64) as i32);
608 Ok(HostBuffer::I32(out))
609 }
610 (HostBuffer::I64(a), HostBuffer::I64(b)) => {
611 let out = binary_map(lhs_l, rhs_l, a, b, fi);
612 Ok(HostBuffer::I64(out))
613 }
614 (HostBuffer::F8E4M3(a), HostBuffer::F8E4M3(b)) => {
615 dispatch_pair!(a, b, F8E4M3, |v: F8E4M3| v.to_f64(), |v: f64| {
616 F8E4M3::from_f64(v)
617 })
618 }
619 _ => Err(Error::DTypeMismatchBinaryOp {
620 lhs: lhs.dtype(),
621 rhs: rhs.dtype(),
622 op: op_name,
623 }
624 .bt()),
625 }
626}
627
628fn copy2d_<T: Copy>(
633 src: &[T],
634 dst: &mut [T],
635 d1: usize,
636 d2: usize,
637 src_stride1: usize,
638 dst_stride1: usize,
639 src_offset: usize,
640 dst_offset: usize,
641) {
642 for i1 in 0..d1 {
643 let dst_idx = i1 * dst_stride1 + dst_offset;
644 let src_idx = i1 * src_stride1 + src_offset;
645 dst[dst_idx..dst_idx + d2].copy_from_slice(&src[src_idx..src_idx + d2]);
646 }
647}
648
649fn copy_strided_src_<T: Copy>(src: &[T], dst: &mut [T], dst_offset: usize, src_l: &Layout) {
650 match src_l.strided_blocks() {
651 fuel_ir::StridedBlocks::SingleBlock { start_offset, len } => {
652 let to_copy = (dst.len() - dst_offset).min(len);
653 dst[dst_offset..dst_offset + to_copy]
654 .copy_from_slice(&src[start_offset..start_offset + to_copy])
655 }
656 fuel_ir::StridedBlocks::MultipleBlocks {
657 block_start_index,
658 block_len: 1,
659 } => {
660 for (dst_index, src_index) in block_start_index.enumerate() {
661 let dst_index = dst_index + dst_offset;
662 if dst_index >= dst.len() {
663 break;
664 }
665 dst[dst_index] = src[src_index];
666 }
667 }
668 fuel_ir::StridedBlocks::MultipleBlocks {
669 block_start_index,
670 block_len,
671 } => {
672 let mut dst_index = dst_offset;
673 for src_index in block_start_index {
674 let next_dst_index = dst_index + block_len;
675 if dst_index >= dst.len() {
676 break;
677 }
678 let to_copy = usize::min(block_len, dst.len() - dst_index);
679 dst[dst_index..dst_index + to_copy]
680 .copy_from_slice(&src[src_index..src_index + to_copy]);
681 dst_index = next_dst_index;
682 }
683 }
684 }
685}
686
687fn elu<T: num_traits::Float>(v: T, alpha: T) -> T {
688 if v.is_sign_positive() {
689 v
690 } else {
691 (v.exp() - T::one()) * alpha
692 }
693}
694
695impl DynBackendStorage for CpuStorage {
700 fn try_clone_dyn(&self, _layout: &Layout) -> Result<Box<dyn DynBackendStorage>> {
701 Ok(Box::new(self.clone()))
702 }
703
704 fn dtype_dyn(&self) -> DType {
705 self.0.dtype()
706 }
707
708 fn device_dyn(&self) -> &dyn DynBackendDevice {
709 &CpuBackendDevice
711 }
712
713 fn device_arc_dyn(&self) -> Arc<dyn DynBackendDevice> {
714 Arc::new(CpuBackendDevice)
715 }
716
717 fn to_host_buffer_dyn(&self) -> Result<HostBuffer> {
718 Ok(self.0.clone())
719 }
720
721 fn affine_dyn(
722 &self,
723 layout: &Layout,
724 mul: f64,
725 add: f64,
726 ) -> Result<Box<dyn DynBackendStorage>> {
727 let result = Map1::map(&crate::ops::Affine(mul, add), &self.0, layout)?;
728 Ok(Box::new(CpuStorage(result)))
729 }
730
731 fn powf_dyn(&self, layout: &Layout, e: f64) -> Result<Box<dyn DynBackendStorage>> {
732 use num_traits::Float;
733 let result = match &self.0 {
734 HostBuffer::BF16(d) => {
735 HostBuffer::BF16(unary_map(d, layout, |v: bf16| v.powf(bf16::from_f64(e))))
736 }
737 HostBuffer::F16(d) => {
738 HostBuffer::F16(unary_map(d, layout, |v: f16| v.powf(f16::from_f64(e))))
739 }
740 HostBuffer::F32(d) => {
741 HostBuffer::F32(unary_map(d, layout, |v: f32| quiet_f32(v.powf(e as f32))))
742 }
743 HostBuffer::F64(d) => {
744 HostBuffer::F64(unary_map(d, layout, |v: f64| quiet_f64(v.powf(e))))
745 }
746 HostBuffer::F8E4M3(d) => HostBuffer::F8E4M3(unary_map(d, layout, |v: F8E4M3| {
747 v.powf(F8E4M3::from_f64(e))
748 })),
749 other => return Err(Error::UnsupportedDTypeForOp(other.dtype(), "powf").bt()),
750 };
751 Ok(Box::new(CpuStorage(result)))
752 }
753
754 fn elu_dyn(&self, layout: &Layout, alpha: f64) -> Result<Box<dyn DynBackendStorage>> {
755 let result = match &self.0 {
756 HostBuffer::BF16(d) => {
757 HostBuffer::BF16(unary_map(d, layout, |v| elu(v, bf16::from_f64(alpha))))
758 }
759 HostBuffer::F16(d) => {
760 HostBuffer::F16(unary_map(d, layout, |v| elu(v, f16::from_f64(alpha))))
761 }
762 HostBuffer::F32(d) => HostBuffer::F32(unary_map(d, layout, |v| elu(v, alpha as f32))),
763 HostBuffer::F64(d) => HostBuffer::F64(unary_map(d, layout, |v| elu(v, alpha))),
764 HostBuffer::F8E4M3(d) => {
765 HostBuffer::F8E4M3(unary_map(d, layout, |v| elu(v, F8E4M3::from_f64(alpha))))
766 }
767 other => return Err(Error::UnsupportedDTypeForOp(other.dtype(), "elu").bt()),
768 };
769 Ok(Box::new(CpuStorage(result)))
770 }
771
772 fn reduce_op_dyn(
773 &self,
774 op: ReduceOp,
775 layout: &Layout,
776 axes: &[usize],
777 ) -> Result<Box<dyn DynBackendStorage>> {
778 let result = match op {
779 ReduceOp::Sum => {
780 let src_dims = layout.dims();
781 let mut dst_dims = src_dims.to_vec();
782 for &dim in axes.iter() {
783 dst_dims[dim] = 1;
784 }
785 let dst_shape = Shape::from(dst_dims);
786 let mut reduce_dims = axes.to_vec();
787 reduce_dims.sort();
788 let reduce_dims_and_stride: Vec<_> = reduce_dims
789 .iter()
790 .map(|&d| (src_dims[d], src_dims[d + 1..].iter().product::<usize>()))
791 .collect();
792 Map1::map(
793 &crate::ops::ReduceSum {
794 dst_shape: &dst_shape,
795 reduce_dims: &reduce_dims,
796 reduce_dims_and_stride,
797 },
798 &self.0,
799 layout,
800 )?
801 }
802 ReduceOp::Min | ReduceOp::ArgMin | ReduceOp::Max | ReduceOp::ArgMax => {
803 let reduce_dim_index = match axes {
804 [dim] => *dim,
805 _ => {
806 let op_name = match op {
807 ReduceOp::Min => "min",
808 ReduceOp::ArgMin => "argmin",
809 ReduceOp::Max => "max",
810 ReduceOp::ArgMax => "argmax",
811 _ => unreachable!(),
812 };
813 return Err(Error::OnlySingleDimension {
814 op: op_name,
815 dims: axes.to_vec(),
816 }
817 .bt());
818 }
819 };
820 let (use_min, return_index) = match op {
821 ReduceOp::Min => (true, false),
822 ReduceOp::ArgMin => (true, true),
823 ReduceOp::Max => (false, false),
824 ReduceOp::ArgMax => (false, true),
825 _ => unreachable!(),
826 };
827 Map1Any::map(
828 &crate::ops::ReduceIndex {
829 reduce_dim_index,
830 use_min,
831 return_index,
832 },
833 &self.0,
834 layout,
835 )?
836 }
837 };
838 Ok(Box::new(CpuStorage(result)))
839 }
840
841 fn cmp_dyn(
842 &self,
843 op: CmpOp,
844 rhs: &dyn DynBackendStorage,
845 lhs_layout: &Layout,
846 rhs_layout: &Layout,
847 ) -> Result<Box<dyn DynBackendStorage>> {
848 let rhs = downcast(rhs)?;
849 let result = Map2U8::map(
850 &crate::ops::Cmp(op),
851 &self.0,
852 lhs_layout,
853 &rhs.0,
854 rhs_layout,
855 )?;
856 Ok(Box::new(CpuStorage(result)))
857 }
858
859 fn to_dtype_dyn(&self, layout: &Layout, dtype: DType) -> Result<Box<dyn DynBackendStorage>> {
860 let result = cpu_to_dtype(&self.0, layout, dtype)?;
867 Ok(Box::new(CpuStorage(result)))
868 }
869
870 fn unary_op_dyn(&self, layout: &Layout, op: UnaryOp) -> Result<Box<dyn DynBackendStorage>> {
871 let result = cpu_unary_op(&self.0, layout, op)?;
872 Ok(Box::new(CpuStorage(result)))
873 }
874
875 fn binary_op_dyn(
876 &self,
877 rhs: &dyn DynBackendStorage,
878 lhs_layout: &Layout,
879 rhs_layout: &Layout,
880 op: BinaryOp,
881 ) -> Result<Box<dyn DynBackendStorage>> {
882 let rhs = downcast(rhs)?;
883 let result = cpu_binary_op(&self.0, &rhs.0, lhs_layout, rhs_layout, op)?;
884 Ok(Box::new(CpuStorage(result)))
885 }
886
887 fn where_cond_dyn(
888 &self,
889 cond_layout: &Layout,
890 on_true: &dyn DynBackendStorage,
891 on_true_layout: &Layout,
892 on_false: &dyn DynBackendStorage,
893 on_false_layout: &Layout,
894 ) -> Result<Box<dyn DynBackendStorage>> {
895 let t = downcast(on_true)?;
896 let f = downcast(on_false)?;
897 let result = match &self.0 {
898 HostBuffer::U8(pred) => Map2::map(
899 &crate::ops::WCond(pred, cond_layout),
900 &t.0,
901 on_true_layout,
902 &f.0,
903 on_false_layout,
904 )?,
905 HostBuffer::U32(pred) => Map2::map(
906 &crate::ops::WCond(pred, cond_layout),
907 &t.0,
908 on_true_layout,
909 &f.0,
910 on_false_layout,
911 )?,
912 HostBuffer::I16(pred) => Map2::map(
913 &crate::ops::WCond(pred, cond_layout),
914 &t.0,
915 on_true_layout,
916 &f.0,
917 on_false_layout,
918 )?,
919 HostBuffer::I32(pred) => Map2::map(
920 &crate::ops::WCond(pred, cond_layout),
921 &t.0,
922 on_true_layout,
923 &f.0,
924 on_false_layout,
925 )?,
926 HostBuffer::I64(pred) => Map2::map(
927 &crate::ops::WCond(pred, cond_layout),
928 &t.0,
929 on_true_layout,
930 &f.0,
931 on_false_layout,
932 )?,
933 HostBuffer::Bool(pred) => Map2::map(
936 &crate::ops::WCond(pred, cond_layout),
937 &t.0,
938 on_true_layout,
939 &f.0,
940 on_false_layout,
941 )?,
942 _ => return Err(Error::UnsupportedDTypeForOp(self.0.dtype(), "where-cond").bt()),
943 };
944 Ok(Box::new(CpuStorage(result)))
945 }
946
947 fn conv1d_dyn(
948 &self,
949 l: &Layout,
950 kernel: &dyn DynBackendStorage,
951 kernel_l: &Layout,
952 params: &ParamsConv1D,
953 ) -> Result<Box<dyn DynBackendStorage>> {
954 let kernel = downcast(kernel)?;
955 let l_k = params.k_size;
957 let col = Map1::map(
958 &crate::ops::Im2Col1D {
959 l_k,
960 padding: params.padding,
961 stride: params.stride,
962 dilation: params.dilation,
963 },
964 &self.0,
965 l,
966 )?;
967 let b = params.b_size;
968 let n = params.c_out;
969 let l_out = params.l_out();
970 let k = l_k * params.c_in;
971 let m = l_out;
972 let col_l = Layout::contiguous((b, m, k));
973 let kernel_l_c = if kernel_l.is_contiguous() {
974 Layout::contiguous_with_offset((1, n, k), kernel_l.start_offset())
975 .transpose(1, 2)?
976 .broadcast_as((b, k, n))?
977 } else {
978 let mut kernel_c = unsafe { cpu_alloc_uninit(kernel_l.shape(), kernel.0.dtype())? };
980 cpu_copy_strided_src(&kernel.0, &mut kernel_c, 0, kernel_l)?;
981 let new_l = Layout::contiguous_with_offset((1, n, k), kernel_l.start_offset())
982 .transpose(1, 2)?
983 .broadcast_as((b, k, n))?;
984 return {
986 let res = Map2::map(
987 &crate::ops::MatMul((b, m, n, k)),
988 &col,
989 &col_l,
990 &kernel_c,
991 &new_l,
992 )?;
993 let res_l = Layout::contiguous((b, l_out, params.c_out)).transpose(1, 2)?;
994 let mut res_t = unsafe { cpu_alloc_uninit(res_l.shape(), res.dtype())? };
995 cpu_copy_strided_src(&res, &mut res_t, 0, &res_l)?;
996 Ok(Box::new(CpuStorage(res_t)))
997 };
998 };
999 let res = Map2::map(
1000 &crate::ops::MatMul((b, m, n, k)),
1001 &col,
1002 &col_l,
1003 &kernel.0,
1004 &kernel_l_c,
1005 )?;
1006 let res_l = Layout::contiguous((b, l_out, params.c_out)).transpose(1, 2)?;
1007 let mut res_t = unsafe { cpu_alloc_uninit(res_l.shape(), res.dtype())? };
1008 cpu_copy_strided_src(&res, &mut res_t, 0, &res_l)?;
1009 Ok(Box::new(CpuStorage(res_t)))
1010 }
1011
1012 fn conv_transpose1d_dyn(
1013 &self,
1014 l: &Layout,
1015 kernel: &dyn DynBackendStorage,
1016 kernel_l: &Layout,
1017 params: &ParamsConvTranspose1D,
1018 ) -> Result<Box<dyn DynBackendStorage>> {
1019 let kernel = downcast(kernel)?;
1020 let result = Map2::map(
1021 &crate::ops::ConvTranspose1D(params),
1022 &self.0,
1023 l,
1024 &kernel.0,
1025 kernel_l,
1026 )?;
1027 Ok(Box::new(CpuStorage(result)))
1028 }
1029
1030 fn conv2d_dyn(
1031 &self,
1032 l: &Layout,
1033 kernel: &dyn DynBackendStorage,
1034 kernel_l: &Layout,
1035 params: &ParamsConv2D,
1036 ) -> Result<Box<dyn DynBackendStorage>> {
1037 let kernel = downcast(kernel)?;
1038 let result = Map2::map(
1039 &crate::conv2d::Conv2D(params),
1040 &self.0,
1041 l,
1042 &kernel.0,
1043 kernel_l,
1044 )?;
1045 Ok(Box::new(CpuStorage(result)))
1046 }
1047
1048 fn conv_transpose2d_dyn(
1049 &self,
1050 l: &Layout,
1051 kernel: &dyn DynBackendStorage,
1052 kernel_l: &Layout,
1053 params: &ParamsConvTranspose2D,
1054 ) -> Result<Box<dyn DynBackendStorage>> {
1055 let kernel = downcast(kernel)?;
1056 let result = Map2::map(
1057 &crate::ops::ConvTranspose2D(params),
1058 &self.0,
1059 l,
1060 &kernel.0,
1061 kernel_l,
1062 )?;
1063 Ok(Box::new(CpuStorage(result)))
1064 }
1065
1066 fn avg_pool2d_dyn(
1067 &self,
1068 layout: &Layout,
1069 kernel: (usize, usize),
1070 stride: (usize, usize),
1071 ) -> Result<Box<dyn DynBackendStorage>> {
1072 let result = Map1::map(&crate::ops::AvgPool2D(kernel, stride), &self.0, layout)?;
1073 Ok(Box::new(CpuStorage(result)))
1074 }
1075
1076 fn max_pool2d_dyn(
1077 &self,
1078 layout: &Layout,
1079 kernel: (usize, usize),
1080 stride: (usize, usize),
1081 ) -> Result<Box<dyn DynBackendStorage>> {
1082 let result = Map1::map(&crate::ops::MaxPool2D(kernel, stride), &self.0, layout)?;
1083 Ok(Box::new(CpuStorage(result)))
1084 }
1085
1086 fn upsample_nearest1d_dyn(
1087 &self,
1088 layout: &Layout,
1089 target_size: usize,
1090 ) -> Result<Box<dyn DynBackendStorage>> {
1091 let result = Map1::map(&crate::ops::UpsampleNearest1D(target_size), &self.0, layout)?;
1092 Ok(Box::new(CpuStorage(result)))
1093 }
1094
1095 fn upsample_nearest2d_dyn(
1096 &self,
1097 layout: &Layout,
1098 target_h: usize,
1099 target_w: usize,
1100 ) -> Result<Box<dyn DynBackendStorage>> {
1101 let result = Map1::map(
1102 &crate::ops::UpsampleNearest2D(target_h, target_w),
1103 &self.0,
1104 layout,
1105 )?;
1106 Ok(Box::new(CpuStorage(result)))
1107 }
1108
1109 fn upsample_bilinear2d_dyn(
1110 &self,
1111 layout: &Layout,
1112 target_h: usize,
1113 target_w: usize,
1114 align_corners: bool,
1115 scale_h: Option<f64>,
1116 scale_w: Option<f64>,
1117 ) -> Result<Box<dyn DynBackendStorage>> {
1118 let result = Map1::map(
1119 &crate::ops::UpsampleBilinear2D {
1120 target_h,
1121 target_w,
1122 align_corners,
1123 scale_h_factor: scale_h,
1124 scale_w_factor: scale_w,
1125 },
1126 &self.0,
1127 layout,
1128 )?;
1129 Ok(Box::new(CpuStorage(result)))
1130 }
1131
1132 fn gather_dyn(
1133 &self,
1134 src_layout: &Layout,
1135 ids: &dyn DynBackendStorage,
1136 ids_layout: &Layout,
1137 dim: usize,
1138 ) -> Result<Box<dyn DynBackendStorage>> {
1139 let ids_s = downcast(ids)?;
1140 let result = match &ids_s.0 {
1141 HostBuffer::U8(ids) => Map1::map(
1142 &crate::ops::Gather {
1143 ids,
1144 ids_l: ids_layout,
1145 dim,
1146 },
1147 &self.0,
1148 src_layout,
1149 )?,
1150 HostBuffer::U32(ids) => Map1::map(
1151 &crate::ops::Gather {
1152 ids,
1153 ids_l: ids_layout,
1154 dim,
1155 },
1156 &self.0,
1157 src_layout,
1158 )?,
1159 HostBuffer::I64(ids) => Map1::map(
1160 &crate::ops::Gather {
1161 ids,
1162 ids_l: ids_layout,
1163 dim,
1164 },
1165 &self.0,
1166 src_layout,
1167 )?,
1168 _ => return Err(Error::UnsupportedDTypeForOp(ids_s.0.dtype(), "gather").bt()),
1169 };
1170 Ok(Box::new(CpuStorage(result)))
1171 }
1172
1173 fn scatter_set_dyn(
1174 &mut self,
1175 self_layout: &Layout,
1176 src: &dyn DynBackendStorage,
1177 src_layout: &Layout,
1178 ids: &dyn DynBackendStorage,
1179 ids_layout: &Layout,
1180 dim: usize,
1181 ) -> Result<()> {
1182 let ids_s = downcast(ids)?;
1183 let src_s = downcast(src)?;
1184 match &ids_s.0 {
1185 HostBuffer::U8(ids) => Map2InPlace::map(
1186 &crate::ops::Scatter::<_, crate::ops::Set>::new(ids, ids_layout, dim),
1187 &mut self.0,
1188 self_layout,
1189 &src_s.0,
1190 src_layout,
1191 )?,
1192 HostBuffer::U32(ids) => Map2InPlace::map(
1193 &crate::ops::Scatter::<_, crate::ops::Set>::new(ids, ids_layout, dim),
1194 &mut self.0,
1195 self_layout,
1196 &src_s.0,
1197 src_layout,
1198 )?,
1199 HostBuffer::I64(ids) => Map2InPlace::map(
1200 &crate::ops::Scatter::<_, crate::ops::Set>::new(ids, ids_layout, dim),
1201 &mut self.0,
1202 self_layout,
1203 &src_s.0,
1204 src_layout,
1205 )?,
1206 _ => return Err(Error::UnsupportedDTypeForOp(ids_s.0.dtype(), "scatter").bt()),
1207 };
1208 Ok(())
1209 }
1210
1211 fn scatter_add_set_dyn(
1212 &mut self,
1213 self_layout: &Layout,
1214 src: &dyn DynBackendStorage,
1215 src_layout: &Layout,
1216 ids: &dyn DynBackendStorage,
1217 ids_layout: &Layout,
1218 dim: usize,
1219 ) -> Result<()> {
1220 let ids_s = downcast(ids)?;
1221 let src_s = downcast(src)?;
1222 match &ids_s.0 {
1223 HostBuffer::U8(ids) => Map2InPlace::map(
1224 &crate::ops::Scatter::<_, crate::ops::Add>::new(ids, ids_layout, dim),
1225 &mut self.0,
1226 self_layout,
1227 &src_s.0,
1228 src_layout,
1229 )?,
1230 HostBuffer::U32(ids) => Map2InPlace::map(
1231 &crate::ops::Scatter::<_, crate::ops::Add>::new(ids, ids_layout, dim),
1232 &mut self.0,
1233 self_layout,
1234 &src_s.0,
1235 src_layout,
1236 )?,
1237 HostBuffer::I16(ids) => Map2InPlace::map(
1238 &crate::ops::Scatter::<_, crate::ops::Add>::new(ids, ids_layout, dim),
1239 &mut self.0,
1240 self_layout,
1241 &src_s.0,
1242 src_layout,
1243 )?,
1244 HostBuffer::I32(ids) => Map2InPlace::map(
1245 &crate::ops::Scatter::<_, crate::ops::Add>::new(ids, ids_layout, dim),
1246 &mut self.0,
1247 self_layout,
1248 &src_s.0,
1249 src_layout,
1250 )?,
1251 HostBuffer::I64(ids) => Map2InPlace::map(
1252 &crate::ops::Scatter::<_, crate::ops::Add>::new(ids, ids_layout, dim),
1253 &mut self.0,
1254 self_layout,
1255 &src_s.0,
1256 src_layout,
1257 )?,
1258 _ => return Err(Error::UnsupportedDTypeForOp(ids_s.0.dtype(), "scatter-add").bt()),
1259 };
1260 Ok(())
1261 }
1262
1263 fn index_select_dyn(
1264 &self,
1265 ids: &dyn DynBackendStorage,
1266 src_layout: &Layout,
1267 ids_layout: &Layout,
1268 dim: usize,
1269 ) -> Result<Box<dyn DynBackendStorage>> {
1270 let ids_s = downcast(ids)?;
1271 let result = match &ids_s.0 {
1272 HostBuffer::U8(ids) => Map1::map(
1273 &crate::ops::IndexSelect {
1274 ids,
1275 ids_l: ids_layout,
1276 dim,
1277 },
1278 &self.0,
1279 src_layout,
1280 )?,
1281 HostBuffer::U32(ids) => Map1::map(
1282 &crate::ops::IndexSelect {
1283 ids,
1284 ids_l: ids_layout,
1285 dim,
1286 },
1287 &self.0,
1288 src_layout,
1289 )?,
1290 HostBuffer::I64(ids) => Map1::map(
1291 &crate::ops::IndexSelect {
1292 ids,
1293 ids_l: ids_layout,
1294 dim,
1295 },
1296 &self.0,
1297 src_layout,
1298 )?,
1299 _ => return Err(Error::UnsupportedDTypeForOp(ids_s.0.dtype(), "index-select").bt()),
1300 };
1301 Ok(Box::new(CpuStorage(result)))
1302 }
1303
1304 fn index_add_dyn(
1305 &self,
1306 self_layout: &Layout,
1307 ids: &dyn DynBackendStorage,
1308 ids_layout: &Layout,
1309 src: &dyn DynBackendStorage,
1310 src_layout: &Layout,
1311 dim: usize,
1312 ) -> Result<Box<dyn DynBackendStorage>> {
1313 let ids_s = downcast(ids)?;
1314 let src_s = downcast(src)?;
1315 let result = match &ids_s.0 {
1316 HostBuffer::U8(ids) => {
1317 let ids = match ids_layout.contiguous_offsets() {
1318 Some((a, b)) => &ids[a..b],
1319 None => return Err(Error::RequiresContiguous { op: "index-add" }.bt()),
1320 };
1321 Map2::map(
1322 &crate::ops::IndexAdd { ids, dim },
1323 &self.0,
1324 self_layout,
1325 &src_s.0,
1326 src_layout,
1327 )?
1328 }
1329 HostBuffer::U32(ids) => {
1330 let ids = match ids_layout.contiguous_offsets() {
1331 Some((a, b)) => &ids[a..b],
1332 None => return Err(Error::RequiresContiguous { op: "index-add" }.bt()),
1333 };
1334 Map2::map(
1335 &crate::ops::IndexAdd { ids, dim },
1336 &self.0,
1337 self_layout,
1338 &src_s.0,
1339 src_layout,
1340 )?
1341 }
1342 HostBuffer::I16(ids) => {
1343 let ids = match ids_layout.contiguous_offsets() {
1344 Some((a, b)) => &ids[a..b],
1345 None => return Err(Error::RequiresContiguous { op: "index-add" }.bt()),
1346 };
1347 Map2::map(
1348 &crate::ops::IndexAdd { ids, dim },
1349 &self.0,
1350 self_layout,
1351 &src_s.0,
1352 src_layout,
1353 )?
1354 }
1355 HostBuffer::I32(ids) => {
1356 let ids = match ids_layout.contiguous_offsets() {
1357 Some((a, b)) => &ids[a..b],
1358 None => return Err(Error::RequiresContiguous { op: "index-add" }.bt()),
1359 };
1360 Map2::map(
1361 &crate::ops::IndexAdd { ids, dim },
1362 &self.0,
1363 self_layout,
1364 &src_s.0,
1365 src_layout,
1366 )?
1367 }
1368 HostBuffer::I64(ids) => {
1369 let ids = match ids_layout.contiguous_offsets() {
1370 Some((a, b)) => &ids[a..b],
1371 None => return Err(Error::RequiresContiguous { op: "index-add" }.bt()),
1372 };
1373 Map2::map(
1374 &crate::ops::IndexAdd { ids, dim },
1375 &self.0,
1376 self_layout,
1377 &src_s.0,
1378 src_layout,
1379 )?
1380 }
1381 _ => return Err(Error::UnsupportedDTypeForOp(ids_s.0.dtype(), "index-add").bt()),
1382 };
1383 Ok(Box::new(CpuStorage(result)))
1384 }
1385
1386 fn matmul_dyn(
1387 &self,
1388 rhs: &dyn DynBackendStorage,
1389 bmnk: (usize, usize, usize, usize),
1390 lhs_layout: &Layout,
1391 rhs_layout: &Layout,
1392 ) -> Result<Box<dyn DynBackendStorage>> {
1393 let rhs = downcast(rhs)?;
1394 let result = Map2::map(
1395 &crate::ops::MatMul(bmnk),
1396 &self.0,
1397 lhs_layout,
1398 &rhs.0,
1399 rhs_layout,
1400 )?;
1401 Ok(Box::new(CpuStorage(result)))
1402 }
1403
1404 fn copy_strided_src_dyn(
1405 &self,
1406 dst: &mut dyn DynBackendStorage,
1407 dst_offset: usize,
1408 src_layout: &Layout,
1409 ) -> Result<()> {
1410 let dst = downcast_mut(dst)?;
1411 cpu_copy_strided_src(&self.0, &mut dst.0, dst_offset, src_layout)
1412 }
1413
1414 fn copy2d_dyn(
1415 &self,
1416 dst: &mut dyn DynBackendStorage,
1417 d1: usize,
1418 d2: usize,
1419 src_stride1: usize,
1420 dst_stride1: usize,
1421 src_offset: usize,
1422 dst_offset: usize,
1423 ) -> Result<()> {
1424 let dst = downcast_mut(dst)?;
1425 cpu_copy2d(
1426 &self.0,
1427 &mut dst.0,
1428 d1,
1429 d2,
1430 src_stride1,
1431 dst_stride1,
1432 src_offset,
1433 dst_offset,
1434 )
1435 }
1436
1437 fn const_set_dyn(&mut self, value: Scalar, layout: &Layout) -> Result<()> {
1438 cpu_const_set(&mut self.0, value, layout)
1439 }
1440
1441 fn as_any(&self) -> &dyn Any {
1442 self
1443 }
1444
1445 fn as_any_mut(&mut self) -> &mut dyn Any {
1446 self
1447 }
1448}
1449
1450impl DynBackendDevice for CpuBackendDevice {
1455 fn location_dyn(&self) -> DeviceLocation {
1456 DeviceLocation::Cpu
1457 }
1458
1459 fn as_backend_runtime(&self) -> Option<&dyn fuel_backend_contract::backend::BackendRuntime> {
1460 Some(self)
1461 }
1462
1463 fn same_device_dyn(&self, other: &dyn DynBackendDevice) -> bool {
1464 other.as_any().downcast_ref::<CpuBackendDevice>().is_some()
1465 }
1466
1467 fn zeros_impl_dyn(&self, shape: &Shape, dtype: DType) -> Result<Box<dyn DynBackendStorage>> {
1468 let storage = cpu_zeros(shape, dtype)?;
1469 Ok(Box::new(CpuStorage(storage)))
1470 }
1471
1472 unsafe fn alloc_uninit_dyn(
1473 &self,
1474 shape: &Shape,
1475 dtype: DType,
1476 ) -> Result<Box<dyn DynBackendStorage>> {
1477 let storage = unsafe { cpu_alloc_uninit(shape, dtype)? };
1478 Ok(Box::new(CpuStorage(storage)))
1479 }
1480
1481 fn storage_from_host_buffer_dyn(&self, buf: &HostBuffer) -> Result<Box<dyn DynBackendStorage>> {
1482 Ok(Box::new(CpuStorage(buf.clone())))
1483 }
1484
1485 fn storage_from_host_buffer_owned_dyn(
1486 &self,
1487 buf: HostBuffer,
1488 ) -> Result<Box<dyn DynBackendStorage>> {
1489 Ok(Box::new(CpuStorage(buf)))
1490 }
1491
1492 fn rand_uniform_dyn(
1493 &self,
1494 shape: &Shape,
1495 dtype: DType,
1496 lo: f64,
1497 hi: f64,
1498 ) -> Result<Box<dyn DynBackendStorage>> {
1499 let storage = cpu_rand_uniform(shape, dtype, lo, hi)?;
1500 Ok(Box::new(CpuStorage(storage)))
1501 }
1502
1503 fn rand_normal_dyn(
1504 &self,
1505 shape: &Shape,
1506 dtype: DType,
1507 mean: f64,
1508 std: f64,
1509 ) -> Result<Box<dyn DynBackendStorage>> {
1510 let storage = cpu_rand_normal(shape, dtype, mean, std)?;
1511 Ok(Box::new(CpuStorage(storage)))
1512 }
1513
1514 fn set_seed_dyn(&self, _seed: u64) -> Result<()> {
1515 Err(Error::Msg("cannot seed the CPU rng with set_seed".into()).bt())
1516 }
1517
1518 fn get_current_seed_dyn(&self) -> Result<u64> {
1519 Err(Error::Msg("cannot get the CPU rng seed with get_current_seed".into()).bt())
1520 }
1521
1522 fn synchronize_dyn(&self) -> Result<()> {
1523 Ok(())
1524 }
1525
1526 fn as_any(&self) -> &dyn Any {
1527 self
1528 }
1529
1530 fn as_quantized_kernels(
1531 &self,
1532 ) -> Option<&dyn fuel_backend_contract::quantized::QuantizedDeviceKernels> {
1533 Some(self)
1534 }
1535}
1536
1537fn cpu_zeros(shape: &Shape, dtype: DType) -> Result<HostBuffer> {
1542 let elem_count = shape.elem_count();
1543 let storage = match dtype {
1544 DType::U8 => HostBuffer::U8(vec![0u8; elem_count]),
1545 DType::I8 => HostBuffer::I8(vec![0i8; elem_count]),
1546 DType::U32 => HostBuffer::U32(vec![0u32; elem_count]),
1547 DType::I16 => HostBuffer::I16(vec![0i16; elem_count]),
1548 DType::I32 => HostBuffer::I32(vec![0i32; elem_count]),
1549 DType::I64 => HostBuffer::I64(vec![0i64; elem_count]),
1550 DType::BF16 => HostBuffer::BF16(vec![bf16::ZERO; elem_count]),
1551 DType::F16 => HostBuffer::F16(vec![f16::ZERO; elem_count]),
1552 DType::F32 => HostBuffer::F32(vec![0f32; elem_count]),
1553 DType::F64 => HostBuffer::F64(vec![0f64; elem_count]),
1554 DType::F8E4M3 => HostBuffer::F8E4M3(vec![F8E4M3::ZERO; elem_count]),
1555 DType::F8E5M2 => return Err(Error::UnsupportedDTypeForOp(dtype, "zeros").bt()),
1562 DType::Bool => HostBuffer::Bool(vec![0u8; elem_count]),
1564 DType::F6E2M3 | DType::F6E3M2 | DType::F4 | DType::F8E8M0 | DType::F8E6M2 => {
1565 return Err(Error::UnsupportedDTypeForOp(dtype, "zeros").bt());
1566 }
1567 };
1568 Ok(storage)
1569}
1570
1571#[allow(clippy::uninit_vec)]
1572unsafe fn cpu_alloc_uninit(shape: &Shape, dtype: DType) -> Result<HostBuffer> {
1573 let elem_count = shape.elem_count();
1574 let storage = match dtype {
1575 DType::U8 => {
1576 let mut v = Vec::with_capacity(elem_count);
1577 unsafe { v.set_len(elem_count) };
1578 HostBuffer::U8(v)
1579 }
1580 DType::I8 => {
1581 let mut v = Vec::with_capacity(elem_count);
1582 unsafe { v.set_len(elem_count) };
1583 HostBuffer::I8(v)
1584 }
1585 DType::U32 => {
1586 let mut v = Vec::with_capacity(elem_count);
1587 unsafe { v.set_len(elem_count) };
1588 HostBuffer::U32(v)
1589 }
1590 DType::I16 => {
1591 let mut v = Vec::with_capacity(elem_count);
1592 unsafe { v.set_len(elem_count) };
1593 HostBuffer::I16(v)
1594 }
1595 DType::I32 => {
1596 let mut v = Vec::with_capacity(elem_count);
1597 unsafe { v.set_len(elem_count) };
1598 HostBuffer::I32(v)
1599 }
1600 DType::I64 => {
1601 let mut v = Vec::with_capacity(elem_count);
1602 unsafe { v.set_len(elem_count) };
1603 HostBuffer::I64(v)
1604 }
1605 DType::BF16 => {
1606 let mut v = Vec::with_capacity(elem_count);
1607 unsafe { v.set_len(elem_count) };
1608 HostBuffer::BF16(v)
1609 }
1610 DType::F16 => {
1611 let mut v = Vec::with_capacity(elem_count);
1612 unsafe { v.set_len(elem_count) };
1613 HostBuffer::F16(v)
1614 }
1615 DType::F32 => {
1616 let mut v = Vec::with_capacity(elem_count);
1617 unsafe { v.set_len(elem_count) };
1618 HostBuffer::F32(v)
1619 }
1620 DType::F64 => {
1621 let mut v = Vec::with_capacity(elem_count);
1622 unsafe { v.set_len(elem_count) };
1623 HostBuffer::F64(v)
1624 }
1625 DType::F8E4M3 => {
1626 let mut v = Vec::with_capacity(elem_count);
1627 unsafe { v.set_len(elem_count) };
1628 HostBuffer::F8E4M3(v)
1629 }
1630 DType::F8E5M2 => return Err(Error::UnsupportedDTypeForOp(dtype, "alloc_uninit").bt()),
1637 DType::Bool => {
1639 let mut v = Vec::with_capacity(elem_count);
1640 unsafe { v.set_len(elem_count) };
1641 HostBuffer::Bool(v)
1642 }
1643 DType::F6E2M3 | DType::F6E3M2 | DType::F4 | DType::F8E8M0 | DType::F8E6M2 => {
1644 return Err(Error::UnsupportedDTypeForOp(dtype, "alloc_uninit").bt());
1645 }
1646 };
1647 Ok(storage)
1648}
1649
1650fn sample_narrow_in_range<T: Copy>(
1656 rng: &mut impl rand::Rng,
1657 uniform: rand::distr::Uniform<f32>,
1658 min: f32,
1659 max: f32,
1660 narrow: impl Fn(f32) -> T,
1661 widen: impl Fn(T) -> f32,
1662) -> T {
1663 use rand::RngExt;
1664 for _ in 0..64 {
1665 let v = narrow(rng.sample::<f32, _>(uniform));
1666 let w = widen(v);
1667 if w >= min && w < max {
1668 return v;
1669 }
1670 }
1671 narrow(min)
1672}
1673
1674fn cpu_rand_uniform(shape: &Shape, dtype: DType, min: f64, max: f64) -> Result<HostBuffer> {
1675 use rand::prelude::*;
1676 let elem_count = shape.elem_count();
1677 let mut rng = rand::rng();
1678 match dtype {
1679 DType::BF16 => {
1680 let uniform =
1681 rand::distr::Uniform::new(min as f32, max as f32).map_err(fuel_ir::Error::wrap)?;
1682 let data: Vec<_> = (0..elem_count)
1683 .map(|_| {
1684 sample_narrow_in_range(
1685 &mut rng,
1686 uniform,
1687 min as f32,
1688 max as f32,
1689 bf16::from_f32,
1690 |x| x.to_f32(),
1691 )
1692 })
1693 .collect();
1694 Ok(HostBuffer::BF16(data))
1695 }
1696 DType::F16 => {
1697 let uniform =
1698 rand::distr::Uniform::new(min as f32, max as f32).map_err(fuel_ir::Error::wrap)?;
1699 let data: Vec<_> = (0..elem_count)
1700 .map(|_| {
1701 sample_narrow_in_range(
1702 &mut rng,
1703 uniform,
1704 min as f32,
1705 max as f32,
1706 f16::from_f32,
1707 |x| x.to_f32(),
1708 )
1709 })
1710 .collect();
1711 Ok(HostBuffer::F16(data))
1712 }
1713 DType::F8E4M3 => {
1714 let uniform =
1715 rand::distr::Uniform::new(min as f32, max as f32).map_err(fuel_ir::Error::wrap)?;
1716 let data: Vec<_> = (0..elem_count)
1717 .map(|_| {
1718 sample_narrow_in_range(
1719 &mut rng,
1720 uniform,
1721 min as f32,
1722 max as f32,
1723 F8E4M3::from_f32,
1724 |x| x.to_f32(),
1725 )
1726 })
1727 .collect();
1728 Ok(HostBuffer::F8E4M3(data))
1729 }
1730 DType::F32 => {
1731 let uniform =
1732 rand::distr::Uniform::new(min as f32, max as f32).map_err(fuel_ir::Error::wrap)?;
1733 let data: Vec<_> = (0..elem_count)
1734 .map(|_| rng.sample::<f32, _>(uniform))
1735 .collect();
1736 Ok(HostBuffer::F32(data))
1737 }
1738 DType::F64 => {
1739 let uniform = rand::distr::Uniform::new(min, max).map_err(fuel_ir::Error::wrap)?;
1740 let data: Vec<_> = (0..elem_count)
1741 .map(|_| rng.sample::<f64, _>(uniform))
1742 .collect();
1743 Ok(HostBuffer::F64(data))
1744 }
1745 _ => Err(Error::UnsupportedDTypeForOp(dtype, "rand_uniform").bt()),
1746 }
1747}
1748
1749fn cpu_rand_normal(shape: &Shape, dtype: DType, mean: f64, std: f64) -> Result<HostBuffer> {
1750 use rand::prelude::*;
1751 let elem_count = shape.elem_count();
1752 let mut rng = rand::rng();
1753 match dtype {
1754 DType::BF16 => {
1755 let normal =
1756 rand_distr::Normal::new(mean as f32, std as f32).map_err(fuel_ir::Error::wrap)?;
1757 let data: Vec<_> = (0..elem_count)
1758 .map(|_| bf16::from_f32(normal.sample(&mut rng)))
1759 .collect();
1760 Ok(HostBuffer::BF16(data))
1761 }
1762 DType::F16 => {
1763 let normal =
1764 rand_distr::Normal::new(mean as f32, std as f32).map_err(fuel_ir::Error::wrap)?;
1765 let data: Vec<_> = (0..elem_count)
1766 .map(|_| f16::from_f32(normal.sample(&mut rng)))
1767 .collect();
1768 Ok(HostBuffer::F16(data))
1769 }
1770 DType::F8E4M3 => {
1771 let normal =
1772 rand_distr::Normal::new(mean as f32, std as f32).map_err(fuel_ir::Error::wrap)?;
1773 let data: Vec<_> = (0..elem_count)
1774 .map(|_| F8E4M3::from_f32(normal.sample(&mut rng)))
1775 .collect();
1776 Ok(HostBuffer::F8E4M3(data))
1777 }
1778 DType::F32 => {
1779 let normal =
1780 rand_distr::Normal::new(mean as f32, std as f32).map_err(fuel_ir::Error::wrap)?;
1781 let data: Vec<_> = (0..elem_count).map(|_| normal.sample(&mut rng)).collect();
1782 Ok(HostBuffer::F32(data))
1783 }
1784 DType::F64 => {
1785 let normal = rand_distr::Normal::new(mean, std).map_err(fuel_ir::Error::wrap)?;
1786 let data: Vec<_> = (0..elem_count).map(|_| normal.sample(&mut rng)).collect();
1787 Ok(HostBuffer::F64(data))
1788 }
1789 _ => Err(Error::UnsupportedDTypeForOp(dtype, "rand_normal").bt()),
1790 }
1791}
1792
1793fn cpu_copy_strided_src(
1798 src: &HostBuffer,
1799 dst: &mut HostBuffer,
1800 dst_offset: usize,
1801 src_l: &Layout,
1802) -> Result<()> {
1803 match (src, dst) {
1804 (HostBuffer::U8(s), HostBuffer::U8(d)) => copy_strided_src_(s, d, dst_offset, src_l),
1805 (HostBuffer::U32(s), HostBuffer::U32(d)) => copy_strided_src_(s, d, dst_offset, src_l),
1806 (HostBuffer::I16(s), HostBuffer::I16(d)) => copy_strided_src_(s, d, dst_offset, src_l),
1807 (HostBuffer::I32(s), HostBuffer::I32(d)) => copy_strided_src_(s, d, dst_offset, src_l),
1808 (HostBuffer::I64(s), HostBuffer::I64(d)) => copy_strided_src_(s, d, dst_offset, src_l),
1809 (HostBuffer::BF16(s), HostBuffer::BF16(d)) => copy_strided_src_(s, d, dst_offset, src_l),
1810 (HostBuffer::F16(s), HostBuffer::F16(d)) => copy_strided_src_(s, d, dst_offset, src_l),
1811 (HostBuffer::F32(s), HostBuffer::F32(d)) => copy_strided_src_(s, d, dst_offset, src_l),
1812 (HostBuffer::F64(s), HostBuffer::F64(d)) => copy_strided_src_(s, d, dst_offset, src_l),
1813 (HostBuffer::F8E4M3(s), HostBuffer::F8E4M3(d)) => {
1814 copy_strided_src_(s, d, dst_offset, src_l)
1815 }
1816 (_, d) => {
1817 return Err(Error::DTypeMismatchBinaryOp {
1818 lhs: src.dtype(),
1819 rhs: d.dtype(),
1820 op: "copy_strided",
1821 }
1822 .bt());
1823 }
1824 }
1825 Ok(())
1826}
1827
1828fn cpu_copy2d(
1829 src: &HostBuffer,
1830 dst: &mut HostBuffer,
1831 d1: usize,
1832 d2: usize,
1833 src_s: usize,
1834 dst_s: usize,
1835 src_o: usize,
1836 dst_o: usize,
1837) -> Result<()> {
1838 match (src, dst) {
1839 (HostBuffer::U8(s), HostBuffer::U8(d)) => copy2d_(s, d, d1, d2, src_s, dst_s, src_o, dst_o),
1840 (HostBuffer::U32(s), HostBuffer::U32(d)) => {
1841 copy2d_(s, d, d1, d2, src_s, dst_s, src_o, dst_o)
1842 }
1843 (HostBuffer::I16(s), HostBuffer::I16(d)) => {
1844 copy2d_(s, d, d1, d2, src_s, dst_s, src_o, dst_o)
1845 }
1846 (HostBuffer::I32(s), HostBuffer::I32(d)) => {
1847 copy2d_(s, d, d1, d2, src_s, dst_s, src_o, dst_o)
1848 }
1849 (HostBuffer::I64(s), HostBuffer::I64(d)) => {
1850 copy2d_(s, d, d1, d2, src_s, dst_s, src_o, dst_o)
1851 }
1852 (HostBuffer::BF16(s), HostBuffer::BF16(d)) => {
1853 copy2d_(s, d, d1, d2, src_s, dst_s, src_o, dst_o)
1854 }
1855 (HostBuffer::F16(s), HostBuffer::F16(d)) => {
1856 copy2d_(s, d, d1, d2, src_s, dst_s, src_o, dst_o)
1857 }
1858 (HostBuffer::F32(s), HostBuffer::F32(d)) => {
1859 copy2d_(s, d, d1, d2, src_s, dst_s, src_o, dst_o)
1860 }
1861 (HostBuffer::F64(s), HostBuffer::F64(d)) => {
1862 copy2d_(s, d, d1, d2, src_s, dst_s, src_o, dst_o)
1863 }
1864 (HostBuffer::F8E4M3(s), HostBuffer::F8E4M3(d)) => {
1865 copy2d_(s, d, d1, d2, src_s, dst_s, src_o, dst_o)
1866 }
1867 (_, d) => {
1868 return Err(Error::DTypeMismatchBinaryOp {
1869 lhs: src.dtype(),
1870 rhs: d.dtype(),
1871 op: "copy2d",
1872 }
1873 .bt());
1874 }
1875 }
1876 Ok(())
1877}
1878
1879fn cpu_const_set(storage: &mut HostBuffer, s: Scalar, l: &Layout) -> Result<()> {
1880 use fuel_ir::Scalar as S;
1881 fn set<T: fuel_ir::WithDType + Copy>(src: &mut [T], l: &Layout, s: T) {
1882 match l.strided_blocks() {
1883 fuel_ir::StridedBlocks::SingleBlock { start_offset, len } => {
1884 src[start_offset..start_offset + len].fill(s)
1885 }
1886 fuel_ir::StridedBlocks::MultipleBlocks {
1887 block_start_index,
1888 block_len: 1,
1889 } => {
1890 for src_index in block_start_index {
1891 src[src_index] = s;
1892 }
1893 }
1894 fuel_ir::StridedBlocks::MultipleBlocks {
1895 block_start_index,
1896 block_len,
1897 } => {
1898 for src_index in block_start_index {
1899 src[src_index..src_index + block_len].fill(s);
1900 }
1901 }
1902 }
1903 }
1904 match (storage, s) {
1905 (HostBuffer::BF16(d), S::BF16(v)) => set(d, l, v),
1906 (HostBuffer::F16(d), S::F16(v)) => set(d, l, v),
1907 (HostBuffer::F32(d), S::F32(v)) => set(d, l, v),
1908 (HostBuffer::F64(d), S::F64(v)) => set(d, l, v),
1909 (HostBuffer::U8(d), S::U8(v)) => set(d, l, v),
1910 (HostBuffer::U32(d), S::U32(v)) => set(d, l, v),
1911 (HostBuffer::I16(d), S::I16(v)) => set(d, l, v),
1912 (HostBuffer::I32(d), S::I32(v)) => set(d, l, v),
1913 (HostBuffer::I64(d), S::I64(v)) => set(d, l, v),
1914 (HostBuffer::F8E4M3(d), S::F8E4M3(v)) => set(d, l, v),
1915 (st, s) => {
1916 return Err(Error::Msg(format!(
1917 "const_set dtype mismatch, expected {:?} but got {:?}",
1918 st.dtype(),
1919 s
1920 ))
1921 .bt());
1922 }
1923 }
1924 Ok(())
1925}
1926
1927fn cpu_to_dtype(src: &HostBuffer, layout: &Layout, dtype: DType) -> Result<HostBuffer> {
1930 if src.dtype() == dtype {
1932 return match src {
1934 HostBuffer::U8(d) => Ok(HostBuffer::U8(unary_map(d, layout, |v: u8| v))),
1935 HostBuffer::U32(d) => Ok(HostBuffer::U32(unary_map(d, layout, |v: u32| v))),
1936 HostBuffer::I16(d) => Ok(HostBuffer::I16(unary_map(d, layout, |v: i16| v))),
1937 HostBuffer::I32(d) => Ok(HostBuffer::I32(unary_map(d, layout, |v: i32| v))),
1938 HostBuffer::I64(d) => Ok(HostBuffer::I64(unary_map(d, layout, |v: i64| v))),
1939 HostBuffer::BF16(d) => Ok(HostBuffer::BF16(unary_map(d, layout, |v: bf16| v))),
1940 HostBuffer::F16(d) => Ok(HostBuffer::F16(unary_map(d, layout, |v: f16| v))),
1941 HostBuffer::F32(d) => Ok(HostBuffer::F32(unary_map(d, layout, |v: f32| v))),
1942 HostBuffer::F64(d) => Ok(HostBuffer::F64(unary_map(d, layout, |v: f64| v))),
1943 HostBuffer::F8E4M3(d) => Ok(HostBuffer::F8E4M3(unary_map(d, layout, |v: F8E4M3| v))),
1944 HostBuffer::Bool(d) => Ok(HostBuffer::Bool(unary_map(d, layout, |v: u8| v))),
1946 _ => Err(Error::UnsupportedDTypeForOp(src.dtype(), "to_dtype").bt()),
1947 };
1948 }
1949
1950 let as_f64: Vec<f64> = match src {
1953 HostBuffer::U8(d) => unary_map(d, layout, |v: u8| v as f64),
1954 HostBuffer::U32(d) => unary_map(d, layout, |v: u32| v as f64),
1955 HostBuffer::I16(d) => unary_map(d, layout, |v: i16| v as f64),
1956 HostBuffer::I32(d) => unary_map(d, layout, |v: i32| v as f64),
1957 HostBuffer::I64(d) => unary_map(d, layout, |v: i64| v as f64),
1958 HostBuffer::BF16(d) => unary_map(d, layout, |v: bf16| v.to_f64()),
1959 HostBuffer::F16(d) => unary_map(d, layout, |v: f16| v.to_f64()),
1960 HostBuffer::F32(d) => unary_map(d, layout, |v: f32| v as f64),
1961 HostBuffer::F64(d) => unary_map(d, layout, |v: f64| v),
1962 HostBuffer::F8E4M3(d) => unary_map(d, layout, |v: F8E4M3| v.to_f64()),
1963 HostBuffer::Bool(d) => unary_map(d, layout, |v: u8| v as f64),
1966 _ => return Err(Error::UnsupportedDTypeForOp(src.dtype(), "to_dtype").bt()),
1967 };
1968
1969 match dtype {
1970 DType::U8 => Ok(HostBuffer::U8(
1971 as_f64.into_iter().map(|v| v as u8).collect(),
1972 )),
1973 DType::U32 => Ok(HostBuffer::U32(
1974 as_f64.into_iter().map(|v| v as u32).collect(),
1975 )),
1976 DType::I16 => Ok(HostBuffer::I16(
1977 as_f64.into_iter().map(|v| v as i16).collect(),
1978 )),
1979 DType::I32 => Ok(HostBuffer::I32(
1980 as_f64.into_iter().map(|v| v as i32).collect(),
1981 )),
1982 DType::I64 => Ok(HostBuffer::I64(
1983 as_f64.into_iter().map(|v| v as i64).collect(),
1984 )),
1985 DType::BF16 => Ok(HostBuffer::BF16(
1986 as_f64.into_iter().map(bf16::from_f64).collect(),
1987 )),
1988 DType::F16 => Ok(HostBuffer::F16(
1989 as_f64.into_iter().map(f16::from_f64).collect(),
1990 )),
1991 DType::F32 => Ok(HostBuffer::F32(
1992 as_f64.into_iter().map(|v| v as f32).collect(),
1993 )),
1994 DType::F64 => Ok(HostBuffer::F64(as_f64)),
1995 DType::F8E4M3 => Ok(HostBuffer::F8E4M3(
1996 as_f64.into_iter().map(F8E4M3::from_f64).collect(),
1997 )),
1998 DType::Bool => Ok(HostBuffer::Bool(
2003 as_f64
2004 .into_iter()
2005 .map(|v| if v != 0.0 { 1u8 } else { 0u8 })
2006 .collect(),
2007 )),
2008 _ => Err(Error::UnsupportedDTypeForOp(dtype, "to_dtype").bt()),
2009 }
2010}
2011
2012#[cfg(test)]
2015mod gap_275_e5m2_tripwire {
2016 use super::*;
2017
2018 #[test]
2050 fn f8e5m2_zeros_declines_until_someone_wires_it() {
2051 let shape = Shape::from_dims(&[4]);
2052
2053 assert!(
2058 cpu_zeros(&shape, DType::F8E4M3).is_ok(),
2059 "CONTROL FAILED: cpu_zeros declined F8E4M3, which IS wired. The F8E5M2 assertion below is therefore vacuous and this test proves nothing -- fix the control before reading the tripwire."
2060 );
2061
2062 assert!(
2063 cpu_zeros(&shape, DType::F8E5M2).is_err(),
2064 "GAP-275 TRIPWIRE FIRED: cpu_zeros now ACCEPTS DType::F8E5M2, so the E5M2 storage path has been wired. E5M2 conversions have NO boundary coverage, and float8 0.7.0 is_infinite returns true for 0x7E (the FINITE 448) on both f8 types. Add boundary tests modelled on cast_f32_f8e4m3_boundary_saturates_and_preserves_nan in byte_kernels.rs, then delete this test. See GAP-275."
2065 );
2066 }
2067}
2068
2069#[cfg(test)]
2070mod backend_runtime_tests {
2071 use super::*;
2072 use fuel_backend_contract::backend::BackendRuntime;
2073 use fuel_ir::backend::FitStatus;
2074
2075 #[test]
2080 fn cpu_backend_device_implements_backend_runtime() {
2081 let dev = CpuBackendDevice;
2082 let avail = dev.available_bytes();
2083 let total = dev.total_bytes();
2084 match (avail, total) {
2087 (Some(a), Some(t)) => {
2088 assert!(t > 0);
2089 assert!(a <= t);
2090 }
2091 (None, None) => {
2092 }
2094 mixed => panic!("inconsistent availability report: {mixed:?}"),
2095 }
2096 }
2097
2098 #[test]
2103 fn cpu_backend_device_would_fit_smoke() {
2104 let dev = CpuBackendDevice;
2105 let status = dev.would_fit(1);
2106 match (dev.available_bytes(), status) {
2107 (Some(_), FitStatus::Comfortable | FitStatus::Tight) => {
2108 }
2110 (None, FitStatus::Unknown) => {
2111 }
2113 other => panic!("unexpected (signal, status) pair: {other:?}"),
2114 }
2115 }
2116}
2117
2118#[cfg(test)]
2119mod binary_op_nan_tests {
2120 use super::*;
2127
2128 fn f32_buf(v: &[f32]) -> HostBuffer {
2129 HostBuffer::F32(v.to_vec())
2130 }
2131
2132 #[test]
2146 fn cpu_unary_abs_moves_a_signalling_nan_without_quieting_it() {
2147 const QUIET: u16 = 0x0040;
2148 let s = bf16::from_bits(0x7F81);
2149 assert_eq!(s.to_bits() & QUIET, 0, "fixture must be SIGNALLING");
2150
2151 assert_ne!(
2154 bf16::from_f32(s.to_f32().abs()).to_bits() & QUIET,
2155 0,
2156 "control failed: the promoting form no longer quiets, so the dyn-path assertion below is vacuous"
2157 );
2158
2159 let buf = HostBuffer::BF16(vec![s]);
2160 let l = Layout::contiguous(Shape::from_dims(&[1]));
2161 let out = cpu_unary_op(&buf, &l, UnaryOp::Abs).expect("abs");
2162 let HostBuffer::BF16(got) = out else {
2163 panic!("expected BF16 output");
2164 };
2165 assert_eq!(
2166 got[0].to_bits(),
2167 s.to_bits() & 0x7FFF,
2168 "dyn-path abs must clear the sign bit and PRESERVE the rest (0x{:04X} -> 0x{:04X}); it bound to the promoting `Float::abs` and quieted the NaN. See KISS-OPS-6.16-0009.",
2169 s.to_bits(),
2170 got[0].to_bits()
2171 );
2172 assert_eq!(
2173 got[0].to_bits() & QUIET,
2174 0,
2175 "dyn-path abs QUIETED a signalling NaN"
2176 );
2177 }
2178
2179 #[test]
2180 fn cpu_binary_op_maximum_minimum_propagate_nan() {
2181 let n = f32::NAN;
2182 let a = f32_buf(&[1.0, n, 3.0, n]);
2185 let b = f32_buf(&[n, 2.0, n, 4.0]);
2186 let l = Layout::contiguous(Shape::from_dims(&[4]));
2187
2188 for op in [BinaryOp::Maximum, BinaryOp::Minimum] {
2189 let out = cpu_binary_op(&a, &b, &l, &l, op).expect("binary op");
2190 let HostBuffer::F32(got) = out else {
2191 panic!("expected F32 output for {op:?}");
2192 };
2193 for (i, &g) in got.iter().enumerate() {
2194 assert!(
2195 g.is_nan(),
2196 "{op:?} at {i}: NaN must propagate (torch convention), got {g}"
2197 );
2198 }
2199 }
2200 }
2201
2202 #[test]
2203 fn cpu_binary_op_maximum_minimum_basic() {
2204 let a = f32_buf(&[1.0, 5.0]);
2207 let b = f32_buf(&[2.0, 3.0]);
2208 let l = Layout::contiguous(Shape::from_dims(&[2]));
2209
2210 let HostBuffer::F32(max) = cpu_binary_op(&a, &b, &l, &l, BinaryOp::Maximum).expect("max")
2211 else {
2212 panic!("expected F32");
2213 };
2214 assert_eq!(max, vec![2.0, 5.0]);
2215
2216 let HostBuffer::F32(min) = cpu_binary_op(&a, &b, &l, &l, BinaryOp::Minimum).expect("min")
2217 else {
2218 panic!("expected F32");
2219 };
2220 assert_eq!(min, vec![1.0, 3.0]);
2221 }
2222
2223 #[test]
2229 fn cpu_binary_op_integer_add_sub_mul_wrap_mod_2n() {
2230 let l = Layout::contiguous(Shape::from_dims(&[1]));
2231
2232 let a = HostBuffer::U8(vec![200]);
2233 let b = HostBuffer::U8(vec![100]);
2234 let HostBuffer::U8(sum) = cpu_binary_op(&a, &b, &l, &l, BinaryOp::Add).expect("add") else {
2235 panic!("expected U8");
2236 };
2237 assert_eq!(
2238 sum,
2239 vec![44],
2240 "200u8 + 100u8 must wrap to 44 (300 mod 256), not saturate"
2241 );
2242
2243 let a = HostBuffer::U8(vec![10]);
2244 let b = HostBuffer::U8(vec![20]);
2245 let HostBuffer::U8(diff) = cpu_binary_op(&a, &b, &l, &l, BinaryOp::Sub).expect("sub")
2246 else {
2247 panic!("expected U8");
2248 };
2249 assert_eq!(
2250 diff,
2251 vec![246],
2252 "10u8 - 20u8 must wrap to 246, not saturate to 0"
2253 );
2254
2255 let a = HostBuffer::I32(vec![i32::MAX]);
2256 let b = HostBuffer::I32(vec![1]);
2257 let HostBuffer::I32(sum) = cpu_binary_op(&a, &b, &l, &l, BinaryOp::Add).expect("add")
2258 else {
2259 panic!("expected I32");
2260 };
2261 assert_eq!(
2262 sum,
2263 vec![i32::MIN],
2264 "i32::MAX + 1 must wrap to i32::MIN, not saturate to i32::MAX"
2265 );
2266
2267 let a = HostBuffer::I32(vec![100_000]);
2268 let b = HostBuffer::I32(vec![100_000]);
2269 let HostBuffer::I32(prod) = cpu_binary_op(&a, &b, &l, &l, BinaryOp::Mul).expect("mul")
2270 else {
2271 panic!("expected I32");
2272 };
2273 assert_eq!(
2274 prod,
2275 vec![100_000i32.wrapping_mul(100_000)],
2276 "100_000 * 100_000 (exceeds i32 range) must wrap, not saturate"
2277 );
2278
2279 let a = HostBuffer::I32(vec![5]);
2282 let b = HostBuffer::I32(vec![9]);
2283 let HostBuffer::I32(max) = cpu_binary_op(&a, &b, &l, &l, BinaryOp::Maximum).expect("max")
2284 else {
2285 panic!("expected I32");
2286 };
2287 assert_eq!(
2288 max,
2289 vec![9],
2290 "positive control: Maximum still correct on integers"
2291 );
2292 }
2293
2294 #[test]
2301 fn cpu_unary_neg_abs_on_int_min_wraps_to_int_min_without_panicking() {
2302 let l = Layout::contiguous(Shape::from_dims(&[1]));
2303
2304 for dtype_buf in [
2305 HostBuffer::I16(vec![i16::MIN]),
2306 HostBuffer::I32(vec![i32::MIN]),
2307 HostBuffer::I64(vec![i64::MIN]),
2308 ] {
2309 let neg = cpu_unary_op(&dtype_buf, &l, UnaryOp::Neg).expect("neg must not panic");
2310 let abs = cpu_unary_op(&dtype_buf, &l, UnaryOp::Abs).expect("abs must not panic");
2311 match (&dtype_buf, &neg, &abs) {
2312 (HostBuffer::I16(_), HostBuffer::I16(n), HostBuffer::I16(a)) => {
2313 assert_eq!(n[0], i16::MIN, "neg(i16::MIN) must wrap to i16::MIN");
2314 assert_eq!(a[0], i16::MIN, "abs(i16::MIN) must wrap to i16::MIN");
2315 }
2316 (HostBuffer::I32(_), HostBuffer::I32(n), HostBuffer::I32(a)) => {
2317 assert_eq!(n[0], i32::MIN, "neg(i32::MIN) must wrap to i32::MIN");
2318 assert_eq!(a[0], i32::MIN, "abs(i32::MIN) must wrap to i32::MIN");
2319 }
2320 (HostBuffer::I64(_), HostBuffer::I64(n), HostBuffer::I64(a)) => {
2321 assert_eq!(n[0], i64::MIN, "neg(i64::MIN) must wrap to i64::MIN");
2322 assert_eq!(a[0], i64::MIN, "abs(i64::MIN) must wrap to i64::MIN");
2323 }
2324 _ => panic!("dtype mismatch in neg/abs output"),
2325 }
2326 }
2327
2328 let buf = HostBuffer::I32(vec![-5]);
2331 let HostBuffer::I32(n) = cpu_unary_op(&buf, &l, UnaryOp::Neg).expect("neg") else {
2332 panic!("expected I32");
2333 };
2334 assert_eq!(n, vec![5], "positive control: neg(-5) is still 5");
2335 }
2336}
2337
2338#[cfg(test)]
2339mod rand_bounds_tests {
2340 use super::*;
2341
2342 #[test]
2346 fn low_precision_uniform_stays_inside_half_open_range() {
2347 let shape = Shape::from((200_000,));
2348 match cpu_rand_uniform(&shape, DType::BF16, 0.0, 1.0).unwrap() {
2349 HostBuffer::BF16(v) => assert!(v.iter().all(|x| (0.0..1.0).contains(&x.to_f32()))),
2350 _ => panic!("wrong buffer"),
2351 }
2352 match cpu_rand_uniform(&shape, DType::F16, 0.0, 1.0).unwrap() {
2353 HostBuffer::F16(v) => assert!(v.iter().all(|x| (0.0..1.0).contains(&x.to_f32()))),
2354 _ => panic!("wrong buffer"),
2355 }
2356 }
2357
2358 #[test]
2361 fn naive_narrowing_escapes_the_range() {
2362 use rand::RngExt;
2363 let mut rng = rand::rng();
2364 let u = rand::distr::Uniform::new(0.0f32, 1.0).unwrap();
2365 let escaped = (0..200_000)
2366 .filter(|_| bf16::from_f32(rng.sample::<f32, _>(u)).to_f32() >= 1.0)
2367 .count();
2368 assert!(escaped > 0, "control found no escape in 200k draws");
2369 }
2370}