1use alloc::string::String;
2use burn_std::{BoolStore, DeviceSettings, QuantConfig, QuantScheme, QuantStore};
3
4use burn_backend::{
5 Backend, BackendTypes, DType, DTypeUsage, DTypeUsageSet, DeviceId, DeviceOps, ExecutionError,
6};
7use burn_ir::{BackendIr, HandleKind, TensorHandle};
8use burn_std::device::Device;
9use burn_std::rand::{SeedableRng, StdRng};
10use burn_std::sync::Mutex;
11
12use crate::qtensor::FlexQTensor;
13use crate::tensor::FlexTensor;
14
15pub type FlexRng = StdRng;
17
18pub(crate) static SEED: Mutex<Option<FlexRng>> = Mutex::new(None);
21
22pub(crate) fn get_seeded_rng() -> FlexRng {
30 burn_std::rand::get_seeded_rng()
31}
32
33#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
37pub struct FlexDevice;
38
39impl Device for FlexDevice {
40 fn to_id(&self) -> DeviceId {
41 DeviceId::new(0, 0)
42 }
43
44 fn from_id(_id: DeviceId) -> Self {
45 Self
46 }
47}
48
49impl DeviceOps for FlexDevice {
50 fn defaults(&self) -> DeviceSettings {
51 DeviceSettings::new(
52 DType::F32,
53 DType::I32,
54 DType::Bool(BoolStore::Native),
55 QuantConfig::new(
56 QuantScheme::default().with_store(QuantStore::Native),
57 Default::default(),
58 ),
59 )
60 }
61}
62
63impl core::fmt::Display for FlexDevice {
64 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
65 write!(f, "Cpu")
66 }
67}
68
69impl core::fmt::Debug for FlexDevice {
70 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
71 core::fmt::Display::fmt(self, f)
72 }
73}
74
75#[derive(Clone, Copy, Debug, Default)]
113pub struct Flex {}
114
115impl BackendTypes for Flex {
116 type Device = FlexDevice;
117
118 type FloatTensorPrimitive = FlexTensor;
119 type IntTensorPrimitive = FlexTensor;
120 type BoolTensorPrimitive = FlexTensor;
121 type QuantizedTensorPrimitive = FlexQTensor;
122
123 type GraphPrimitive = burn_backend::GraphUnsupported;
124}
125
126impl Backend for Flex {
127 fn name(_device: &Self::Device) -> String {
128 "flex".into()
129 }
130
131 fn seed(_device: &Self::Device, seed: u64) {
132 let rng = FlexRng::seed_from_u64(seed);
133 let mut seed_lock = SEED.lock();
134 *seed_lock = Some(rng);
135 }
136
137 fn device_count(_type_id: u16) -> usize {
138 1
139 }
140
141 fn dtype_usage(_device: &Self::Device, dtype: DType) -> DTypeUsageSet {
142 match dtype {
143 DType::F64 | DType::F32 | DType::F16 | DType::BF16 => {
145 DTypeUsage::Storage | DTypeUsage::Arithmetic
146 }
147 DType::I64 | DType::I32 | DType::I16 | DType::I8 => {
148 DTypeUsage::Storage | DTypeUsage::Arithmetic
149 }
150 DType::U64 | DType::U32 | DType::U16 | DType::U8 => {
151 DTypeUsage::Storage | DTypeUsage::Arithmetic
152 }
153 DType::Bool(burn_std::BoolStore::Native | burn_std::BoolStore::U8) => {
158 DTypeUsage::Storage | DTypeUsage::Arithmetic
159 }
160 DType::Bool(burn_std::BoolStore::U32) => DTypeUsageSet::empty(),
161 DType::QFloat(scheme) if burn_std::quantization::quantizable(&scheme) => {
163 DTypeUsage::Storage.into()
164 }
165 DType::QFloat(_) => DTypeUsageSet::empty(),
166 _ => DTypeUsageSet::empty(),
167 }
168 }
169
170 fn flush(_device: &Self::Device) -> Result<(), ExecutionError> {
171 Ok(())
172 }
173}
174
175impl BackendIr for Flex {
176 type Handle = HandleKind<Self>;
177
178 fn float_tensor(handle: TensorHandle<Self::Handle>) -> FlexTensor {
179 match handle.handle {
180 HandleKind::Float(t) => t,
181 _ => panic!("Expected float handle, got {}", handle.handle.name()),
182 }
183 }
184
185 fn int_tensor(handle: TensorHandle<Self::Handle>) -> FlexTensor {
186 match handle.handle {
187 HandleKind::Int(t) => t,
188 _ => panic!("Expected int handle, got {}", handle.handle.name()),
189 }
190 }
191
192 fn bool_tensor(handle: TensorHandle<Self::Handle>) -> FlexTensor {
193 match handle.handle {
194 HandleKind::Bool(t) => t,
195 _ => panic!("Expected bool handle, got {}", handle.handle.name()),
196 }
197 }
198
199 fn quantized_tensor(handle: TensorHandle<Self::Handle>) -> FlexQTensor {
200 match handle.handle {
201 HandleKind::Quantized(t) => t,
202 _ => panic!("Expected quantized handle, got {}", handle.handle.name()),
203 }
204 }
205
206 fn float_tensor_handle(tensor: FlexTensor) -> Self::Handle {
207 HandleKind::Float(tensor)
208 }
209
210 fn int_tensor_handle(tensor: FlexTensor) -> Self::Handle {
211 HandleKind::Int(tensor)
212 }
213
214 fn bool_tensor_handle(tensor: FlexTensor) -> Self::Handle {
215 HandleKind::Bool(tensor)
216 }
217
218 fn quantized_tensor_handle(tensor: FlexQTensor) -> Self::Handle {
219 HandleKind::Quantized(tensor)
220 }
221}
222
223#[cfg(test)]
226mod tests {
227 use burn_backend::{Backend, DType};
228 use burn_std::BoolStore;
229
230 use super::*;
231
232 #[test]
233 fn supports_bool_native() {
234 let device = FlexDevice;
235 assert!(Flex::supports_dtype(
236 &device,
237 DType::Bool(BoolStore::Native)
238 ));
239 }
240
241 #[test]
242 fn supports_bool_u8() {
243 let device = FlexDevice;
244 assert!(Flex::supports_dtype(&device, DType::Bool(BoolStore::U8)));
245 }
246
247 #[test]
248 fn does_not_support_bool_u32() {
249 let device = FlexDevice;
250 assert!(
251 !Flex::supports_dtype(&device, DType::Bool(BoolStore::U32)),
252 "Bool(U32) should not be supported: flex stores bools as 1 byte per element"
253 );
254 }
255
256 #[test]
257 fn bool_empty_preserves_native_dtype() {
258 use burn_backend::ops::BoolTensorOps;
259 let shape = burn_std::Shape::from(alloc::vec![3]);
260 let t = Flex::bool_empty(shape, &FlexDevice, burn_std::BoolDType::Native);
261 assert_eq!(t.dtype(), DType::Bool(BoolStore::Native));
262 }
263
264 #[test]
265 fn bool_empty_preserves_u8_dtype() {
266 use burn_backend::ops::BoolTensorOps;
267 let shape = burn_std::Shape::from(alloc::vec![3]);
268 let t = Flex::bool_empty(shape, &FlexDevice, burn_std::BoolDType::U8);
269 assert_eq!(t.dtype(), DType::Bool(BoolStore::U8));
270 }
271
272 #[test]
273 fn device_prints_as_cpu() {
274 use alloc::format;
275 assert_eq!(format!("{:?}", FlexDevice), "Cpu");
276 assert_eq!(format!("{}", FlexDevice), "Cpu");
277 }
278
279 #[test]
280 fn comparison_preserves_out_dtype_native() {
281 let lhs = FlexTensor::from_data(burn_backend::TensorData::from([1.0f32, 2.0, 3.0]));
282 let rhs = FlexTensor::from_data(burn_backend::TensorData::from([2.0f32, 2.0, 1.0]));
283 let result = crate::ops::comparison::greater(lhs, rhs, burn_std::BoolDType::Native);
284 assert_eq!(result.dtype(), DType::Bool(BoolStore::Native));
285 }
286
287 #[test]
288 fn comparison_preserves_out_dtype_u8() {
289 let lhs = FlexTensor::from_data(burn_backend::TensorData::from([1.0f32, 2.0, 3.0]));
290 let rhs = FlexTensor::from_data(burn_backend::TensorData::from([2.0f32, 2.0, 1.0]));
291 let result = crate::ops::comparison::greater(lhs, rhs, burn_std::BoolDType::U8);
292 assert_eq!(result.dtype(), DType::Bool(BoolStore::U8));
293 }
294
295 #[test]
296 #[should_panic(expected = "Bool(U32)")]
297 fn comparison_u32_panics() {
298 let lhs = FlexTensor::from_data(burn_backend::TensorData::from([1.0f32, 2.0]));
299 let rhs = FlexTensor::from_data(burn_backend::TensorData::from([2.0f32, 1.0]));
300 let _ = crate::ops::comparison::greater(lhs, rhs, burn_std::BoolDType::U32);
301 }
302
303 #[test]
304 fn bool_not_preserves_u8_dtype() {
305 use burn_backend::ops::BoolTensorOps;
306 let t_u8 = crate::ops::comparison::make_bool_tensor(
310 alloc::vec![1, 0, 1],
311 burn_std::Shape::from(alloc::vec![3]),
312 burn_std::BoolDType::U8,
313 );
314 let result = Flex::bool_not(t_u8);
315 assert_eq!(result.dtype(), DType::Bool(BoolStore::U8));
316 let data: &[u8] = result.bytes();
317 assert_eq!(&data[..3], &[0, 1, 0]);
318 }
319}