|
| 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