feat: update workspace paths and enhance gitignore

- Updated stablediffusion crate path from "../stable-diffusion-burn" to "./crates/stable-diffusion-burn" for proper workspace resolution
- Enhanced .gitignore to include generated model files (.mpk, .pt, .bin, .safetensors, .ckpt) and user_data directory
- Added Cargo.lock to gitignore with appropriate comment
- Reorganized IDE files section in gitignore for better clarity
- Added newline at end of file for proper formatting
This commit is contained in:
2026-03-05 19:39:14 +01:00
parent 4bb7ca9074
commit 3a67c0979c
1605 changed files with 537032 additions and 2 deletions

View File

@@ -0,0 +1,32 @@
[package]
authors = [
"nathanielsimard <nathaniel.simard.42@gmail.com>",
"Dilshod Tadjibaev (@antimora)",
]
edition.workspace = true
license.workspace = true
name = "burn-no-std-tests"
readme.workspace = true
repository = "https://github.com/tracel-ai/burn/tree/main/crates/burn-no-std-tests"
version.workspace = true
[lints]
workspace = true
[features]
default = []
tracing = [
"burn/tracing",
"burn-ndarray/tracing",
"burn-store/tracing",
]
[dependencies]
# ** Please make sure all dependencies support no_std **
burn = { path = "../burn", version = "=0.21.0-pre.2", default-features = false }
burn-ndarray = { path = "../burn-ndarray", version = "=0.21.0-pre.2", default-features = false }
burn-store = { path = "../burn-store", version = "=0.21.0-pre.2", default-features = false, features = ["safetensors", "burnpack"]}

View File

@@ -0,0 +1,29 @@
The `burn-no-std-tests` contains integration tests aimed to check `no_std` compatibility of `burn`, `burn-core`, `burn-tensor` and `burn-ndarray` packages.
Currently there is only a minimal test that checks if mnist model can be built with `no_std`. More tests should be added to check completeness.
The continuous integration (CI) should build with additional targets:
* `wasm32-unknown-unknown` - WebAssembly
* `thumbv7m-none-eabi` - ARM Cortex-M3
* `thumbv6m-none-eabi` - ARM Cortex-M0+
Shell commands to build and test the package:
```sh
# install the new targets if not installed previously
rustup target add thumbv6m-none-eabi
rustup target add thumbv7m-none-eabi
rustup target add wasm32-unknown-unknown
# build for various targets
cargo build # regular build
cargo build --target thumbv7m-none-eabi
cargo build --target wasm32-unknown-unknown
RUSTFLAGS="--cfg portable_atomic_unsafe_assume_single_core" cargo build --target thumbv6m-none-eabi
# test
cargo test
```

View File

