pub use burn_std::{
DeviceError, DeviceSettings, ExecutionError, backtrace::BackTrace, device::DeviceId,
};
#[cfg(feature = "cubecl")]
pub use burn_backend::cubecl::{ThroughputKey, ThroughputValue};
use burn_backend::{Backend, DeviceOps};
#[allow(unused)]
use burn_dispatch::DispatchDeviceId;
use burn_dispatch::{Dispatch, DispatchDevice};
use burn_std::{BoolDType, FloatDType, IntDType, TensorData};
#[cfg(feature = "remote-websocket")]
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
#[cfg_attr(
feature = "autodiff",
doc = "Wrap a device with [`.autodiff()`](Device::autodiff) to enable automatic differentiation with the device."
)]
#[cfg_attr(
not(feature = "autodiff"),
doc = "Enable the `autodiff` feature to add automatic differentiation support to devices."
)]
pub struct Device {
blob: device_opaque::Opaque,
}
burn_std::obfuscate!(
type: DispatchDevice,
module: device_opaque,
derives: [Send, Sync]
);
impl Clone for Device {
fn clone(&self) -> Self {
Self::new(self.as_dispatch().clone())
}
}
impl Default for Device {
fn default() -> Self {
Self::new(DispatchDevice::default())
}
}
impl core::fmt::Debug for Device {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Device<{:?}>", self.as_dispatch())
}
}
#[allow(clippy::partialeq_ne_impl)]
impl PartialEq for Device {
fn eq(&self, other: &Self) -> bool {
self.as_dispatch() == other.as_dispatch()
}
fn ne(&self, other: &Self) -> bool {
!self.eq(other)
}
}
impl Eq for Device {}
impl Device {
pub fn new(device: impl Into<DispatchDevice>) -> Self {
Self {
blob: device_opaque::Opaque::new(device.into()),
}
}
pub fn as_dispatch(&self) -> &DispatchDevice {
self.blob.as_ref()
}
pub(crate) fn into_dispatch(self) -> DispatchDevice {
self.blob.into_inner()
}
}
impl<D: Into<DispatchDevice>> From<D> for Device {
fn from(device: D) -> Self {
Self::new(device)
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
pub enum DeviceIndex {
Specified(usize),
#[default]
Default,
}
impl DeviceIndex {
pub fn new(index: impl Into<usize>) -> Self {
Self::Specified(index.into())
}
#[allow(dead_code)]
fn resolve(self) -> usize {
match self {
DeviceIndex::Specified(i) => i,
DeviceIndex::Default => 0,
}
}
}
impl From<usize> for DeviceIndex {
fn from(i: usize) -> Self {
Self::Specified(i)
}
}
impl From<u32> for DeviceIndex {
fn from(i: u32) -> Self {
Self::Specified(i as usize)
}
}
impl From<u64> for DeviceIndex {
fn from(i: u64) -> Self {
Self::Specified(i as usize)
}
}
impl From<i32> for DeviceIndex {
fn from(i: i32) -> Self {
Self::Specified(usize::try_from(i).expect("device index must be non-negative"))
}
}
impl From<i64> for DeviceIndex {
fn from(i: i64) -> Self {
Self::Specified(usize::try_from(i).expect("device index must be non-negative"))
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, Default)]
pub enum DeviceKind {
DiscreteGpu(usize),
IntegratedGpu(usize),
VirtualGpu(usize),
Cpu,
#[default]
DefaultDevice,
Existing(u32),
}
impl Device {
#[cfg(feature = "cpu")]
pub fn cpu() -> Self {
Self::new(burn_dispatch::devices::CpuDevice::default())
}
#[cfg(feature = "cuda")]
pub fn cuda(index: impl Into<DeviceIndex>) -> Self {
Self::new(burn_dispatch::devices::CudaDevice::new(
index.into().resolve(),
))
}
#[cfg(feature = "rocm")]
pub fn rocm(index: impl Into<DeviceIndex>) -> Self {
Self::new(burn_dispatch::devices::RocmDevice::new(
index.into().resolve(),
))
}
#[cfg(feature = "flex")]
pub fn flex() -> Self {
Self::new(burn_dispatch::devices::FlexDevice)
}
#[cfg(feature = "ndarray")]
pub fn ndarray() -> Self {
Self::new(burn_dispatch::devices::NdArrayDevice::default())
}
#[cfg(feature = "tch")]
pub fn libtorch() -> Self {
Self::new(burn_dispatch::devices::LibTorchDevice::Cpu)
}
#[cfg(feature = "tch")]
pub fn libtorch_cuda(index: impl Into<DeviceIndex>) -> Self {
Self::new(burn_dispatch::devices::LibTorchDevice::Cuda(
index.into().resolve(),
))
}
#[cfg(feature = "tch")]
pub fn libtorch_mps() -> Self {
Self::new(burn_dispatch::devices::LibTorchDevice::Mps)
}
#[cfg(feature = "tch")]
pub fn libtorch_vulkan() -> Self {
Self::new(burn_dispatch::devices::LibTorchDevice::Vulkan)
}
#[cfg(feature = "remote-websocket")]
pub fn remote_websocket(address: &str, index: impl Into<DeviceIndex>) -> Self {
let index = index.into().resolve();
let device = burn_dispatch::devices::RemoteDevice::websocket(address, index);
device.connect(); Self::new(device)
}
#[cfg(all(feature = "remote", not(target_family = "wasm")))]
pub fn remote_iroh(
endpoint: &burn_dispatch::backends::remote::Endpoint,
peer: impl Into<burn_dispatch::backends::remote::EndpointAddr>,
index: impl Into<DeviceIndex>,
) -> Self {
let index = index.into().resolve();
let device =
burn_dispatch::backends::remote::RemoteDevice::iroh(endpoint, peer.into(), index);
device.connect();
Self::new(device)
}
#[cfg(all(feature = "remote", any(target_family = "wasm", doc)))]
pub async fn remote_iroh_async(
endpoint: &burn_dispatch::backends::remote::Endpoint,
peer: impl Into<burn_dispatch::backends::remote::EndpointAddr>,
index: impl Into<DeviceIndex>,
) -> Self {
let index = index.into().resolve();
let device =
burn_dispatch::backends::remote::RemoteDevice::iroh(endpoint, peer.into(), index);
device.connect_async().await;
Self::new(device)
}
#[cfg(all(feature = "remote", not(target_family = "wasm")))]
pub fn remote_iroh_authorized(
endpoint: &burn_dispatch::backends::remote::Endpoint,
peer: impl Into<burn_dispatch::backends::remote::EndpointAddr>,
index: impl Into<DeviceIndex>,
credential: Vec<u8>,
) -> Self {
let index = index.into().resolve();
let device = burn_dispatch::backends::remote::RemoteDevice::iroh_authorized(
endpoint,
peer.into(),
index,
credential,
);
device.connect();
Self::new(device)
}
#[cfg(all(feature = "remote", any(target_family = "wasm", doc)))]
pub async fn remote_iroh_authorized_async(
endpoint: &burn_dispatch::backends::remote::Endpoint,
peer: impl Into<burn_dispatch::backends::remote::EndpointAddr>,
index: impl Into<DeviceIndex>,
credential: Vec<u8>,
) -> Self {
let index = index.into().resolve();
let device = burn_dispatch::backends::remote::RemoteDevice::iroh_authorized(
endpoint,
peer.into(),
index,
credential,
);
device.connect_async().await;
Self::new(device)
}
#[cfg(feature = "wgpu")]
pub fn wgpu(device_kind: DeviceKind) -> Self {
Self::new(DispatchDevice::Wgpu(wgpu_device(device_kind)))
}
#[cfg(all(feature = "wgpu", target_family = "wasm"))]
pub async fn wgpu_async(device_kind: DeviceKind) -> Self {
Self::new(DispatchDevice::Wgpu(wgpu_init_async(device_kind).await))
}
#[cfg(feature = "vulkan")]
pub fn vulkan(device_kind: DeviceKind) -> Self {
Self::new(DispatchDevice::Vulkan(wgpu_device(device_kind)))
}
#[cfg(feature = "metal")]
pub fn metal(device_kind: DeviceKind) -> Self {
Self::new(DispatchDevice::Metal(wgpu_device(device_kind)))
}
#[cfg(feature = "webgpu")]
pub fn webgpu(device_kind: DeviceKind) -> Self {
Self::new(DispatchDevice::WebGpu(wgpu_device(device_kind)))
}
#[cfg(feature = "autodiff")]
pub fn autodiff(self) -> Self {
match self.into_dispatch() {
DispatchDevice::Autodiff(_) => unimplemented!("Only first-order autodiff is supported"),
other => Self::new(DispatchDevice::autodiff(other)),
}
}
#[cfg(feature = "autodiff")]
pub fn gradient_checkpointing(self) -> Self {
match self.into_dispatch() {
DispatchDevice::Autodiff(device) => {
use burn_dispatch::CheckpointingStrategy;
Self::new(DispatchDevice::autodiff_checkpointed(
device.inner(),
CheckpointingStrategy::Balanced,
))
}
_ => panic!("Autodiff is not enabled on this device"),
}
}
pub fn inner(self) -> Self {
if self.is_autodiff() {
Self::new(self.into_dispatch().inner())
} else {
self
}
}
pub fn sync(&self) -> Result<(), ExecutionError> {
Dispatch::sync(self.as_dispatch())
}
pub fn flush(&self) {
Dispatch::flush(self.as_dispatch())
}
pub fn seed(&self, seed: u64) {
Dispatch::seed(self.as_dispatch(), seed)
}
pub fn is_autodiff(&self) -> bool {
Dispatch::ad_enabled(self.as_dispatch())
}
pub fn supports_dtype(&self, dtype: impl Into<burn_std::DType>) -> bool {
Dispatch::supports_dtype(self.as_dispatch(), dtype.into())
}
pub fn memory_persistent_allocations<
Output: Send,
Input: Send,
Func: Fn(Input) -> Output + Send,
>(
&self,
input: Input,
func: Func,
) -> Output {
Dispatch::memory_persistent_allocations(self.as_dispatch(), input, func)
}
pub fn memory_cleanup(&self) {
Dispatch::memory_cleanup(self.as_dispatch());
}
pub fn staging<'a, Iter>(&self, data: Iter)
where
Iter: Iterator<Item = &'a mut TensorData>,
{
Dispatch::staging(data, self.as_dispatch());
}
pub fn settings(&self) -> DeviceSettings {
burn_backend::get_device_settings::<Dispatch>(self.as_dispatch())
}
pub fn configure(&mut self, config: impl Into<DeviceConfig>) -> Result<(), DeviceError> {
let mut config = config.into();
let defaults = self.as_dispatch().defaults();
let float_dtype = config.float_dtype.take().unwrap_or(defaults.float_dtype);
let int_dtype = config.int_dtype.take().unwrap_or(defaults.int_dtype);
let bool_dtype = config.bool_dtype.take().unwrap_or(defaults.bool_dtype);
burn_backend::set_default_dtypes::<Dispatch>(
self.as_dispatch(),
float_dtype,
int_dtype,
bool_dtype,
)
}
pub fn enumerate(filter: impl Into<DeviceFilter>) -> Devices {
#[allow(unused)]
let mut devices = Vec::new();
#[allow(clippy::never_loop)] for device_type in filter.into() {
#[allow(unused)]
let type_id = match device_type {
#[cfg(feature = "cpu")]
DeviceType::Cpu => DispatchDeviceId::Cpu,
#[cfg(feature = "cuda")]
DeviceType::Cuda => DispatchDeviceId::Cuda,
#[cfg(feature = "rocm")]
DeviceType::Rocm => DispatchDeviceId::Rocm,
#[cfg(feature = "wgpu")]
DeviceType::Wgpu => DispatchDeviceId::Wgpu,
#[cfg(feature = "metal")]
DeviceType::Metal => DispatchDeviceId::Metal,
#[cfg(feature = "vulkan")]
DeviceType::Vulkan => DispatchDeviceId::Vulkan,
#[cfg(feature = "webgpu")]
DeviceType::WebGpu => DispatchDeviceId::WebGpu,
#[cfg(feature = "flex")]
DeviceType::Flex => DispatchDeviceId::Flex,
#[cfg(feature = "ndarray")]
DeviceType::NdArray => DispatchDeviceId::NdArray,
#[cfg(feature = "tch")]
DeviceType::LibTorch => DispatchDeviceId::LibTorch,
#[cfg(feature = "remote-websocket")]
DeviceType::Remote(address) => {
for device in Dispatch::enumerate_remote_websocket(&address) {
devices.push(Device::new(device));
}
continue;
}
};
#[allow(unreachable_code)] for device in Dispatch::enumerate(type_id) {
devices.push(Device::new(device))
}
}
Devices(devices)
}
#[cfg(feature = "cubecl")]
pub fn performance_stats(&self, keys: &[ThroughputKey]) -> Vec<ThroughputStat> {
self.as_dispatch()
.performance_stats(keys)
.into_iter()
.zip(keys.iter().copied())
.map(|(value, key)| ThroughputStat { key, value })
.collect()
}
}
#[cfg(feature = "cubecl")]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ThroughputStat {
pub key: ThroughputKey,
pub value: ThroughputValue,
}
#[cfg(feature = "cubecl")]
impl core::fmt::Display for ThroughputStat {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mode = alloc::format!("{:?}", self.key.mode);
let dtype = alloc::format!("{}", self.key.dtype);
let value = self.value.format(&self.key);
write!(f, "{mode:<14} {dtype:<5} {value}")
}
}
#[cfg(feature = "wgpu")]
fn wgpu_device(device_kind: DeviceKind) -> burn_dispatch::devices::WgpuDevice {
use burn_dispatch::devices::WgpuDevice;
match device_kind {
DeviceKind::DiscreteGpu(i) => WgpuDevice::DiscreteGpu(i),
DeviceKind::IntegratedGpu(i) => WgpuDevice::IntegratedGpu(i),
DeviceKind::VirtualGpu(i) => WgpuDevice::VirtualGpu(i),
DeviceKind::Cpu => WgpuDevice::Cpu,
DeviceKind::DefaultDevice => WgpuDevice::DefaultDevice,
DeviceKind::Existing(id) => WgpuDevice::Existing(id),
}
}
#[cfg(all(feature = "wgpu", target_family = "wasm"))]
async fn wgpu_init_async(device_kind: DeviceKind) -> burn_dispatch::devices::WgpuDevice {
use burn_dispatch::backends::wgpu::{graphics::AutoGraphicsApi, init_setup_async};
let device = wgpu_device(device_kind);
init_setup_async::<AutoGraphicsApi>(&device, Default::default()).await;
device
}
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeviceType {
#[cfg(feature = "cpu")]
Cpu,
#[cfg(feature = "cuda")]
Cuda,
#[cfg(feature = "rocm")]
Rocm,
#[cfg(feature = "wgpu")]
Wgpu,
#[cfg(feature = "metal")]
Metal,
#[cfg(feature = "vulkan")]
Vulkan,
#[cfg(feature = "webgpu")]
WebGpu,
#[cfg(feature = "flex")]
Flex,
#[cfg(feature = "ndarray")]
NdArray,
#[cfg(feature = "tch")]
LibTorch,
#[cfg(feature = "remote-websocket")]
Remote(String),
}
#[cfg(feature = "remote-websocket")]
impl DeviceType {
pub fn remote_websocket(address: impl Into<String>) -> Self {
DeviceType::Remote(address.into())
}
}
#[derive(Debug, Clone, Default)]
pub struct DeviceFilter(Vec<DeviceType>);
impl DeviceFilter {
pub fn new() -> Self {
Self::default()
}
pub fn with(mut self, device_type: DeviceType) -> Self {
self.0.push(device_type);
self
}
}
impl From<DeviceType> for DeviceFilter {
fn from(value: DeviceType) -> Self {
DeviceFilter(vec![value])
}
}
impl From<Vec<DeviceType>> for DeviceFilter {
fn from(value: Vec<DeviceType>) -> Self {
DeviceFilter(value)
}
}
impl IntoIterator for DeviceFilter {
type Item = DeviceType;
type IntoIter = alloc::vec::IntoIter<DeviceType>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl core::ops::BitOr for DeviceType {
type Output = DeviceFilter;
fn bitor(self, rhs: Self) -> DeviceFilter {
DeviceFilter(vec![self, rhs])
}
}
impl core::ops::BitOr<DeviceType> for DeviceFilter {
type Output = DeviceFilter;
fn bitor(mut self, rhs: DeviceType) -> DeviceFilter {
self.0.push(rhs);
self
}
}
#[derive(new, Debug, Clone, Default)]
pub struct DeviceConfig {
pub float_dtype: Option<FloatDType>,
pub int_dtype: Option<IntDType>,
pub bool_dtype: Option<BoolDType>,
}
impl DeviceConfig {
pub fn float_dtype(mut self, dtype: impl Into<FloatDType>) -> Self {
self.float_dtype = Some(dtype.into());
self
}
pub fn int_dtype(mut self, dtype: impl Into<IntDType>) -> Self {
self.int_dtype = Some(dtype.into());
self
}
pub fn bool_dtype(mut self, dtype: impl Into<BoolDType>) -> Self {
self.bool_dtype = Some(dtype.into());
self
}
}
impl From<FloatDType> for DeviceConfig {
fn from(value: FloatDType) -> Self {
DeviceConfig::new(Some(value), None, None)
}
}
impl From<IntDType> for DeviceConfig {
fn from(value: IntDType) -> Self {
DeviceConfig::new(None, Some(value), None)
}
}
impl From<BoolDType> for DeviceConfig {
fn from(value: BoolDType) -> Self {
DeviceConfig::new(None, None, Some(value))
}
}
impl From<(FloatDType, IntDType)> for DeviceConfig {
fn from(value: (FloatDType, IntDType)) -> Self {
DeviceConfig::new(Some(value.0), Some(value.1), None)
}
}
pub struct Devices(Vec<Device>);
impl Devices {
#[cfg(feature = "autodiff")]
pub fn autodiff(mut self) -> Self {
for device in &mut self.0 {
*device = core::mem::take(device).autodiff();
}
self
}
pub fn configure(&mut self, config: impl Into<DeviceConfig>) -> Result<(), DeviceError> {
let config = config.into();
for device in &mut self.0 {
device.configure(config.clone())?;
}
Ok(())
}
pub fn into_vec(self) -> Vec<Device> {
self.0
}
}
impl IntoIterator for Devices {
type Item = Device;
type IntoIter = alloc::vec::IntoIter<Device>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl core::ops::Deref for Devices {
type Target = [Device];
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(all(test, feature = "flex", feature = "autodiff"))]
mod autodiff_move_tests {
use crate::{Device, Tensor};
#[test]
fn move_non_autodiff_float_tensor_to_autodiff_device() {
let device = Device::default();
let ad_device = device.clone().autodiff();
let t = Tensor::<2>::from_floats([[1.0, 2.0], [3.0, 4.0]], &device);
let moved = t.to_device(&ad_device);
assert_eq!(
moved.to_data().to_vec::<f32>().unwrap(),
vec![1.0, 2.0, 3.0, 4.0]
);
}
}