1extern crate alloc;
9
10use crate::v3::cache::KvPrecision;
11use alloc::vec::Vec;
12
13#[derive(Debug, Clone, PartialEq)]
15pub struct V3Engram {
16 pub orders: Vec<usize>,
18 pub heads: usize,
20 pub slots: usize,
22 pub sub_dim: usize,
24 pub sites: Vec<usize>,
26 pub conv_taps: usize,
27 pub conv_dilation: usize,
28 pub seed_heads: usize,
29}
30
31impl V3Engram {
32 pub fn num_tables(&self) -> usize {
34 self.orders.len() * self.heads
35 }
36
37 pub fn site_of(&self, layer: usize) -> Option<usize> {
39 self.sites.iter().position(|&l| l == layer)
40 }
41}
42
43#[derive(Debug, Clone, PartialEq)]
45pub struct V3Config {
46 pub vocab_size: usize,
47 pub out_vocab: usize,
50 pub d_model: usize,
51 pub num_heads: usize,
52 pub num_kv_heads: usize,
53 pub num_layers: usize,
54 pub qk_head_dim: usize,
56 pub v_head_dim: usize,
58 pub max_seq_len: usize,
59 pub hada_n: usize,
61 pub mhc_lanes: usize,
62 pub rope_theta: f32,
63 pub sliding_window: usize,
66 pub global_layers: Vec<usize>,
68 pub qkv_conv_taps: usize,
70 pub engram: V3Engram,
71}
72
73impl V3Config {
74 pub fn q_dim(&self) -> usize {
76 self.num_heads * self.qk_head_dim
77 }
78
79 pub fn k_dim(&self) -> usize {
81 self.num_kv_heads * self.qk_head_dim
82 }
83
84 pub fn v_dim(&self) -> usize {
86 self.num_kv_heads * self.v_head_dim
87 }
88
89 pub fn attn_out_dim(&self) -> usize {
91 self.num_heads * self.v_head_dim
92 }
93
94 pub fn kv_repeat(&self) -> usize {
96 self.num_heads / self.num_kv_heads
97 }
98
99 pub fn mhc_width(&self) -> usize {
101 self.mhc_lanes * self.d_model
102 }
103
104 pub fn logit_rows(&self) -> usize {
106 if self.out_vocab == 0 {
107 self.vocab_size
108 } else {
109 self.out_vocab
110 }
111 }
112
113 pub fn is_global(&self, layer: usize) -> bool {
115 self.global_layers.contains(&layer)
116 }
117
118 pub fn attention_span(&self, layer: usize) -> Option<usize> {
124 if self.is_global(layer) || self.sliding_window == 0 {
125 None
126 } else {
127 Some(self.sliding_window)
128 }
129 }
130
131 pub fn active_lane(&self, layer: usize) -> usize {
134 layer % self.mhc_lanes
135 }
136
137 pub fn kv_bytes(&self, seq_len: usize, bytes_per: usize) -> usize {
144 let per_pos = self.num_kv_heads * (self.qk_head_dim + self.v_head_dim);
145 let mut total = 0;
146 for layer in 0..self.num_layers {
147 let slots = match self.attention_span(layer) {
148 Some(w) => seq_len.min(w),
149 None => seq_len,
150 };
151 total += slots * per_pos * bytes_per;
152 }
153 total
154 }
155
156 pub fn kv_bytes_at(&self, seq_len: usize, precision: KvPrecision) -> usize {
163 match precision {
164 KvPrecision::F32 => self.kv_bytes(seq_len, 4),
165 KvPrecision::Int8 => {
166 let per_pos = self.num_kv_heads * (self.qk_head_dim + self.v_head_dim)
167 + self.num_kv_heads * 8;
168 let mut total = 0;
169 for layer in 0..self.num_layers {
170 let slots = match self.attention_span(layer) {
171 Some(w) => seq_len.min(w),
172 None => seq_len,
173 };
174 total += slots * per_pos;
175 }
176 total
177 }
178 }
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use alloc::vec;
186
187 fn shipped() -> V3Config {
189 V3Config {
190 vocab_size: 8192,
191 out_vocab: 8192,
192 d_model: 768,
193 num_heads: 12,
194 num_kv_heads: 2,
195 num_layers: 20,
196 qk_head_dim: 48,
197 v_head_dim: 64,
198 max_seq_len: 8192,
199 hada_n: 1024,
200 mhc_lanes: 4,
201 rope_theta: 100_000.0,
202 sliding_window: 1024,
203 global_layers: vec![4, 9, 14, 19],
204 qkv_conv_taps: 3,
205 engram: V3Engram {
206 orders: vec![2, 3],
207 heads: 3,
208 slots: 18432,
209 sub_dim: 128,
210 sites: vec![3, 7, 11, 15, 19],
211 conv_taps: 4,
212 conv_dilation: 3,
213 seed_heads: 0,
214 },
215 }
216 }
217
218 #[test]
219 fn projection_widths_match_the_container() {
220 let c = shipped();
221 assert_eq!(c.q_dim(), 576);
224 assert_eq!(c.k_dim(), 96);
225 assert_eq!(c.v_dim(), 128);
226 assert_eq!(c.attn_out_dim(), 768);
227 assert_eq!(c.kv_repeat(), 6);
228 }
229
230 #[test]
231 fn engram_tables_are_orders_times_heads() {
232 let c = shipped();
236 assert_eq!(c.engram.num_tables(), 6);
237 assert_eq!(c.d_model / (c.engram.orders.len() * c.engram.sub_dim), 3);
238 assert_eq!(c.engram.site_of(11), Some(2));
239 assert_eq!(c.engram.site_of(12), None);
240 }
241
242 #[test]
243 fn global_layers_have_no_attention_span() {
244 let c = shipped();
245 for l in [4, 9, 14, 19] {
246 assert!(c.is_global(l), "layer {l} should be global");
247 assert_eq!(c.attention_span(l), None, "layer {l} span");
248 }
249 for l in [0, 3, 10, 18] {
250 assert!(!c.is_global(l));
251 assert_eq!(c.attention_span(l), Some(1024), "layer {l} span");
252 }
253 }
254
255 #[test]
256 fn kv_cost_is_paid_for_what_a_session_uses() {
257 let c = shipped();
258 let short = c.kv_bytes(512, 1);
262 let full = c.kv_bytes(8192, 1);
263 assert_eq!(short, 512 * 20 * 224);
266 assert_eq!(full, (16 * 1024 + 4 * 8192) * 224);
267 assert!(
268 short * 4 < full,
269 "a 512-token session should cost far less than a full-context one: \
270 {short} vs {full}"
271 );
272 let at_window = c.kv_bytes(1024, 1);
274 let beyond = c.kv_bytes(2048, 1);
275 let per_pos = c.num_kv_heads * (c.qk_head_dim + c.v_head_dim);
276 assert_eq!(
277 beyond - at_window,
278 c.global_layers.len() * 1024 * per_pos,
279 "past the window only the global layers should keep growing"
280 );
281 }
282}