1use alloc::string::String;
2use burn_std::{BoolStore, DeviceSettings, QuantConfig, QuantMode, 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)]
111pub struct Flex {}
112
113impl BackendTypes for Flex {
114 type Device = FlexDevice;
115
116 type FloatTensorPrimitive = FlexTensor;
117 type IntTensorPrimitive = FlexTensor;
118 type BoolTensorPrimitive = FlexTensor;
119 type QuantizedTensorPrimitive = FlexQTensor;
120
121 type GraphPrimitive = burn_backend::GraphUnsupported;
122}
123
124impl Backend for Flex {
125 fn name(_device: &Self::Device) -> String {
126 "flex".into()
127 }
128
129 fn seed(_device: &Self::Device, seed: u64) {
130 let rng = FlexRng::seed_from_u64(seed);
131 let mut seed_lock = SEED.lock();
132 *seed_lock = Some(rng);
133 }
134
135 fn device_count(_type_id: u16) -> usize {
136 1
137 }
138
139 fn dtype_usage(_device: &Self::Device, dtype: DType) -> DTypeUsageSet {
140 match dtype {
141 DType::F64 | DType::F32 | DType::F16 | DType::BF16 => {
143 DTypeUsage::Storage | DTypeUsage::Arithmetic
144 }
145 DType::I64 | DType::I32 | DType::I16 | DType::I8 => {
146 DTypeUsage::Storage | DTypeUsage::Arithmetic
147 }
148 DType::U64 | DType::U32 | DType::U16 | DType::U8 => {
149 DTypeUsage::Storage | DTypeUsage::Arithmetic
150 }
151 DType::Bool(burn_std::BoolStore::Native | burn_std::BoolStore::U8) => {
156 DTypeUsage::Storage | DTypeUsage::Arithmetic
157 }
158 DType::Bool(burn_std::BoolStore::U32) => DTypeUsageSet::empty(),
159 DType::QFloat(scheme)
161 if scheme.mode == QuantMode::Symmetric
162 && burn_std::quantization::quantizable(&scheme) =>
163 {
164 DTypeUsage::general()
165 }
166 DType::QFloat(_) => DTypeUsageSet::empty(),
167 _ => DTypeUsageSet::empty(),
168 }
169 }
170
171 fn flush(_device: &Self::Device) -> Result<(), ExecutionError> {
172 Ok(())
173 }
174}
175
176impl BackendIr for Flex {
177 type Handle = HandleKind<Self>;
178
179 fn float_tensor(handle: TensorHandle<Self::Handle>) -> FlexTensor {
180 match handle.handle {
181 HandleKind::Float(t) => t,
182 _ => panic!("Expected float handle, got {}", handle.handle.name()),
183 }
184 }
185
186 fn int_tensor(handle: TensorHandle<Self::Handle>) -> FlexTensor {
187 match handle.handle {
188 HandleKind::Int(t) => t,
189 _ => panic!("Expected int handle, got {}", handle.handle.name()),
190 }
191 }
192
193 fn bool_tensor(handle: TensorHandle<Self::Handle>) -> FlexTensor {
194 match handle.handle {
195 HandleKind::Bool(t) => t,
196 _ => panic!("Expected bool handle, got {}", handle.handle.name()),
197 }
198 }
199
200 fn quantized_tensor(handle: TensorHandle<Self::Handle>) -> FlexQTensor {
201 match handle.handle {
202 HandleKind::Quantized(t) => t,
203 _ => panic!("Expected quantized handle, got {}", handle.handle.name()),
204 }
205 }
206
207 fn float_tensor_handle(tensor: FlexTensor) -> Self::Handle {
208 HandleKind::Float(tensor)
209 }
210
211 fn int_tensor_handle(tensor: FlexTensor) -> Self::Handle {
212 HandleKind::Int(tensor)
213 }
214
215 fn bool_tensor_handle(tensor: FlexTensor) -> Self::Handle {
216 HandleKind::Bool(tensor)
217 }
218
219 fn quantized_tensor_handle(tensor: FlexQTensor) -> Self::Handle {
220 HandleKind::Quantized(tensor)
221 }
222}
223
224#[cfg(test)]
227mod tests {
228 use burn_backend::{Backend, DType};
229 use burn_std::{BoolStore, QuantValue};
230
231 use super::*;
232
233 #[test]
234 fn supports_every_symmetric_quantized_scheme() {
235 let device = FlexDevice;
236 let q8 = QuantScheme::default();
237 let e2m1 = q8.with_value(QuantValue::E2M1);
238
239 for scheme in [
240 q8,
241 q8.with_value(QuantValue::Q4S)
242 .with_store(QuantStore::Native),
243 q8.with_value(QuantValue::E4M3),
244 e2m1,
245 e2m1.with_store(QuantStore::PackedNative(0)),
246 ] {
247 assert!(
248 Flex::supports_dtype(&device, DType::QFloat(scheme)),
249 "{scheme:?}"
250 );
251 }
252 assert!(!Flex::supports_dtype(
253 &device,
254 DType::QFloat(q8.with_mode(QuantMode::Lookup))
255 ));
256 }
257
258 #[test]
259 fn supports_bool_native() {
260 let device = FlexDevice;
261 assert!(Flex::supports_dtype(
262 &device,
263 DType::Bool(BoolStore::Native)
264 ));
265 }
266
267 #[test]
268 fn supports_bool_u8() {
269 let device = FlexDevice;
270 assert!(Flex::supports_dtype(&device, DType::Bool(BoolStore::U8)));
271 }
272
273 #[test]
274 fn does_not_support_bool_u32() {
275 let device = FlexDevice;
276 assert!(
277 !Flex::supports_dtype(&device, DType::Bool(BoolStore::U32)),
278 "Bool(U32) should not be supported: flex stores bools as 1 byte per element"
279 );
280 }
281
282 #[test]
283 fn bool_empty_preserves_native_dtype() {
284 use burn_backend::ops::BoolTensorOps;
285 let shape = burn_std::Shape::from(alloc::vec![3]);
286 let t = Flex::bool_empty(shape, &FlexDevice, burn_std::BoolDType::Native);
287 assert_eq!(t.dtype(), DType::Bool(BoolStore::Native));
288 }
289
290 #[test]
291 fn bool_empty_preserves_u8_dtype() {
292 use burn_backend::ops::BoolTensorOps;
293 let shape = burn_std::Shape::from(alloc::vec![3]);
294 let t = Flex::bool_empty(shape, &FlexDevice, burn_std::BoolDType::U8);
295 assert_eq!(t.dtype(), DType::Bool(BoolStore::U8));
296 }
297
298 #[test]
299 fn device_prints_as_cpu() {
300 use alloc::format;
301 assert_eq!(format!("{:?}", FlexDevice), "Cpu");
302 assert_eq!(format!("{}", FlexDevice), "Cpu");
303 }
304
305 #[test]
306 fn comparison_preserves_out_dtype_native() {
307 let lhs = FlexTensor::from_data(burn_backend::TensorData::from([1.0f32, 2.0, 3.0]));
308 let rhs = FlexTensor::from_data(burn_backend::TensorData::from([2.0f32, 2.0, 1.0]));
309 let result = crate::ops::comparison::greater(lhs, rhs, burn_std::BoolDType::Native);
310 assert_eq!(result.dtype(), DType::Bool(BoolStore::Native));
311 }
312
313 #[test]
314 fn comparison_preserves_out_dtype_u8() {
315 let lhs = FlexTensor::from_data(burn_backend::TensorData::from([1.0f32, 2.0, 3.0]));
316 let rhs = FlexTensor::from_data(burn_backend::TensorData::from([2.0f32, 2.0, 1.0]));
317 let result = crate::ops::comparison::greater(lhs, rhs, burn_std::BoolDType::U8);
318 assert_eq!(result.dtype(), DType::Bool(BoolStore::U8));
319 }
320
321 #[test]
322 #[should_panic(expected = "Bool(U32)")]
323 fn comparison_u32_panics() {
324 let lhs = FlexTensor::from_data(burn_backend::TensorData::from([1.0f32, 2.0]));
325 let rhs = FlexTensor::from_data(burn_backend::TensorData::from([2.0f32, 1.0]));
326 let _ = crate::ops::comparison::greater(lhs, rhs, burn_std::BoolDType::U32);
327 }
328
329 #[test]
330 fn bool_not_preserves_u8_dtype() {
331 use burn_backend::ops::BoolTensorOps;
332 let t_u8 = crate::ops::comparison::make_bool_tensor(
336 alloc::vec![1, 0, 1],
337 burn_std::Shape::from(alloc::vec![3]),
338 burn_std::BoolDType::U8,
339 );
340 let result = Flex::bool_not(t_u8);
341 assert_eq!(result.dtype(), DType::Bool(BoolStore::U8));
342 let data: &[u8] = result.bytes();
343 assert_eq!(&data[..3], &[0, 1, 0]);
344 }
345}