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