@@ -0,0 +1,158 @@
// Test Burnpack storage in no-std environment
use burn::{
module::Module,
nn,
tensor::{Tensor, backend::Backend},
};
use burn_store::{BurnpackStore, ModuleSnapshot, PathFilter};
/// Simple model for testing Burnpack storage
#[derive(Module, Debug)]
pub struct TestModel<B: Backend> {
linear1: nn::Linear<B>,
linear2: nn::Linear<B>,
batch_norm: nn::BatchNorm<B>,
}
impl<B: Backend> TestModel<B> {
pub fn new(device: &B::Device) -> Self {
Self {
linear1: nn::LinearConfig::new(10, 20).init(device),
linear2: nn::LinearConfig::new(20, 10).init(device),
batch_norm: nn::BatchNormConfig::new(10).init(device),
}
}
pub fn forward(&self, x: Tensor<B, 2>) -> Tensor<B, 2> {
let x = self.linear1.forward(x);
let x = self.linear2.forward(x);
// Apply batch norm (expand to 3D, apply, then squeeze back)
let x: Tensor<B, 3> = x.unsqueeze_dim(2);
let x = self.batch_norm.forward(x);
x.squeeze_dim(2)
}
}
/// Test basic Burnpack save and load in no-std
pub fn test_burnpack_basic<B: Backend>(device: &B::Device) {
// Create a model
let model = TestModel::<B>::new(device);
// Save to bytes (no file I/O in no-std)
let mut save_store = BurnpackStore::from_bytes(None);
model
.save_into(&mut save_store)
.expect("Failed to save model");
// Get the serialized bytes
let bytes = save_store.get_bytes().expect("Failed to get bytes");
// Load from bytes
let mut load_store = BurnpackStore::from_bytes(Some(bytes));
let mut loaded_model = TestModel::<B>::new(device);
let result = loaded_model
.load_from(&mut load_store)
.expect("Failed to load model");
// Verify all tensors were loaded
assert!(result.is_success(), "Should have no errors");
assert!(!result.applied.is_empty(), "Should have loaded tensors");
// Test that the model still works
let input = Tensor::<B, 2>::ones([2, 10], device);
let _output = loaded_model.forward(input);
}
/// Test Burnpack with filtering in no-std
pub fn test_burnpack_filtering<B: Backend>(device: &B::Device) {
let model = TestModel::<B>::new(device);
// Save only linear1 weights
let filter = PathFilter::new()
.with_full_path("linear1.weight")
.with_full_path("linear1.bias");
let mut save_store = BurnpackStore::from_bytes(None).with_filter(filter);
model
.save_into(&mut save_store)
.expect("Failed to save filtered model");
let bytes = save_store.get_bytes().expect("Failed to get bytes");
// Load with partial loading allowed
let mut load_store = BurnpackStore::from_bytes(Some(bytes)).allow_partial(true);
let mut partial_model = TestModel::<B>::new(device);
let result = partial_model
.load_from(&mut load_store)
.expect("Failed to load partial model");
// Verify that only linear1 was loaded
assert_eq!(result.applied.len(), 2, "Should have loaded 2 tensors");
assert!(!result.missing.is_empty(), "Should have missing tensors");
}
/// Test Burnpack with metadata in no-std
pub fn test_burnpack_metadata<B: Backend>(device: &B::Device) {
let model = TestModel::<B>::new(device);
// Save with metadata
let mut save_store = BurnpackStore::from_bytes(None)
.metadata("version", "1.0.0")
.metadata("environment", "no-std")
.metadata("model_type", "test");
model
.save_into(&mut save_store)
.expect("Failed to save model with metadata");
let bytes = save_store.get_bytes().expect("Failed to get bytes");
// Load and verify it works
let mut load_store = BurnpackStore::from_bytes(Some(bytes));
let mut loaded_model = TestModel::<B>::new(device);
let result = loaded_model
.load_from(&mut load_store)
.expect("Failed to load model with metadata");
assert!(result.is_success(), "Should load successfully");
}
// Note: Key remapping test is omitted as KeyRemapper requires std feature
// Note: Regex filtering test is omitted as with_regex requires std feature
/// Test Burnpack with match_all in no-std
pub fn test_burnpack_match_all<B: Backend>(device: &B::Device) {
let model = TestModel::<B>::new(device);
// Save with match_all (should save everything)
let mut save_store = BurnpackStore::from_bytes(None).match_all();
model
.save_into(&mut save_store)
.expect("Failed to save model");
let bytes = save_store.get_bytes().expect("Failed to get bytes");
// Load everything
let mut load_store = BurnpackStore::from_bytes(Some(bytes));
let mut loaded_model = TestModel::<B>::new(device);
let result = loaded_model
.load_from(&mut load_store)
.expect("Failed to load model");
assert!(result.is_success(), "Should load successfully");
// linear1 (weight, bias) + linear2 (weight, bias) + batch_norm (4 params)
assert_eq!(result.applied.len(), 8, "Should load all 8 tensors");
assert!(result.missing.is_empty(), "Should have no missing tensors");
assert!(result.unused.is_empty(), "Should have no unused tensors");
}
/// Run all Burnpack no-std tests
pub fn run_all_tests<B: Backend>(device: &B::Device) {
test_burnpack_basic::<B>(device);
test_burnpack_filtering::<B>(device);
test_burnpack_metadata::<B>(device);
// test_burnpack_remapping requires KeyRemapper which needs std
// test_burnpack_regex_filter requires with_regex which needs std
test_burnpack_match_all::<B>(device);
}

View File

@@ -0,0 +1,49 @@
// Originally copied from the burn/examples/mnist package
use burn::{
config::Config,
module::Module,
nn,
tensor::{Tensor, backend::Backend},
};
#[derive(Module, Debug)]
pub struct ConvBlock<B: Backend> {
conv: nn::conv::Conv2d<B>,
pool: nn::pool::MaxPool2d,
activation: nn::Gelu,
}
#[derive(Config, Debug)]
pub struct ConvBlockConfig {
channels: [usize; 2],
#[config(default = "[3, 3]")]
kernel_size: [usize; 2],
}
impl<B: Backend> ConvBlock<B> {
pub fn new(config: &ConvBlockConfig, device: &B::Device) -> Self {
let conv = nn::conv::Conv2dConfig::new(config.channels, config.kernel_size)
.with_padding(nn::PaddingConfig2d::Same)
.init(device);
let pool = nn::pool::MaxPool2dConfig::new(config.kernel_size)
.with_strides([1, 1])
.with_padding(nn::PaddingConfig2d::Same)
.init();
let activation = nn::Gelu::new();
Self {
conv,
pool,
activation,
}
}
pub fn forward(&self, input: Tensor<B, 4>) -> Tensor<B, 4> {
let x = self.conv.forward(input.clone());
let x = self.pool.forward(x);
let x = self.activation.forward(x);
(x + input) / 2.0
}
}

