Skip to main content

vyre_driver_reference/
lib.rs

1#![forbid(unsafe_code)]
2
3//! Registry adapter that exposes `vyre-reference` as a `VyreBackend`.
4
5use std::sync::Arc;
6
7use vyre_driver::backend::private;
8use vyre_driver::backend::{
9    core_supported_ops, BackendCapability, BackendError, BackendPrecedence, BackendRegistration,
10};
11use vyre_driver::{DispatchConfig, VyreBackend};
12use vyre_foundation::ir::{BufferAccess, BufferDecl, Program};
13use vyre_reference::value::Value;
14
15/// Stable backend id for the pure-Rust reference interpreter.
16pub const CPU_REF_BACKEND_ID: &str = "cpu-ref";
17
18/// Dispatch backend backed by `vyre_reference::reference_eval`.
19#[derive(Debug, Default, Clone, Copy)]
20pub struct CpuRefBackend;
21
22impl private::Sealed for CpuRefBackend {}
23
24impl VyreBackend for CpuRefBackend {
25    fn id(&self) -> &'static str {
26        CPU_REF_BACKEND_ID
27    }
28
29    fn version(&self) -> &'static str {
30        env!("CARGO_PKG_VERSION")
31    }
32
33    fn dispatch(
34        &self,
35        program: &Program,
36        inputs: &[Vec<u8>],
37        config: &DispatchConfig,
38    ) -> Result<Vec<Vec<u8>>, BackendError> {
39        let values = reference_values(program, inputs)?;
40        // The interpreter infers its grid from buffer SHAPES, which cannot express
41        // the per-invocation count of a byte-scan program (the haystack is packed
42        // 4 bytes/u32 and the scan length is a runtime value). When the caller
43        // declares the true element-grid coverage via `dispatch_elements`, pass it
44        // as the interpreter's dispatch floor so high positions are covered exactly
45        // as the real GPU dispatch would, otherwise the tail is silently skipped
46        // (the Law-10 under-coverage this backend used to exhibit). `None` (every
47        // megakernel, whose `grid_override` is a work-queue length, not an element
48        // count) keeps buffer-shape inference so its grid is never over-run.
49        let result = match config.dispatch_elements {
50            Some(elements) => {
51                vyre_reference::reference_eval_with_dispatch(program, &values, elements)
52            }
53            None => vyre_reference::reference_eval(program, &values),
54        };
55        result
56            .map(|outputs| outputs.iter().map(Value::to_bytes).collect())
57            .map_err(|error| {
58                BackendError::new(format!(
59                    "cpu-ref reference dispatch failed: {error}. Fix: validate the Program and input buffer ABI before dispatch."
60                ))
61            })
62    }
63
64    fn supported_ops(&self) -> &std::collections::HashSet<vyre_foundation::ir::OpId> {
65        core_supported_ops()
66    }
67
68    fn max_workgroup_size(&self) -> [u32; 3] {
69        [1024, 1, 1]
70    }
71
72    fn max_compute_workgroups_per_dimension(&self) -> u32 {
73        u32::MAX
74    }
75}
76
77fn reference_values(program: &Program, inputs: &[Vec<u8>]) -> Result<Vec<Value>, BackendError> {
78    // `is_backend_allocated_output` is the SINGLE cross-backend contract in
79    // vyre-foundation, shared verbatim with the reference interpreter, do NOT re-inline
80    // it here (drift would make this backend disagree with the interpreter on outputs).
81    let logical_input_count = program
82        .buffers()
83        .iter()
84        .filter(|buffer| {
85            buffer.access() != BufferAccess::Workgroup && !buffer.is_backend_allocated_output()
86        })
87        .count();
88    let legacy_input_count = program
89        .buffers()
90        .iter()
91        .filter(|buffer| buffer.access() != BufferAccess::Workgroup)
92        .count();
93    let legacy_input_mode =
94        inputs.len() == legacy_input_count && inputs.len() != logical_input_count;
95    let mut next_input = 0usize;
96    let mut values = Vec::new();
97    for buffer in program.buffers() {
98        if buffer.access() == BufferAccess::Workgroup {
99            continue;
100        }
101        let bytes = if buffer.is_backend_allocated_output() {
102            if legacy_input_mode {
103                let _legacy_initializer = inputs.get(next_input).ok_or_else(|| {
104                    BackendError::new(format!(
105                        "cpu-ref missing legacy output initializer for buffer `{}`. Fix: pass one buffer for every non-workgroup declaration or migrate to logical backend inputs.",
106                        buffer.name()
107                    ))
108                })?;
109                next_input += 1;
110            }
111            synthesized_zero_buffer(buffer, "backend-allocated output")?
112        } else if let Some(input) = inputs.get(next_input) {
113            next_input += 1;
114            input.clone()
115        } else {
116            synthesized_zero_buffer(buffer, "missing input")?
117        };
118        values.push(Value::Bytes(Arc::from(bytes)));
119    }
120    if next_input != inputs.len() {
121        return Err(BackendError::new(format!(
122            "cpu-ref received {} extra input buffer(s). Fix: pass inputs in Program::buffers order without trailing buffers.",
123            inputs.len() - next_input
124        )));
125    }
126    Ok(values)
127}
128
129fn synthesized_zero_buffer(
130    buffer: &BufferDecl,
131    role: &'static str,
132) -> Result<Vec<u8>, BackendError> {
133    let element_size = buffer.element().size_bytes().ok_or_else(|| {
134        BackendError::new(format!(
135            "cpu-ref cannot synthesize {role} buffer `{}` because its element type is unsized. Fix: declare fixed-width buffers or pass an explicit input buffer.",
136            buffer.name()
137        ))
138    })?;
139    let byte_len = usize::try_from(buffer.count())
140        .ok()
141        .and_then(|count| count.checked_mul(element_size))
142        .ok_or_else(|| {
143            BackendError::new(format!(
144                "cpu-ref {role} buffer `{}` size overflows usize. Fix: use a representable buffer size.",
145                buffer.name()
146            ))
147        })?;
148    Ok(vec![0u8; byte_len])
149}
150
151fn acquire_cpu_ref() -> Result<Box<dyn VyreBackend>, BackendError> {
152    Ok(Box::new(CpuRefBackend))
153}
154
155inventory::submit! {
156    BackendRegistration {
157        id: CPU_REF_BACKEND_ID,
158        factory: acquire_cpu_ref,
159        supported_ops: core_supported_ops,
160    }
161}
162
163inventory::submit! {
164    BackendCapability {
165        id: CPU_REF_BACKEND_ID,
166        dispatches: true,
167    }
168}
169
170inventory::submit! {
171    BackendPrecedence {
172        id: CPU_REF_BACKEND_ID,
173        rank: 900,
174    }
175}