Skip to main content

rten_vecmath/
convert.rs

1use std::mem::MaybeUninit;
2
3use rten_simd::ops::{BitOps, Extend, NarrowSaturate};
4use rten_simd::{Isa, SimdOp, SliceWriter, f16};
5
6/// Convert a slice of `f16` values to `f32`.
7pub struct F16ToF32<'s, 'd> {
8    src: &'s [f16],
9    dest: &'d mut [MaybeUninit<f32>],
10}
11
12impl<'s, 'd> F16ToF32<'s, 'd> {
13    /// Create a conversion operation which reads from `src` and writes the
14    /// converted values to `dest`.
15    ///
16    /// Panics if `src` and `dest` have different lengths.
17    pub fn new(src: &'s [f16], dest: &'d mut [MaybeUninit<f32>]) -> Self {
18        assert_eq!(src.len(), dest.len());
19        F16ToF32 { src, dest }
20    }
21}
22
23impl<'d> SimdOp for F16ToF32<'_, 'd> {
24    type Output = &'d mut [f32];
25
26    #[inline(always)]
27    fn eval<I: Isa>(self, isa: I) -> Self::Output {
28        let f16_ops = isa.f16();
29        let f32_ops = isa.f32();
30        let f16_v_len = f16_ops.len();
31
32        let mut dest_writer = SliceWriter::new(self.dest);
33
34        // Main loop, unrolled by two.
35        let mut chunks = self.src.chunks_exact(f16_v_len * 2);
36        for chunk in chunks.by_ref() {
37            let xs = f16_ops.load_many::<2>(chunk);
38            let lo0 = f16_ops.extend_low(xs[0]);
39            let hi0 = f16_ops.extend_high(xs[0]);
40            let lo1 = f16_ops.extend_low(xs[1]);
41            let hi1 = f16_ops.extend_high(xs[1]);
42            // Store all four f32 vectors with a single bounds check.
43            dest_writer.write_vecs(f32_ops, [lo0, hi0, lo1, hi1]);
44        }
45
46        // Convert a remaining whole `f16` vector, if any.
47        let mut chunks = chunks.remainder().chunks_exact(f16_v_len);
48        for chunk in chunks.by_ref() {
49            let x = f16_ops.load(chunk);
50            let low = f16_ops.extend_low(x);
51            let high = f16_ops.extend_high(x);
52            dest_writer.write_vec(f32_ops, low);
53            dest_writer.write_vec(f32_ops, high);
54        }
55
56        // Convert tail elements which don't fill a whole vector.
57        for &x in chunks.remainder() {
58            dest_writer.write_scalar(x.to_f32());
59        }
60
61        dest_writer.into_mut_slice()
62    }
63}
64
65/// Convert a slice of `f32` values to `f16`.
66///
67/// Values are rounded to the nearest `f16`, with ties to even. Values whose
68/// magnitude exceeds the `f16` range are rounded to infinity.
69pub struct F32ToF16<'s, 'd> {
70    src: &'s [f32],
71    dest: &'d mut [MaybeUninit<f16>],
72}
73
74impl<'s, 'd> F32ToF16<'s, 'd> {
75    /// Create a conversion operation which reads from `src` and writes the
76    /// converted values to `dest`.
77    ///
78    /// Panics if `src` and `dest` have different lengths.
79    pub fn new(src: &'s [f32], dest: &'d mut [MaybeUninit<f16>]) -> Self {
80        assert_eq!(src.len(), dest.len());
81        F32ToF16 { src, dest }
82    }
83}
84
85impl<'d> SimdOp for F32ToF16<'_, 'd> {
86    type Output = &'d mut [f16];
87
88    #[inline(always)]
89    fn eval<I: Isa>(self, isa: I) -> Self::Output {
90        let f32_ops = isa.f32();
91        let f16_ops = isa.f16();
92        let f32_v_len = f32_ops.len();
93
94        let mut src_chunks = self.src.chunks_exact(f32_v_len * 2);
95        let mut dest_writer = SliceWriter::new(self.dest);
96
97        for src_chunk in src_chunks.by_ref() {
98            let xs = f32_ops.load_many::<2>(src_chunk);
99            let half = f32_ops.narrow_saturate(xs[0], xs[1]);
100            dest_writer.write_vec(f16_ops, half);
101        }
102
103        for &x in src_chunks.remainder() {
104            dest_writer.write_scalar(f16::from_f32(x));
105        }
106
107        dest_writer.into_mut_slice()
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use rten_simd::ops::BitOps;
114    use rten_simd::{Isa, SimdOp, f16};
115
116    use super::{F16ToF32, F32ToF16};
117
118    /// Return the number of `f16` lanes in a SIMD vector.
119    fn f16_vec_len() -> usize {
120        struct F16VecLen {}
121        impl SimdOp for F16VecLen {
122            type Output = usize;
123            fn eval<I: Isa>(self, isa: I) -> usize {
124                isa.f16().len()
125            }
126        }
127        F16VecLen {}.dispatch()
128    }
129
130    #[test]
131    fn test_f16_to_f32() {
132        // Length chosen to exercise all three code paths: the main loop
133        // (unrolled by two, so it needs at least two whole vectors), the
134        // single-vector cleanup loop, and the scalar tail.
135        let len = f16_vec_len() * 3 + 1;
136        let src: Vec<f16> = (0..len)
137            .map(|i| f16::from_f32(i as f32 * 0.5 - 3.0))
138            .collect();
139        let expected: Vec<f32> = src.iter().map(|x| x.to_f32()).collect();
140
141        let mut buf = Vec::with_capacity(src.len());
142        let actual = F16ToF32::new(&src, buf.spare_capacity_mut()).dispatch();
143
144        assert_eq!(actual, expected);
145    }
146
147    #[test]
148    fn test_f16_to_f32_empty() {
149        let src: Vec<f16> = Vec::new();
150        let mut buf: Vec<f32> = Vec::new();
151        let actual = F16ToF32::new(&src, buf.spare_capacity_mut()).dispatch();
152        assert!(actual.is_empty());
153    }
154
155    #[test]
156    fn test_f32_to_f16() {
157        // Length larger than the max `f16` vector width, and not an exact
158        // multiple, so we exercise both the vectorized body and the scalar
159        // tail.
160        let len = f16_vec_len() + 1;
161        let src: Vec<f32> = (0..len).map(|i| i as f32 * 0.5 - 3.0).collect();
162        let expected: Vec<f16> = src.iter().map(|&x| f16::from_f32(x)).collect();
163
164        let mut buf = Vec::with_capacity(src.len());
165        let actual = F32ToF16::new(&src, buf.spare_capacity_mut()).dispatch();
166
167        assert_eq!(actual, expected);
168    }
169
170    #[test]
171    fn test_f32_to_f16_empty() {
172        let src: Vec<f32> = Vec::new();
173        let mut buf: Vec<f16> = Vec::new();
174        let actual = F32ToF16::new(&src, buf.spare_capacity_mut()).dispatch();
175        assert!(actual.is_empty());
176    }
177
178    // Round-trip f32 -> f16 -> f32 for values exactly representable in f16.
179    #[test]
180    fn test_roundtrip() {
181        let len = f16_vec_len() * 2 + 3;
182        let src: Vec<f32> = (0..len).map(|i| i as f32 * 0.25 - 5.0).collect();
183
184        let mut half_buf = Vec::with_capacity(len);
185        let half = F32ToF16::new(&src, half_buf.spare_capacity_mut()).dispatch();
186        let half: Vec<f16> = half.to_vec();
187
188        let mut back_buf = Vec::with_capacity(len);
189        let back = F16ToF32::new(&half, back_buf.spare_capacity_mut()).dispatch();
190
191        let expected: Vec<f32> = src.iter().map(|&x| f16::from_f32(x).to_f32()).collect();
192        assert_eq!(back, expected);
193    }
194}