View File

@@ -0,0 +1,9 @@
#![no_std]
pub mod burnpack;
pub mod conv;
pub mod mlp;
pub mod model;
pub mod safetensors;
extern crate alloc;

View File

@@ -0,0 +1,67 @@
// Originally copied from the burn/examples/mnist package
use alloc::vec::Vec;
use burn::{
config::Config,
module::Module,
nn,
tensor::{Tensor, backend::Backend},
};
/// Configuration to create a [Multilayer Perceptron](Mlp) layer.
#[derive(Config, Debug)]
pub struct MlpConfig {
/// The number of layers.
#[config(default = 3)]
pub num_layers: usize,
/// The dropout rate.
#[config(default = 0.5)]
pub dropout: f64,
/// The size of each layer.
#[config(default = 256)]
pub d_model: usize,
}
/// Multilayer Perceptron module.
#[derive(Module, Debug)]
pub struct Mlp<B: Backend> {
linears: Vec<nn::Linear<B>>,
dropout: nn::Dropout,
activation: nn::Relu,
}
impl<B: Backend> Mlp<B> {
/// Create the module from the given configuration.
pub fn new(config: &MlpConfig, device: &B::Device) -> Self {
let mut linears = Vec::with_capacity(config.num_layers);
for _ in 0..config.num_layers {
linears.push(nn::LinearConfig::new(config.d_model, config.d_model).init(device));
}
Self {
linears,
dropout: nn::DropoutConfig::new(0.3).init(),
activation: nn::Relu::new(),
}
}
/// Applies the forward pass on the input tensor.
///
/// # Shapes
///
/// - input: `[batch_size, d_model]`
/// - output: `[batch_size, d_model]`
pub fn forward(&self, input: Tensor<B, 2>) -> Tensor<B, 2> {
let mut x = input;
for linear in self.linears.iter() {
x = linear.forward(x);
x = self.dropout.forward(x);
x = self.activation.forward(x);
}
x
}
}

View File

@@ -0,0 +1,66 @@
// Originally copied from the burn/examples/mnist package
use crate::{
conv::{ConvBlock, ConvBlockConfig},
mlp::{Mlp, MlpConfig},
};
use burn::{
config::Config,
module::Module,
nn,
tensor::{Tensor, backend::Backend},
};
#[derive(Config, Debug)]
pub struct MnistConfig {
#[config(default = 42)]
pub seed: u64,
pub mlp: MlpConfig,
#[config(default = 784)]
pub input_size: usize,
#[config(default = 10)]
pub output_size: usize,
}
#[derive(Module, Debug)]
pub struct Model<B: Backend> {
mlp: Mlp<B>,
conv: ConvBlock<B>,
input: nn::Linear<B>,
output: nn::Linear<B>,
num_classes: usize,
}
impl<B: Backend> Model<B> {
pub fn new(config: &MnistConfig, device: &B::Device) -> Self {
let mlp = Mlp::new(&config.mlp, device);
let input = nn::LinearConfig::new(config.input_size, config.mlp.d_model).init(device);
let output = nn::LinearConfig::new(config.mlp.d_model, config.output_size).init(device);
let conv = ConvBlock::new(&ConvBlockConfig::new([1, 1]), device);
Self {
mlp,
conv,
output,
input,
num_classes: config.output_size,
}
}
pub fn forward(&self, input: Tensor<B, 3>) -> Tensor<B, 2> {
let [batch_size, height, width] = input.dims();
let x = input.reshape([batch_size, 1, height, width]).detach();
let x = self.conv.forward(x);
let x = x.reshape([batch_size, height * width]);
let x = self.input.forward(x);
let x = self.mlp.forward(x);
self.output.forward(x)
}
}

View File

