1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//! An inference server for [Burn](https://burn.dev) models.
//!
//! Furnace serves the models you write in Burn, with what a production inference server needs:
//! the KServe / Triton Open Inference Protocol over HTTP and gRPC (so Triton and KServe clients
//! work unchanged), dynamic batching, several models per process with reloads that don't drop
//! a request, Prometheus metrics, readiness after a warm-up, API keys, rate limits and TLS, all
//! in pure Rust. It also ships as the `furnace` command, which serves ONNX models compiled to
//! Burn at build time; see the [README](https://github.com/Gilfeather/furnace#readme).
//!
//! # Serving a Burn model
//!
//! A model that takes one float tensor and returns one is served by its `forward`, through
//! [`Model::from_fn`]. It answers `POST /predict` and `POST /v2/models/{name}/infer`, and
//! concurrent requests are batched together.
//!
//! ```no_run
//! use burn::module::Module;
//! use burn::nn::{Linear, LinearConfig};
//! use burn::tensor::{Device, Tensor};
//! use furnace::{BackendKind, Model, Server};
//!
//! #[derive(Module, Debug)]
//! struct Net {
//! linear: Linear,
//! }
//!
//! impl Net {
//! fn new(device: &Device) -> Self {
//! Self { linear: LinearConfig::new(784, 10).init(device) }
//! }
//!
//! fn forward(&self, images: Tensor<2>) -> Tensor<2> {
//! self.linear.forward(images)
//! }
//! }
//!
//! #[tokio::main]
//! async fn main() -> furnace::Result<()> {
//! let device = BackendKind::Cpu.device();
//! let mut net = Net::new(&device);
//! // The weights saved after training, as a Burnpack file (burn-store)
//! furnace::weights::load_burnpack(&mut net, "net.bpk".as_ref())?;
//!
//! // Per-item shapes, without the batch: [batch, 784] in, [batch, 10] out
//! let model = Model::from_fn("digits", BackendKind::Cpu, &[784], &[10],
//! move |images: Tensor<2>| net.forward(images))?;
//! Server::new().port(3000).model(model).serve().await
//! }
//! ```
//!
//! # Several inputs or outputs, integer tensors
//!
//! Implement [`BurnModel`] to name a model's tensors: a text classifier taking token ids and a
//! mask, a detector returning boxes and scores. The server checks every request against
//! [`BurnModel::inputs`] before [`BurnModel::run`] sees it, and serves the model on
//! `/v2/models/{name}/infer`, batching requests when every input has a fixed shape.
//!
//! ```no_run
//! use burn::tensor::{Device, Int, Tensor};
//! use furnace::{BackendKind, BurnModel, DataType, HostTensor, IoSpec, Model, Server};
//!
//! # #[derive(Debug)] struct TextNet;
//! # impl TextNet {
//! # fn forward(&self, ids: Tensor<2, Int>, mask: Tensor<2, Int>) -> Tensor<2> { mask.float() }
//! # }
//! #[derive(Debug)]
//! struct Classifier {
//! net: TextNet, // your Burn Module
//! device: Device,
//! }
//!
//! impl BurnModel for Classifier {
//! fn name(&self) -> &str {
//! "classifier"
//! }
//!
//! fn backend(&self) -> BackendKind {
//! BackendKind::Cpu
//! }
//!
//! fn inputs(&self) -> Vec<IoSpec> {
//! vec![
//! IoSpec::new("input_ids", DataType::Int64, &[Some(128)]),
//! IoSpec::new("attention_mask", DataType::Int64, &[Some(128)]),
//! ]
//! }
//!
//! fn outputs(&self) -> Vec<IoSpec> {
//! vec![IoSpec::fp32("logits", &[2])]
//! }
//!
//! fn run(&self, inputs: Vec<HostTensor>) -> furnace::Result<Vec<HostTensor>> {
//! // Checked already: two tensors, in the order of inputs(), [batch, 128] each
//! let mut inputs = inputs.into_iter();
//! let ids = inputs.next().unwrap().into_int::<2>(&self.device)?;
//! let mask = inputs.next().unwrap().into_int::<2>(&self.device)?;
//! Ok(vec![HostTensor::from_float(self.net.forward(ids, mask))?])
//! }
//! }
//!
//! #[tokio::main]
//! async fn main() -> furnace::Result<()> {
//! let device = BackendKind::Cpu.device();
//! let model = Model::custom(Classifier { net: TextNet, device });
//! Server::new().model(model).serve().await
//! }
//! ```
//!
//! # Configuration
//!
//! [`Server::configure`] reaches every [`ServerConfig`] setting: limits, timeouts, batching,
//! gRPC (feature `grpc`), TLS (feature `tls`), API keys, rate limits, the admin token for
//! [`Server::reloadable_model`]. [`Server::router`] returns the HTTP API as an axum `Router`
//! to mount in your own application. The HTTP API is described in
//! [docs/API.md](https://github.com/Gilfeather/furnace/blob/main/docs/API.md).
//!
//! # Features
//!
//! - `onnx` (default): compile the ONNX files in `models/` (or `$FURNACE_MODELS_DIR`) into
//! built-in models. Not needed to serve your own Burn models.
//! - `grpc`: the Open Inference Protocol over gRPC as well.
//! - `tls`: HTTPS and mutual TLS, with a pure-Rust crypto provider.
//! - `image`: JPEG / PNG input on `/predict`, with ImageNet preprocessing.
//! - `wgpu`, `metal`, `cuda`: Burn's GPU backends, chosen with [`BackendKind`].
//! - `embed-weights`: compile built-in models' weights into the binary.
pub use ;
pub use BackendKind;
pub use ;
pub use ;
pub use ;
pub use TensorSpec;