use crate::lazy::Tensor;
use fuel_dispatch::pipelined::StorageCache;
use fuel_ir::probe::BackendId;
use fuel_ir::{DType, Error, Result};
pub trait Realizer {
fn realize_f32(&mut self, tensor: &Tensor) -> Result<Vec<f32>>;
fn realize_capture_f32(&mut self, tensor: &Tensor) -> Result<Vec<f32>> {
self.realize_f32(tensor)
}
fn last_kernel_source(&self) -> Option<&'static str> {
None
}
fn last_kernel_revision(&self) -> Option<u64> {
None
}
}
struct BridgeRealizer {
device: crate::Device,
cache: StorageCache,
last_kernel_source: Option<&'static str>,
last_kernel_revision: Option<u64>,
}
impl BridgeRealizer {
fn new(device: crate::Device) -> Self {
Self {
device,
cache: StorageCache::new(),
last_kernel_source: None,
last_kernel_revision: None,
}
}
fn realize_as<T: bytemuck::Pod + fuel_ir::WithDType>(
&mut self,
tensor: &Tensor,
) -> Result<Vec<T>> {
let graph = tensor.graph_tensor().graph().clone();
let target = tensor.graph_tensor().id();
let order = {
let g = graph
.read()
.map_err(|_| Error::Msg("graph lock poisoned".into()).bt())?;
fuel_graph::topo_order_multi(&g, &[target])
};
self.cache = crate::pipelined_bridge::build_const_cache(
&graph,
&order,
&self.device,
std::mem::take(&mut self.cache),
)?;
{
let mut g = graph
.write()
.map_err(|_| Error::Msg("graph lock poisoned".into()).bt())?;
let loc = self.device.location();
for &id in &order {
g.set_placement(id, loc);
}
}
let (bytes, root_ident) =
crate::pipelined_bridge::realize_one_as_with_initial_reporting::<T>(
&graph,
target,
&self.device,
self.cache.clone(),
&fuel_ir::SymEnv::default(),
)?;
self.last_kernel_source = root_ident.map(|(s, _)| s);
self.last_kernel_revision = root_ident.map(|(_, r)| r);
Ok(bytes)
}
}
impl Realizer for BridgeRealizer {
fn realize_f32(&mut self, tensor: &Tensor) -> Result<Vec<f32>> {
self.realize_as::<f32>(tensor)
}
fn realize_capture_f32(&mut self, tensor: &Tensor) -> Result<Vec<f32>> {
match tensor.dtype() {
DType::F32 => self.realize_as::<f32>(tensor),
DType::F16 => Ok(self
.realize_as::<half::f16>(tensor)?
.into_iter()
.map(|x| x.to_f32())
.collect()),
DType::BF16 => Ok(self
.realize_as::<half::bf16>(tensor)?
.into_iter()
.map(|x| x.to_f32())
.collect()),
other => Err(Error::Msg(format!(
"judge: realize_capture_f32 has no readback path for root dtype \
{other:?} — only F32/F16/BF16 are wired into the Judge dtype axis",
))
.bt()),
}
}
fn last_kernel_source(&self) -> Option<&'static str> {
self.last_kernel_source
}
fn last_kernel_revision(&self) -> Option<u64> {
self.last_kernel_revision
}
}
pub trait BackendFactory: Send + Sync {
fn id(&self) -> BackendId;
fn try_make_realizer(&self, device_index: u32) -> Result<Box<dyn Realizer>>;
}
pub fn registry() -> Vec<&'static dyn BackendFactory> {
#[allow(unused_mut)]
let mut v: Vec<&'static dyn BackendFactory> = vec![&CpuFactory];
#[cfg(feature = "cuda")]
v.push(&CudaFactory);
#[cfg(feature = "vulkan")]
v.push(&VulkanFactory);
v
}
pub fn factory_for(id: BackendId) -> Option<&'static dyn BackendFactory> {
registry().into_iter().find(|f| f.id() == id)
}
pub struct CpuFactory;
impl BackendFactory for CpuFactory {
fn id(&self) -> BackendId {
BackendId::Cpu
}
fn try_make_realizer(&self, _device_index: u32) -> Result<Box<dyn Realizer>> {
Ok(Box::new(BridgeRealizer::new(crate::Device::cpu())))
}
}
#[cfg(feature = "cuda")]
pub struct CudaFactory;
#[cfg(feature = "cuda")]
impl BackendFactory for CudaFactory {
fn id(&self) -> BackendId {
BackendId::Cuda
}
fn try_make_realizer(&self, device_index: u32) -> Result<Box<dyn Realizer>> {
let dev = fuel_cuda_backend::CudaDevice::new(device_index as usize).map_err(|e| {
fuel_ir::Error::Msg(format!("CudaDevice::new({device_index}) failed: {e}"))
})?;
Ok(Box::new(BridgeRealizer::new(dev.into())))
}
}
#[cfg(feature = "vulkan")]
pub struct VulkanFactory;
#[cfg(feature = "vulkan")]
impl BackendFactory for VulkanFactory {
fn id(&self) -> BackendId {
BackendId::Vulkan
}
fn try_make_realizer(&self, device_index: u32) -> Result<Box<dyn Realizer>> {
let backend = fuel_vulkan_backend::VulkanBackend::with_selection(
fuel_vulkan_backend::DeviceSelection::Index(device_index as usize),
)
.map_err(|e| fuel_ir::Error::Msg(format!("VulkanBackend init failed: {e}")))?;
Ok(Box::new(BridgeRealizer::new(backend.into())))
}
}