Skip to main content

embedded_nn/
float_ops.rs

1//! Floating-point (`f32` and `f16`) fallback operations and layers.
2
3use crate::types::{Dims, Result, Tile};
4
5/// IEEE-754 16-bit half-precision floating-point conversion to 32-bit single precision float.
6pub fn f16_to_f32(h: u16) -> f32 {
7    let sign = (h >> 15) & 0x0001;
8    let exp = (h >> 10) & 0x001F;
9    let mant = h & 0x03FF;
10
11    if exp == 0 {
12        if mant == 0 {
13            f32::from_bits((sign as u32) << 31)
14        } else {
15            // Subnormal f16
16            let mut m = mant as u32;
17            let mut e = 0i32;
18            while (m & 0x0400) == 0 {
19                m <<= 1;
20                e -= 1;
21            }
22            m &= 0x03FF;
23            let exp_f32 = (127 - 15 + 1 + e) as u32;
24            f32::from_bits(((sign as u32) << 31) | (exp_f32 << 23) | (m << 13))
25        }
26    } else if exp == 0x1F {
27        // Inf / NaN
28        f32::from_bits(((sign as u32) << 31) | (0xFF << 23) | ((mant as u32) << 13))
29    } else {
30        // Normal f16
31        let exp_f32 = (exp as u32 + 127 - 15) & 0xFF;
32        f32::from_bits(((sign as u32) << 31) | (exp_f32 << 23) | ((mant as u32) << 13))
33    }
34}
35
36/// 32-bit single precision float conversion to IEEE-754 16-bit half-precision float.
37pub fn f32_to_f16(f: f32) -> u16 {
38    let bits = f.to_bits();
39    let sign = (bits >> 31) & 0x0001;
40    let exp = (bits >> 23) & 0x00FF;
41    let mant = bits & 0x007F_FFFF;
42
43    if exp == 0xFF {
44        // Inf / NaN
45        ((sign as u16) << 15) | (0x001F << 10) | ((mant >> 13) as u16)
46    } else if exp == 0 {
47        (sign as u16) << 15
48    } else {
49        let new_exp = exp as i32 - 127 + 15;
50        if new_exp >= 31 {
51            // Overflow to Inf
52            ((sign as u16) << 15) | (0x001F << 10)
53        } else if new_exp <= 0 {
54            // Underflow
55            (sign as u16) << 15
56        } else {
57            ((sign as u16) << 15) | ((new_exp as u16) << 10) | ((mant >> 13) as u16)
58        }
59    }
60}
61
62/// Performs 2D Convolution for 32-bit floating point tensors.
63pub fn convolve_f32(
64    stride: Tile,
65    padding: Tile,
66    dilation: Tile,
67    input_dims: &Dims,
68    input: &[f32],
69    filter_dims: &Dims,
70    kernel: &[f32],
71    bias: Option<&[f32]>,
72    output_dims: &Dims,
73    output: &mut [f32],
74) -> Result<()> {
75    let input_batches = input_dims.n as usize;
76    let input_h = input_dims.h as usize;
77    let input_w = input_dims.w as usize;
78    let input_c = input_dims.c as usize;
79
80    let kernel_h = filter_dims.h as usize;
81    let kernel_w = filter_dims.w as usize;
82    let kernel_c = filter_dims.c as usize;
83
84    let output_h = output_dims.h as usize;
85    let output_w = output_dims.w as usize;
86    let output_c = output_dims.c as usize;
87
88    let groups = input_c / kernel_c;
89    let output_c_per_group = output_c / groups;
90
91    for b in 0..input_batches {
92        for out_y in 0..output_h {
93            let base_y = out_y as i32 * stride.h - padding.h;
94            for out_x in 0..output_w {
95                let base_x = out_x as i32 * stride.w - padding.w;
96
97                for g in 0..groups {
98                    for out_ch_idx in 0..output_c_per_group {
99                        let out_c = g * output_c_per_group + out_ch_idx;
100                        let mut acc: f32 = match bias {
101                            Some(b_slice) => b_slice[out_c],
102                            None => 0.0,
103                        };
104
105                        for ky in 0..kernel_h {
106                            let in_y = base_y + ky as i32 * dilation.h;
107                            if in_y >= 0 && in_y < input_dims.h {
108                                for kx in 0..kernel_w {
109                                    let in_x = base_x + kx as i32 * dilation.w;
110                                    if in_x >= 0 && in_x < input_dims.w {
111                                        let in_idx_base = ((b * input_h + in_y as usize) * input_w
112                                            + in_x as usize)
113                                            * input_c
114                                            + g * kernel_c;
115                                        let ker_idx_base =
116                                            ((out_c * kernel_h + ky) * kernel_w + kx) * kernel_c;
117
118                                        for ic in 0..kernel_c {
119                                            acc +=
120                                                input[in_idx_base + ic] * kernel[ker_idx_base + ic];
121                                        }
122                                    }
123                                }
124                            }
125                        }
126
127                        let out_idx =
128                            ((b * output_h + out_y) * output_w + out_x) * output_c + out_c;
129                        output[out_idx] = acc;
130                    }
131                }
132            }
133        }
134    }
135
136    Ok(())
137}
138
139/// Performs Fully Connected layer for 32-bit floating point tensors.
140pub fn fully_connected_f32(
141    input_dims: &Dims,
142    input: &[f32],
143    filter_dims: &Dims,
144    kernel: &[f32],
145    bias: Option<&[f32]>,
146    output_dims: &Dims,
147    output: &mut [f32],
148) -> Result<()> {
149    let batches = input_dims.n as usize;
150    let accum_depth = filter_dims.n as usize;
151    let output_depth = output_dims.c as usize;
152
153    for b in 0..batches {
154        let input_batch = &input[b * accum_depth..(b + 1) * accum_depth];
155        let output_batch = &mut output[b * output_depth..(b + 1) * output_depth];
156
157        for out_c in 0..output_depth {
158            let mut acc: f32 = match bias {
159                Some(b_slice) => b_slice[out_c],
160                None => 0.0,
161            };
162
163            let kernel_row = &kernel[out_c * accum_depth..(out_c + 1) * accum_depth];
164
165            for i in 0..accum_depth {
166                acc += input_batch[i] * kernel_row[i];
167            }
168
169            output_batch[out_c] = acc;
170        }
171    }
172    Ok(())
173}
174
175/// In-place ReLU for 32-bit float buffer (`max(val, 0.0)`).
176pub fn relu_f32(data: &mut [f32]) {
177    for val in data.iter_mut() {
178        if *val < 0.0 {
179            *val = 0.0;
180        }
181    }
182}
183
184/// In-place ReLU6 for 32-bit float buffer (`min(max(val, 0.0), 6.0)`).
185pub fn relu6_f32(data: &mut [f32]) {
186    for val in data.iter_mut() {
187        if *val < 0.0 {
188            *val = 0.0;
189        } else if *val > 6.0 {
190            *val = 6.0;
191        }
192    }
193}
194
195/// Fast and accurate pure no-std exp(x) implementation for f32 using range reduction.
196fn exp_f32(x: f32) -> f32 {
197    if x < -88.0 {
198        return 0.0;
199    }
200    if x > 88.0 {
201        return f32::INFINITY;
202    }
203    // exp(x) = exp(x / 64)^64
204    let y = x / 64.0;
205    let mut poly = 1.0
206        + y * (1.0
207            + y * (0.5
208                + y * (1.0 / 6.0
209                    + y * (1.0 / 24.0
210                        + y * (1.0 / 120.0 + y * (1.0 / 720.0 + y * (1.0 / 5040.0)))))));
211    for _ in 0..6 {
212        poly *= poly;
213    }
214    poly
215}
216
217/// Softmax for 32-bit floating point tensors.
218pub fn softmax_f32(
219    input: &[f32],
220    num_rows: usize,
221    row_size: usize,
222    output: &mut [f32],
223) -> Result<()> {
224    for row in 0..num_rows {
225        let in_row = &input[row * row_size..(row + 1) * row_size];
226        let out_row = &mut output[row * row_size..(row + 1) * row_size];
227
228        let mut max_val = in_row[0];
229        for &val in &in_row[1..] {
230            if val > max_val {
231                max_val = val;
232            }
233        }
234
235        let mut sum = 0.0f32;
236        for (i, &val) in in_row.iter().enumerate() {
237            let diff = val - max_val;
238            let exp_val = exp_f32(diff);
239            out_row[i] = exp_val;
240            sum += exp_val;
241        }
242
243        if sum > 0.0 {
244            let inv_sum = 1.0 / sum;
245            for val in out_row.iter_mut() {
246                *val *= inv_sum;
247            }
248        }
249    }
250    Ok(())
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    #[test]
258    fn test_f16_conversion() {
259        let val = 3.14159f32;
260        let h = f32_to_f16(val);
261        let back = f16_to_f32(h);
262        assert!((back - val).abs() < 0.01);
263    }
264
265    #[test]
266    fn test_fully_connected_f32() {
267        let input_dims = Dims::new(1, 1, 1, 2);
268        let input = [1.5f32, 2.5f32];
269        let filter_dims = Dims::new(2, 1, 1, 1);
270        let kernel = [2.0f32, 4.0f32];
271        let output_dims = Dims::new(1, 1, 1, 1);
272        let mut output = [0.0f32; 1];
273
274        fully_connected_f32(
275            &input_dims,
276            &input,
277            &filter_dims,
278            &kernel,
279            None,
280            &output_dims,
281            &mut output,
282        )
283        .unwrap();
284
285        // 1.5 * 2.0 + 2.5 * 4.0 = 3.0 + 10.0 = 13.0
286        assert_eq!(output[0], 13.0);
287    }
288}