Skip to content

Commit 46b19ad

Browse files
Added documentation on usage in no_std
1 parent 21802d7 commit 46b19ad

File tree

3 files changed

+98
-1
lines changed

3 files changed

+98
-1
lines changed

burn-book/book.toml

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ authors = [
66
"Dilshod Tadjibaev",
77
"Guillaume Lagrange",
88
"Sylvain Benner",
9+
"Bjorn Beishline"
910
]
1011
language = "en"
1112
multilingual = false

burn-book/src/SUMMARY.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@
3131
- [Custom WGPU Kernel](./advanced/backend-extension/custom-wgpu-kernel.md)
3232
- [Custom Optimizer]()
3333
- [WebAssembly]()
34-
- [No-Std]()
34+
- [No-Std](./advanced/no-std.md)

burn-book/src/advanced/no-std.md

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# No Standard Library
2+
3+
In this section, you will learn how to run an onnx inference model on an embedded system, with no standard library support on a Raspberry Pi Pico. This should be universally applicable to other platforms. All the code can be found under the
4+
[examples directory](https://github.com/tracel-ai/burn/tree/main/examples/onnx-inference-rp2040).
5+
6+
## Step-by-Step Guide
7+
8+
Let's walk through the process of running an embedded ONNX model:
9+
10+
### Setup
11+
Follow the [embassy guide](https://embassy.dev/book/#_getting_started) for your specific environment. Once setup, you should have something similar to the following.
12+
```
13+
./inference
14+
├── Cargo.lock
15+
├── Cargo.toml
16+
├── build.rs
17+
├── memory.x
18+
└── src
19+
└── main.rs
20+
```
21+
22+
Some other dependencies have to be added
23+
```toml
24+
[dependencies]
25+
embedded-alloc = "0.5.1" # Only if there is no default allocator for your chip
26+
burn = { version = "0.14", default-features = false, features = ["ndarray"] } # Backend must be ndarray
27+
28+
[build-dependencies]
29+
burn-import = { version = "0.14" } # Used to auto generate the rust code to import the model
30+
```
31+
32+
### Import the Model
33+
Follow the directions to [import models](./import/README.md).
34+
35+
Use the following ModelGen config
36+
```rs
37+
ModelGen::new()
38+
.input(my_model)
39+
.out_dir("model/")
40+
.record_type(RecordType::Bincode)
41+
.embed_states(true)
42+
.run_from_script();
43+
```
44+
45+
### Global Allocator
46+
First define a global allocator (if you are on a no_std system without alloc).
47+
48+
```rs
49+
use embedded_alloc::Heap;
50+
51+
#[global_allocator]
52+
static HEAP: Heap = Heap::empty();
53+
54+
#[embassy_executor::main]
55+
async fn main(_spawner: Spawner) {
56+
{
57+
use core::mem::MaybeUninit;
58+
const HEAP_SIZE: usize = 100 * 1024; // This is dependent on the model size in memory.
59+
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
60+
unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) }
61+
}
62+
}
63+
```
64+
65+
### Define Backend
66+
We are using ndarray, so we just need to define the NdArray backend as usual
67+
```rs
68+
use burn::{backend::NdArray, tensor::Tensor};
69+
70+
type Backend = NdArray<f32>;
71+
type BackendDeice = <Backend as burn::tensor::backend::Backend>::Device;
72+
```
73+
74+
Then inside the `main` function add
75+
```rs
76+
use your_model::Model;
77+
78+
// Get a default device for the backend
79+
let device = BackendDeice::default();
80+
81+
// Create a new model and load the state
82+
let model: Model<Backend> = Model::default();
83+
```
84+
85+
### Running the Model
86+
To run the model, just call it as you would normally
87+
```rs
88+
// Define the tensor
89+
let input = Tensor::<Backend, 2>::from_floats([[input]], &device);
90+
91+
// Run the model on the input
92+
let output = model.forward(input);
93+
```
94+
95+
## Conclusion
96+
Running a model in a no_std environment is pretty much identical to a normal environment. All that is needed is a global allocator.

0 commit comments

Comments
 (0)