@@ -0,0 +1,111 @@
// Test SafeTensors storage in no-std environment
use burn::{
module::Module,
nn,
tensor::{Tensor, backend::Backend},
};
use burn_store::{ModuleSnapshot, SafetensorsStore};
/// Simple model for testing SafeTensors storage
#[derive(Module, Debug)]
pub struct TestModel<B: Backend> {
linear1: nn::Linear<B>,
linear2: nn::Linear<B>,
}
impl<B: Backend> TestModel<B> {
pub fn new(device: &B::Device) -> Self {
Self {
linear1: nn::LinearConfig::new(10, 20).init(device),
linear2: nn::LinearConfig::new(20, 10).init(device),
}
}
pub fn forward(&self, x: Tensor<B, 2>) -> Tensor<B, 2> {
let x = self.linear1.forward(x);
self.linear2.forward(x)
}
}
/// Test basic SafeTensors save and load in no-std
pub fn test_safetensors_basic<B: Backend>(device: &B::Device) {
// Create a model
let model = TestModel::<B>::new(device);
// Save to bytes (no file I/O in no-std)
let mut save_store = SafetensorsStore::from_bytes(None);
model
.save_into(&mut save_store)
.expect("Failed to save model");
// Get the serialized bytes
let bytes = save_store.get_bytes().expect("Failed to get bytes");
// Load from bytes
let mut load_store = SafetensorsStore::from_bytes(Some(bytes));
let mut loaded_model = TestModel::<B>::new(device);
loaded_model
.load_from(&mut load_store)
.expect("Failed to load model");
// Test that the model still works
let input = Tensor::<B, 2>::ones([2, 10], device);
let _output = loaded_model.forward(input);
}
/// Test SafeTensors with filtering in no-std
pub fn test_safetensors_filtering<B: Backend>(device: &B::Device) {
let model = TestModel::<B>::new(device);
// Save only linear1 weights
let mut save_store = SafetensorsStore::from_bytes(None)
.with_full_path("linear1.weight")
.with_full_path("linear1.bias");
model
.save_into(&mut save_store)
.expect("Failed to save filtered model");
let bytes = save_store.get_bytes().expect("Failed to get bytes");
// Load with partial loading allowed
let mut load_store = SafetensorsStore::from_bytes(Some(bytes)).allow_partial(true);
let mut partial_model = TestModel::<B>::new(device);
let result = partial_model
.load_from(&mut load_store)
.expect("Failed to load partial model");
// Verify that only linear1 was loaded
assert_eq!(result.applied.len(), 2, "Should have loaded 2 tensors");
assert!(!result.missing.is_empty(), "Should have missing tensors");
}
/// Test SafeTensors with metadata in no-std
pub fn test_safetensors_metadata<B: Backend>(device: &B::Device) {
let model = TestModel::<B>::new(device);
// Save with metadata
let mut save_store = SafetensorsStore::from_bytes(None)
.metadata("version", "1.0.0")
.metadata("environment", "no-std");
model
.save_into(&mut save_store)
.expect("Failed to save model with metadata");
let bytes = save_store.get_bytes().expect("Failed to get bytes");
// Load and verify it works
let mut load_store = SafetensorsStore::from_bytes(Some(bytes));
let mut loaded_model = TestModel::<B>::new(device);
loaded_model
.load_from(&mut load_store)
.expect("Failed to load model with metadata");
}
/// Run all SafeTensors no-std tests
pub fn run_all_tests<B: Backend>(device: &B::Device) {
test_safetensors_basic::<B>(device);
test_safetensors_filtering::<B>(device);
test_safetensors_metadata::<B>(device);
}

View File

@@ -0,0 +1,12 @@
extern crate alloc;
#[test]
fn test_burnpack_no_std() {
use burn_ndarray::NdArray;
use burn_no_std_tests::burnpack;
type Backend = NdArray<f32>;
let device = Default::default();
// Run all Burnpack tests
burnpack::run_all_tests::<Backend>(&device);
}

View File

@@ -0,0 +1,12 @@
extern crate alloc;
#[test]
fn test_safetensors_no_std() {
use burn_ndarray::NdArray;
use burn_no_std_tests::safetensors;
type Backend = NdArray<f32>;
let device = Default::default();
// Run all SafeTensors tests
safetensors::run_all_tests::<Backend>(&device);
}

View File

@@ -0,0 +1,31 @@
#![no_std] // Must keep it for testing
use burn_no_std_tests::mlp::*;
use burn_no_std_tests::model::*;
use burn::tensor::{Distribution, Tensor, backend::Backend};
use burn_ndarray::NdArray;
#[test]
fn test_mnist_model_with_random_input() {
type Backend = NdArray<f32>;
// Model configurations
let device = Default::default();
let mlp_config = MlpConfig::new();
let mnist_config = MnistConfig::new(mlp_config);
let mnist_model: Model<Backend> = Model::new(&mnist_config, &device);
// Pass a fixed seed for random, otherwise a build generated random seed is used
Backend::seed(&device, mnist_config.seed);
// Some random input
let input_shape = [1, 28, 28];
let input = Tensor::<Backend, 3>::random(input_shape, Distribution::Default, &device);
// Run through the model
let output = mnist_model.forward(input);
assert_eq!(&*output.shape(), [1, 10]);
assert!(output.to_data().iter::<f32>().all(|x| x <= 1.0));
}