use std::collections::BTreeSet;
use bytes::Bytes;
use serde_json::Value;
use super::{
contract::Contract,
manifest::Manifest,
route::{pipeline_plan, validate_owner_filters, Fragment, OwnerFilters, Planner, MAX_TP_SIZE},
LayoutError,
};
use crate::{net::parse_uint, wire};
#[derive(Debug, Clone)]
pub struct RegistrationInfo {
pub endpoint: String,
pub port: u16,
pub session_id: String,
pub arena_base: u64,
pub layout: Contract,
pub tp_size: i64,
pub tp_rank: i64,
}
impl RegistrationInfo {
pub fn from_frames(frames: &[Bytes]) -> Result<Self, LayoutError> {
let (endpoint, port, session_id) =
wire::registration_route(frames).map_err(|error| LayoutError::new(error.0))?;
let pointer: [u8; 8] = frames[4]
.as_ref()
.try_into()
.map_err(|_| LayoutError::new("CachePD slab pointer frame must be exactly 8 bytes"))?;
let layout = Contract::from_wire(&frames[5])?;
let tp_size = parse_uint(&frames[6], MAX_TP_SIZE as u64)
.filter(|&size| size >= 1)
.ok_or_else(|| LayoutError::new("CachePD decode_tp_size is invalid"))?;
let tp_rank = parse_uint(&frames[7], tp_size - 1)
.ok_or_else(|| LayoutError::new("CachePD decode_tp_rank is invalid"))?;
let arena_base = u64::from_ne_bytes(pointer);
if arena_base == 0 {
return Err(LayoutError::new("CachePD slab pointer must be nonzero"));
}
Ok(Self {
endpoint,
port,
session_id,
arena_base,
layout,
tp_size: tp_size as i64,
tp_rank: tp_rank as i64,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Preallocation {
pub room: u64,
pub session_id: String,
pub manifest: Option<Manifest>,
}
impl Preallocation {
pub fn from_frames(frames: &[Bytes]) -> Result<Self, LayoutError> {
let (room, session_id) =
wire::preallocation_route(frames).map_err(|error| LayoutError::new(error.0))?;
let manifest = if frames[2].is_empty() {
None
} else {
Some(Manifest::from_wire(&frames[2])?)
};
Ok(Self {
room,
session_id,
manifest,
})
}
}
#[derive(Debug, Clone)]
pub struct PrefillLayout {
pub local: Contract,
pub wire: Contract,
pub arena_base: u64,
pub tp_size: i64,
pub tp_rank: i64,
pub pp_rank: i64,
pub fields_by_stage: Vec<Vec<String>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PlannedRegistration {
pub fragments: Vec<Fragment>,
pub owner_filters: OwnerFilters,
pub is_dummy: bool,
pub expected_decode_ranks: BTreeSet<i64>,
}
pub fn plan_registration(
layout: &PrefillLayout,
registration: &RegistrationInfo,
) -> Result<PlannedRegistration, LayoutError> {
let stage_fields: BTreeSet<String> = usize::try_from(layout.pp_rank)
.ok()
.and_then(|stage| layout.fields_by_stage.get(stage))
.ok_or_else(|| LayoutError::new("this rank's pipeline stage has no field placement"))?
.iter()
.cloned()
.collect();
let planner = Planner::new(
layout.tp_size,
registration.tp_size,
&layout.wire,
®istration.layout,
Some(&stage_fields),
)?;
let route = planner.plan_for_decode_rank(registration.tp_rank)?;
let served = planner
.decode_ranks_by_prefill_rank()
.get(&layout.tp_rank)
.cloned()
.ok_or_else(|| LayoutError::new("this rank is outside the Prefill TP size"))?;
let planned = if let Some(fragments) = route.fragments_by_prefill_rank.get(&layout.tp_rank) {
PlannedRegistration {
fragments: fragments.clone(),
owner_filters: route.owner_filters_by_prefill_rank[&layout.tp_rank].clone(),
is_dummy: false,
expected_decode_ranks: served,
}
} else if registration.tp_rank == 0 && served.is_empty() {
PlannedRegistration {
fragments: Vec::new(),
owner_filters: planner
.sharded_group_ids()
.into_iter()
.map(|group| (group, None))
.collect(),
is_dummy: true,
expected_decode_ranks: BTreeSet::from([0]),
}
} else {
return Err(LayoutError::new(
"CachePD Decode registration targets the wrong Prefill TP rank",
));
};
validate_owner_filters(
&layout.local.group_specs,
&planned.fragments,
&planned.owner_filters,
)?;
Ok(planned)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrefillRoute {
pub target_tp_ranks: Vec<i64>,
pub dummy_tp_ranks: Vec<i64>,
pub expected_prefill_ranks: Vec<i64>,
pub prefill_dp_size: i64,
}
fn integer(value: &Value, name: &str) -> Result<i64, LayoutError> {
value
.as_i64()
.or_else(|| value.as_str().and_then(|text| text.trim().parse().ok()))
.ok_or_else(|| LayoutError::new(format!("the Prefill description's {name} is invalid")))
}
pub fn plan_route(
prefill_info: &str,
decode_layout: &Contract,
decode_tp_size: i64,
decode_tp_rank: i64,
) -> Result<PrefillRoute, LayoutError> {
let payload: Value = serde_json::from_str(prefill_info)
.map_err(|_| LayoutError::new("the Prefill description is not JSON"))?;
let field = |key: &str| {
payload
.get(key)
.ok_or_else(|| LayoutError::new(format!("the Prefill description misses {key:?}")))
};
let layout_wire = match payload.get("cache_layout") {
None | Some(Value::Null) => {
return Err(LayoutError::new(
"Cache-transfer decode connected to a non-cache-transfer prefill",
))
}
Some(Value::String(wire)) if wire.is_ascii() => wire,
Some(_) => {
return Err(LayoutError::new(
"the Prefill layout must be an ASCII string",
))
}
};
let tp_size = integer(field("prefill_tp_size")?, "prefill_tp_size")?;
let dp_size = integer(field("prefill_dp_size")?, "prefill_dp_size")?;
let pp_size = match payload.get("prefill_pp_size") {
None => 1,
Some(value) => integer(value, "prefill_pp_size")?,
};
let fields_by_stage = field("cache_fields_by_stage")?
.as_array()
.ok_or_else(|| LayoutError::new("the Prefill field placement must be a list"))?
.iter()
.map(|stage| {
stage
.as_array()
.ok_or_else(|| LayoutError::new("a Prefill stage's fields must be a list"))?
.iter()
.map(|field| {
field
.as_str()
.map(str::to_owned)
.ok_or_else(|| LayoutError::new("a Prefill field id must be a string"))
})
.collect::<Result<Vec<_>, _>>()
})
.collect::<Result<Vec<_>, _>>()?;
let prefill_layout = Contract::from_wire(layout_wire.as_bytes())?;
if dp_size <= 0 || pp_size <= 0 {
return Err(LayoutError::new(
"the Prefill DP and PP sizes must be positive",
));
}
let prefill_tp_size = tp_size / (dp_size * pp_size);
let (plan, dummy) = pipeline_plan(
prefill_tp_size,
decode_tp_size,
decode_tp_rank,
&prefill_layout,
decode_layout,
&fields_by_stage,
)?;
let expected = plan.target_prefill_ranks();
let mut targets: Vec<i64> = expected.iter().chain(&dummy).copied().collect();
targets.sort_unstable();
Ok(PrefillRoute {
target_tp_ranks: targets,
dummy_tp_ranks: dummy,
expected_prefill_ranks: expected,
prefill_dp_size: dp_size,
})
}