1use std::collections::HashMap;
12
13use crate::Error;
14use crate::circuit::{Circuit, OpKind, OutOfRange, resolve_index_position};
15use crate::gather::{GatherPlan, plan_gather_lowering};
16use crate::numeric::{Range, evaluate_boolean, relu32};
17use crate::wire::Wire;
18
19#[derive(Clone, Debug)]
20pub struct IndexReadRow {
21 pub row: u32,
22 pub index_row: u32,
23 pub below_zero: bool,
24 pub above_zero: bool,
25 pub candidate_positions: Vec<u32>,
27}
28
29#[derive(Clone, Debug)]
33pub struct GatherCompute {
34 pub slot: u32,
35 pub bit_positions: Vec<u32>,
37}
38
39#[derive(Clone, Debug)]
42pub struct GatherRead {
43 pub row: u32,
44 pub slot: u32,
45 pub masked: bool,
46 pub candidate_positions: Vec<u32>,
47}
48
49#[derive(Clone, Debug, Copy)]
50pub struct CompileOptions {
51 pub lower_gathers: bool,
53 pub recompute_depths: bool,
56}
57
58impl Default for CompileOptions {
59 fn default() -> CompileOptions {
60 CompileOptions { lower_gathers: true, recompute_depths: false }
61 }
62}
63
64#[derive(Clone, Debug)]
65pub struct TensorLayer {
66 pub input_indices: Vec<u32>,
68 pub output_size: usize,
69 pub bias: Vec<f32>,
71 pub row_offsets: Vec<u32>,
73 pub columns: Vec<u32>,
74 pub weights: Vec<f32>,
75 pub gate_rows: Vec<u32>,
77 pub boolean_rows: Vec<u32>,
78 pub gather_computes: Vec<GatherCompute>,
79 pub gather_reads: Vec<GatherRead>,
80 pub index_reads: Vec<IndexReadRow>,
81}
82
83#[derive(Clone, Debug)]
84pub struct TensorProgram {
85 pub layers: Vec<TensorLayer>,
86 pub roots: Vec<Wire>,
87 pub source_size: usize,
88 pub value_count: usize,
90 pub wire_order: Vec<Wire>,
92 pub width: usize,
94 pub gather_slot_count: usize,
96 pub ranges: Vec<Range>,
98 pub grids: Vec<Option<f64>>,
99}
100
101impl TensorProgram {
102 pub fn stored_weight_count(&self) -> usize {
103 self.layers.iter().map(|layer| layer.weights.len()).sum()
104 }
105
106 pub fn live_operation_count(&self) -> usize {
107 self.value_count - self.source_size
108 }
109
110 pub fn gather_read_count(&self) -> usize {
111 self.layers.iter().map(|layer| layer.gather_reads.len()).sum()
112 }
113}
114
115impl Circuit {
116 pub fn compile(&self, roots: Option<&[Wire]>) -> TensorProgram {
119 self.compile_with(roots, CompileOptions::default())
120 }
121
122 pub fn compile_with(&self, roots: Option<&[Wire]>, options: CompileOptions) -> TensorProgram {
123 let width = self.width();
124 let source_size = self.source_size();
125 let roots: Vec<Wire> = match roots {
126 Some(roots) => roots.to_vec(),
127 None => (source_size..width).map(Wire::from_index).collect(),
128 };
129 let plans = if options.lower_gathers { plan_gather_lowering(self) } else { HashMap::new() };
130 let live = self.find_live_operations(&roots, &plans);
131 let compiled_depths = options.recompute_depths.then(|| self.compute_compiled_depths(&live, &plans));
132 let mut operations_by_layer: Vec<Vec<usize>> = Vec::new();
133 for (operation_index, operation) in self.operations.iter().enumerate() {
134 if !live[operation_index] {
135 continue;
136 }
137 let depth = compiled_depths.as_ref().map_or(operation.depth, |depths| depths[operation_index]);
138 let layer = depth as usize - 1;
139 if operations_by_layer.len() <= layer {
140 operations_by_layer.resize(layer + 1, Vec::new());
141 }
142 operations_by_layer[layer].push(operation_index);
143 }
144
145 let live_count = live.iter().filter(|&&is_live| is_live).count();
146 let value_count = source_size + live_count;
147 let mut wire_order: Vec<Wire> = (0..source_size).map(Wire::from_index).collect();
148 let mut packed_by_wire = vec![u32::MAX; width];
149 for (wire, packed) in packed_by_wire.iter_mut().enumerate().take(source_size) {
150 *packed = wire as u32;
151 }
152 let mut column_by_wire = vec![u32::MAX; width];
154 let mut available = source_size;
155 let mut layers = Vec::new();
156 let mut slot_by_group: HashMap<usize, u32> = HashMap::new();
157 for operation_indices in operations_by_layer.iter().filter(|indices| !indices.is_empty()) {
158 let output_size = operation_indices.len();
159 let mut input_wires: Vec<Wire> = Vec::new();
160 for &operation_index in operation_indices {
161 if plans.contains_key(&operation_index) {
163 continue;
164 }
165 let operation = &self.operations[operation_index];
166 for &(wire, _) in operation.left_terms.iter().chain(&operation.gate_terms) {
167 if column_by_wire[wire.index()] == u32::MAX {
168 column_by_wire[wire.index()] = 0;
169 input_wires.push(wire);
170 }
171 }
172 }
173 input_wires.sort_by_key(|wire| packed_by_wire[wire.index()]);
174 for (column, wire) in input_wires.iter().enumerate() {
175 column_by_wire[wire.index()] = column as u32;
176 }
177 let gate_count =
178 operation_indices.iter().filter(|&&index| self.operations[index].kind == OpKind::Reglu).count();
179 let index_count =
180 operation_indices.iter().filter(|&&index| self.operations[index].kind == OpKind::Index).count();
181 let projection_size = output_size + gate_count + index_count;
182 let mut bias = vec![0f32; projection_size];
183 let mut projection_terms: Vec<&[(Wire, f64)]> = vec![&[]; projection_size];
184 let mut gate_rows = Vec::new();
185 let mut boolean_rows = Vec::new();
186 let mut pending_gather_reads = Vec::new();
187 let mut pending_index_reads = Vec::new();
188 let (mut gate_row, mut index_row) = (0, 0);
189 for (row, &operation_index) in operation_indices.iter().enumerate() {
190 let operation = &self.operations[operation_index];
191 let output_wire = source_size + operation_index;
192 wire_order.push(Wire::from_index(output_wire));
193 packed_by_wire[output_wire] = (available + row) as u32;
194 bias[row] = operation.left_bias as f32;
195 if let Some(plan) = plans.get(&operation_index) {
196 pending_gather_reads.push((row, plan));
198 continue;
199 }
200 projection_terms[row] = &operation.left_terms;
201 match operation.kind {
202 OpKind::Reglu => {
203 let projection_row = output_size + gate_row;
204 bias[projection_row] = operation.gate_bias as f32;
205 projection_terms[projection_row] = &operation.gate_terms;
206 gate_rows.push(row as u32);
207 gate_row += 1;
208 }
209 OpKind::Boolean => boolean_rows.push(row as u32),
210 OpKind::Index => {
211 let projection_row = output_size + gate_count + index_row;
212 bias[projection_row] = operation.gate_bias as f32;
213 projection_terms[projection_row] = &operation.gate_terms;
214 pending_index_reads.push((row, projection_row, operation.index_read.as_ref().unwrap()));
215 index_row += 1;
216 }
217 OpKind::Affine => {}
218 }
219 }
220 let mut gather_computes = Vec::new();
221 let mut gather_reads = Vec::new();
222 for (row, plan) in pending_gather_reads {
223 let group = &self.one_hot_groups[plan.group];
224 let packed = |wire: Wire| {
225 let position = packed_by_wire[wire.index()];
226 assert!((position as usize) < available, "a gather's bit or candidate is not available");
227 position
228 };
229 let next_slot = slot_by_group.len() as u32;
230 let slot = *slot_by_group.entry(plan.group).or_insert_with(|| {
231 gather_computes.push(GatherCompute {
233 slot: next_slot,
234 bit_positions: group.members.iter().map(|&m| packed(m)).collect(),
235 });
236 next_slot
237 });
238 gather_reads.push(GatherRead {
239 row: row as u32,
240 slot,
241 masked: group.allow_all_zero,
242 candidate_positions: plan.candidates.iter().map(|&candidate| packed(candidate)).collect(),
243 });
244 }
245 let index_reads = pending_index_reads
246 .into_iter()
247 .map(|(row, index_row, read)| IndexReadRow {
248 row: row as u32,
249 index_row: index_row as u32,
250 below_zero: read.below == OutOfRange::Zero,
251 above_zero: read.above == OutOfRange::Zero,
252 candidate_positions: self.tables[read.table]
253 .candidates
254 .iter()
255 .map(|candidate| {
256 let position = packed_by_wire[candidate.index()];
257 assert!((position as usize) < available, "index read candidate is not available");
258 position
259 })
260 .collect(),
261 })
262 .collect();
263 let mut row_offsets = Vec::with_capacity(projection_size + 1);
264 let mut columns = Vec::new();
265 let mut weights = Vec::new();
266 row_offsets.push(0);
267 for terms in &projection_terms {
268 for &(wire, coefficient) in terms.iter() {
269 let column = column_by_wire[wire.index()];
270 assert!(column != u32::MAX, "tensor dependency is not available");
271 columns.push(column);
272 weights.push(coefficient as f32);
273 }
274 row_offsets.push(columns.len() as u32);
275 }
276 for wire in &input_wires {
277 column_by_wire[wire.index()] = u32::MAX;
278 }
279 layers.push(TensorLayer {
280 input_indices: input_wires.iter().map(|wire| packed_by_wire[wire.index()]).collect(),
281 output_size,
282 bias,
283 row_offsets,
284 columns,
285 weights,
286 gate_rows,
287 boolean_rows,
288 gather_computes,
289 gather_reads,
290 index_reads,
291 });
292 available += output_size;
293 }
294 let ranges = wire_order.iter().map(|&wire| self.range(wire)).collect();
295 let grids = wire_order.iter().map(|&wire| self.grid(wire)).collect();
296 TensorProgram {
297 ranges,
298 grids,
299 layers,
300 roots,
301 source_size,
302 value_count,
303 wire_order,
304 width,
305 gather_slot_count: slot_by_group.len(),
306 }
307 }
308
309 fn compute_compiled_depths(&self, live: &[bool], plans: &HashMap<usize, GatherPlan>) -> Vec<u32> {
312 let source_size = self.source_size();
313 let mut depths = vec![0u32; self.operations.len()];
314 let mut table_depths: HashMap<usize, u32> = HashMap::new();
315 for (operation_index, operation) in self.operations.iter().enumerate() {
316 if !live[operation_index] {
317 continue;
318 }
319 let depth_of = |wire: Wire| wire.index().checked_sub(source_size).map_or(0, |index| depths[index]);
320 let depth = match plans.get(&operation_index) {
321 Some(plan) => self.one_hot_groups[plan.group]
322 .members
323 .iter()
324 .chain(&plan.candidates)
325 .map(|&wire| depth_of(wire))
326 .max()
327 .unwrap_or(0),
328 None => {
329 let mut depth = operation
330 .left_terms
331 .iter()
332 .chain(&operation.gate_terms)
333 .map(|&(wire, _)| depth_of(wire))
334 .max()
335 .unwrap_or(0);
336 if let Some(read) = &operation.index_read {
337 let table_depth = match table_depths.get(&read.table) {
338 Some(&table_depth) => table_depth,
339 None => {
340 let table_depth = self.tables[read.table]
341 .candidates
342 .iter()
343 .map(|&candidate| depth_of(candidate))
344 .max()
345 .unwrap_or(0);
346 table_depths.insert(read.table, table_depth);
347 table_depth
348 }
349 };
350 depth = depth.max(table_depth);
351 }
352 depth
353 }
354 };
355 depths[operation_index] = depth + 1;
356 }
357 depths
358 }
359
360 fn find_live_operations(&self, roots: &[Wire], plans: &HashMap<usize, GatherPlan>) -> Vec<bool> {
361 let source_size = self.source_size();
362 let mut live = vec![false; self.operations.len()];
363 let mut visited_tables = vec![false; self.tables.len()];
364 let mut pending = roots.to_vec();
365 while let Some(wire) = pending.pop() {
366 self.assert_wire(wire);
367 let Some(operation_index) = wire.index().checked_sub(source_size) else { continue };
368 if live[operation_index] {
369 continue;
370 }
371 live[operation_index] = true;
372 if let Some(plan) = plans.get(&operation_index) {
373 pending.extend(&self.one_hot_groups[plan.group].members);
375 pending.extend(&plan.candidates);
376 continue;
377 }
378 let operation = &self.operations[operation_index];
379 pending.extend(operation.left_terms.iter().chain(&operation.gate_terms).map(|&(wire, _)| wire));
380 if let Some(read) = &operation.index_read
381 && !visited_tables[read.table]
382 {
383 visited_tables[read.table] = true;
384 pending.extend(&self.tables[read.table].candidates);
385 }
386 }
387 live
388 }
389
390 pub fn evaluate_tensor(&self, source: &[f32], program: &TensorProgram) -> Result<Vec<f32>, Error> {
392 self.validate_source_ranges(source)?;
393 let mut executor = TensorExecutor::new(program);
394 executor.run(source);
395 let mut values = vec![0f32; program.width];
396 for (packed, wire) in program.wire_order.iter().enumerate() {
397 values[wire.index()] = executor.packed_values[packed];
398 }
399 Ok(values)
400 }
401
402 pub fn validate_tensor_evaluation(
405 &self,
406 source: &[f32],
407 program: &TensorProgram,
408 tensor_values: &[f32],
409 ) -> Result<(), String> {
410 let expected = self.evaluate_unchecked(source);
411 for wire in &program.wire_order {
412 let (got, want) = (tensor_values[wire.index()], expected[wire.index()]);
413 if got != want {
414 return Err(format!("tensor wire {} is {got}, the reference evaluator gives {want}", wire.index()));
415 }
416 }
417 Ok(())
418 }
419}
420
421pub struct TensorExecutor<'program> {
423 program: &'program TensorProgram,
424 pub packed_values: Vec<f32>,
426 input: Vec<f32>,
427 projection: Vec<f32>,
428 gather_indices: Vec<i32>,
429 gather_masses: Vec<f32>,
430 root_positions: Vec<usize>,
431 outputs: Vec<f32>,
432}
433
434impl<'program> TensorExecutor<'program> {
435 pub fn new(program: &'program TensorProgram) -> TensorExecutor<'program> {
436 let max_input = program.layers.iter().map(|layer| layer.input_indices.len()).max().unwrap_or(0);
437 let max_projection = program.layers.iter().map(|layer| layer.bias.len()).max().unwrap_or(0);
438 let root_positions = program
439 .roots
440 .iter()
441 .map(|root| program.wire_order.iter().position(|wire| wire == root).expect("root was removed"))
442 .collect();
443 TensorExecutor {
444 program,
445 packed_values: vec![0.0; program.value_count],
446 input: vec![0.0; max_input],
447 projection: vec![0.0; max_projection],
448 gather_indices: vec![0; program.gather_slot_count],
449 gather_masses: vec![0.0; program.gather_slot_count],
450 root_positions,
451 outputs: vec![0.0; program.roots.len()],
452 }
453 }
454
455 pub fn run(&mut self, source: &[f32]) -> &[f32] {
458 let program = self.program;
459 assert_eq!(source.len(), program.source_size, "source table has the wrong length");
460 let values = &mut self.packed_values;
461 values[..source.len()].copy_from_slice(source);
462 let mut available = source.len();
463 for layer in &program.layers {
464 for (column, &packed) in layer.input_indices.iter().enumerate() {
465 self.input[column] = values[packed as usize];
466 }
467 let projection = &mut self.projection[..layer.bias.len()];
468 for (row, output) in projection.iter_mut().enumerate() {
469 let mut value = layer.bias[row];
470 for entry in layer.row_offsets[row] as usize..layer.row_offsets[row + 1] as usize {
471 value += layer.weights[entry] * self.input[layer.columns[entry] as usize];
472 }
473 *output = value;
474 }
475 for compute in &layer.gather_computes {
476 let (index, mass) = resolve_gather(compute.bit_positions.iter().map(|&bit| values[bit as usize]));
477 self.gather_indices[compute.slot as usize] = index;
478 self.gather_masses[compute.slot as usize] = mass;
479 }
480 for read in &layer.gather_reads {
481 let slot = read.slot as usize;
482 let contribution = gathered(values, read, self.gather_indices[slot], self.gather_masses[slot]);
483 projection[read.row as usize] += contribution;
484 }
485 for read in &layer.index_reads {
486 let index = projection[read.index_row as usize] as f64;
487 let below = if read.below_zero { OutOfRange::Zero } else { OutOfRange::Clamp };
488 let above = if read.above_zero { OutOfRange::Zero } else { OutOfRange::Clamp };
489 projection[read.row as usize] =
490 match resolve_index_position(index, read.candidate_positions.len(), below, above) {
491 -2 => f32::NAN,
492 -1 => 0.0,
493 position => values[read.candidate_positions[position as usize] as usize],
494 };
495 }
496 for (gate, &row) in layer.gate_rows.iter().enumerate() {
497 let gate_value = relu32(projection[layer.output_size + gate]);
498 projection[row as usize] *= gate_value;
499 }
500 for &row in &layer.boolean_rows {
501 projection[row as usize] = evaluate_boolean(projection[row as usize]);
502 }
503 values[available..available + layer.output_size].copy_from_slice(&projection[..layer.output_size]);
504 available += layer.output_size;
505 }
506 for (output, &position) in self.outputs.iter_mut().zip(&self.root_positions) {
507 *output = values[position];
508 }
509 &self.outputs
510 }
511}
512
513pub(crate) fn resolve_gather(bits: impl Iterator<Item = f32>) -> (i32, f32) {
515 let (mut index, mut mass) = (0f64, 0f64);
516 for (position, bit) in bits.enumerate() {
517 index += position as f64 * bit as f64;
518 mass += bit as f64;
519 }
520 (index as i32, mass as f32)
521}
522
523pub(crate) fn gathered(values: &[f32], read: &GatherRead, index: i32, mass: f32) -> f32 {
526 let index = index.clamp(0, read.candidate_positions.len() as i32 - 1) as usize;
527 let candidate = values[read.candidate_positions[index] as usize];
528 if read.masked { mass * candidate } else { candidate }
529}