1use crate::partition::{BlockRole, block_role};
27use anyhow::{Result, bail};
28use rlx_driver::ProcessGroup;
29
30const TAG_PIPE_HIDDEN: u32 = 100;
33
34pub enum BlockInput<'a> {
36 Tokens(&'a [u32]),
38 Hidden(&'a [f32]),
40}
41
42pub enum BlockOutput {
44 Hidden(Vec<f32>),
46 Logits(Vec<f32>),
48}
49
50impl BlockOutput {
51 fn into_hidden(self) -> Result<Vec<f32>> {
52 match self {
53 BlockOutput::Hidden(v) => Ok(v),
54 BlockOutput::Logits(_) => bail!("block produced Logits where Hidden was expected"),
55 }
56 }
57 fn into_logits(self) -> Result<Vec<f32>> {
58 match self {
59 BlockOutput::Logits(v) => Ok(v),
60 BlockOutput::Hidden(_) => bail!("block produced Hidden where Logits was expected"),
61 }
62 }
63}
64
65pub trait BlockRunner {
68 fn role(&self) -> BlockRole;
70
71 fn run(&mut self, input: BlockInput<'_>) -> Result<BlockOutput>;
76}
77
78pub struct PipelineCoordinator {
80 group: ProcessGroup,
81}
82
83impl PipelineCoordinator {
84 pub fn new(group: ProcessGroup) -> Self {
85 Self { group }
86 }
87
88 pub fn rank(&self) -> u32 {
89 self.group.rank()
90 }
91 pub fn world_size(&self) -> u32 {
92 self.group.world_size()
93 }
94 pub fn is_leader(&self) -> bool {
95 self.group.is_leader()
96 }
97 pub fn group(&self) -> &ProcessGroup {
98 &self.group
99 }
100
101 pub fn barrier(&self) -> Result<()> {
104 Ok(self.group.barrier()?)
105 }
106
107 pub fn forward_step(
115 &self,
116 runner: &mut dyn BlockRunner,
117 token_ids: &[u32],
118 sample: impl FnOnce(&[f32]) -> u32,
119 ) -> Result<u32> {
120 let world = self.group.world_size();
121 let rank = self.group.rank();
122
123 let role = block_role(rank, world);
125 if runner.role() != role {
126 bail!(
127 "BlockRunner role {:?} disagrees with rank {rank}/{world} role {role:?}",
128 runner.role()
129 );
130 }
131
132 let mut sampled: Option<u32> = None;
133 match role {
134 BlockRole::Single => {
135 let logits = runner.run(BlockInput::Tokens(token_ids))?.into_logits()?;
136 sampled = Some(sample(&logits));
137 }
138 BlockRole::First => {
139 let hidden = runner.run(BlockInput::Tokens(token_ids))?.into_hidden()?;
140 self.group.send_f32(rank - 1, TAG_PIPE_HIDDEN, &hidden)?;
141 }
142 BlockRole::Middle => {
143 let hidden_in = self.group.recv_f32(rank + 1, TAG_PIPE_HIDDEN)?;
144 let hidden = runner.run(BlockInput::Hidden(&hidden_in))?.into_hidden()?;
145 self.group.send_f32(rank - 1, TAG_PIPE_HIDDEN, &hidden)?;
146 }
147 BlockRole::Last => {
148 let hidden_in = self.group.recv_f32(rank + 1, TAG_PIPE_HIDDEN)?;
149 let logits = runner.run(BlockInput::Hidden(&hidden_in))?.into_logits()?;
150 sampled = Some(sample(&logits));
151 }
152 }
153
154 let mut tok = [sampled.unwrap_or(0) as f32];
156 self.group.broadcast(0, &mut tok)?;
157 Ok(tok[0] as u32)
158 }
159
160 pub fn generate(
169 &self,
170 runner: &mut dyn BlockRunner,
171 tokens: &mut Vec<u32>,
172 max_tokens: usize,
173 mut sample: impl FnMut(&[f32]) -> u32,
174 mut should_stop: impl FnMut(u32) -> bool,
175 ) -> Result<Vec<u32>> {
176 let start = tokens.len();
177 for _ in 0..max_tokens {
178 let tok = self.forward_step(runner, tokens, |l| sample(l))?;
179 tokens.push(tok);
180 if should_stop(tok) {
181 break;
182 }
183 }
184 self.barrier()?;
185 Ok(tokens[start..].to_vec())
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use crate::partition::pipeline_layer_range;
193 use rlx_driver::NetTransport;
194 use std::net::{Ipv4Addr, SocketAddr, TcpListener};
195 use std::ops::Range;
196 use std::sync::Arc;
197 use std::thread;
198
199 struct MockStage {
209 role: BlockRole,
210 layers: Range<usize>,
211 hidden: usize,
212 }
213
214 impl BlockRunner for MockStage {
215 fn role(&self) -> BlockRole {
216 self.role
217 }
218 fn run(&mut self, input: BlockInput<'_>) -> Result<BlockOutput> {
219 let mut h: Vec<f32> = match input {
220 BlockInput::Tokens(ids) => {
221 let t = *ids.last().unwrap() as f32;
222 (0..self.hidden).map(|i| t + i as f32).collect()
223 }
224 BlockInput::Hidden(hv) => hv.to_vec(),
225 };
226 for g in self.layers.clone() {
227 for x in h.iter_mut() {
228 *x += (g + 1) as f32;
229 }
230 }
231 Ok(match self.role {
232 BlockRole::Last | BlockRole::Single => BlockOutput::Logits(h),
233 _ => BlockOutput::Hidden(h),
234 })
235 }
236 }
237
238 fn run_group<F>(world: u32, body: F)
241 where
242 F: Fn(ProcessGroup) + Send + Sync + 'static,
243 {
244 let listeners: Vec<TcpListener> = (0..world)
245 .map(|_| TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).unwrap())
246 .collect();
247 let addrs: Vec<SocketAddr> = listeners.iter().map(|l| l.local_addr().unwrap()).collect();
248 let body = Arc::new(body);
249 let handles: Vec<_> = listeners
250 .into_iter()
251 .enumerate()
252 .map(|(rank, listener)| {
253 let addrs = addrs.clone();
254 let body = body.clone();
255 thread::spawn(move || {
256 let t =
257 NetTransport::from_listener(rank as u32, world, listener, addrs, 1 << 20)
258 .expect("build transport");
259 body(ProcessGroup::new(Arc::new(t)));
260 })
261 })
262 .collect();
263 for h in handles {
264 h.join().unwrap();
265 }
266 }
267
268 fn run_pipeline_case(world: u32, num_layers: usize, hidden: usize, token: u32) {
269 let expected = token as usize + num_layers * (num_layers + 1) / 2;
270 run_group(world, move |group| {
271 let rank = group.rank();
272 let mut stage = MockStage {
273 role: block_role(rank, world),
274 layers: pipeline_layer_range(num_layers, rank, world),
275 hidden,
276 };
277 let coord = PipelineCoordinator::new(group);
278 let tok = coord
279 .forward_step(&mut stage, &[token], |logits| logits[0].round() as u32)
280 .unwrap();
281 assert_eq!(tok as usize, expected, "rank {rank}");
282 let tok2 = coord
284 .forward_step(&mut stage, &[token], |logits| logits[0].round() as u32)
285 .unwrap();
286 assert_eq!(tok2 as usize, expected, "rank {rank} step 2");
287 coord.barrier().unwrap();
288 });
289 }
290
291 #[test]
292 fn pipeline_even_split_matches_serial() {
293 run_pipeline_case(4, 16, 8, 5);
294 }
295
296 #[test]
297 fn pipeline_uneven_split_matches_serial() {
298 run_pipeline_case(3, 10, 6, 7);
299 }
300
301 #[test]
302 fn pipeline_two_ranks() {
303 run_pipeline_case(2, 5, 4, 11);
304 }
305
306 #[test]
307 fn single_rank_runs_whole_model() {
308 run_group(1, |group| {
309 let mut stage = MockStage {
310 role: BlockRole::Single,
311 layers: 0..6,
312 hidden: 4,
313 };
314 let coord = PipelineCoordinator::new(group);
315 let tok = coord
316 .forward_step(&mut stage, &[9], |logits| logits[0].round() as u32)
317 .unwrap();
318 assert_eq!(tok as usize, 9 + 6 * 7 / 2);
319 });
320 }
321
322 #[test]
323 fn generate_loops_and_appends() {
324 let world = 2u32;
327 let num_layers = 5usize;
328 let hidden = 4usize;
329 let prompt0 = 11u32;
330 let s = (num_layers * (num_layers + 1) / 2) as u32;
331 run_group(world, move |group| {
332 let rank = group.rank();
333 let mut stage = MockStage {
334 role: block_role(rank, world),
335 layers: pipeline_layer_range(num_layers, rank, world),
336 hidden,
337 };
338 let coord = PipelineCoordinator::new(group);
339 let mut tokens = vec![prompt0];
340 let produced = coord
341 .generate(
342 &mut stage,
343 &mut tokens,
344 3,
345 |l| l[0].round() as u32,
346 |_| false,
347 )
348 .unwrap();
349 assert_eq!(
350 produced,
351 vec![prompt0 + s, prompt0 + 2 * s, prompt0 + 3 * s],
352 "rank {rank}"
353 );
354 assert_eq!(tokens.len(), 4, "prompt + 3 generated");
355 });
356 }
357
358 #[test]
359 fn generate_stops_early_on_should_stop() {
360 run_group(2, |group| {
361 let rank = group.rank();
362 let mut stage = MockStage {
363 role: block_role(rank, 2),
364 layers: pipeline_layer_range(4, rank, 2),
365 hidden: 4,
366 };
367 let coord = PipelineCoordinator::new(group);
368 let mut tokens = vec![1u32];
369 let produced = coord
371 .generate(
372 &mut stage,
373 &mut tokens,
374 10,
375 |l| l[0].round() as u32,
376 |_| true,
377 )
378 .unwrap();
379 assert_eq!(produced.len(), 1, "rank {rank}: stopped after one token");
380 });
381 }
382}