burn_core/module/parallel/
model.rs1use alloc::vec::Vec;
2
3use burn_tensor::Device;
4
5use super::{DistributedLayer, LayerParallelism, LayerPlacement, LayerSequence, LayerStage};
6use crate::module::{Devices, Module, ModuleMapper, ModuleVisitor};
7
8#[derive(Clone, Debug)]
12pub struct DistributedLayeredModel<M: LayerParallelism> {
13 model: M,
14 placement: LayerPlacement,
15}
16
17impl<M: LayerParallelism> DistributedLayeredModel<M> {
18 pub fn new(model: M, placement: &LayerPlacement) -> Self {
26 assert_built_where_placed(&model, placement);
27
28 Self {
29 model,
30 placement: placement.clone(),
31 }
32 }
33
34 pub fn placement(&self) -> &LayerPlacement {
36 &self.placement
37 }
38
39 pub fn forward(
41 &self,
42 input: <M::InputLayer as DistributedLayer>::Input,
43 ) -> <M::OutputLayer as DistributedLayer>::Output {
44 let placement = &self.placement;
45 let mut signal = self
46 .model
47 .layer_input()
48 .forward(input.to_device(&placement.input));
49 for (layer, device) in self.model.layers_hidden().zip(&placement.hidden) {
50 signal = layer.forward(signal.to_device(device));
51 }
52 self.model
53 .layer_output()
54 .forward(signal.to_device(&placement.output))
55 }
56
57 pub fn model(&self) -> &M {
59 &self.model
60 }
61
62 pub fn into_inner(self) -> M {
64 self.model
65 }
66
67 pub fn into_stages(self) -> StagedLayers<M> {
75 let LayerSequence {
76 input,
77 hidden,
78 output,
79 } = self.model.into_layers();
80 let mut hidden = hidden.into_iter();
81 let stages = self
82 .placement
83 .stages()
84 .into_iter()
85 .enumerate()
86 .map(|(index, stage)| {
87 let layers = hidden.by_ref().take(stage.num_hidden_layers).collect();
88 hold_stage::<M>(index, stage, layers)
89 })
90 .collect();
91 StagedLayers {
92 input,
93 stages,
94 output,
95 }
96 }
97
98 fn on_one_device(&self, device: &Device) -> LayerPlacement {
100 LayerPlacement::new(&[LayerStage {
101 device: device.clone(),
102 num_hidden_layers: self.placement.hidden.len(),
103 }])
104 }
105}
106
107impl<M: LayerParallelism> Module for DistributedLayeredModel<M> {
110 fn collect_devices(&self, devices: Devices) -> Devices {
111 self.model.collect_devices(devices)
112 }
113
114 fn fork(self, device: &Device) -> Self {
115 let placement = self.on_one_device(device);
116 Self {
117 model: self.model.fork(device),
118 placement,
119 }
120 }
121
122 fn to_device(self, device: &Device) -> Self {
123 let placement = self.on_one_device(device);
124 Self {
125 model: self.model.to_device(device),
126 placement,
127 }
128 }
129
130 fn train(self) -> Self {
131 Self {
132 model: self.model.train(),
133 placement: self.placement,
134 }
135 }
136
137 fn valid(&self) -> Self {
138 Self {
139 model: self.model.valid(),
140 placement: self.placement.clone(),
141 }
142 }
143
144 fn visit<Visitor: ModuleVisitor>(&self, visitor: &mut Visitor) {
145 self.model.visit(visitor);
146 }
147
148 fn map<Mapper: ModuleMapper>(self, mapper: &mut Mapper) -> Self {
149 Self {
150 model: self.model.map(mapper),
151 placement: self.placement,
152 }
153 }
154
155 fn materialize(self) -> Self {
156 Self {
157 model: self.model.materialize(),
158 placement: self.placement,
159 }
160 }
161}
162
163#[derive(Debug)]
165pub struct StagedLayers<M: LayerParallelism> {
166 pub input: M::InputLayer,
168 pub stages: Vec<HeldStage<M>>,
170 pub output: M::OutputLayer,
172}
173
174#[derive(Debug)]
176pub struct HeldStage<M: LayerParallelism> {
177 pub device: Device,
179 pub layers: Vec<M::HiddenLayer>,
181}
182
183fn hold_stage<M: LayerParallelism>(
185 index: usize,
186 stage: LayerStage,
187 layers: Vec<M::HiddenLayer>,
188) -> HeldStage<M> {
189 assert!(
190 layers.iter().all(|layer| layer_is_on(layer, &stage.device)),
191 "stage {index} holds a layer off {:?}: `into_layers` must give the hidden layers in \
192 the order `layers_hidden` does",
193 stage.device
194 );
195 HeldStage {
196 layers,
197 device: stage.device,
198 }
199}
200
201fn assert_built_where_placed<M: LayerParallelism>(model: &M, placement: &LayerPlacement) {
202 let count = model.layers_hidden().count();
203 assert!(
204 count == placement.hidden.len(),
205 "the placement must give a device to every hidden layer: the model has {count}, the \
206 placement gives {}",
207 placement.hidden.len()
208 );
209
210 assert!(
211 layer_is_on(model.layer_input(), &placement.input),
212 "the input layer must be built on {:?}",
213 placement.input
214 );
215 for (index, (layer, device)) in model.layers_hidden().zip(&placement.hidden).enumerate() {
216 assert!(
217 layer_is_on(layer, device),
218 "hidden layer {index} must be built on {device:?}"
219 );
220 }
221 assert!(
222 layer_is_on(model.layer_output(), &placement.output),
223 "the output layer must be built on {:?}",
224 placement.output
225 );
226}
227
228fn layer_is_on(layer: &impl Module, device: &Device) -> bool {
229 layer.devices().iter().all(|found| found == device)
230}
231
232#[cfg(test)]
233mod tests {
234 use alloc::vec::Vec;
235
236 use burn_tensor::{Distribution, Tensor, Tolerance};
237
238 use super::*;
239 use crate as burn;
240 use crate::module::parallel::LayerSequence;
241 use crate::{test_device, test_utils::SimpleLinear};
242
243 const WIDTH: usize = 8;
244
245 #[test]
246 fn the_forward_on_one_device_is_the_plain_forward() {
247 let device = test_device();
248 let stack = Stack::new(3, &device);
249 let input = Tensor::random([4, WIDTH], Distribution::Default, &device);
250 let expected = stack.plain_forward(input.clone());
251
252 let placement = LayerPlacement::even(core::slice::from_ref(&device), 3);
253 DistributedLayeredModel::new(stack, &placement)
254 .forward(input)
255 .into_data()
256 .assert_approx_eq::<f32>(&expected.into_data(), Tolerance::default());
257 }
258
259 #[test]
260 #[should_panic(expected = "every hidden layer")]
261 fn a_placement_short_of_a_hidden_layer_is_refused() {
262 let device = test_device();
263 let placement = LayerPlacement::even(core::slice::from_ref(&device), 2);
264 DistributedLayeredModel::new(Stack::new(3, &device), &placement);
265 }
266
267 #[test]
268 #[should_panic(expected = "every hidden layer")]
269 fn a_placement_past_the_last_hidden_layer_is_refused() {
270 let device = test_device();
271 let placement = LayerPlacement::even(core::slice::from_ref(&device), 4);
272 DistributedLayeredModel::new(Stack::new(3, &device), &placement);
273 }
274
275 #[cfg(feature = "autodiff")]
276 #[test]
277 fn every_parameter_receives_a_gradient() {
278 let device = test_device().autodiff();
279 let placement = LayerPlacement::even(core::slice::from_ref(&device), 3);
280 let stack = DistributedLayeredModel::new(Stack::new(3, &device), &placement);
281 let input = Tensor::random([4, WIDTH], Distribution::Default, &device);
282
283 let grads = stack.forward(input).sum().backward();
284
285 let model = stack.model();
286 let hidden = model.hidden.iter().map(|layer| &layer.linear);
287 for (index, linear) in [&model.input.linear, &model.output.linear]
288 .into_iter()
289 .chain(hidden)
290 .enumerate()
291 {
292 assert!(
293 linear.weight.grad(&grads).is_some(),
294 "layer {index} has no gradient"
295 );
296 }
297 }
298
299 #[derive(Module, Debug)]
300 struct Stack {
301 input: Tanh,
302 hidden: Vec<Tanh>,
303 output: Head,
304 }
305
306 impl Stack {
307 fn new(hidden: usize, device: &Device) -> Self {
308 Self {
309 input: Tanh::new(device),
310 hidden: (0..hidden).map(|_| Tanh::new(device)).collect(),
311 output: Head {
312 linear: SimpleLinear::new(WIDTH, 2, device),
313 },
314 }
315 }
316
317 fn plain_forward(&self, input: Tensor<2>) -> Tensor<2> {
319 let hidden = self
320 .hidden
321 .iter()
322 .fold(self.input.forward(input), |x, layer| layer.forward(x));
323 self.output.forward(hidden)
324 }
325 }
326
327 impl LayerParallelism for Stack {
328 type InputLayer = Tanh;
329 type HiddenLayer = Tanh;
330 type OutputLayer = Head;
331
332 fn layer_input(&self) -> &Tanh {
333 &self.input
334 }
335
336 fn layers_hidden(&self) -> impl Iterator<Item = &Tanh> {
337 self.hidden.iter()
338 }
339
340 fn layer_output(&self) -> &Head {
341 &self.output
342 }
343
344 fn into_layers(self) -> LayerSequence<Self> {
345 LayerSequence {
346 input: self.input,
347 hidden: self.hidden,
348 output: self.output,
349 }
350 }
351 }
352
353 #[derive(Module, Debug)]
354 struct Tanh {
355 linear: SimpleLinear,
356 }
357
358 impl Tanh {
359 fn new(device: &Device) -> Self {
360 Self {
361 linear: SimpleLinear::new(WIDTH, WIDTH, device),
362 }
363 }
364 }
365
366 impl DistributedLayer for Tanh {
367 type Input = Tensor<2>;
368 type Output = Tensor<2>;
369
370 fn forward(&self, input: Tensor<2>) -> Tensor<2> {
371 linear(&self.linear, input).tanh()
372 }
373 }
374
375 #[derive(Module, Debug)]
376 struct Head {
377 linear: SimpleLinear,
378 }
379
380 impl DistributedLayer for Head {
381 type Input = Tensor<2>;
382 type Output = Tensor<2>;
383
384 fn forward(&self, input: Tensor<2>) -> Tensor<2> {
385 linear(&self.linear, input)
386 }
387 }
388
389 fn linear(layer: &SimpleLinear, x: Tensor<2>) -> Tensor<2> {
391 let out = x.matmul(layer.weight.val().transpose());
392 match &layer.bias {
393 Some(bias) => out + bias.val().unsqueeze(),
394 None => out,
395 }
396 }
397}