1use std::cell::{Cell, RefCell};
18use std::collections::HashMap;
19use std::collections::hash_map::DefaultHasher;
20use std::hash::{Hash, Hasher};
21use std::rc::Rc;
22
23use rlx_ir::{Graph, NodeId, Op};
24use rlx_runtime::{CompiledGraph, Device, PrecisionPolicy, Session};
25
26const MAX_ENTRIES: usize = 512;
29
30thread_local! {
31 static CACHE: RefCell<HashMap<u64, Rc<RefCell<CompiledGraph>>>> =
32 RefCell::new(HashMap::new());
33 static HITS: Cell<u64> = const { Cell::new(0) };
34 static MISSES: Cell<u64> = const { Cell::new(0) };
35}
36
37fn fingerprint(graph: &Graph, outputs: &[NodeId], device: Device) -> u64 {
41 let mut h = DefaultHasher::new();
42 device.hash(&mut h);
43 graph.name.hash(&mut h);
44 for node in graph.nodes() {
45 node.id.0.hash(&mut h);
46 match &node.op {
47 Op::Constant { data } => {
49 0u8.hash(&mut h);
50 data.hash(&mut h);
51 }
52 other => format!("{other:?}").hash(&mut h),
53 }
54 for inp in &node.inputs {
55 inp.0.hash(&mut h);
56 }
57 format!("{:?}", node.shape).hash(&mut h);
58 }
59 for out in outputs {
60 out.0.hash(&mut h);
61 }
62 h.finish()
63}
64
65fn lookup(key: u64) -> Option<Rc<RefCell<CompiledGraph>>> {
66 let hit = CACHE.with(|c| c.borrow().get(&key).cloned());
67 if hit.is_some() {
68 HITS.with(|n| n.set(n.get() + 1));
69 } else {
70 MISSES.with(|n| n.set(n.get() + 1));
71 }
72 hit
73}
74
75fn precision_policy_from_env() -> Option<PrecisionPolicy> {
81 match rlx_ir::env::var("RLX_PRECISION").as_deref() {
82 Some("auto") => Some(PrecisionPolicy::AutoMixed),
83 Some("f16") => Some(PrecisionPolicy::AutoMixedConservative),
84 Some("bf16") => Some(PrecisionPolicy::AutoMixedBf16),
85 Some("f16-all") => Some(PrecisionPolicy::AlwaysF16),
86 _ => None,
87 }
88}
89
90fn store(key: u64, device: Device, graph: Graph) -> Rc<RefCell<CompiledGraph>> {
91 let mut session = Session::new(device);
92 if let Some(policy) = precision_policy_from_env() {
93 session = session.with_policy(policy);
94 }
95 let compiled = Rc::new(RefCell::new(session.compile(graph)));
96 CACHE.with(|c| {
97 let mut map = c.borrow_mut();
98 if map.len() >= MAX_ENTRIES {
99 map.clear();
100 }
101 map.insert(key, compiled.clone());
102 });
103 compiled
104}
105
106pub(crate) fn compiled(graph: &Graph, device: Device) -> Rc<RefCell<CompiledGraph>> {
109 let key = fingerprint(graph, &graph.outputs, device);
110 lookup(key).unwrap_or_else(|| store(key, device, graph.clone()))
111}
112
113pub(crate) fn compiled_output(
118 graph: &Graph,
119 output: NodeId,
120 device: Device,
121) -> Rc<RefCell<CompiledGraph>> {
122 let key = fingerprint(graph, &[output], device);
123 lookup(key).unwrap_or_else(|| {
124 let mut g = graph.clone();
125 g.set_outputs(vec![output]);
126 store(key, device, g)
127 })
128}
129
130pub fn cache_stats() -> (u64, u64) {
133 (HITS.with(Cell::get), MISSES.with(Cell::get))
134}
135
136pub fn clear_cache() {
138 CACHE.with(|c| c.borrow_mut().clear());
139 HITS.with(|n| n.set(0));
140 MISSES.with(|n| n.set(0));
141}