1use std::{
33 collections::{BTreeSet, HashMap, HashSet},
34 sync::{
35 atomic::{AtomicBool, Ordering},
36 mpsc, Arc, Mutex, MutexGuard,
37 },
38 thread,
39 time::{Duration, Instant},
40};
41
42use ts_collective::{Group, Mesh, Reduce};
43
44use crate::{
45 batch::{BatchRows, Staged},
46 control::{BootstrapInfo, PdError, PdEvent, RemotePrefill, TransferPeer},
47 http,
48 layout::{
49 contract::Contract,
50 manifest::{build_manifest, validate_manifest},
51 registration::{plan_route, PrefillRoute},
52 },
53 sleep_or_stop,
54 status::{RoomTable, TransferPoll},
55 wire::{self, StatusMessage},
56 PeerConfig, StartError, Transport,
57};
58
59const SOCKET_POLL: Duration = Duration::from_millis(100);
61
62const LOOKUP_TIMEOUT: Duration = Duration::from_secs(5);
64
65#[derive(Debug, Clone)]
67pub struct DecodeConfig {
68 pub peer: PeerConfig,
70 pub arena_base: u64,
74 pub layout: Vec<u8>,
76 pub tp_size: i64,
78 pub tp_rank: i64,
80 pub waiting_timeout: Duration,
83 pub heartbeat_interval: Duration,
85 pub heartbeat_max_failures: u32,
87}
88
89#[derive(Debug, Clone)]
91struct Target {
92 host: String,
93 port: u16,
94 is_dummy: bool,
95}
96
97#[derive(Debug, Clone, Default)]
99struct Landed {
100 bootstrap_token: Option<i32>,
101 spec_candidate_ids: Option<Vec<i32>>,
102 bootstrap_logprob: Option<f64>,
103}
104
105type ConnectionKey = (String, u64, Vec<i64>);
108
109#[derive(Default)]
110struct State {
111 rooms: RoomTable,
112 tracked: HashSet<u64>,
116 rooms_by_rid: HashMap<String, u64>,
118 expected: HashMap<u64, BTreeSet<i64>>,
119 responses: HashMap<u64, BTreeSet<i64>>,
120 pending: HashMap<u64, Landed>,
121 landed: HashMap<u64, Landed>,
122 cached_tokens: HashMap<u64, i64>,
123 targets: HashMap<u64, Arc<Vec<Target>>>,
124 admissions: HashMap<u64, i32>,
125 waiting_since: HashMap<u64, Instant>,
126 addr_rooms: HashMap<String, HashSet<u64>>,
128 routes: HashMap<String, Arc<PrefillRoute>>,
130 connections: HashMap<ConnectionKey, Arc<Vec<Target>>>,
131}
132
133impl State {
134 fn fail(&mut self, room: u64, reason: impl Into<String>) {
136 if !self.tracked.contains(&room) {
137 return;
138 }
139 self.rooms.record_failure(room, reason);
140 self.rooms.update(room, TransferPoll::Failed);
141 }
142
143 fn apply(&mut self, message: StatusMessage) {
145 let room = message.room;
146 if !self.tracked.contains(&room) {
147 return;
148 }
149 match message.status {
150 TransferPoll::Success => {
151 let Some(expected) = self.expected.get(&room).cloned() else {
152 self.fail(
153 room,
154 "Received KV completion before its transfer route was registered",
155 );
156 return;
157 };
158 if !expected.contains(&message.prefill_rank) {
159 self.fail(
160 room,
161 "Received KV completion from an unexpected Prefill TP rank",
162 );
163 return;
164 }
165 let responses = self.responses.entry(room).or_default();
166 responses.insert(message.prefill_rank);
167 let arrived = responses.clone();
168 let cached = self.cached_tokens.entry(room).or_insert(0);
170 *cached = (*cached).max(message.cached_tokens);
171 let token = match message.bootstrap_token {
174 -1 => None,
175 token => {
176 match i32::try_from(token).ok().filter(|token| *token >= 0) {
177 Some(token) => Some(token),
178 None => {
179 self.fail(
180 room,
181 format!("Received KV completion with an invalid first token {token}"),
182 );
183 return;
184 }
185 }
186 }
187 };
188 let pending = self.pending.entry(room).or_default();
189 if let Some(token) = token {
190 pending.bootstrap_token.get_or_insert(token);
191 }
192 if let Some(ids) = message.spec_candidate_ids {
193 pending.spec_candidate_ids.get_or_insert(ids);
194 }
195 if let Some(logprob) = message.bootstrap_logprob {
196 pending.bootstrap_logprob.get_or_insert(logprob);
197 }
198 if arrived.len() < expected.len() {
199 return;
200 }
201 if arrived != expected {
202 self.fail(
203 room,
204 "Prefill TP completion set disagrees with the transfer route",
205 );
206 return;
207 }
208 let landed = self.pending.remove(&room).unwrap_or_default();
209 if landed.bootstrap_token.is_none() {
210 self.fail(
213 room,
214 "Every Prefill rank reported the KV landed without the request's first token",
215 );
216 return;
217 }
218 self.landed.insert(room, landed);
219 self.rooms.update(room, TransferPoll::Success);
220 }
221 TransferPoll::Failed => {
222 self.fail(
223 room,
224 "Failed to get kvcache from prefill instance, it might be dead",
225 );
226 }
227 status => self.rooms.update(room, status),
228 }
229 }
230
231 fn attach(
235 &mut self,
236 room: u64,
237 addr: &str,
238 route: &PrefillRoute,
239 targets: Arc<Vec<Target>>,
240 ) -> bool {
241 if !self.tracked.contains(&room) {
242 return false;
243 }
244 self.expected
245 .insert(room, route.expected_prefill_ranks.iter().copied().collect());
246 self.targets.insert(room, targets);
247 self.addr_rooms
248 .entry(addr.to_owned())
249 .or_default()
250 .insert(room);
251 self.rooms.update(room, TransferPoll::Bootstrapped);
252 true
253 }
254
255 fn lose_node(&mut self, addr: &str) -> usize {
259 self.connections
260 .retain(|(connected, _, _), _| connected != addr);
261 self.routes.remove(addr);
262 let rooms = self.addr_rooms.remove(addr).unwrap_or_default();
263 let mut affected = 0;
264 for room in rooms {
265 if matches!(
266 self.rooms.get(room),
267 None | Some(TransferPoll::Success | TransferPoll::Failed)
268 ) || self.rooms.has_failure(room)
269 {
270 continue;
271 }
272 self.fail(
273 room,
274 format!("Losing connection with prefill instance (bootstrap_addr: {addr})"),
275 );
276 affected += 1;
277 }
278 affected
279 }
280
281 fn clear(&mut self, room: u64, addr: &str) {
282 self.rooms.remove(room);
283 self.tracked.remove(&room);
284 self.expected.remove(&room);
285 self.responses.remove(&room);
286 self.pending.remove(&room);
287 self.landed.remove(&room);
288 self.cached_tokens.remove(&room);
289 self.targets.remove(&room);
290 self.admissions.remove(&room);
291 self.waiting_since.remove(&room);
292 if let Some(rooms) = self.addr_rooms.get_mut(addr) {
293 rooms.remove(&room);
294 }
295 }
296}
297
298struct Shared {
299 transport: Arc<Transport>,
300 state: Mutex<State>,
301 local_ip: String,
302 engine_rank: i64,
303 arena_base: u64,
304 layout_wire: Vec<u8>,
306 layout: Contract,
307 tp_size: i64,
308 tp_rank: i64,
309 metrics: Option<Arc<ts_metrics::KvTransferMetrics>>,
310}
311
312impl Shared {
313 fn state(&self) -> MutexGuard<'_, State> {
314 self.state.lock().expect("the PD state lock is poisoned")
315 }
316
317 fn route(&self, addr: &str) -> Result<Arc<PrefillRoute>, String> {
319 if let Some(route) = self.state().routes.get(addr).cloned() {
320 return Ok(route);
321 }
322 let info = http::get_text(
323 &format!("http://{addr}/route?engine_rank=-1&target_dp_group=-1"),
324 LOOKUP_TIMEOUT,
325 )
326 .map_err(|error| {
327 tracing::error!(addr, error, "cannot fetch the prefill parallel info");
328 format!("Could not fetch prefill parallel info from bootstrap_addr: {addr}")
329 })?;
330 let route = Arc::new(
331 plan_route(&info, &self.layout, self.tp_size, self.tp_rank).map_err(|error| error.0)?,
332 );
333 self.state().routes.insert(addr.to_owned(), route.clone());
334 Ok(route)
335 }
336
337 fn connect(&self, room: u64, addr: &str) -> Result<(), String> {
341 let route = self.route(addr)?;
342 let dp_group = room % route.prefill_dp_size.max(1) as u64;
343 let key: ConnectionKey = (addr.to_owned(), dp_group, route.target_tp_ranks.clone());
344 let known = self.state().connections.get(&key).cloned();
345 let targets = match known {
346 Some(targets) => targets,
347 None => {
348 let mut targets = Vec::with_capacity(route.target_tp_ranks.len());
349 for &rank in &route.target_tp_ranks {
350 let lookup_failed = || {
351 format!(
352 "Could not fetch bootstrap info for engine rank: {} and \
353 target_dp_group: {dp_group}",
354 self.engine_rank
355 )
356 };
357 let body = http::get_text(
358 &format!(
359 "http://{addr}/route?engine_rank={rank}&target_dp_group={dp_group}"
360 ),
361 LOOKUP_TIMEOUT,
362 )
363 .map_err(|error| {
364 tracing::error!(addr, rank, error, "cannot fetch a prefill rank");
365 lookup_failed()
366 })?;
367 let info: serde_json::Value =
368 serde_json::from_str(&body).map_err(|_| lookup_failed())?;
369 let host = info["rank_ip"].as_str().ok_or_else(lookup_failed)?;
370 let port = info["rank_port"]
371 .as_u64()
372 .and_then(|port| u16::try_from(port).ok())
373 .ok_or_else(lookup_failed)?;
374 targets.push(Target {
375 host: host.to_owned(),
376 port,
377 is_dummy: route.dummy_tp_ranks.contains(&rank),
378 });
379 }
380 let targets = Arc::new(targets);
381 self.state().connections.insert(key, targets.clone());
382 let session_id = self.transport.session_id();
385 for target in targets.iter() {
386 self.transport.sockets.send(
387 &target.host,
388 target.port,
389 wire::registration_frames(
390 &self.local_ip,
391 self.transport.sockets.port(),
392 &session_id,
393 self.arena_base,
394 &self.layout_wire,
395 self.tp_size,
396 self.tp_rank,
397 ),
398 );
399 }
400 targets
401 }
402 };
403 if self.state().attach(room, addr, &route, targets) {
404 tracing::debug!(room, "connected");
405 }
406 Ok(())
407 }
408
409 fn lose_node(&self, addr: &str) {
411 let affected = self.state().lose_node(addr);
412 if let Some(metrics) = &self.metrics {
413 for _ in 0..affected {
414 metrics.record_failure();
415 }
416 }
417 tracing::error!(
418 addr,
419 affected,
420 "lost the connection with a prefill instance"
421 );
422 }
423
424 fn watch(&self, interval: Duration, max_failures: u32, stop: &mpsc::Receiver<()>) {
425 let mut failures: HashMap<String, u32> = HashMap::new();
426 while !sleep_or_stop(stop, interval) {
427 let addrs: Vec<String> = self.state().routes.keys().cloned().collect();
428 for addr in addrs {
429 let count = failures.entry(addr.clone()).or_insert(0);
430 if http::healthy(
431 &format!("http://{addr}/health"),
432 Duration::from_secs(2),
433 Duration::from_secs(3),
434 ) {
435 *count = 0;
436 let mut state = self.state();
438 let State {
439 rooms, addr_rooms, ..
440 } = &mut *state;
441 if let Some(tracked) = addr_rooms.get_mut(&addr) {
442 tracked.retain(|&room| rooms.get(room).is_some());
443 }
444 } else {
445 tracing::info!(addr, "attempting to reconnect to a prefill instance");
446 *count += 1;
447 }
448 if *count >= max_failures {
449 self.lose_node(&addr);
450 }
451 }
452 }
453 }
454}
455
456#[derive(Clone)]
466pub struct DecodeOutbox {
467 shared: Arc<Shared>,
468 staged: Staged,
469}
470
471impl DecodeOutbox {
472 pub fn release_registration(&self) -> Result<(), String> {
475 let mut receiving: Vec<u64> = self.shared.state().tracked.iter().copied().collect();
476 if !receiving.is_empty() {
477 receiving.sort_unstable();
478 return Err(format!(
479 "cannot release the cache while KV receives are in flight: rooms {receiving:?}"
480 ));
481 }
482 self.shared.transport.release_regions()
483 }
484
485 pub fn restore_registration(&self) -> Result<(), String> {
490 self.shared.transport.restore_regions(true)?;
491 self.shared.state().connections.clear();
492 Ok(())
493 }
494
495 pub fn stage_remote_prefill(&self, rows: BatchRows) -> Result<(), String> {
497 self.staged.stage(rows)
498 }
499
500 pub fn remote_prefill_pending(&self) -> bool {
502 self.staged.is_pending()
503 }
504
505 pub fn hand_over_remote_prefill(&self) -> Result<(), String> {
509 let rows = self.staged.take()?;
510 if rows.num_extends != rows.request_ids.len() {
511 return Err("CachePD Decode admission does not support mixed batches".to_owned());
512 }
513 let layout = &self.shared.layout;
514 let mut pending = Vec::new();
515 for (index, rid) in rows.request_ids.iter().enumerate() {
516 let Some(room) = self.shared.state().rooms_by_rid.get(rid).copied() else {
517 continue;
518 };
519 let manifest = build_manifest(
520 &rows.tables,
521 layout,
522 index,
523 i64::from(rows.extend_prefix_lens[index]),
524 i64::from(rows.prefill_lengths[index]),
525 rows.state_prefix_cached[index],
526 )
527 .and_then(|manifest| {
528 validate_manifest(&manifest, layout, "destination")?;
529 manifest.to_wire()
530 })
531 .map_err(|error| error.0)?;
532 pending.push((rid, room, rows.request_pool_indices[index], manifest));
533 }
534 for (rid, room, request_pool_index, manifest) in pending {
535 self.receive(rid, room, request_pool_index, &manifest);
536 }
537 Ok(())
538 }
539
540 fn receive(&self, rid: &str, room: u64, request_pool_index: i32, manifest: &[u8]) {
543 let targets = {
544 let mut state = self.shared.state();
545 if !state.tracked.contains(&room) {
546 tracing::debug!(rid, room, "not pre-allocating an ended request");
550 return;
551 }
552 state.admissions.insert(room, request_pool_index);
553 state.waiting_since.insert(room, Instant::now());
554 state.rooms.update(room, TransferPoll::WaitingForInput);
555 state.targets.get(&room).cloned()
556 };
557 let Some(targets) = targets else {
558 tracing::warn!(
559 rid,
560 room,
561 "a request without a route reached its remote prefill"
562 );
563 return;
564 };
565 let session_id = self.shared.transport.session_id();
566 for target in targets.iter() {
567 self.shared.transport.sockets.send(
568 &target.host,
569 target.port,
570 wire::preallocation_frames(
571 room,
572 &session_id,
573 (!target.is_dummy).then_some(manifest),
574 ),
575 );
576 }
577 tracing::debug!(rid, room, "pre-allocation sent");
578 }
579}
580
581struct Receiver {
583 room: u64,
584 addr: String,
585 local: TransferPoll,
586 concluded: Option<TransferPoll>,
587}
588
589pub struct DecodePeer {
591 shared: Arc<Shared>,
592 connect: mpsc::Sender<(u64, String)>,
593 stop: Arc<AtomicBool>,
594 _heartbeat_stop: mpsc::Sender<()>,
595 waiting_timeout: Duration,
596 order: Vec<String>,
599 receivers: HashMap<String, Receiver>,
600 timeout_logged: bool,
601}
602
603impl DecodePeer {
604 pub fn start(config: DecodeConfig) -> Result<(Self, DecodeOutbox), StartError> {
607 let layout = Contract::from_wire(&config.layout)?;
608 let (incoming, messages) = mpsc::channel::<Vec<bytes::Bytes>>();
609 let transport = Transport::start(&config.peer, incoming)?;
610 let shared = Arc::new(Shared {
611 transport,
612 state: Mutex::new(State::default()),
613 local_ip: config.peer.local_ip.clone(),
614 engine_rank: config.peer.engine_rank,
615 arena_base: config.arena_base,
616 layout_wire: config.layout,
617 layout,
618 tp_size: config.tp_size,
619 tp_rank: config.tp_rank,
620 metrics: config.peer.metrics.clone(),
621 });
622 let stop = Arc::new(AtomicBool::new(false));
623 {
624 let shared = shared.clone();
625 let stop = stop.clone();
626 thread::Builder::new()
627 .name("ts-pd-status".to_owned())
628 .spawn(move || loop {
629 match messages.recv_timeout(SOCKET_POLL) {
630 Ok(frames) => match StatusMessage::from_frames(&frames) {
631 Ok(message) => {
632 tracing::debug!(room = message.room, status = ?message.status, "status");
633 shared.state().apply(message);
634 }
635 Err(error) => {
636 tracing::error!(%error, "rejecting a malformed Prefill status message");
637 }
638 },
639 Err(mpsc::RecvTimeoutError::Timeout) => {
640 if stop.load(Ordering::Relaxed) {
641 return;
642 }
643 }
644 Err(mpsc::RecvTimeoutError::Disconnected) => return,
645 }
646 })?;
647 }
648 let (connect, rooms) = mpsc::channel::<(u64, String)>();
649 {
650 let shared = shared.clone();
651 thread::Builder::new()
652 .name("ts-pd-connect".to_owned())
653 .spawn(move || {
654 while let Ok((room, addr)) = rooms.recv() {
655 if let Err(reason) = shared.connect(room, &addr) {
656 shared.state().fail(room, reason);
657 }
658 }
659 })?;
660 }
661 let (heartbeat_stop, stopped) = mpsc::channel();
662 {
663 let shared = shared.clone();
664 let interval = config.heartbeat_interval;
665 let max_failures = config.heartbeat_max_failures.max(1);
666 thread::Builder::new()
667 .name("ts-pd-heartbeat".to_owned())
668 .spawn(move || shared.watch(interval, max_failures, &stopped))?;
669 }
670 let outbox = DecodeOutbox {
671 shared: shared.clone(),
672 staged: Staged::default(),
673 };
674 Ok((
675 Self {
676 shared,
677 connect,
678 stop,
679 _heartbeat_stop: heartbeat_stop,
680 waiting_timeout: config.waiting_timeout,
681 order: Vec::new(),
682 receivers: HashMap::new(),
683 timeout_logged: false,
684 },
685 outbox,
686 ))
687 }
688
689 pub fn port(&self) -> u16 {
691 self.shared.transport.sockets.port()
692 }
693
694 pub fn session_id(&self) -> String {
696 self.shared.transport.session_id()
697 }
698
699 fn forget(&mut self, rid: &str) {
700 let Some(receiver) = self.receivers.remove(rid) else {
701 return;
702 };
703 self.order.retain(|tracked| tracked != rid);
704 let mut state = self.shared.state();
705 state.clear(receiver.room, &receiver.addr);
706 state.rooms_by_rid.remove(rid);
707 }
708}
709
710impl Drop for DecodePeer {
711 fn drop(&mut self) {
712 self.stop.store(true, Ordering::Relaxed);
713 }
714}
715
716fn join_host_port(host: &str, port: u16) -> String {
718 if host.contains(':') && !host.starts_with('[') {
719 format!("[{host}]:{port}")
720 } else {
721 format!("{host}:{port}")
722 }
723}
724
725impl TransferPeer for DecodePeer {
726 fn register(&mut self, rid: &str, bootstrap: &BootstrapInfo) -> Result<(), PdError> {
727 let room = bootstrap.room;
728 let addr = join_host_port(&bootstrap.host, bootstrap.port);
729 {
730 let mut state = self.shared.state();
731 state.tracked.insert(room);
732 state.rooms_by_rid.insert(rid.to_owned(), room);
733 state.rooms.update(room, TransferPoll::Bootstrapping);
734 }
735 if self
736 .receivers
737 .insert(
738 rid.to_owned(),
739 Receiver {
740 room,
741 addr: addr.clone(),
742 local: TransferPoll::Bootstrapping,
743 concluded: None,
744 },
745 )
746 .is_none()
747 {
748 self.order.push(rid.to_owned());
749 }
750 self.connect
751 .send((room, addr))
752 .map_err(|_| PdError::Peer("the PD connect thread stopped".to_owned()))
753 }
754
755 fn poll(&mut self, mesh: &mut Mesh, replica: &Group) -> Result<Vec<PdEvent>, PdError> {
756 if self.order.is_empty() {
757 return Ok(Vec::new());
758 }
759 let now = Instant::now();
760 let mut polls = Vec::with_capacity(self.order.len());
761 {
762 let mut state = self.shared.state();
763 for rid in &self.order {
764 let receiver = self.receivers.get_mut(rid).expect("tracked");
765 let poll = match receiver.concluded {
766 Some(poll) => poll,
767 None => match state
768 .rooms
769 .get(receiver.room)
770 .unwrap_or(TransferPoll::Failed)
771 {
772 status @ (TransferPoll::Success | TransferPoll::Failed) => {
773 receiver.concluded = Some(status);
774 status
775 }
776 TransferPoll::WaitingForInput => {
777 let waited = state
778 .waiting_since
779 .get(&receiver.room)
780 .map(|since| now - *since);
781 match waited {
782 Some(waited) if waited >= self.waiting_timeout => {
783 if !self.timeout_logged {
784 self.timeout_logged = true;
785 tracing::warn!(
786 "Some requests never received their KV after \
787 bootstrapping. Raise \
788 TOKENSPEED_DISAGGREGATION_WAITING_TIMEOUT if a \
789 longer TTFT is acceptable."
790 );
791 }
792 state.rooms.record_failure(
793 receiver.room,
794 format!(
795 "Request {} timed out after {:.1}s in \
796 TransferPoll.WaitingForInput",
797 receiver.room,
798 waited.as_secs_f64()
799 ),
800 );
801 if let Some(metrics) = &self.shared.metrics {
802 metrics.record_timeout("waiting_for_input");
803 }
804 receiver.concluded = Some(TransferPoll::Failed);
805 TransferPoll::Failed
806 }
807 _ => TransferPoll::WaitingForInput,
808 }
809 }
810 status => status,
811 },
812 };
813 polls.push(poll as i64);
814 }
815 }
816 let agreed = if replica.size() > 1 {
817 mesh.all_reduce(replica, &polls, Reduce::Min)?
818 } else {
819 polls
820 };
821
822 let mut events = Vec::new();
823 let mut ended = Vec::new();
824 for (rid, &poll) in self.order.iter().zip(&agreed) {
825 let receiver = self.receivers.get_mut(rid).expect("tracked");
826 let poll = TransferPoll::from_wire(poll).unwrap_or(TransferPoll::Failed);
827 if receiver.local == TransferPoll::Bootstrapping && poll == TransferPoll::Bootstrapped {
828 receiver.local = TransferPoll::Bootstrapped;
829 events.push(PdEvent::Bootstrapped(rid.clone()));
830 } else if poll == TransferPoll::Failed {
831 let reason = self
832 .shared
833 .state()
834 .rooms
835 .failure(receiver.room)
836 .map(str::to_owned);
837 tracing::warn!(rid, room = receiver.room, reason, "KV transfer failed");
838 events.push(PdEvent::Failed(rid.clone()));
839 ended.push(rid.clone());
840 } else if receiver.local == TransferPoll::Bootstrapped && poll == TransferPoll::Success
841 {
842 receiver.local = TransferPoll::Success;
843 let mut state = self.shared.state();
844 let landed = state.landed.remove(&receiver.room).unwrap_or_default();
845 let cached_tokens = state.cached_tokens.remove(&receiver.room).unwrap_or(0);
846 let request_pool_index =
847 *state.admissions.get(&receiver.room).ok_or_else(|| {
848 PdError::Peer(format!("{rid}'s KV landed before its pages were admitted"))
849 })?;
850 let bootstrap_token = landed.bootstrap_token.ok_or_else(|| {
852 PdError::Peer(format!("{rid}'s KV landed without its first token"))
853 })?;
854 events.push(PdEvent::RemotePrefillDone(RemotePrefill {
855 rid: rid.clone(),
856 bootstrap_token,
857 cached_tokens,
858 bootstrap_logprob: landed.bootstrap_logprob,
859 spec_candidate_ids: landed.spec_candidate_ids,
860 request_pool_index,
861 }));
862 ended.push(rid.clone());
863 }
864 }
865 for rid in ended {
866 self.forget(&rid);
867 }
868 Ok(events)
869 }
870
871 fn record_usage(&mut self, _rid: &str, _cached_tokens: i64, _bootstrap_logprob: Option<f64>) {}
872
873 fn has_in_flight(&self) -> bool {
874 !self.order.is_empty()
875 }
876
877 fn abort_room(&mut self, _: &str, _: u64) {}
879}
880
881#[cfg(test)]
882mod tests {
883 use super::*;
884
885 fn success(room: u64, prefill_rank: i64, bootstrap_token: i64) -> StatusMessage {
886 StatusMessage {
887 room,
888 status: TransferPoll::Success,
889 prefill_rank,
890 bootstrap_token,
891 spec_candidate_ids: None,
892 cached_tokens: 0,
893 bootstrap_logprob: None,
894 }
895 }
896
897 fn route() -> PrefillRoute {
898 PrefillRoute {
899 target_tp_ranks: vec![0, 1],
900 dummy_tp_ranks: Vec::new(),
901 expected_prefill_ranks: vec![0, 1],
902 prefill_dp_size: 1,
903 }
904 }
905
906 fn connected(state: &mut State, room: u64, addr: &str) {
908 state.tracked.insert(room);
909 state.rooms.update(room, TransferPoll::Bootstrapping);
910 assert!(state.attach(room, addr, &route(), Arc::new(Vec::new())));
911 }
912
913 #[test]
914 fn a_room_lands_with_the_first_token_any_rank_carried() {
915 let mut state = State::default();
916 connected(&mut state, 1, "prefill:80");
917 state.apply(success(1, 1, -1));
918 assert_eq!(state.rooms.get(1), Some(TransferPoll::Bootstrapped));
919 state.apply(success(1, 0, 7));
920 assert_eq!(state.rooms.get(1), Some(TransferPoll::Success));
921 assert_eq!(state.landed[&1].bootstrap_token, Some(7));
922 }
923
924 #[test]
925 fn a_landing_without_a_valid_first_token_fails_the_room() {
926 let mut state = State::default();
927 connected(&mut state, 1, "prefill:80");
928 state.apply(success(1, 0, -1));
929 state.apply(success(1, 1, -1));
930 assert_eq!(state.rooms.get(1), Some(TransferPoll::Failed));
931 assert!(state.rooms.failure(1).unwrap().contains("first token"));
932 connected(&mut state, 2, "prefill:80");
933 state.apply(success(2, 0, i64::from(i32::MAX) + 1));
934 assert_eq!(state.rooms.get(2), Some(TransferPoll::Failed));
935 }
936
937 #[test]
938 fn a_forgotten_room_is_not_brought_back() {
939 let mut state = State::default();
940 connected(&mut state, 1, "prefill:80");
941 state.clear(1, "prefill:80");
942 state.apply(success(1, 0, 7));
944 state.fail(1, "late");
945 assert!(!state.attach(1, "prefill:80", &route(), Arc::new(Vec::new())));
946 assert_eq!(state.rooms.get(1), None);
947 assert!(state.expected.is_empty());
948 assert!(state.targets.is_empty());
949 assert!(state.pending.is_empty() && state.landed.is_empty());
950 }
951
952 #[test]
953 fn losing_a_node_keeps_another_whose_address_extends_its_own() {
954 let mut state = State::default();
955 for (room, addr) in [(1, "host:80"), (2, "host:8000")] {
956 connected(&mut state, room, addr);
957 state
958 .connections
959 .insert((addr.to_owned(), 0, vec![0, 1]), Arc::new(Vec::new()));
960 }
961 assert_eq!(state.lose_node("host:80"), 1);
962 assert_eq!(state.rooms.get(1), Some(TransferPoll::Failed));
963 assert_eq!(state.rooms.get(2), Some(TransferPoll::Bootstrapped));
964 let kept: Vec<&str> = state
965 .connections
966 .keys()
967 .map(|(addr, _, _)| addr.as_str())
968 .collect();
969 assert_eq!(kept, ["host:8000"]);
970 }
971}