1use crate::quantized::{QuantParams, QuantizationType};
6use heapless::Vec as HVec;
7use serde::{Deserialize, Serialize};
8
9pub const MAX_LAYERS: usize = 2;
11pub const MAX_EMBEDDING_SIZE: usize = 32 * 1024; pub const MAX_LAYER_SIZE: usize = 16 * 1024; #[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct ModelConfig {
19 pub vocab_size: usize,
21 pub embed_dim: usize,
23 pub hidden_dim: usize,
25 pub num_layers: usize,
27 pub num_heads: usize,
29 pub max_seq_len: usize,
31 pub quant_type: QuantizationType,
33}
34
35impl Default for ModelConfig {
36 fn default() -> Self {
37 Self {
39 vocab_size: 256,
40 embed_dim: 32,
41 hidden_dim: 64,
42 num_layers: 1,
43 num_heads: 2,
44 max_seq_len: 16,
45 quant_type: QuantizationType::Int8,
46 }
47 }
48}
49
50impl ModelConfig {
51 pub fn validate(&self, variant: crate::Esp32Variant) -> crate::Result<()> {
53 let model_size = self.estimate_size();
54 let max_ram = variant.max_model_ram();
55
56 if model_size > max_ram {
57 return Err(crate::Error::ModelTooLarge {
58 required: model_size,
59 available: max_ram,
60 });
61 }
62
63 if self.embed_dim % self.num_heads != 0 {
64 return Err(crate::Error::InvalidModel(
65 "embed_dim must be divisible by num_heads"
66 ));
67 }
68
69 if self.num_layers > MAX_LAYERS {
70 return Err(crate::Error::InvalidModel("Too many layers"));
71 }
72
73 Ok(())
74 }
75
76 pub fn estimate_size(&self) -> usize {
78 let bytes_per_weight = match self.quant_type {
79 QuantizationType::Int8 => 1,
80 QuantizationType::Int4 => 1, QuantizationType::Binary => 1, QuantizationType::Fixed16 => 2,
83 };
84
85 let divisor = match self.quant_type {
86 QuantizationType::Int4 => 2,
87 QuantizationType::Binary => 8,
88 _ => 1,
89 };
90
91 let embed_size = (self.vocab_size * self.embed_dim * bytes_per_weight) / divisor;
93
94 let qkv_size = 3 * self.embed_dim * self.embed_dim * bytes_per_weight / divisor;
96 let ffn_size = 3 * self.embed_dim * self.hidden_dim * bytes_per_weight / divisor;
97 let layer_size = qkv_size + ffn_size;
98
99 let output_size = (self.vocab_size * self.embed_dim * bytes_per_weight) / divisor;
101
102 embed_size + (layer_size * self.num_layers) + output_size
103 }
104
105 pub fn for_variant(variant: crate::Esp32Variant) -> Self {
107 match variant {
108 crate::Esp32Variant::Esp32 | crate::Esp32Variant::Esp32S3 => {
109 Self {
111 vocab_size: 256,
112 embed_dim: 64,
113 hidden_dim: 128,
114 num_layers: 2,
115 num_heads: 4,
116 max_seq_len: 32,
117 quant_type: QuantizationType::Int8,
118 }
119 }
120 crate::Esp32Variant::Esp32S2 => {
121 Self {
123 vocab_size: 128,
124 embed_dim: 32,
125 hidden_dim: 64,
126 num_layers: 1,
127 num_heads: 2,
128 max_seq_len: 16,
129 quant_type: QuantizationType::Int8,
130 }
131 }
132 crate::Esp32Variant::Esp32C3 | crate::Esp32Variant::Esp32C6 => {
133 Self {
135 vocab_size: 256,
136 embed_dim: 48,
137 hidden_dim: 96,
138 num_layers: 2,
139 num_heads: 3,
140 max_seq_len: 24,
141 quant_type: QuantizationType::Int8,
142 }
143 }
144 }
145 }
146}
147
148#[derive(Clone)]
150pub struct LayerWeights {
151 pub wq: HVec<i8, MAX_LAYER_SIZE>,
153 pub wk: HVec<i8, MAX_LAYER_SIZE>,
155 pub wv: HVec<i8, MAX_LAYER_SIZE>,
157 pub wo: HVec<i8, MAX_LAYER_SIZE>,
159
160 pub w_up: HVec<i8, MAX_LAYER_SIZE>,
162 pub w_gate: HVec<i8, MAX_LAYER_SIZE>,
164 pub w_down: HVec<i8, MAX_LAYER_SIZE>,
166
167 pub q_params: QuantParams,
169 pub k_params: QuantParams,
170 pub v_params: QuantParams,
171 pub o_params: QuantParams,
172 pub up_params: QuantParams,
173 pub gate_params: QuantParams,
174 pub down_params: QuantParams,
175}
176
177impl Default for LayerWeights {
178 fn default() -> Self {
179 Self {
180 wq: HVec::new(),
181 wk: HVec::new(),
182 wv: HVec::new(),
183 wo: HVec::new(),
184 w_up: HVec::new(),
185 w_gate: HVec::new(),
186 w_down: HVec::new(),
187 q_params: QuantParams::default(),
188 k_params: QuantParams::default(),
189 v_params: QuantParams::default(),
190 o_params: QuantParams::default(),
191 up_params: QuantParams::default(),
192 gate_params: QuantParams::default(),
193 down_params: QuantParams::default(),
194 }
195 }
196}
197
198impl LayerWeights {
199 pub fn random(config: &ModelConfig, seed: u32) -> crate::Result<Self> {
201 let mut layer = Self::default();
202
203 let embed_dim = config.embed_dim;
204 let hidden_dim = config.hidden_dim;
205
206 let mut rng_state = seed;
208 let mut next_rand = || {
209 rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
210 (((rng_state >> 16) & 0x7F) as i16 - 64) as i8
212 };
213
214 let qkv_size = embed_dim * embed_dim;
216 for _ in 0..qkv_size {
217 layer.wq.push(next_rand()).map_err(|_| crate::Error::BufferOverflow)?;
218 layer.wk.push(next_rand()).map_err(|_| crate::Error::BufferOverflow)?;
219 layer.wv.push(next_rand()).map_err(|_| crate::Error::BufferOverflow)?;
220 layer.wo.push(next_rand()).map_err(|_| crate::Error::BufferOverflow)?;
221 }
222
223 let up_size = embed_dim * hidden_dim;
225 for _ in 0..up_size {
226 layer.w_up.push(next_rand()).map_err(|_| crate::Error::BufferOverflow)?;
227 layer.w_gate.push(next_rand()).map_err(|_| crate::Error::BufferOverflow)?;
228 }
229
230 let down_size = hidden_dim * embed_dim;
231 for _ in 0..down_size {
232 layer.w_down.push(next_rand()).map_err(|_| crate::Error::BufferOverflow)?;
233 }
234
235 let scale = 1.0 / 64.0; layer.q_params = QuantParams { scale, zero_point: 0.0, min_val: -1.0, max_val: 1.0 };
238 layer.k_params = layer.q_params;
239 layer.v_params = layer.q_params;
240 layer.o_params = layer.q_params;
241 layer.up_params = layer.q_params;
242 layer.gate_params = layer.q_params;
243 layer.down_params = layer.q_params;
244
245 Ok(layer)
246 }
247
248 pub fn memory_size(&self) -> usize {
250 self.wq.len() + self.wk.len() + self.wv.len() + self.wo.len()
251 + self.w_up.len() + self.w_gate.len() + self.w_down.len()
252 }
253}
254
255pub struct TinyModel {
257 pub config: ModelConfig,
259 pub embedding_table: HVec<i8, MAX_EMBEDDING_SIZE>,
261 pub layers: [LayerWeights; MAX_LAYERS],
263 pub output_proj: HVec<i8, MAX_EMBEDDING_SIZE>,
265 pub input_params: QuantParams,
267 pub output_params: QuantParams,
269}
270
271impl TinyModel {
272 pub fn new(config: ModelConfig) -> crate::Result<Self> {
274 config.validate(crate::Esp32Variant::Esp32)?;
275
276 let mut embedding_table = HVec::new();
277 let mut output_proj = HVec::new();
278
279 let embed_size = config.vocab_size * config.embed_dim;
281 let mut rng_state = 12345u32;
282 let mut next_rand = || {
283 rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
284 (((rng_state >> 16) & 0xFF) as i16 - 128) as i8
286 };
287
288 for _ in 0..embed_size {
289 embedding_table.push(next_rand()).map_err(|_| crate::Error::BufferOverflow)?;
290 }
291
292 for _ in 0..embed_size {
294 output_proj.push(next_rand()).map_err(|_| crate::Error::BufferOverflow)?;
295 }
296
297 let mut layers: [LayerWeights; MAX_LAYERS] = Default::default();
299 for i in 0..config.num_layers {
300 layers[i] = LayerWeights::random(&config, (i * 1000) as u32)?;
301 }
302
303 Ok(Self {
304 config,
305 embedding_table,
306 layers,
307 output_proj,
308 input_params: QuantParams::default(),
309 output_params: QuantParams::default(),
310 })
311 }
312
313 pub fn memory_size(&self) -> usize {
315 let mut size = self.embedding_table.len();
316 size += self.output_proj.len();
317 for i in 0..self.config.num_layers {
318 size += self.layers[i].memory_size();
319 }
320 size
321 }
322
323 pub fn from_bytes(data: &[u8]) -> crate::Result<Self> {
325 if data.len() < 32 {
327 return Err(crate::Error::InvalidModel("Data too small"));
328 }
329
330 if &data[0..4] != b"RUVM" {
332 return Err(crate::Error::InvalidModel("Invalid magic number"));
333 }
334
335 let vocab_size = u16::from_le_bytes([data[4], data[5]]) as usize;
337 let embed_dim = u16::from_le_bytes([data[6], data[7]]) as usize;
338 let hidden_dim = u16::from_le_bytes([data[8], data[9]]) as usize;
339 let num_layers = data[10] as usize;
340 let num_heads = data[11] as usize;
341 let max_seq_len = data[12] as usize;
342 let quant_type = match data[13] {
343 0 => QuantizationType::Int8,
344 1 => QuantizationType::Int4,
345 2 => QuantizationType::Binary,
346 3 => QuantizationType::Fixed16,
347 _ => return Err(crate::Error::InvalidModel("Unknown quantization type")),
348 };
349
350 let config = ModelConfig {
351 vocab_size,
352 embed_dim,
353 hidden_dim,
354 num_layers,
355 num_heads,
356 max_seq_len,
357 quant_type,
358 };
359
360 config.validate(crate::Esp32Variant::Esp32)?;
361
362 Self::new(config)
364 }
365
366 pub fn to_bytes(&self) -> HVec<u8, 256> {
368 let mut header: HVec<u8, 256> = HVec::new();
369
370 let _ = header.extend_from_slice(b"RUVM");
372
373 let _ = header.extend_from_slice(&(self.config.vocab_size as u16).to_le_bytes());
375 let _ = header.extend_from_slice(&(self.config.embed_dim as u16).to_le_bytes());
376 let _ = header.extend_from_slice(&(self.config.hidden_dim as u16).to_le_bytes());
377 let _ = header.push(self.config.num_layers as u8);
378 let _ = header.push(self.config.num_heads as u8);
379 let _ = header.push(self.config.max_seq_len as u8);
380 let _ = header.push(match self.config.quant_type {
381 QuantizationType::Int8 => 0,
382 QuantizationType::Int4 => 1,
383 QuantizationType::Binary => 2,
384 QuantizationType::Fixed16 => 3,
385 });
386
387 while header.len() < 32 {
389 let _ = header.push(0);
390 }
391
392 header
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399
400 #[test]
401 fn test_default_config() {
402 let config = ModelConfig::default();
403 assert!(config.validate(crate::Esp32Variant::Esp32S2).is_ok());
404
405 let size = config.estimate_size();
406 println!("Default model size: {} bytes ({:.1} KB)", size, size as f32 / 1024.0);
407 assert!(size < 50 * 1024); }
409
410 #[test]
411 fn test_variant_configs() {
412 for variant in [
413 crate::Esp32Variant::Esp32,
414 crate::Esp32Variant::Esp32S2,
415 crate::Esp32Variant::Esp32S3,
416 crate::Esp32Variant::Esp32C3,
417 crate::Esp32Variant::Esp32C6,
418 ] {
419 let config = ModelConfig::for_variant(variant);
420 assert!(config.validate(variant).is_ok());
421
422 let size = config.estimate_size();
423 println!("{:?}: {} bytes ({:.1} KB)", variant, size, size as f32 / 1024.0);
424 }
425 }
426
427 #[test]
428 fn test_model_creation() {
429 let config = ModelConfig::default();
430 let model = TinyModel::new(config).unwrap();
431
432 let size = model.memory_size();
433 println!("Actual model size: {} bytes ({:.1} KB)", size, size as f32 / 1024.0);
434 }
435
436 #[test]
437 fn test_serialization() {
438 let config = ModelConfig::default();
439 let model = TinyModel::new(config).unwrap();
440
441 let header = model.to_bytes();
442 assert_eq!(&header[0..4], b"RUVM");
443 }
444}