1use std::hash::{Hash, Hasher};
2use std::mem::{size_of, size_of_val};
3use std::sync::Arc;
4
5use lru::LruCache;
6use tenferro_ops::ext_op::ExtensionOp;
7use tenferro_tensor::CacheStats;
8
9use crate::exec::{ExecInstruction, ExecOp, ExecOutputExtents, ExecOutputShapes, ExecProgram};
10
11#[allow(dead_code)]
15pub const DEFAULT_GRAPH_COMPILE_CACHE_CAPACITY: usize = 256;
16
17pub(crate) const DEFAULT_COMPILE_CACHE_CAPACITY: usize = DEFAULT_GRAPH_COMPILE_CACHE_CAPACITY;
19
20#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
36pub struct GraphCompilerCacheStats {
37 pub compile: CacheStats,
39 pub extensions: CacheStats,
41}
42
43#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
59pub struct GraphExecutorCacheStats {
60 pub extensions: CacheStats,
62 pub backend: CacheStats,
64}
65
66#[derive(Clone, Debug)]
68pub(crate) struct CacheKey {
69 fingerprint: ExecProgramKey,
70 extensions: Vec<Arc<dyn ExtensionOp>>,
71}
72
73impl PartialEq for CacheKey {
74 fn eq(&self, other: &Self) -> bool {
75 self.fingerprint == other.fingerprint
76 && self.extensions.len() == other.extensions.len()
77 && self
78 .extensions
79 .iter()
80 .zip(&other.extensions)
81 .all(|(lhs, rhs)| {
82 lhs.family_id() == rhs.family_id() && lhs.payload_eq(rhs.as_ref())
83 })
84 }
85}
86
87impl Eq for CacheKey {}
88
89impl Hash for CacheKey {
90 fn hash<H: Hasher>(&self, state: &mut H) {
91 self.fingerprint.hash(state);
92 }
93}
94
95pub(crate) fn compute_cache_key(exec: &ExecProgram) -> CacheKey {
96 let mut extensions = Vec::new();
97 let fingerprint = exec_program_key(exec, &mut extensions);
98 CacheKey {
99 fingerprint,
100 extensions,
101 }
102}
103
104fn cache_key_retained_bytes(key: &CacheKey) -> usize {
105 saturating_sum([
106 size_of::<CacheKey>(),
107 exec_program_key_retained_bytes(&key.fingerprint),
108 key.extensions
109 .capacity()
110 .saturating_mul(size_of::<Arc<dyn ExtensionOp>>()),
111 ])
112}
113
114#[derive(Clone, Debug, Hash, PartialEq, Eq)]
115struct ExecProgramKey {
116 instructions: Vec<ExecInstructionKey>,
117 input_slots: Vec<usize>,
118 output_slots: Vec<usize>,
119 n_slots: usize,
120}
121
122#[derive(Clone, Debug, Hash, PartialEq, Eq)]
123struct ExecInstructionKey {
124 op: ExecOpKey,
125 input_slots: Vec<usize>,
126 output_slots: Vec<usize>,
127 dtype: tenferro_tensor::DType,
128 output_shapes: ExecOutputShapes,
129 output_extents: ExecOutputExtents,
130 last_use: Vec<bool>,
131}
132
133#[derive(Clone, Debug, Hash, PartialEq, Eq)]
134enum ExecOpKey {
135 Transpose {
136 perm: Vec<usize>,
137 },
138 Reshape {
139 shape: Vec<tenferro_ops::dim_expr::DimExpr>,
140 },
141 BroadcastInDim {
142 shape: Vec<tenferro_ops::dim_expr::DimExpr>,
143 dims: Vec<usize>,
144 },
145 Convert {
146 to: tenferro_tensor::DType,
147 },
148 Constant {
149 dtype: tenferro_tensor::DType,
150 bytes: Vec<u8>,
151 },
152 DotGeneral(tenferro_tensor::DotGeneralConfig),
153 DotGeneralWithConj {
154 config: tenferro_tensor::DotGeneralConfig,
155 lhs_conj: bool,
156 rhs_conj: bool,
157 },
158 ReduceSum {
159 axes: Vec<usize>,
160 },
161 ExtractDiag {
162 axis_a: usize,
163 axis_b: usize,
164 },
165 EmbedDiag {
166 axis_a: usize,
167 axis_b: usize,
168 },
169 Tril {
170 k: i64,
171 },
172 Triu {
173 k: i64,
174 },
175 Add,
176 Subtract,
177 Multiply,
178 Negate,
179 Conj,
180 Divide,
181 Remainder,
182 Abs,
183 Sign,
184 Maximum,
185 Minimum,
186 Compare(tenferro_tensor::CompareDir),
187 Select,
188 Clamp,
189 Exp,
190 Log,
191 Sin,
192 Cos,
193 Tanh,
194 Sqrt,
195 Rsqrt,
196 Pow,
197 Expm1,
198 Log1p,
199 Gather(tenferro_tensor::GatherConfig),
200 GatherDynamicSliceSizes {
201 offset_dims: Vec<usize>,
202 collapsed_slice_dims: Vec<usize>,
203 start_index_map: Vec<usize>,
204 index_vector_dim: usize,
205 slice_sizes: Vec<tenferro_ops::dim_expr::DimExpr>,
206 },
207 Scatter(tenferro_tensor::ScatterConfig),
208 Slice(tenferro_tensor::SliceConfig),
209 DynamicSlice {
210 slice_sizes: Vec<usize>,
211 },
212 DynamicUpdateSlice,
213 Pad(tenferro_tensor::PadConfig),
214 Concatenate {
215 axis: usize,
216 },
217 Reverse {
218 axes: Vec<usize>,
219 },
220 ShapeOf {
221 axis: usize,
222 },
223 DynamicTruncate {
224 axis: usize,
225 },
226 PadToMatch {
227 axis: usize,
228 },
229 ReduceProd {
230 axes: Vec<usize>,
231 },
232 ReduceMax {
233 axes: Vec<usize>,
234 },
235 ReduceMin {
236 axes: Vec<usize>,
237 },
238 Extension {
239 family_id: &'static str,
240 payload_hash: u64,
241 },
242}
243
244fn exec_program_key(
245 exec: &ExecProgram,
246 extensions: &mut Vec<Arc<dyn ExtensionOp>>,
247) -> ExecProgramKey {
248 ExecProgramKey {
249 instructions: exec
250 .instructions
251 .iter()
252 .map(|inst| exec_instruction_key(inst, extensions))
253 .collect(),
254 input_slots: exec.input_slots.clone(),
255 output_slots: exec.output_slots.clone(),
256 n_slots: exec.n_slots,
257 }
258}
259
260fn exec_instruction_key(
261 inst: &ExecInstruction,
262 extensions: &mut Vec<Arc<dyn ExtensionOp>>,
263) -> ExecInstructionKey {
264 ExecInstructionKey {
265 op: exec_op_key(&inst.op, extensions),
266 input_slots: inst.input_slots.clone(),
267 output_slots: inst.output_slots.clone(),
268 dtype: inst.dtype,
269 output_shapes: inst.output_shapes.clone(),
270 output_extents: inst.output_extents.clone(),
271 last_use: inst.last_use.clone(),
272 }
273}
274
275fn exec_op_key(op: &ExecOp, extensions: &mut Vec<Arc<dyn ExtensionOp>>) -> ExecOpKey {
276 match op {
277 ExecOp::Transpose { perm } => ExecOpKey::Transpose { perm: perm.clone() },
278 ExecOp::Reshape { shape } => ExecOpKey::Reshape {
279 shape: shape.clone(),
280 },
281 ExecOp::BroadcastInDim { shape, dims } => ExecOpKey::BroadcastInDim {
282 shape: shape.clone(),
283 dims: dims.clone(),
284 },
285 ExecOp::Convert { to } => ExecOpKey::Convert { to: *to },
286 ExecOp::Constant { dtype, bytes } => ExecOpKey::Constant {
287 dtype: *dtype,
288 bytes: bytes.clone(),
289 },
290 ExecOp::DotGeneral(config) => ExecOpKey::DotGeneral(config.clone()),
291 ExecOp::DotGeneralWithConj {
292 config,
293 lhs_conj,
294 rhs_conj,
295 } => ExecOpKey::DotGeneralWithConj {
296 config: config.clone(),
297 lhs_conj: *lhs_conj,
298 rhs_conj: *rhs_conj,
299 },
300 ExecOp::ReduceSum { axes } => ExecOpKey::ReduceSum { axes: axes.clone() },
301 ExecOp::ExtractDiag { axis_a, axis_b } => ExecOpKey::ExtractDiag {
302 axis_a: *axis_a,
303 axis_b: *axis_b,
304 },
305 ExecOp::EmbedDiag { axis_a, axis_b } => ExecOpKey::EmbedDiag {
306 axis_a: *axis_a,
307 axis_b: *axis_b,
308 },
309 ExecOp::Tril { k } => ExecOpKey::Tril { k: *k },
310 ExecOp::Triu { k } => ExecOpKey::Triu { k: *k },
311 ExecOp::Add => ExecOpKey::Add,
312 ExecOp::Subtract => ExecOpKey::Subtract,
313 ExecOp::Multiply => ExecOpKey::Multiply,
314 ExecOp::Negate => ExecOpKey::Negate,
315 ExecOp::Conj => ExecOpKey::Conj,
316 ExecOp::Divide => ExecOpKey::Divide,
317 ExecOp::Remainder => ExecOpKey::Remainder,
318 ExecOp::Abs => ExecOpKey::Abs,
319 ExecOp::Sign => ExecOpKey::Sign,
320 ExecOp::Maximum => ExecOpKey::Maximum,
321 ExecOp::Minimum => ExecOpKey::Minimum,
322 ExecOp::Compare(dir) => ExecOpKey::Compare(dir.clone()),
323 ExecOp::Select => ExecOpKey::Select,
324 ExecOp::Clamp => ExecOpKey::Clamp,
325 ExecOp::Exp => ExecOpKey::Exp,
326 ExecOp::Log => ExecOpKey::Log,
327 ExecOp::Sin => ExecOpKey::Sin,
328 ExecOp::Cos => ExecOpKey::Cos,
329 ExecOp::Tanh => ExecOpKey::Tanh,
330 ExecOp::Sqrt => ExecOpKey::Sqrt,
331 ExecOp::Rsqrt => ExecOpKey::Rsqrt,
332 ExecOp::Pow => ExecOpKey::Pow,
333 ExecOp::Expm1 => ExecOpKey::Expm1,
334 ExecOp::Log1p => ExecOpKey::Log1p,
335 ExecOp::Gather(config) => ExecOpKey::Gather(config.clone()),
336 ExecOp::GatherDynamicSliceSizes {
337 offset_dims,
338 collapsed_slice_dims,
339 start_index_map,
340 index_vector_dim,
341 slice_sizes,
342 } => ExecOpKey::GatherDynamicSliceSizes {
343 offset_dims: offset_dims.clone(),
344 collapsed_slice_dims: collapsed_slice_dims.clone(),
345 start_index_map: start_index_map.clone(),
346 index_vector_dim: *index_vector_dim,
347 slice_sizes: slice_sizes.clone(),
348 },
349 ExecOp::Scatter(config) => ExecOpKey::Scatter(config.clone()),
350 ExecOp::Slice(config) => ExecOpKey::Slice(config.clone()),
351 ExecOp::DynamicSlice { slice_sizes } => ExecOpKey::DynamicSlice {
352 slice_sizes: slice_sizes.clone(),
353 },
354 ExecOp::DynamicUpdateSlice => ExecOpKey::DynamicUpdateSlice,
355 ExecOp::Pad(config) => ExecOpKey::Pad(config.clone()),
356 ExecOp::Concatenate { axis } => ExecOpKey::Concatenate { axis: *axis },
357 ExecOp::Reverse { axes } => ExecOpKey::Reverse { axes: axes.clone() },
358 ExecOp::ShapeOf { axis } => ExecOpKey::ShapeOf { axis: *axis },
359 ExecOp::DynamicTruncate { axis } => ExecOpKey::DynamicTruncate { axis: *axis },
360 ExecOp::PadToMatch { axis } => ExecOpKey::PadToMatch { axis: *axis },
361 ExecOp::ReduceProd { axes } => ExecOpKey::ReduceProd { axes: axes.clone() },
362 ExecOp::ReduceMax { axes } => ExecOpKey::ReduceMax { axes: axes.clone() },
363 ExecOp::ReduceMin { axes } => ExecOpKey::ReduceMin { axes: axes.clone() },
364 ExecOp::Extension(extension) => {
365 let key = ExecOpKey::Extension {
366 family_id: extension.family_id(),
367 payload_hash: extension_payload_hash(extension.as_ref()),
368 };
369 extensions.push(Arc::clone(extension));
370 key
371 }
372 }
373}
374
375fn extension_payload_hash(extension: &dyn ExtensionOp) -> u64 {
376 let mut hasher = std::collections::hash_map::DefaultHasher::new();
377 extension.payload_hash(&mut DynHasherProxy::new(&mut hasher));
378 hasher.finish()
379}
380
381struct DynHasherProxy<'a, H: Hasher + ?Sized> {
382 inner: &'a mut H,
383}
384
385impl<'a, H: Hasher + ?Sized> DynHasherProxy<'a, H> {
386 fn new(inner: &'a mut H) -> Self {
387 Self { inner }
388 }
389}
390
391impl<H: Hasher + ?Sized> Hasher for DynHasherProxy<'_, H> {
392 fn finish(&self) -> u64 {
393 self.inner.finish()
394 }
395
396 fn write(&mut self, bytes: &[u8]) {
397 self.inner.write(bytes);
398 }
399}
400
401fn vec_retained_bytes<T>(values: &Vec<T>) -> usize {
402 values.capacity().saturating_mul(size_of::<T>())
403}
404
405fn vec_of_vec_retained_bytes<T>(values: &[Vec<T>]) -> usize {
406 saturating_sum(values.iter().map(vec_retained_bytes))
407}
408
409fn exec_program_key_retained_bytes(key: &ExecProgramKey) -> usize {
410 saturating_sum([
411 size_of::<ExecProgramKey>(),
412 vec_retained_bytes(&key.instructions),
413 saturating_sum(
414 key.instructions
415 .iter()
416 .map(exec_instruction_key_retained_bytes),
417 ),
418 vec_retained_bytes(&key.input_slots),
419 vec_retained_bytes(&key.output_slots),
420 ])
421}
422
423fn exec_instruction_key_retained_bytes(key: &ExecInstructionKey) -> usize {
424 saturating_sum([
425 size_of::<ExecInstructionKey>(),
426 exec_op_key_retained_bytes(&key.op),
427 vec_retained_bytes(&key.input_slots),
428 vec_retained_bytes(&key.output_slots),
429 vec_of_vec_retained_bytes(&key.output_shapes),
430 vec_of_vec_retained_bytes(&key.output_extents),
431 vec_retained_bytes(&key.last_use),
432 ])
433}
434
435fn exec_op_key_retained_bytes(key: &ExecOpKey) -> usize {
436 saturating_sum([
437 size_of::<ExecOpKey>(),
438 match key {
439 ExecOpKey::Transpose { perm } => vec_retained_bytes(perm),
440 ExecOpKey::Reshape { shape } => vec_retained_bytes(shape),
441 ExecOpKey::BroadcastInDim { shape, dims } => {
442 saturating_sum([vec_retained_bytes(shape), vec_retained_bytes(dims)])
443 }
444 ExecOpKey::Constant { bytes, .. } => vec_retained_bytes(bytes),
445 ExecOpKey::DotGeneral(config) => dot_general_config_retained_bytes(config),
446 ExecOpKey::DotGeneralWithConj { config, .. } => {
447 dot_general_config_retained_bytes(config)
448 }
449 ExecOpKey::ReduceSum { axes }
450 | ExecOpKey::Reverse { axes }
451 | ExecOpKey::ReduceProd { axes }
452 | ExecOpKey::ReduceMax { axes }
453 | ExecOpKey::ReduceMin { axes } => vec_retained_bytes(axes),
454 ExecOpKey::Gather(config) => gather_config_retained_bytes(config),
455 ExecOpKey::GatherDynamicSliceSizes {
456 offset_dims,
457 collapsed_slice_dims,
458 start_index_map,
459 slice_sizes,
460 ..
461 } => saturating_sum([
462 vec_retained_bytes(offset_dims),
463 vec_retained_bytes(collapsed_slice_dims),
464 vec_retained_bytes(start_index_map),
465 vec_retained_bytes(slice_sizes),
466 ]),
467 ExecOpKey::Scatter(config) => scatter_config_retained_bytes(config),
468 ExecOpKey::Slice(config) => slice_config_retained_bytes(config),
469 ExecOpKey::DynamicSlice { slice_sizes } => vec_retained_bytes(slice_sizes),
470 ExecOpKey::Pad(config) => pad_config_retained_bytes(config),
471 ExecOpKey::Convert { .. }
472 | ExecOpKey::ExtractDiag { .. }
473 | ExecOpKey::EmbedDiag { .. }
474 | ExecOpKey::Tril { .. }
475 | ExecOpKey::Triu { .. }
476 | ExecOpKey::Add
477 | ExecOpKey::Subtract
478 | ExecOpKey::Multiply
479 | ExecOpKey::Negate
480 | ExecOpKey::Conj
481 | ExecOpKey::Divide
482 | ExecOpKey::Remainder
483 | ExecOpKey::Abs
484 | ExecOpKey::Sign
485 | ExecOpKey::Maximum
486 | ExecOpKey::Minimum
487 | ExecOpKey::Compare(_)
488 | ExecOpKey::Select
489 | ExecOpKey::Clamp
490 | ExecOpKey::Exp
491 | ExecOpKey::Log
492 | ExecOpKey::Sin
493 | ExecOpKey::Cos
494 | ExecOpKey::Tanh
495 | ExecOpKey::Sqrt
496 | ExecOpKey::Rsqrt
497 | ExecOpKey::Pow
498 | ExecOpKey::Expm1
499 | ExecOpKey::Log1p
500 | ExecOpKey::DynamicUpdateSlice
501 | ExecOpKey::Concatenate { .. }
502 | ExecOpKey::ShapeOf { .. }
503 | ExecOpKey::DynamicTruncate { .. }
504 | ExecOpKey::PadToMatch { .. }
505 | ExecOpKey::Extension { .. } => 0,
506 },
507 ])
508}
509
510fn dot_general_config_retained_bytes(config: &tenferro_tensor::DotGeneralConfig) -> usize {
511 saturating_sum([
512 vec_retained_bytes(&config.lhs_contracting_dims),
513 vec_retained_bytes(&config.rhs_contracting_dims),
514 vec_retained_bytes(&config.lhs_batch_dims),
515 vec_retained_bytes(&config.rhs_batch_dims),
516 ])
517}
518
519fn gather_config_retained_bytes(config: &tenferro_tensor::GatherConfig) -> usize {
520 saturating_sum([
521 vec_retained_bytes(&config.offset_dims),
522 vec_retained_bytes(&config.collapsed_slice_dims),
523 vec_retained_bytes(&config.start_index_map),
524 vec_retained_bytes(&config.slice_sizes),
525 ])
526}
527
528fn scatter_config_retained_bytes(config: &tenferro_tensor::ScatterConfig) -> usize {
529 saturating_sum([
530 vec_retained_bytes(&config.update_window_dims),
531 vec_retained_bytes(&config.inserted_window_dims),
532 vec_retained_bytes(&config.scatter_dims_to_operand_dims),
533 ])
534}
535
536fn slice_config_retained_bytes(config: &tenferro_tensor::SliceConfig) -> usize {
537 saturating_sum([
538 vec_retained_bytes(&config.starts),
539 vec_retained_bytes(&config.limits),
540 vec_retained_bytes(&config.strides),
541 ])
542}
543
544fn pad_config_retained_bytes(config: &tenferro_tensor::PadConfig) -> usize {
545 saturating_sum([
546 vec_retained_bytes(&config.edge_padding_low),
547 vec_retained_bytes(&config.edge_padding_high),
548 vec_retained_bytes(&config.interior_padding),
549 ])
550}
551
552fn exec_op_retained_bytes(op: &ExecOp) -> usize {
553 match op {
554 ExecOp::Constant { bytes, .. } => vec_retained_bytes(bytes),
555 ExecOp::Extension(extension) => size_of_val(extension),
556 _ => 0,
557 }
558}
559
560fn exec_instruction_retained_bytes(inst: &ExecInstruction) -> usize {
561 saturating_sum([
562 size_of::<ExecInstruction>(),
563 exec_op_retained_bytes(&inst.op),
564 vec_retained_bytes(&inst.input_slots),
565 vec_retained_bytes(&inst.output_slots),
566 vec_of_vec_retained_bytes(&inst.output_shapes),
567 vec_of_vec_retained_bytes(&inst.output_extents),
568 vec_retained_bytes(&inst.last_use),
569 ])
570}
571
572fn exec_program_retained_bytes(program: &ExecProgram) -> usize {
573 saturating_sum([
574 size_of::<ExecProgram>(),
575 vec_retained_bytes(&program.instructions),
576 saturating_sum(
577 program
578 .instructions
579 .iter()
580 .map(exec_instruction_retained_bytes),
581 ),
582 vec_retained_bytes(&program.input_slots),
583 vec_retained_bytes(&program.output_slots),
584 ])
585}
586
587pub(crate) fn compile_cache_stats(cache: &LruCache<CacheKey, ExecProgram>) -> CacheStats {
588 CacheStats {
589 entries: cache.len(),
590 retained_bytes: cache
591 .iter()
592 .map(|(key, program)| {
593 saturating_sum([
594 cache_key_retained_bytes(key),
595 exec_program_retained_bytes(program),
596 ])
597 })
598 .fold(0usize, usize::saturating_add),
599 }
600}
601
602fn saturating_sum(values: impl IntoIterator<Item = usize>) -> usize {
603 values.into_iter().fold(0usize, usize::saturating_add)
604}
605
606#[cfg(test)]
607mod tests;