pub use burn_std::{
DeviceError, DeviceSettings, ExecutionError, backtrace::BackTrace, device::DeviceId,
};
#[cfg(feature = "cubecl")]
pub use burn_backend::cubecl::{
AdapterLuid, DeviceIdentity, MemoryAccess, PciAddress, PciVendor, PhysicalDevice,
ThroughputError, ThroughputKey, ThroughputMode, ThroughputValue,
};
use burn_backend::{Backend, DeviceOps};
pub use burn_backend::{
MemoryPoolUsage, ProfileDuration, ProfileOptions, ProfileTicks, SlicedPoolReport, TimingMethod,
};
#[allow(unused)]
use burn_dispatch::DispatchDeviceId;
#[cfg(feature = "autodiff")]
use burn_dispatch::GradientCheckpointingStrategy;
#[cfg(any(feature = "cpu", feature = "cuda", feature = "rocm", feature = "wgpu"))]
use burn_dispatch::devices::RuntimeId;
#[cfg(any(feature = "metal", feature = "vulkan", feature = "webgpu"))]
use burn_dispatch::devices::WgpuBackend;
use burn_dispatch::{Dispatch, DispatchDevice};
use burn_std::{BoolDType, FloatDType, IntDType, TensorData};
#[cfg(feature = "capture")]
pub use burn_dispatch::backends::capture::{
CaptureError, CaptureScope, CapturedGraph, CompletedCaptureScope, TensorId,
};
#[cfg(any(feature = "cpu", feature = "cuda", feature = "rocm", feature = "wgpu"))]
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)]
pub(crate) 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 = "capture")]
pub fn capture() -> Self {
Self::new(DispatchDevice::capture())
}
#[cfg(feature = "capture")]
pub fn capture_scope(
&self,
capture: impl FnOnce(CaptureScope) -> CompletedCaptureScope,
) -> Result<CapturedGraph, CaptureError> {
match self.as_dispatch() {
DispatchDevice::Capture(device) => device.capture_scope(capture),
#[allow(unreachable_patterns)] _ => Err(CaptureError::InvalidDevice),
}
}
#[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")]
#[deprecated(
since = "0.22.0",
note = "burn-ndarray is deprecated and will be removed in a future release. Use `Device::flex()` for pure-Rust CPU execution instead."
)]
#[allow(deprecated)] pub fn ndarray() -> Self {
Self::new(burn_dispatch::devices::NdArrayDevice::default())
}
#[cfg(feature = "tch")]
#[deprecated(
since = "0.22.0",
note = "burn-tch is deprecated and will be removed in a future release. Use a CubeCL backend (`Device::cuda`, `Device::rocm`, `Device::metal`, `Device::vulkan`, `Device::cpu`) or `Device::flex()` instead."
)]
#[allow(deprecated)] pub fn libtorch() -> Self {
Self::new(burn_dispatch::devices::LibTorchDevice::Cpu)
}
#[cfg(feature = "tch")]
#[deprecated(
since = "0.22.0",
note = "burn-tch is deprecated and will be removed in a future release. Use a CubeCL backend (`Device::cuda`, `Device::rocm`, `Device::metal`, `Device::vulkan`, `Device::cpu`) or `Device::flex()` instead."
)]
#[allow(deprecated)] pub fn libtorch_cuda(index: impl Into<DeviceIndex>) -> Self {
Self::new(burn_dispatch::devices::LibTorchDevice::Cuda(
index.into().resolve(),
))
}
#[cfg(feature = "tch")]
#[deprecated(
since = "0.22.0",
note = "burn-tch is deprecated and will be removed in a future release. Use a CubeCL backend (`Device::cuda`, `Device::rocm`, `Device::metal`, `Device::vulkan`, `Device::cpu`) or `Device::flex()` instead."
)]
#[allow(deprecated)] pub fn libtorch_mps() -> Self {
Self::new(burn_dispatch::devices::LibTorchDevice::Mps)
}
#[cfg(feature = "tch")]
#[deprecated(
since = "0.22.0",
note = "burn-tch is deprecated and will be removed in a future release. Use a CubeCL backend (`Device::cuda`, `Device::rocm`, `Device::metal`, `Device::vulkan`, `Device::cpu`) or `Device::flex()` instead."
)]
#[allow(deprecated)] pub fn libtorch_vulkan() -> Self {
Self::new(burn_dispatch::devices::LibTorchDevice::Vulkan)
}
#[cfg(feature = "wgpu")]
pub fn wgpu(device_kind: DeviceKind) -> Self {
Self::new(wgpu_device(
device_kind,
burn_dispatch::devices::WgpuBackend::Auto,
))
}
#[cfg(feature = "vulkan")]
pub fn vulkan(device_kind: DeviceKind) -> Self {
Self::new(wgpu_device(
device_kind,
burn_dispatch::devices::WgpuBackend::Vulkan,
))
}
#[cfg(feature = "metal")]
pub fn metal(device_kind: DeviceKind) -> Self {
Self::new(wgpu_device(
device_kind,
burn_dispatch::devices::WgpuBackend::Metal,
))
}
#[cfg(feature = "webgpu")]
pub fn webgpu(device_kind: DeviceKind) -> Self {
Self::new(wgpu_device(
device_kind,
burn_dispatch::devices::WgpuBackend::WebGpu,
))
}
#[cfg(feature = "autodiff")]
#[must_use]
pub fn autodiff(self) -> Self {
match self.into_dispatch() {
device @ DispatchDevice::Autodiff(_) => Self::new(device),
other => Self::new(DispatchDevice::autodiff(other)),
}
}
#[cfg(feature = "autodiff")]
pub fn gradient_checkpointing_strategy(&self) -> Option<GradientCheckpointingStrategy> {
match self.as_dispatch() {
DispatchDevice::Autodiff(device) => Some(device.gradient_checkpointing_strategy()),
_ => None,
}
}
#[cfg(feature = "autodiff")]
#[must_use]
pub fn gradient_checkpointing(self) -> Self {
self.with_gradient_checkpointing_strategy(GradientCheckpointingStrategy::Balanced)
}
#[cfg(feature = "autodiff")]
#[must_use]
pub(crate) fn with_gradient_checkpointing_strategy(
self,
strategy: GradientCheckpointingStrategy,
) -> Self {
match self.into_dispatch() {
DispatchDevice::Autodiff(device) => Self::new(
DispatchDevice::autodiff_with_gradient_checkpointing(device.inner(), strategy),
),
_ => panic!(
"Gradient checkpointing requires autodiff; use Device::autodiff().gradient_checkpointing()"
),
}
}
#[must_use]
pub fn without_autodiff(self) -> Self {
if self.is_autodiff() {
Self::new(self.into_dispatch().inner())
} else {
self
}
}
#[must_use]
#[doc(hidden)]
pub fn with_autodiff_context_from(self, source: &Self) -> Self {
#[cfg(feature = "autodiff")]
{
match source.gradient_checkpointing_strategy() {
Some(strategy) => self
.autodiff()
.with_gradient_checkpointing_strategy(strategy),
None => self.without_autodiff(),
}
}
#[cfg(not(feature = "autodiff"))]
{
let _ = source;
self
}
}
#[must_use]
pub fn inner(self) -> Self {
self.without_autodiff()
}
pub fn sync(&self) -> Result<(), ExecutionError> {
Dispatch::sync(self.as_dispatch())
}
pub fn flush(&self) -> Result<(), ExecutionError> {
Dispatch::flush(self.as_dispatch())
}
pub fn profile<O: Send + 'static>(
&self,
func: impl FnOnce() -> O + Send,
) -> Result<(O, ProfileDuration), ExecutionError> {
self.profile_with(ProfileOptions::default(), func)
}
pub fn profile_with<O: Send + 'static>(
&self,
options: ProfileOptions,
func: impl FnOnce() -> O + Send,
) -> Result<(O, ProfileDuration), ExecutionError> {
Dispatch::profile(self.as_dispatch(), options, func)
}
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 memory_pool_report(&self) -> Option<Vec<SlicedPoolReport>> {
Dispatch::memory_pool_report(self.as_dispatch())
}
pub fn memory_pool_usage(&self) -> Option<MemoryPoolUsage> {
Dispatch::memory_pool_usage(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(crate) fn get_or_init_settings(&self) -> DeviceSettings {
burn_backend::get_or_init_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 => {
push_cube(&mut devices, RuntimeId::Cpu);
continue;
}
#[cfg(feature = "cuda")]
DeviceType::Cuda => {
push_cube(&mut devices, RuntimeId::Cuda);
continue;
}
#[cfg(feature = "rocm")]
DeviceType::Rocm => {
push_cube(&mut devices, RuntimeId::Hip);
continue;
}
#[cfg(feature = "wgpu")]
DeviceType::Wgpu => {
push_cube(&mut devices, RuntimeId::Wgpu);
continue;
}
#[cfg(feature = "metal")]
DeviceType::Metal => {
push_wgpu(&mut devices, WgpuBackend::Metal);
continue;
}
#[cfg(feature = "vulkan")]
DeviceType::Vulkan => {
push_wgpu(&mut devices, WgpuBackend::Vulkan);
continue;
}
#[cfg(feature = "webgpu")]
DeviceType::WebGpu => {
push_wgpu(&mut devices, WgpuBackend::WebGpu);
continue;
}
#[cfg(feature = "flex")]
DeviceType::Flex => DispatchDeviceId::Flex,
#[cfg(feature = "ndarray")]
DeviceType::NdArray => DispatchDeviceId::NdArray,
#[cfg(feature = "tch")]
DeviceType::LibTorch => DispatchDeviceId::LibTorch,
#[cfg(feature = "remote")]
DeviceType::Remote(host) => {
devices.extend(host.enumerate());
continue;
}
};
#[allow(unreachable_code)] for device in Dispatch::enumerate(type_id) {
devices.push(Device::new(device))
}
}
Devices(devices)
}
#[cfg(feature = "cubecl")]
pub fn identity(&self) -> Option<DeviceIdentity> {
self.as_dispatch().identity()
}
#[cfg(any(feature = "cpu", feature = "cuda", feature = "rocm", feature = "wgpu"))]
pub fn enumerate_physical() -> Vec<PhysicalGpu> {
let mut gpus: Vec<PhysicalGpu> = Vec::new();
for device in Dispatch::enumerate(DispatchDeviceId::Cube) {
let device = Device::new(device);
let Some(DeviceIdentity {
name,
physical: Some(physical),
..
}) = device.identity()
else {
continue;
};
match gpus
.iter_mut()
.find(|gpu| gpu.physical.is_same_card(&physical))
{
Some(gpu) => {
gpu.physical.fill_from(&physical);
gpu.devices.0.push(device);
}
None => gpus.push(PhysicalGpu {
physical,
name,
devices: Devices(vec![device]),
}),
}
}
gpus
}
#[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(any(feature = "cpu", feature = "cuda", feature = "rocm", feature = "wgpu"))]
#[derive(Debug, Clone)]
pub struct PhysicalGpu {
pub physical: PhysicalDevice,
pub name: String,
pub devices: Devices,
}
#[cfg(feature = "cubecl")]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ThroughputStat {
pub key: ThroughputKey,
pub value: Result<ThroughputValue, ThroughputError>,
}
#[cfg(feature = "cubecl")]
fn mode_label(mode: &ThroughputMode) -> &'static str {
match mode {
ThroughputMode::ComputeDirect { .. } => "compute-direct",
ThroughputMode::ComputeCmma { .. } => "compute-cmma",
ThroughputMode::Memory(spec) => match spec.access {
MemoryAccess::Copy => "memory",
MemoryAccess::Read => "memory-read",
MemoryAccess::Write => "memory-write",
},
ThroughputMode::Launch => "launch",
}
}
#[cfg(feature = "cubecl")]
impl core::fmt::Display for ThroughputStat {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mode = mode_label(&self.key.mode);
let dtype = match self.key.mode {
ThroughputMode::ComputeDirect { dtype } | ThroughputMode::ComputeCmma { dtype, .. } => {
alloc::format!("{dtype}")
}
ThroughputMode::Memory(_) | ThroughputMode::Launch => alloc::string::String::new(),
};
let value = match &self.value {
Ok(value) => value.format(&self.key),
Err(error) => alloc::format!("{error}"),
};
write!(f, "{mode:<14} {dtype:<5} {value}")
}
}
#[cfg(any(feature = "cpu", feature = "cuda", feature = "rocm", feature = "wgpu"))]
fn push_cube(devices: &mut Vec<Device>, runtime: RuntimeId) {
for device in Dispatch::enumerate_cube(runtime) {
let device = Device::new(device);
if !devices.contains(&device) {
devices.push(device);
}
}
}
#[cfg(any(feature = "metal", feature = "vulkan", feature = "webgpu"))]
fn push_wgpu(devices: &mut Vec<Device>, backend: WgpuBackend) {
push_wgpu_with(
devices,
backend,
burn_dispatch::devices::CubeDevice::enumerate,
);
}
#[cfg(any(feature = "metal", feature = "vulkan", feature = "webgpu"))]
fn push_wgpu_with(
devices: &mut Vec<Device>,
backend: WgpuBackend,
mut enumerate: impl FnMut(DeviceId) -> Vec<DeviceId>,
) {
use burn_dispatch::devices::{CubeDevice, WgpuDevice, WgpuDeviceKind};
for kind in [
WgpuDeviceKind::DiscreteGpu(0),
WgpuDeviceKind::IntegratedGpu(0),
WgpuDeviceKind::VirtualGpu(0),
WgpuDeviceKind::Cpu,
WgpuDeviceKind::Other(0),
] {
let seed = CubeDevice::Wgpu(WgpuDevice::new(kind).on(backend));
for id in enumerate(seed.to_id()) {
let device = Device::new(CubeDevice::from_id(id));
if !devices.contains(&device) {
devices.push(device);
}
}
}
}
#[cfg(feature = "wgpu")]
pub(crate) fn wgpu_device(
device_kind: DeviceKind,
backend: burn_dispatch::devices::WgpuBackend,
) -> burn_dispatch::devices::WgpuDevice {
use burn_dispatch::devices::{WgpuDevice, WgpuDeviceKind};
let kind = match device_kind {
DeviceKind::DiscreteGpu(i) => WgpuDeviceKind::DiscreteGpu(i),
DeviceKind::IntegratedGpu(i) => WgpuDeviceKind::IntegratedGpu(i),
DeviceKind::VirtualGpu(i) => WgpuDeviceKind::VirtualGpu(i),
DeviceKind::Cpu => WgpuDeviceKind::Cpu,
DeviceKind::DefaultDevice => WgpuDeviceKind::DefaultDevice,
DeviceKind::Existing(id) => WgpuDeviceKind::Existing(id),
};
WgpuDevice::new(kind).on(backend)
}
#[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")]
Remote(crate::remote::RemoteHost),
}
#[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)
}
}
#[derive(Debug, Clone)]
pub struct Devices(Vec<Device>);
impl Devices {
#[cfg(feature = "autodiff")]
#[must_use]
pub fn autodiff(mut self) -> Self {
for device in &mut self.0 {
*device = core::mem::take(device).autodiff();
}
self
}
#[must_use]
pub fn without_autodiff(mut self) -> Self {
for device in &mut self.0 {
*device = core::mem::take(device).without_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 FromIterator<Device> for Devices {
fn from_iter<I: IntoIterator<Item = Device>>(devices: I) -> Self {
Self(devices.into_iter().collect())
}
}
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 = "capture"))]
mod capture_tests {
use super::*;
#[cfg(all(feature = "flex", feature = "autodiff"))]
#[test]
#[should_panic(expected = "Cannot move a tensor to a capture device with autodiff enabled")]
fn capture_transfer_rejects_autodiff_context() {
let tensor = crate::Tensor::<1, crate::Int>::from_ints([1, 2], &Device::flex().autodiff());
let capture = Device::capture();
let _ = capture.capture_scope(|scope| {
let _ = tensor.to_device(&capture);
scope.complete([], [])
});
}
#[cfg(all(feature = "flex", feature = "autodiff"))]
#[test]
#[should_panic(expected = "Cannot move a tensor to a capture device with autodiff enabled")]
fn capture_float_transfer_rejects_autodiff_context() {
let tensor =
crate::Tensor::<1>::from_floats([1.0, 2.0], &Device::flex().autodiff()).require_grad();
let capture = Device::capture();
let _ = capture.capture_scope(|scope| {
let _ = tensor.to_device(&capture);
scope.complete([], [])
});
}
#[test]
fn user_facing_capture_device_supports_repeated_scopes() {
let device = Device::capture();
let first = device
.capture_scope(|scope| scope.complete([], []))
.unwrap();
let second = device
.capture_scope(|scope| scope.complete([], []))
.unwrap();
assert!(first.graph.operations.is_empty());
assert!(second.graph.operations.is_empty());
}
#[test]
fn capture_scope_rejects_a_non_capture_device() {
let device = Device::default();
let result = device.capture_scope(|scope| scope.complete([], []));
assert!(matches!(result, Err(CaptureError::InvalidDevice)));
}
#[test]
fn capture_device_reports_recordable_dtype_support() {
let device = Device::capture();
let captured = device.capture_scope(|scope| {
assert!(device.supports_dtype(FloatDType::F32));
assert!(device.supports_dtype(FloatDType::F64));
assert!(device.supports_dtype(FloatDType::BF16));
assert!(device.supports_dtype(IntDType::I32));
assert!(device.supports_dtype(BoolDType::Native));
scope.complete([], [])
});
assert!(captured.is_ok());
}
}
#[cfg(all(test, feature = "flex", feature = "autodiff"))]
mod autodiff_move_tests {
use crate::{Bool, Device, GradientCheckpointingStrategy, Int, Tensor, TensorData};
#[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!(!moved.is_autodiff());
assert!(!moved.is_tracked());
assert_eq!(
moved.try_into_vec_as::<f32>().unwrap(),
vec![1.0, 2.0, 3.0, 4.0]
);
}
#[test]
fn move_autodiff_tensor_preserves_association_strategy_and_graph() {
let device = Device::default();
let ad_device = device.clone().autodiff().gradient_checkpointing();
let tensor = Tensor::<1>::from_floats([1.0, 2.0], &ad_device).require_grad();
let moved = tensor.clone().to_device(&device);
assert!(moved.is_autodiff());
assert!(moved.is_tracked());
assert!(!moved.is_require_grad());
assert_eq!(
moved.gradient_checkpointing_strategy(),
Some(GradientCheckpointingStrategy::Balanced)
);
let grads = moved.sum().backward();
tensor
.grad(&grads)
.expect("the transfer should preserve the graph")
.into_data()
.assert_eq(&TensorData::from([1.0f32, 1.0]), true);
}
#[test]
fn move_non_float_tensors_preserves_context() {
let device = Device::default();
let ad_device = device.clone().autodiff().gradient_checkpointing();
let plain_int = Tensor::<1, Int>::from_ints([1, 2], &device).to_device(&ad_device);
assert!(!plain_int.is_autodiff());
let autodiff_bool =
Tensor::<1, Bool>::from_bool([true, false], &ad_device).to_device(&device);
assert!(autodiff_bool.is_autodiff());
assert_eq!(
autodiff_bool.gradient_checkpointing_strategy(),
Some(GradientCheckpointingStrategy::Balanced)
);
}
#[test]
#[should_panic(
expected = "Gradient checkpointing requires autodiff; use Device::autodiff().gradient_checkpointing()"
)]
fn gradient_checkpointing_requires_autodiff() {
let _ = Device::default().gradient_checkpointing();
}
}
#[cfg(all(test, feature = "cuda", feature = "wgpu"))]
mod tests {
use super::*;
#[test]
#[ignore = "needs an NVIDIA card reachable through CUDA and Vulkan"]
fn a_card_reached_by_two_runtimes_is_listed_once() {
let gpus = Device::enumerate_physical();
let nvidia: Vec<_> = gpus
.iter()
.filter(|gpu| gpu.physical.vendor == Some(PciVendor::Nvidia))
.collect();
assert!(!nvidia.is_empty(), "no NVIDIA card in {gpus:?}");
for gpu in &nvidia {
assert!(
gpu.physical.pci_address.is_some(),
"{} has no PCI address",
gpu.name
);
assert!(
gpu.devices.len() >= 2,
"{} is reached by {:?} only",
gpu.name,
gpu.devices
);
}
for (i, gpu) in gpus.iter().enumerate() {
for other in &gpus[i + 1..] {
assert!(
!gpu.physical.is_same_card(&other.physical),
"one card listed twice: {gpu:?} and {other:?}"
);
}
}
}
}
#[cfg(all(test, any(feature = "metal", feature = "vulkan", feature = "webgpu")))]
mod enumerate_wgpu_tests {
use super::*;
use burn_dispatch::devices::{CubeDevice, WgpuDevice, WgpuDeviceKind};
fn cube_device(kind: WgpuDeviceKind, backend: WgpuBackend) -> CubeDevice {
CubeDevice::Wgpu(WgpuDevice::new(kind).on(backend))
}
#[test]
fn enumerate_requested_api_independently_of_auto() {
for backend in [WgpuBackend::Metal, WgpuBackend::Vulkan, WgpuBackend::WebGpu] {
let auto = cube_device(WgpuDeviceKind::DiscreteGpu(0), WgpuBackend::Auto);
let expected = [
WgpuDeviceKind::DiscreteGpu(0),
WgpuDeviceKind::DiscreteGpu(1),
WgpuDeviceKind::IntegratedGpu(0),
WgpuDeviceKind::VirtualGpu(0),
WgpuDeviceKind::Cpu,
WgpuDeviceKind::Other(0),
]
.map(|kind| cube_device(kind, backend));
let enumerate = |seed: DeviceId| {
let CubeDevice::Wgpu(seed_device) = CubeDevice::from_id(seed) else {
panic!("expected a wgpu seed");
};
assert_eq!(seed_device.backend, backend);
expected
.iter()
.filter(|device| device.to_id().type_id == seed.type_id)
.map(CubeDevice::to_id)
.collect()
};
let mut devices = vec![Device::new(auto.clone())];
push_wgpu_with(&mut devices, backend, enumerate);
let expected_devices: Vec<_> = core::iter::once(&auto)
.chain(expected.iter())
.cloned()
.map(Device::new)
.collect();
assert_eq!(devices, expected_devices);
push_wgpu_with(&mut devices, backend, enumerate);
assert_eq!(
devices, expected_devices,
"repeated filters must not duplicate devices"
);
}
}
#[test]
fn enumerate_unavailable_api_is_empty() {
let mut devices = Vec::new();
push_wgpu_with(&mut devices, WgpuBackend::Vulkan, |_| Vec::new());
assert!(devices.is_empty());
}
#[cfg(not(target_family = "wasm"))]
#[test]
fn enumerate_pins_the_graphics_api() {
let cases = [
#[cfg(feature = "metal")]
(DeviceType::Metal, WgpuBackend::Metal),
#[cfg(feature = "vulkan")]
(DeviceType::Vulkan, WgpuBackend::Vulkan),
#[cfg(feature = "webgpu")]
(DeviceType::WebGpu, WgpuBackend::WebGpu),
];
for (device_type, backend) in cases {
for device in Device::enumerate(device_type).iter() {
match device.as_dispatch() {
DispatchDevice::Cube(CubeDevice::Wgpu(wgpu)) => {
assert_eq!(wgpu.backend, backend);
}
other => panic!("expected a wgpu device, got {other:?}"),
}
}
}
}
}