#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![allow(
// Auto-generated op wrappers replay derive attributes by design.
clippy::duplicated_attributes,
// GPU buffer layout types (bind-group slot tuples) are inherently complex.
clippy::type_complexity,
// Shader-side math and wire-format POD structs do intentional integer
// casts; the conform gate verifies byte-identity with the CPU reference.
clippy::cast_lossless,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_precision_loss,
clippy::cast_sign_loss,
// Explicit clones on Copy improve readability in serial layers where
// semantic ownership matters more than cycle count.
clippy::clone_on_copy,
// Three-branch comparisons are natural in range-check oracles.
clippy::comparison_chain,
// Vyre uses explicit invariant violations (expect/unwrap) with `Fix:`
// prose - not graceful degradation - per the engineering standard.
clippy::expect_used,
// Generic collections take external hashers by design.
clippy::implicit_hasher,
// SHA/hash compressors use the canonical single-letter state vars
// (a,b,c,d,e,f,g,h per FIPS 180-4).
clippy::many_single_char_names,
// Error prose is centralized in the `Error` enum; per-fn `# Errors`
// sections duplicate that contract.
clippy::missing_errors_doc,
// Panics document invariant violations with `Fix:` prose inline.
clippy::missing_panics_doc,
// Template-generated ops don't always merit `#[must_use]`.
clippy::must_use_candidate,
clippy::needless_pass_by_value,
clippy::needless_range_loop,
clippy::needless_raw_string_hashes,
clippy::module_name_repetitions,
clippy::module_inception,
clippy::similar_names,
clippy::should_implement_trait,
clippy::match_same_arms,
clippy::format_push_string,
clippy::too_many_arguments,
clippy::too_many_lines,
clippy::trivially_copy_pass_by_ref,
clippy::unnecessary_wraps,
clippy::unnested_or_patterns,
clippy::unreadable_literal,
clippy::doc_markdown
)]
#![cfg_attr(not(test), deny(clippy::todo, clippy::unimplemented))]
pub use vyre_foundation::ir;
pub use vyre_foundation::soundness;
pub mod lower {
pub use vyre_lower::lower::lower;
pub use vyre_lower::*;
}
pub use vyre_foundation::optimizer;
#[cfg(any(test, feature = "cpu-parity"))]
pub use vyre_foundation::cpu_op;
#[cfg(any(test, feature = "cpu-parity"))]
pub use vyre_foundation::cpu_references;
pub use vyre_foundation::memory_model;
pub use vyre_foundation::MemoryOrdering;
pub use vyre_driver::routing;
pub use vyre_foundation::execution_plan;
pub use vyre_driver::error;
pub use vyre_driver::diagnostics;
pub use vyre_driver::backend;
pub use vyre_foundation::match_result;
pub use vyre_driver::pipeline;
pub use vyre_driver::{
BackendError, BackendRegistration, CompiledPipeline, DispatchConfig, Error, Executable, Memory,
MemoryRef, OutputBuffers, ResidentGraphReuseTelemetry, ResidentGraphReuseTelemetryError,
TypedDispatchExt, VyreBackend,
};
pub use vyre_driver::persistent::PersistentThreadMode;
pub use vyre_driver::speculate::SpeculationMode;
pub use ir::{validate, InterpCtx, NodeId, NodeStorage, OpId, Program, Value};
pub use vyre_foundation::match_result::Match;
pub use vyre_foundation::ByteRange;
#[must_use]
pub fn optimize(program: Program) -> Result<Program, vyre_foundation::optimizer::OptimizerError> {
let key = program.fingerprint();
if let Some(cached) = optimize_cache::get(&key) {
return Ok(cached);
}
let optimized = vyre_foundation::optimizer::pre_lowering::try_optimize(program)?;
optimize_cache::put(key, &optimized);
Ok(optimized)
}
#[must_use]
pub fn optimize_for_device(
program: Program,
profile: &vyre_driver::DeviceProfile,
) -> Result<Program, vyre_foundation::optimizer::OptimizerError> {
let key = device_optimize_key(&program, profile);
if let Some(cached) = optimize_cache::get_device(&key) {
return Ok(cached);
}
let tuned = vyre_foundation::optimizer::passes::autotune::Autotune::transform_for_adapter(
program,
&profile.adapter_caps(),
)
.program;
let optimized = optimize(tuned)?;
optimize_cache::put_device(key, &optimized);
Ok(optimized)
}
#[must_use]
pub fn optimize_for_backend(
program: Program,
backend: &dyn vyre_driver::VyreBackend,
) -> Result<Program, vyre_foundation::optimizer::OptimizerError> {
let profile = backend.device_profile();
optimize_for_device(program, &profile)
}
fn device_optimize_key(program: &Program, profile: &vyre_driver::DeviceProfile) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(b"vyre-core-optimize-device-v2\0"); hasher.update(&program.fingerprint());
hasher.update(profile.backend.as_bytes());
hasher.update(&[u8::from(profile.supports_subgroup_ops)]);
hasher.update(&[u8::from(profile.supports_indirect_dispatch)]);
hasher.update(&[u8::from(profile.supports_f16)]);
hasher.update(&[u8::from(profile.supports_bf16)]);
hasher.update(&[u8::from(profile.supports_tensor_cores)]);
hasher.update(&[u8::from(profile.supports_specialization_constants)]);
hasher.update(&profile.max_workgroup_size[0].to_le_bytes());
hasher.update(&profile.max_workgroup_size[1].to_le_bytes());
hasher.update(&profile.max_workgroup_size[2].to_le_bytes());
hasher.update(&profile.max_invocations_per_workgroup.to_le_bytes());
hasher.update(&profile.max_shared_memory_bytes.to_le_bytes());
hasher.update(&profile.max_storage_buffer_binding_size.to_le_bytes());
hasher.update(&profile.subgroup_size.to_le_bytes());
hasher.update(&profile.compute_units.to_le_bytes());
hasher.update(&profile.regs_per_thread_max.to_le_bytes());
hasher.update(&profile.l1_cache_bytes.to_le_bytes());
hasher.update(&profile.l2_cache_bytes.to_le_bytes());
hasher.update(&profile.mem_bw_gbps.to_le_bytes());
hasher.update(&profile.ideal_unroll_depth.to_le_bytes());
hasher.update(&profile.ideal_vector_pack_bits.to_le_bytes());
hasher.update(&profile.ideal_workgroup_tile[0].to_le_bytes());
hasher.update(&profile.ideal_workgroup_tile[1].to_le_bytes());
hasher.update(&profile.ideal_workgroup_tile[2].to_le_bytes());
hasher.update(&profile.shared_memory_bank_count.to_le_bytes());
hasher.update(&profile.shared_memory_bank_width_bytes.to_le_bytes());
*hasher.finalize().as_bytes()
}
pub const OPTIMIZE_CACHE_CAPACITY: usize = 256;
mod optimize_cache {
use super::Program;
use super::OPTIMIZE_CACHE_CAPACITY;
use std::collections::{HashMap, VecDeque};
use std::sync::Mutex;
struct ProgramCacheShard {
entries: HashMap<[u8; 32], Program>,
fifo: VecDeque<[u8; 32]>,
}
impl ProgramCacheShard {
fn new() -> Self {
Self {
entries: HashMap::with_capacity(OPTIMIZE_CACHE_CAPACITY),
fifo: VecDeque::with_capacity(OPTIMIZE_CACHE_CAPACITY),
}
}
fn get(&self, key: &[u8; 32]) -> Option<Program> {
self.entries.get(key).cloned()
}
fn put(&mut self, key: [u8; 32], program: &Program) {
if self.entries.contains_key(&key) {
return;
}
if self.entries.len() >= OPTIMIZE_CACHE_CAPACITY {
if let Some(evicted) = self.fifo.pop_front() {
self.entries.remove(&evicted);
}
}
self.fifo.push_back(key);
self.entries.insert(key, program.clone());
}
#[cfg(test)]
fn clear(&mut self) {
self.entries.clear();
self.fifo.clear();
}
#[cfg(test)]
fn len(&self) -> usize {
self.entries.len()
}
}
struct Cache {
host: ProgramCacheShard,
device: ProgramCacheShard,
}
impl Cache {
fn new() -> Self {
Self {
host: ProgramCacheShard::new(),
device: ProgramCacheShard::new(),
}
}
}
fn cache() -> &'static Mutex<Cache> {
use std::sync::OnceLock;
static CACHE: OnceLock<Mutex<Cache>> = OnceLock::new();
CACHE.get_or_init(|| Mutex::new(Cache::new()))
}
pub(super) fn get(key: &[u8; 32]) -> Option<Program> {
let cache = cache()
.lock()
.unwrap_or_else(|e| e.into_inner());
cache.host.get(key)
}
pub(super) fn put(key: [u8; 32], program: &Program) {
let mut cache = cache()
.lock()
.unwrap_or_else(|e| e.into_inner());
cache.host.put(key, program);
}
pub(super) fn get_device(key: &[u8; 32]) -> Option<Program> {
let cache = cache()
.lock()
.unwrap_or_else(|e| e.into_inner());
cache.device.get(key)
}
pub(super) fn put_device(key: [u8; 32], program: &Program) {
let mut cache = cache()
.lock()
.unwrap_or_else(|e| e.into_inner());
cache.device.put(key, program);
}
#[cfg(test)]
pub(super) fn clear() {
let mut cache = cache().lock().unwrap_or_else(|e| e.into_inner());
cache.host.clear();
cache.device.clear();
}
#[cfg(test)]
pub(super) fn len() -> usize {
cache().lock().unwrap_or_else(|e| e.into_inner()).host.len()
}
#[cfg(test)]
pub(super) fn len_device() -> usize {
cache().lock().unwrap_or_else(|e| e.into_inner()).device.len()
}
#[cfg(test)]
pub(super) fn test_serial() -> std::sync::MutexGuard<'static, ()> {
use std::sync::{Mutex, OnceLock};
static M: OnceLock<Mutex<()>> = OnceLock::new();
M.get_or_init(|| Mutex::new(()))
.lock()
.unwrap_or_else(|e| e.into_inner())
}
}
#[cfg(test)]
mod optimize_tests {
use super::*;
use std::sync::MutexGuard;
use vyre_foundation::ir::{BufferDecl, DataType, Expr, Node};
fn serial() -> MutexGuard<'static, ()> {
optimize_cache::test_serial()
}
fn sample_program() -> Program {
Program::wrapped(
vec![BufferDecl::output("out", 0, DataType::U32).with_count(1)],
[1, 1, 1],
vec![Node::store("out", Expr::u32(0), Expr::u32(42))],
)
}
fn entry_stores_literal(program: &Program, expected_value: u32) -> bool {
use vyre_foundation::ir::Node;
fn node_stores_literal(n: &Node, expected_value: u32) -> bool {
match n {
Node::Store {
value: Expr::LitU32(v),
..
} => *v == expected_value,
Node::Region { body, .. } => body
.iter()
.any(|child| node_stores_literal(child, expected_value)),
_ => false,
}
}
program
.entry()
.iter()
.any(|n| node_stores_literal(n, expected_value))
}
#[test]
fn optimize_is_cached_by_fingerprint() {
let _g = serial();
optimize_cache::clear();
let p1 = sample_program();
let p2 = sample_program();
let first = optimize(p1).expect("Fix: optimize must succeed on sample_program");
assert!(
entry_stores_literal(&first, 42),
"optimized sample_program must contain a Store of LitU32(42): {:?}",
first.entry()
);
let before = optimize_cache::len();
let second = optimize(p2).expect("Fix: optimize must succeed on cache-hit path");
assert!(
entry_stores_literal(&second, 42),
"cache-hit optimized sample_program must contain a Store of LitU32(42): {:?}",
second.entry()
);
let after = optimize_cache::len();
assert_eq!(
before, after,
"second optimize on identical fingerprint must hit the cache"
);
assert_eq!(before, 1, "cache must contain exactly one entry");
}
#[test]
fn optimize_returns_equivalent_program_on_cache_hit() {
let _g = serial();
optimize_cache::clear();
let p = sample_program();
let first = optimize(p.clone()).expect("Fix: optimize must succeed on sample_program");
let second = optimize(p).expect("Fix: optimize must succeed on cache-hit path");
assert_eq!(
first.fingerprint(),
second.fingerprint(),
"cache hit must return a Program with identical fingerprint"
);
}
fn program_with_literal(value: u32) -> Program {
Program::wrapped(
vec![BufferDecl::output("out", 0, DataType::U32).with_count(1)],
[1, 1, 1],
vec![Node::store("out", Expr::u32(0), Expr::u32(value))],
)
}
#[test]
fn optimize_cache_evicts_at_capacity() {
let _g = serial();
optimize_cache::clear();
let first_key = {
let p0 = program_with_literal(0);
let key = p0.fingerprint();
let opt = optimize(p0).expect("Fix: optimize must succeed on first eviction probe");
assert!(
entry_stores_literal(&opt, 0),
"optimize of literal-0 program must preserve LitU32(0): {:?}",
opt.entry()
);
key
};
for i in 1..=(OPTIMIZE_CACHE_CAPACITY) {
let prog = program_with_literal(i as u32);
let optimized =
optimize(prog).expect("Fix: optimize must succeed on cache-eviction probe");
assert!(
entry_stores_literal(&optimized, i as u32),
"optimized program must store literal {i}: {:?}",
optimized.entry()
);
}
assert_eq!(
optimize_cache::len(),
OPTIMIZE_CACHE_CAPACITY,
"cache must cap at OPTIMIZE_CACHE_CAPACITY entries"
);
assert!(
optimize_cache::get(&first_key).is_none(),
"Fix: FIFO eviction must have removed the first-inserted entry (literal 0)"
);
let last_key = program_with_literal(OPTIMIZE_CACHE_CAPACITY as u32).fingerprint();
assert!(
optimize_cache::get(&last_key).is_some(),
"Fix: last-inserted entry (literal {OPTIMIZE_CACHE_CAPACITY}) must survive eviction"
);
let cached = optimize_cache::get(&last_key)
.expect("Fix: surviving cache entry must be retrievable");
assert!(
entry_stores_literal(&cached, OPTIMIZE_CACHE_CAPACITY as u32),
"cached surviving program must store the correct literal {OPTIMIZE_CACHE_CAPACITY}: {:?}",
cached.entry()
);
}
fn program_with_buffer_and_workgroup_1_1_1() -> Program {
Program::wrapped(
vec![BufferDecl::output("out", 0, DataType::U32).with_count(4096)],
[1, 1, 1],
vec![Node::store("out", Expr::u32(0), Expr::u32(1))],
)
}
#[test]
fn optimize_for_device_uses_device_specific_cache() {
let _g = serial();
optimize_cache::clear();
let mut profile = vyre_driver::DeviceProfile::conservative("test");
profile.max_workgroup_size = [256, 1, 1];
profile.max_invocations_per_workgroup = 256;
let p1 = sample_program();
let p2 = sample_program();
let first =
optimize_for_device(p1, &profile).expect("Fix: optimize_for_device must succeed");
let second = optimize_for_device(p2, &profile)
.expect("Fix: optimize_for_device must succeed on cache hit");
assert_eq!(first.fingerprint(), second.fingerprint());
assert_eq!(
optimize_cache::len_device(),
1,
"same program+device profile must hit the device optimize cache"
);
assert_eq!(
optimize_cache::len(),
1,
"device optimization should still reuse the canonical optimize cache after tuning"
);
}
#[test]
fn optimize_for_device_different_ideal_workgroup_tile_produces_different_cached_program() {
let _g = serial();
optimize_cache::clear();
let p = program_with_buffer_and_workgroup_1_1_1();
let mut compact = vyre_driver::DeviceProfile::conservative("test");
compact.max_workgroup_size = [256, 256, 64];
compact.max_invocations_per_workgroup = 256;
compact.subgroup_size = 32;
compact.ideal_workgroup_tile = [8, 8, 1];
let mut wide = compact.clone();
wide.ideal_workgroup_tile = [16, 16, 1];
let r_compact = optimize_for_device(p.clone(), &compact)
.expect("Fix: optimize_for_device with compact tile must succeed");
let r_wide = optimize_for_device(p.clone(), &wide)
.expect("Fix: optimize_for_device with wide tile must succeed");
assert_ne!(
r_compact.fingerprint(),
r_wide.fingerprint(),
"Fix: different ideal_workgroup_tile must produce different optimized programs \
(different cache keys); if this fails, device_optimize_key is missing \
ideal_workgroup_tile[0..2]"
);
assert_eq!(
r_compact.workgroup_size(),
[64, 1, 1],
"compact ideal_workgroup_tile [8,8,1] must autotune to workgroup [64,1,1]"
);
assert_eq!(
r_wide.workgroup_size(),
[256, 1, 1],
"wide ideal_workgroup_tile [16,16,1] must autotune to workgroup [256,1,1]"
);
}
}
#[cfg(test)]
mod optimize_cache_runtime_tests {
use super::*;
use std::sync::MutexGuard;
use vyre_foundation::ir::{BufferDecl, DataType, Expr, Node};
fn serial() -> MutexGuard<'static, ()> {
optimize_cache::test_serial()
}
fn program_with_literal(value: u32) -> Program {
Program::wrapped(
vec![BufferDecl::output("out", 0, DataType::U32).with_count(1)],
[1, 1, 1],
vec![Node::store("out", Expr::u32(0), Expr::u32(value))],
)
}
#[test]
fn host_cache_fifo_eviction() {
let _g = serial();
optimize_cache::clear();
let first_key = program_with_literal(0).fingerprint();
for i in 0..=OPTIMIZE_CACHE_CAPACITY {
let p = program_with_literal(i as u32);
optimize(p).expect("Fix: optimize must succeed filling host cache");
}
assert_eq!(
optimize_cache::len(),
OPTIMIZE_CACHE_CAPACITY,
"host cache must stay at capacity after inserting CAPACITY+1 entries"
);
assert!(
optimize_cache::get(&first_key).is_none(),
"Fix: FIFO eviction must remove the first-inserted host cache entry"
);
}
#[test]
fn device_cache_fifo_eviction() {
let _g = serial();
optimize_cache::clear();
let mut profile = vyre_driver::DeviceProfile::conservative("test");
profile.max_workgroup_size = [256, 1, 1];
profile.max_invocations_per_workgroup = 256;
let first_key = {
let p = program_with_literal(0);
let key = super::device_optimize_key(&p, &profile);
optimize_for_device(p, &profile)
.expect("Fix: optimize_for_device must succeed filling device cache");
key
};
for i in 1..=OPTIMIZE_CACHE_CAPACITY {
let p = program_with_literal(i as u32);
optimize_for_device(p, &profile)
.expect("Fix: optimize_for_device must succeed in device eviction fill");
}
assert_eq!(
optimize_cache::len_device(),
OPTIMIZE_CACHE_CAPACITY,
"device cache must stay at capacity after inserting CAPACITY+1 entries"
);
assert!(
optimize_cache::get_device(&first_key).is_none(),
"Fix: FIFO eviction must remove the first-inserted device cache entry"
);
}
#[test]
fn host_and_device_caches_are_independent() {
let _g = serial();
optimize_cache::clear();
let p = program_with_literal(99);
let host_key = p.fingerprint();
optimize(p.clone()).expect("Fix: optimize must populate host cache");
assert!(
optimize_cache::get(&host_key).is_some(),
"Fix: host cache must contain the entry just inserted"
);
assert_eq!(
optimize_cache::len_device(),
0,
"device cache must be empty when only host-optimize was called"
);
}
}