1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3mod evidence;
6mod profile;
7mod store;
8
9use std::sync::Arc;
10
11use sim_kernel::{
12 AbiVersion, DefaultFactory, Export, Factory, Lib, LibManifest, LibTarget, Linker, Result,
13 Symbol, Version,
14};
15use sim_lib_compute_model::{ModeledComputeProfile, ModeledTensorExecutor};
16use sim_lib_numbers_tensor::{
17 CpuTensorExecutor, SubmissionEvidence, TensorExecError, TensorExecution, TensorExecutor,
18 TensorExecutorCard, TensorRequest, TensorSite,
19};
20
21pub use evidence::{
22 ComputeEvidenceKind, ComputePhysicalEvidence, PhysicalEvidenceError, verify_physical,
23};
24pub use profile::{
25 AutoComputeRouter, AutoRouteDecision, AutoRoutingEvent, AutoRoutingLedger, BenchmarkBounds,
26 ComputeDeviceIdentity, ComputeProfileLimits, ComputeProfileProvenance, ComputeProfileSamples,
27 ComputeThermalPowerContext, MeasuredComputeProfile, measure_bounded_profile,
28 measured_compute_profile_citizen_symbol, measured_compute_profile_shape_symbol,
29};
30pub use store::{ProfileStore, ProfileStorePolicy};
31
32pub fn auto_executor_symbol() -> Symbol {
34 Symbol::qualified("compute", "executor/auto")
35}
36
37pub fn compute_auto_site_symbol() -> Symbol {
39 Symbol::new("site/compute/auto")
40}
41
42pub fn compute_auto_lib_symbol() -> Symbol {
44 Symbol::qualified("compute", "auto-lib")
45}
46
47#[derive(Clone, Debug, Default, PartialEq, Eq)]
49pub struct AutoComputeProfile {
50 pub modeled: Option<ModeledComputeProfile>,
53 pub measured: Option<MeasuredComputeProfile>,
55 pub expected: Option<ComputeDeviceIdentity>,
57 pub now_tick: u64,
59}
60
61#[derive(Clone)]
63pub struct AutoTensorExecutor {
64 modeled: Option<ModeledTensorExecutor>,
65 decision: AutoRouteDecision,
66 ledger: AutoRoutingLedger,
67}
68
69impl AutoTensorExecutor {
70 pub fn new(profile: AutoComputeProfile) -> Self {
72 let (decision, measured_modeled) = match profile.expected.clone() {
73 Some(expected) => {
74 AutoComputeRouter::new(expected, profile.now_tick).choose(profile.measured.as_ref())
75 }
76 None if profile.measured.is_some() => (AutoRouteDecision::Incompatible, None),
77 None => (AutoRouteDecision::Absent, None),
78 };
79 let modeled = measured_modeled.or(profile.modeled);
80 Self {
81 modeled: modeled.map(ModeledTensorExecutor::new),
82 decision,
83 ledger: AutoRoutingLedger::default(),
84 }
85 }
86
87 pub fn uses_cpu_fallback(&self) -> bool {
89 self.modeled.is_none()
90 }
91
92 pub fn route_decision(&self) -> AutoRouteDecision {
94 self.decision.clone()
95 }
96
97 pub fn routing_events(&self) -> Vec<AutoRoutingEvent> {
99 self.ledger.events()
100 }
101
102 fn selected(&self) -> Arc<dyn TensorExecutor> {
103 self.modeled
104 .clone()
105 .map(|executor| Arc::new(executor) as Arc<dyn TensorExecutor>)
106 .unwrap_or_else(|| Arc::new(CpuTensorExecutor::new()))
107 }
108}
109
110impl TensorExecutor for AutoTensorExecutor {
111 fn card(&self) -> TensorExecutorCard {
112 let selected = self.selected().card();
113 TensorExecutorCard::new(
114 auto_executor_symbol(),
115 if self.uses_cpu_fallback() {
116 "auto/cpu"
117 } else {
118 "auto/modeled"
119 },
120 Symbol::qualified("compute", "auto"),
121 selected.operations.to_vec(),
122 selected.device_capability,
123 )
124 }
125
126 fn execute(
127 &self,
128 cx: &mut sim_kernel::Cx,
129 request: TensorRequest,
130 ) -> std::result::Result<TensorExecution, TensorExecError> {
131 let materialization_bytes = profile::request_materialization_bytes(&request);
132 self.ledger.record(AutoRoutingEvent {
133 provider: if self.uses_cpu_fallback() {
134 "auto/cpu".to_owned()
135 } else {
136 "auto/modeled".to_owned()
137 },
138 decision: self.decision.clone(),
139 materialization_bytes,
140 synchronizations: 0,
141 });
142 self.selected().execute(cx, request)
143 }
144
145 fn flush(&self) -> std::result::Result<SubmissionEvidence, TensorExecError> {
146 let evidence = self.selected().flush()?;
147 self.ledger.record(AutoRoutingEvent {
148 provider: if self.uses_cpu_fallback() {
149 "auto/cpu".to_owned()
150 } else {
151 "auto/modeled".to_owned()
152 },
153 decision: self.decision.clone(),
154 materialization_bytes: 0,
155 synchronizations: 1,
156 });
157 Ok(evidence)
158 }
159}
160
161impl Default for AutoTensorExecutor {
162 fn default() -> Self {
163 Self::new(AutoComputeProfile::default())
164 }
165}
166
167#[derive(Clone, Debug, Default)]
169pub struct ComputeAutoLib {
170 profile: AutoComputeProfile,
171}
172
173impl ComputeAutoLib {
174 pub fn new(profile: AutoComputeProfile) -> Self {
176 Self { profile }
177 }
178}
179
180impl Lib for ComputeAutoLib {
181 fn manifest(&self) -> LibManifest {
182 LibManifest {
183 id: compute_auto_lib_symbol(),
184 version: Version(env!("CARGO_PKG_VERSION").to_owned()),
185 abi: AbiVersion { major: 0, minor: 1 },
186 target: LibTarget::HostRegistered,
187 requires: Vec::new(),
188 capabilities: Vec::new(),
189 exports: vec![Export::Site {
190 symbol: compute_auto_site_symbol(),
191 runtime_id: None,
192 }],
193 }
194 }
195
196 fn load(&self, _cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
197 let executor = Arc::new(AutoTensorExecutor::new(self.profile.clone()));
198 let site = TensorSite::new(compute_auto_site_symbol(), executor, Vec::new());
199 linker.site_value(
200 compute_auto_site_symbol(),
201 DefaultFactory.opaque(Arc::new(site))?,
202 )?;
203 Ok(())
204 }
205}
206
207pub static RECIPES: sim_cookbook::EmbeddedDir =
209 include!(concat!(env!("OUT_DIR"), "/cookbook_recipes.rs"));
210
211#[cfg(test)]
212mod tests;