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