use heapless::Vec as HVec;
use super::protocol::ChipId;
pub const MAX_ROUTER_HIDDEN: usize = 8;
pub const MAX_ROUTER_INPUT: usize = 16;
#[derive(Debug, Clone, Copy)]
pub struct MicroGRNNConfig {
pub input_dim: usize,
pub hidden_dim: usize,
pub num_chips: usize,
pub zeta: i8,
pub nu: i8,
}
impl Default for MicroGRNNConfig {
fn default() -> Self {
Self {
input_dim: 8,
hidden_dim: 4,
num_chips: 5,
zeta: 16,
nu: 16,
}
}
}
pub struct MicroFastGRNN {
config: MicroGRNNConfig,
w_gate: HVec<i8, 128>,
u_gate: HVec<i8, 64>,
w_update: HVec<i8, 128>,
u_update: HVec<i8, 64>,
bias_gate: HVec<i8, MAX_ROUTER_HIDDEN>,
bias_update: HVec<i8, MAX_ROUTER_HIDDEN>,
w_output: HVec<i8, 64>,
hidden: HVec<i32, MAX_ROUTER_HIDDEN>,
}
impl MicroFastGRNN {
pub fn new(config: MicroGRNNConfig, seed: u32) -> crate::Result<Self> {
let mut rng_state = seed;
let mut next_rand = || {
rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
(((rng_state >> 16) & 0x3F) as i16 - 32) as i8
};
let gate_size = config.input_dim * config.hidden_dim;
let hidden_size = config.hidden_dim * config.hidden_dim;
let output_size = config.hidden_dim * config.num_chips;
let mut w_gate = HVec::new();
let mut u_gate = HVec::new();
let mut w_update = HVec::new();
let mut u_update = HVec::new();
let mut w_output = HVec::new();
let mut bias_gate = HVec::new();
let mut bias_update = HVec::new();
let mut hidden = HVec::new();
for _ in 0..gate_size {
w_gate.push(next_rand()).map_err(|_| crate::Error::BufferOverflow)?;
w_update.push(next_rand()).map_err(|_| crate::Error::BufferOverflow)?;
}
for _ in 0..hidden_size {
u_gate.push(next_rand()).map_err(|_| crate::Error::BufferOverflow)?;
u_update.push(next_rand()).map_err(|_| crate::Error::BufferOverflow)?;
}
for _ in 0..output_size {
w_output.push(next_rand()).map_err(|_| crate::Error::BufferOverflow)?;
}
for _ in 0..config.hidden_dim {
bias_gate.push(0).map_err(|_| crate::Error::BufferOverflow)?;
bias_update.push(0).map_err(|_| crate::Error::BufferOverflow)?;
hidden.push(0).map_err(|_| crate::Error::BufferOverflow)?;
}
Ok(Self {
config,
w_gate,
u_gate,
w_update,
u_update,
bias_gate,
bias_update,
w_output,
hidden,
})
}
pub fn reset(&mut self) {
for h in self.hidden.iter_mut() {
*h = 0;
}
}
#[inline]
fn sigmoid_fp(x: i32) -> i32 {
if x < -512 { 0 }
else if x > 512 { 256 }
else { (x + 512) >> 2 }
}
#[inline]
fn tanh_fp(x: i32) -> i32 {
if x < -512 { -256 }
else if x > 512 { 256 }
else { x >> 1 }
}
fn matmul(&self, weights: &[i8], input: &[i32], rows: usize, cols: usize) -> HVec<i32, MAX_ROUTER_HIDDEN> {
let mut output = HVec::new();
for r in 0..rows {
let mut sum: i32 = 0;
for c in 0..cols {
if c < input.len() {
sum += weights[r * cols + c] as i32 * input[c];
}
}
let _ = output.push(sum >> 8); }
output
}
pub fn step(&mut self, input: &[i8]) -> crate::Result<()> {
let input_i32: HVec<i32, MAX_ROUTER_INPUT> = input.iter()
.take(self.config.input_dim)
.map(|&x| x as i32 * 16) .collect();
let wx_gate = self.matmul(&self.w_gate, &input_i32, self.config.hidden_dim, self.config.input_dim);
let uh_gate = self.matmul(&self.u_gate, &self.hidden, self.config.hidden_dim, self.config.hidden_dim);
let mut gate = HVec::<i32, MAX_ROUTER_HIDDEN>::new();
for i in 0..self.config.hidden_dim {
let wx = wx_gate.get(i).copied().unwrap_or(0);
let uh = uh_gate.get(i).copied().unwrap_or(0);
let b = self.bias_gate.get(i).copied().unwrap_or(0) as i32 * 16;
let z = Self::sigmoid_fp((wx + uh + b) * self.config.zeta as i32 / 16);
let _ = gate.push(z);
}
let wx_update = self.matmul(&self.w_update, &input_i32, self.config.hidden_dim, self.config.input_dim);
let uh_update = self.matmul(&self.u_update, &self.hidden, self.config.hidden_dim, self.config.hidden_dim);
for i in 0..self.config.hidden_dim {
let wx = wx_update.get(i).copied().unwrap_or(0);
let uh = uh_update.get(i).copied().unwrap_or(0);
let b = self.bias_update.get(i).copied().unwrap_or(0) as i32 * 16;
let u = Self::tanh_fp((wx + uh + b) * self.config.nu as i32 / 16);
let z = gate.get(i).copied().unwrap_or(128);
let h = self.hidden.get(i).copied().unwrap_or(0);
let h_new = ((256 - z) * h + z * u) >> 8;
self.hidden[i] = h_new;
}
Ok(())
}
pub fn route(&self) -> ChipId {
let mut scores = [0i32; 8];
for chip in 0..self.config.num_chips {
let mut sum: i32 = 0;
for h in 0..self.config.hidden_dim {
let w_idx = chip * self.config.hidden_dim + h;
let w = self.w_output.get(w_idx).copied().unwrap_or(0) as i32;
let hidden = self.hidden.get(h).copied().unwrap_or(0);
sum += w * hidden;
}
scores[chip] = sum;
}
let mut best_chip = 0;
let mut best_score = scores[0];
for (i, &score) in scores[..self.config.num_chips].iter().enumerate() {
if score > best_score {
best_score = score;
best_chip = i;
}
}
ChipId(best_chip as u8)
}
pub fn route_probs(&self) -> HVec<u8, 8> {
let mut probs = HVec::new();
let mut scores = [0i32; 8];
let mut max_score = i32::MIN;
for chip in 0..self.config.num_chips {
let mut sum: i32 = 0;
for h in 0..self.config.hidden_dim {
let w_idx = chip * self.config.hidden_dim + h;
let w = self.w_output.get(w_idx).copied().unwrap_or(0) as i32;
let hidden = self.hidden.get(h).copied().unwrap_or(0);
sum += w * hidden;
}
scores[chip] = sum;
if sum > max_score {
max_score = sum;
}
}
let mut total: i32 = 0;
for chip in 0..self.config.num_chips {
let exp_score = (scores[chip] - max_score + 256).max(1);
scores[chip] = exp_score;
total += exp_score;
}
for chip in 0..self.config.num_chips {
let prob = (scores[chip] * 255 / total.max(1)) as u8;
let _ = probs.push(prob);
}
probs
}
pub fn memory_size(&self) -> usize {
self.w_gate.len() + self.u_gate.len() +
self.w_update.len() + self.u_update.len() +
self.w_output.len() +
self.bias_gate.len() + self.bias_update.len() +
self.hidden.len() * 4
}
}
pub struct RoutingFeatures {
pub embed_mean: i8,
pub embed_var: i8,
pub position: i8,
pub chip_loads: [i8; 5],
}
impl RoutingFeatures {
pub fn to_input(&self) -> [i8; 8] {
[
self.embed_mean,
self.embed_var,
self.position,
self.chip_loads[0],
self.chip_loads[1],
self.chip_loads[2],
self.chip_loads[3],
self.chip_loads[4],
]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_micro_fastgrnn() {
let config = MicroGRNNConfig::default();
let mut router = MicroFastGRNN::new(config, 42).unwrap();
let input = [10i8, 20, 30, 40, 50, 60, 70, 80];
router.step(&input).unwrap();
let chip = router.route();
assert!(chip.0 < 5);
println!("Memory: {} bytes", router.memory_size());
}
#[test]
fn test_routing_probs() {
let config = MicroGRNNConfig::default();
let mut router = MicroFastGRNN::new(config, 42).unwrap();
let input = [10i8; 8];
router.step(&input).unwrap();
let probs = router.route_probs();
assert_eq!(probs.len(), 5);
let sum: i32 = probs.iter().map(|&p| p as i32).sum();
assert!(sum > 200 && sum < 280);
}
}