use std::{
collections::{HashMap, HashSet},
sync::{
atomic::{AtomicBool, Ordering},
mpsc, Arc, Mutex, MutexGuard,
},
thread,
time::{Duration, Instant},
};
use bytes::Bytes;
use super::{parse_status, pool::SlotPool, registration_frames, shard_rows, TransferInfo};
use crate::{
control::{EmbeddingItem, EmbeddingReceiver, LandedItem, LandedRequest},
http, net,
status::TransferPoll,
StartError, Transport,
};
const LOOKUP_TIMEOUT: Duration = Duration::from_secs(5);
const QUARANTINE: Duration = Duration::from_secs(10);
const SOCKET_POLL: Duration = Duration::from_millis(100);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PoolRegion {
pub base: u64,
pub slot_bytes: u64,
pub slots: usize,
}
#[derive(Debug, Clone)]
pub struct ReceiverConfig {
pub tp_size: i64,
pub tp_rank: i64,
pub hidden: i64,
pub num_deepstack: i64,
pub dtype: String,
pub element_size: i64,
pub shard: bool,
pub pool: Option<PoolRegion>,
pub deepstack_pool: Option<PoolRegion>,
pub timeout: Duration,
}
#[derive(Debug, Clone, Copy)]
struct Parallel {
tp_size: i64,
dp_size: i64,
}
#[derive(Debug, Clone)]
struct Room {
status: TransferPoll,
target: Option<(String, u16)>,
fanout: i64,
failure: Option<String>,
}
#[derive(Default)]
struct State {
rooms: HashMap<u64, Room>,
parallel: HashMap<String, Parallel>,
connections: HashMap<(String, i64, i64), (String, u16)>,
}
impl State {
fn fail(&mut self, room: u64, reason: impl Into<String>) {
if let Some(entry) = self.rooms.get_mut(&room) {
entry.status = TransferPoll::Failed;
entry.failure.get_or_insert_with(|| reason.into());
}
}
}
struct Shared {
transport: Arc<Transport>,
sockets: net::Sockets,
tp_size: i64,
tp_rank: i64,
state: Mutex<State>,
}
fn join_host_port(host: &str, port: u16) -> String {
if host.contains(':') && !host.starts_with('[') {
format!("[{host}]:{port}")
} else {
format!("{host}:{port}")
}
}
fn integer(value: &serde_json::Value) -> Option<i64> {
value
.as_i64()
.or_else(|| value.as_str().and_then(|text| text.trim().parse().ok()))
}
impl Shared {
fn state(&self) -> MutexGuard<'_, State> {
self.state.lock().expect("the EPD state lock is poisoned")
}
fn parallel(&self, addr: &str) -> Result<Parallel, String> {
if let Some(parallel) = self.state().parallel.get(addr).copied() {
return Ok(parallel);
}
let incomplete = || "no (complete) parallel info from bootstrap".to_owned();
let body = http::get_text(
&format!("http://{addr}/route?engine_rank=-1&target_dp_group=-1"),
LOOKUP_TIMEOUT,
)
.map_err(|error| {
tracing::error!(addr, error, "EPD bootstrap /route fetch failed");
incomplete()
})?;
let info: serde_json::Value = serde_json::from_str(&body).map_err(|_| incomplete())?;
let parallel = Parallel {
tp_size: integer(&info["prefill_tp_size"]).ok_or_else(incomplete)?,
dp_size: integer(&info["prefill_dp_size"]).ok_or_else(incomplete)?,
};
if parallel.tp_size <= 0 || parallel.dp_size <= 0 {
return Err(incomplete());
}
self.state().parallel.insert(addr.to_owned(), parallel);
Ok(parallel)
}
fn connect(&self, room: u64, addr: &str) -> Result<((String, u16), i64), String> {
let parallel = self.parallel(addr)?;
if self.tp_size % parallel.tp_size != 0 {
return Err(format!(
"EPD requires prefill_tp to be a multiple of encode_tp (encode_tp={}, \
prefill_tp={})",
parallel.tp_size, self.tp_size
));
}
let fanout = self.tp_size / parallel.tp_size;
let encode_rank = self.tp_rank / fanout;
let dp_group = (room % parallel.dp_size as u64) as i64;
let key = (addr.to_owned(), dp_group, encode_rank);
if let Some(target) = self.state().connections.get(&key).cloned() {
return Ok((target, fanout));
}
let missing = || "no encode rank info from bootstrap".to_owned();
let body = http::get_text(
&format!("http://{addr}/route?engine_rank={encode_rank}&target_dp_group={dp_group}"),
LOOKUP_TIMEOUT,
)
.map_err(|error| {
tracing::error!(addr, error, "EPD bootstrap /route fetch failed");
missing()
})?;
let info: serde_json::Value = serde_json::from_str(&body).map_err(|_| missing())?;
let host = info["rank_ip"].as_str().ok_or_else(missing)?.to_owned();
let port = integer(&info["rank_port"])
.and_then(|port| u16::try_from(port).ok())
.ok_or_else(missing)?;
self.sockets.send(
&host,
port,
registration_frames(
&self.transport.local_ip,
self.sockets.port(),
&self.transport.session_id(),
0,
0,
),
);
self.state().connections.insert(key, (host.clone(), port));
Ok(((host, port), fanout))
}
fn apply(&self, frames: &[Bytes]) {
let (room, status, _rank) = match parse_status(frames) {
Ok(status) => status,
Err(error) => {
tracing::error!(%error, "rejecting a malformed EPD status");
return;
}
};
let mut state = self.state();
match status {
TransferPoll::Success => {
if let Some(entry) = state.rooms.get_mut(&room) {
if entry.status != TransferPoll::Failed {
entry.status = TransferPoll::Success;
}
}
}
TransferPoll::Failed => state.fail(room, "encode failed to send embedding"),
_ => {}
}
}
}
#[derive(Debug)]
struct StagedItem {
item: EmbeddingItem,
row_start: i64,
row_count: i64,
sharded: bool,
slot: Option<usize>,
preallocated: bool,
failure: Option<String>,
}
#[derive(Debug)]
struct Staged {
rid: String,
since: Instant,
items: Vec<StagedItem>,
}
pub struct Receiver {
shared: Arc<Shared>,
connect: mpsc::Sender<(u64, String)>,
stop: Arc<AtomicBool>,
config: ReceiverConfig,
pool: Option<SlotPool>,
staged: Vec<Staged>,
awaiting: HashSet<usize>,
timeout_logged: bool,
}
impl Receiver {
pub(crate) fn start(
config: ReceiverConfig,
transport: Arc<Transport>,
) -> Result<Self, StartError> {
for region in config.pool.iter().chain(&config.deepstack_pool) {
let engine = transport.engine();
engine.register(
region.base as usize,
region.slot_bytes as usize * region.slots,
)?;
}
let (incoming, statuses) = mpsc::channel::<Vec<Bytes>>();
let sockets = net::Sockets::start(&transport.local_ip, incoming)?;
let shared = Arc::new(Shared {
transport,
sockets,
tp_size: config.tp_size,
tp_rank: config.tp_rank,
state: Mutex::new(State::default()),
});
let stop = Arc::new(AtomicBool::new(false));
{
let shared = shared.clone();
let stop = stop.clone();
thread::Builder::new()
.name("ts-epd-status".to_owned())
.spawn(move || loop {
match statuses.recv_timeout(SOCKET_POLL) {
Ok(frames) => shared.apply(&frames),
Err(mpsc::RecvTimeoutError::Timeout) => {
if stop.load(Ordering::Relaxed) {
return;
}
}
Err(mpsc::RecvTimeoutError::Disconnected) => return,
}
})?;
}
let (connect, rooms) = mpsc::channel::<(u64, String)>();
{
let shared = shared.clone();
thread::Builder::new()
.name("ts-epd-connect".to_owned())
.spawn(move || {
while let Ok((room, addr)) = rooms.recv() {
match shared.connect(room, &addr) {
Ok((target, fanout)) => {
let mut state = shared.state();
if let Some(entry) = state.rooms.get_mut(&room) {
if entry.status == TransferPoll::Bootstrapping {
entry.status = TransferPoll::Bootstrapped;
}
entry.target = Some(target);
entry.fanout = fanout;
}
}
Err(reason) => shared.state().fail(room, reason),
}
}
})?;
}
let pool = config
.pool
.map(|region| SlotPool::new(region.slots, region.slot_bytes));
tracing::info!(
pool = ?config.pool,
deepstack_pool = ?config.deepstack_pool,
port = shared.sockets.port(),
"EPD embedding receiver up"
);
Ok(Self {
shared,
connect,
stop,
config,
pool,
staged: Vec::new(),
awaiting: HashSet::new(),
timeout_logged: false,
})
}
pub fn port(&self) -> u16 {
self.shared.sockets.port()
}
fn bytes(&self, rows: i64) -> (u64, u64) {
let main = rows * self.config.hidden * self.config.element_size;
(main as u64, (main * self.config.num_deepstack) as u64)
}
fn advance(&mut self, request: usize, index: usize, now: Instant) -> TransferPoll {
let (main_bytes, deep_bytes) = self.bytes(self.staged[request].items[index].row_count);
let item = &mut self.staged[request].items[index];
if item.failure.is_some() {
return TransferPoll::Failed;
}
let room = self.shared.state().rooms.get(&item.item.room).cloned();
let Some(room) = room else {
return TransferPoll::Failed;
};
if room.status == TransferPoll::Failed {
item.failure = room
.failure
.or_else(|| Some("the encode node failed".to_owned()));
return TransferPoll::Failed;
}
if item.slot.is_none() && item.row_count > 0 {
let Some(pool) = &mut self.pool else {
item.failure = Some(
"EPD receive pool is disabled (TOKENSPEED_EPD_RECV_POOL_SLOTS=0)".to_owned(),
);
return TransferPoll::Failed;
};
let deep_slot = self
.config
.deepstack_pool
.map_or(0, |region| region.slot_bytes);
if deep_bytes > deep_slot && self.config.num_deepstack > 0 {
item.failure = Some(format!(
"EPD item's {deep_bytes} deepstack bytes exceed the receive pool's \
{deep_slot}-byte slots; raise TOKENSPEED_EPD_RECV_POOL_SLOT_MB"
));
return TransferPoll::Failed;
}
match pool.lease(main_bytes, now) {
Ok(Some(slot)) => item.slot = Some(slot),
Ok(None) => return room.status,
Err(oversized) => {
item.failure = Some(format!(
"EPD item's {} bytes exceed the receive pool's {}-byte slots; raise \
TOKENSPEED_EPD_RECV_POOL_SLOT_MB",
oversized.bytes, oversized.slot_bytes
));
return TransferPoll::Failed;
}
}
}
if !item.preallocated && room.status >= TransferPoll::Bootstrapped {
if let Some((host, port)) = &room.target {
let pointer = |region: Option<PoolRegion>| {
region.map_or(0, |region| {
region.base + item.slot.unwrap_or(0) as u64 * region.slot_bytes
})
};
let info = TransferInfo {
room: item.item.room,
endpoint: self.shared.transport.local_ip.clone(),
port: self.shared.sockets.port(),
session_id: self.shared.transport.session_id(),
embedding_ptr: pointer(self.config.pool),
deepstack_ptr: if self.config.num_deepstack > 0 {
pointer(self.config.deepstack_pool)
} else {
0
},
rows: item.row_count,
hidden: self.config.hidden,
dtype: self.config.dtype.clone(),
has_deepstack: self.config.num_deepstack > 0,
fanout: room.fanout,
row_start: item.row_start,
span: item.item.rows,
};
self.shared.sockets.send(host, *port, info.to_frames());
item.preallocated = true;
}
}
room.status
}
fn forget(&mut self, staged: &Staged, landed: bool, now: Instant) {
let mut state = self.shared.state();
for item in &staged.items {
state.rooms.remove(&item.item.room);
}
drop(state);
for slot in staged.items.iter().filter_map(|item| item.slot) {
if landed {
self.awaiting.insert(slot);
} else if let Some(pool) = &mut self.pool {
pool.quarantine(slot, now + QUARANTINE);
}
}
}
}
impl Drop for Receiver {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
}
}
impl EmbeddingReceiver for Receiver {
fn stage(&mut self, rid: &str, items: &[EmbeddingItem]) {
let sharded = self.config.shard && self.config.tp_size > 1;
let mut staged_items = Vec::with_capacity(items.len());
for item in items {
let (row_start, row_count) = if sharded {
shard_rows(item.rows, self.config.tp_rank, self.config.tp_size)
} else {
(0, item.rows)
};
self.shared.state().rooms.insert(
item.room,
Room {
status: TransferPoll::Bootstrapping,
target: None,
fanout: 0,
failure: None,
},
);
if self
.connect
.send((item.room, join_host_port(&item.host, item.port)))
.is_err()
{
self.shared
.state()
.fail(item.room, "the EPD connect thread stopped");
}
staged_items.push(StagedItem {
item: item.clone(),
row_start,
row_count,
sharded,
slot: None,
preallocated: false,
failure: None,
});
}
self.staged.push(Staged {
rid: rid.to_owned(),
since: Instant::now(),
items: staged_items,
});
}
fn poll(&mut self) -> Vec<i64> {
let now = Instant::now();
let mut codes = Vec::with_capacity(self.staged.len());
for request in 0..self.staged.len() {
let mut landed = true;
let mut failed = false;
for index in 0..self.staged[request].items.len() {
match self.advance(request, index, now) {
TransferPoll::Success => {}
TransferPoll::Failed => failed = true,
_ => landed = false,
}
}
let staged = &self.staged[request];
let code = if failed {
0
} else if landed {
2
} else if now.duration_since(staged.since) > self.config.timeout {
if !self.timeout_logged {
self.timeout_logged = true;
tracing::warn!(
rid = staged.rid,
timeout = ?self.config.timeout,
"an EPD embedding receive timed out; raise \
TOKENSPEED_DISAGGREGATION_WAITING_TIMEOUT if a longer TTFT is acceptable"
);
}
0
} else {
1
};
codes.push(code);
}
codes
}
fn settle(&mut self, codes: &[i64]) -> Vec<LandedRequest> {
let now = Instant::now();
let mut landed = Vec::new();
let mut leftover = Vec::new();
for (staged, &code) in std::mem::take(&mut self.staged).into_iter().zip(codes) {
match code {
2 => {
self.forget(&staged, true, now);
landed.push(LandedRequest {
rid: staged.rid,
items: staged
.items
.into_iter()
.map(|item| LandedItem {
index: item.item.index,
slot: item.slot,
row_start: item.row_start,
row_count: item.row_count,
rows: item.item.rows,
sharded: item.sharded,
})
.collect(),
});
}
0 => {
let reasons: Vec<&str> = staged
.items
.iter()
.filter_map(|item| item.failure.as_deref())
.collect();
tracing::warn!(rid = staged.rid, ?reasons, "EPD embedding receive failed");
self.forget(&staged, false, now);
}
_ => leftover.push(staged),
}
}
self.staged = leftover;
landed
}
fn release(&mut self, slots: &[usize]) {
for &slot in slots {
if self.awaiting.remove(&slot) {
if let Some(pool) = &mut self.pool {
pool.release(slot);
}
}
}
}
fn awaiting_release(&self) -> bool {
!self.awaiting.is_empty()
}
}