use std::io::{Read, Write};
use std::net::{SocketAddr, TcpStream};
use std::time::{Duration, Instant};
use crate::distributed::controller::{
self, DTYPE_BF16, DTYPE_F32, HANDSHAKE_MAGIC_CONTROLLER_ACK, HANDSHAKE_MAGIC_RANK,
PROTOCOL_VERSION, RoundFrame, RoundKind, TensorPayload,
};
use crate::distributed::wire::SessionSalt;
use crate::tensor::{DType, Device, Result, Tensor, TensorError, TensorOptions};
pub const DECODE_SLOT_PARAMS: usize = 0;
pub const DECODE_SLOT_BUFFERS: usize = 1;
pub fn pinned_decode_affordable(
staging_bytes: u64,
local_ranks: usize,
mem_available_bytes: u64,
) -> bool {
const HEADROOM_FACTOR: u64 = 6;
let need = staging_bytes
.saturating_mul(local_ranks.max(1) as u64)
.saturating_mul(HEADROOM_FACTOR);
mem_available_bytes > need
}
const REDUCE_READ_DEADLINE_SECS: u64 = 120;
#[derive(Debug)]
pub struct CpuReduceClient {
stream: TcpStream,
rank_id: u32,
world_size: u32,
salt: SessionSalt,
prof_serialize_ns: u128,
prof_wire_ns: u128,
prof_deserialize_ns: u128,
prof_bytes: u64,
prof_count: u64,
prof_enabled: bool,
model_wire_dtype: u8,
pinned_decode: bool,
decode_slots: Vec<Vec<Tensor>>,
armed_decode_slot: Option<usize>,
pinned_decode_fallback_logged: bool,
}
impl CpuReduceClient {
pub fn connect(
controller_addr: SocketAddr,
rank_id: u32,
world_size: u32,
salt: SessionSalt,
) -> Result<Self> {
if world_size == 0 {
return Err(TensorError::new(
"cpu_reduce: world_size must be > 0",
));
}
if rank_id >= world_size {
return Err(TensorError::new(&format!(
"cpu_reduce: rank_id {rank_id} must be < world_size {world_size}"
)));
}
let stream = crate::distributed::wire::connect_with_retry(
controller_addr,
"cpu_reduce",
)?;
let _ = stream.set_nodelay(true);
stream
.set_read_timeout(Some(Duration::from_secs(10)))
.map_err(|e| TensorError::new(&format!("cpu_reduce: set_read_timeout: {e}")))?;
stream
.set_write_timeout(Some(crate::distributed::wire::write_stall_timeout()))
.map_err(|e| TensorError::new(&format!("cpu_reduce: set_write_timeout: {e}")))?;
let mut client = CpuReduceClient {
stream,
rank_id,
world_size,
salt,
prof_serialize_ns: 0,
prof_wire_ns: 0,
prof_deserialize_ns: 0,
prof_bytes: 0,
prof_count: 0,
prof_enabled: crate::log::enabled(crate::log::Verbosity::Debug),
model_wire_dtype: DTYPE_F32,
pinned_decode: false,
decode_slots: Vec::new(),
armed_decode_slot: None,
pinned_decode_fallback_logged: false,
};
client.send_handshake()?;
client.read_handshake_ack()?;
client
.stream
.set_read_timeout(Some(Duration::from_secs(
crate::distributed::wire::scaled_deadline_secs(REDUCE_READ_DEADLINE_SECS),
)))
.map_err(|e| {
TensorError::new(&format!("cpu_reduce: set reduce read deadline: {e}"))
})?;
Ok(client)
}
fn send_handshake(&mut self) -> Result<()> {
let mut buf = [0u8; 16];
buf[0..4].copy_from_slice(&HANDSHAKE_MAGIC_RANK.to_le_bytes());
buf[4..8].copy_from_slice(&PROTOCOL_VERSION.to_le_bytes());
buf[8..12].copy_from_slice(&self.rank_id.to_le_bytes());
buf[12..16].copy_from_slice(&self.world_size.to_le_bytes());
self.stream.write_all(&buf).map_err(|e| {
TensorError::new(&format!("cpu_reduce: handshake write failed: {e}"))
})?;
self.stream.flush().map_err(|e| {
TensorError::new(&format!("cpu_reduce: handshake flush failed: {e}"))
})?;
Ok(())
}
fn read_handshake_ack(&mut self) -> Result<()> {
let mut buf = [0u8; 8];
self.stream.read_exact(&mut buf).map_err(|e| {
TensorError::new(&format!(
"cpu_reduce: handshake ack read failed: {e} \
(controller may have rejected our handshake)"
))
})?;
let magic = u32::from_le_bytes(buf[0..4].try_into().unwrap());
if magic != HANDSHAKE_MAGIC_CONTROLLER_ACK {
return Err(TensorError::new(&format!(
"cpu_reduce: handshake ack magic 0x{magic:08x} != \
0x{HANDSHAKE_MAGIC_CONTROLLER_ACK:08x}"
)));
}
let proto_ver = u32::from_le_bytes(buf[4..8].try_into().unwrap());
if proto_ver != PROTOCOL_VERSION {
return Err(TensorError::new(&format!(
"cpu_reduce: controller protocol_version {proto_ver} != \
our version {PROTOCOL_VERSION}"
)));
}
Ok(())
}
pub fn world_size(&self) -> u32 {
self.world_size
}
pub fn set_bf16_wire(&mut self, on: bool) {
self.model_wire_dtype = if on { DTYPE_BF16 } else { DTYPE_F32 };
}
pub fn set_pinned_decode(&mut self, on: bool) {
self.pinned_decode = on;
}
pub fn arm_pinned_decode(&mut self, slot: usize) {
if self.pinned_decode {
self.armed_decode_slot = Some(slot);
}
}
pub fn all_reduce(&mut self, frame: RoundFrame) -> Result<RoundFrame> {
write_framed_round(&mut self.stream, &frame, &self.salt)?;
drop(frame);
match read_framed_round(&mut self.stream, &self.salt)? {
Some(f) => Ok(f),
None => Err(TensorError::new(
"cpu_reduce: controller closed connection before sending averaged \
frame back (controller crashed, or another rank disconnected and \
triggered cluster-wide shutdown mid-round)",
)),
}
}
pub fn all_reduce_weighted(
&mut self,
tensors: &[&Tensor],
kind: RoundKind,
weight: f64,
) -> Result<(Vec<Tensor>, f64)> {
self.all_reduce_scaled(tensors, 1.0, kind, weight)
}
pub fn all_reduce_scaled(
&mut self,
tensors: &[&Tensor],
scale: f64,
kind: RoundKind,
weight: f64,
) -> Result<(Vec<Tensor>, f64)> {
let t0 = Instant::now();
let wire_dtype = self.wire_dtype_for(kind);
let wire_tensor_dtype = wire_tensor_dtype(wire_dtype)?;
let elem = controller::payload_element_size(wire_dtype)? as u64;
for (i, t) in tensors.iter().enumerate() {
if !matches!(t.dtype(), DType::Float32 | DType::BFloat16) {
return Err(TensorError::new(&format!(
"cpu_reduce: tensor[{i}] dtype {:?} not supported (only Float32 \
/ BFloat16). Extend cpu_reduce.rs and the round_frame.rs codec \
helpers together to add support.",
t.dtype()
)));
}
}
let shapes: Vec<Vec<u32>> = tensors
.iter()
.enumerate()
.map(|(i, t)| wire_shape(i, t))
.collect::<Result<_>>()?;
let parts: Vec<controller::PayloadPart<'_>> = tensors
.iter()
.zip(shapes.iter())
.map(|(t, shape)| controller::PayloadPart {
dtype: wire_dtype,
shape,
nbytes: t.numel() as u64 * elem,
})
.collect();
let sent_bytes: u64 = parts.iter().map(|p| p.nbytes).sum();
crate::distributed::relay::mux::write_len_prefix(
&mut self.stream,
controller::round_frame_wire_len(&parts),
)?;
let prof = self.prof_enabled;
let mut produce_ns: u128 = 0;
controller::write_round_frame_streamed(
&mut self.stream,
kind,
weight,
&parts,
&self.salt,
&mut |ti, tee| {
let tp = Instant::now();
let t = tensors[ti];
let bytes = if t.dtype() == wire_tensor_dtype {
let mut b = t.to_blob()?;
if scale != 1.0 {
controller::scale_payload_bytes(&mut b, wire_dtype, scale as f32)?;
}
b
} else {
let src = if scale != 1.0 {
t.mul_scalar(scale)?
} else {
t.clone()
};
src.to_dtype(wire_tensor_dtype)?.to_blob()?
};
if prof {
produce_ns += tp.elapsed().as_nanos();
}
tee.write_all(&bytes)
.map_err(|e| TensorError::new(&e.to_string()))
},
)?;
let (out, weight, decode_ns) = self.read_reduced_tensors()?;
if prof {
let t2 = Instant::now();
self.prof_serialize_ns += produce_ns;
self.prof_wire_ns += (t2 - t0)
.as_nanos()
.saturating_sub(produce_ns)
.saturating_sub(decode_ns);
self.prof_deserialize_ns += decode_ns;
self.prof_bytes += sent_bytes;
self.prof_count += 1;
}
Ok((out, weight))
}
fn read_reduced_tensors(&mut self) -> Result<(Vec<Tensor>, f64, u128)> {
let Some(len) =
crate::distributed::relay::mux::read_len_prefix(&mut self.stream)?
else {
return Err(TensorError::new(
"cpu_reduce: controller closed connection before sending averaged \
frame back (controller crashed, or another rank disconnected and \
triggered cluster-wide shutdown mid-round)",
));
};
let mut staging = match self.armed_decode_slot.take() {
Some(s) => {
if self.decode_slots.len() <= s {
self.decode_slots.resize_with(s + 1, Vec::new);
}
Some(&mut self.decode_slots[s])
}
None => None,
};
let prof = self.prof_enabled;
let mut decode_ns: u128 = 0;
let mut out: Vec<Tensor> = Vec::new();
let mut pin_fallback: Option<String> = None;
let mut body = (&mut self.stream).take(len as u64);
let hdr = controller::read_round_frame_streamed(
&mut body,
&self.salt,
&mut |i, payload| {
let tp = Instant::now();
let t = match staging.as_deref_mut() {
Some(slot) => decode_into_slot(i, &payload, slot, &mut pin_fallback)?,
None => payload_to_cpu_tensor(i, &payload)?,
};
out.push(t);
if prof {
decode_ns += tp.elapsed().as_nanos();
}
Ok(())
},
)?;
let leftover = body.limit();
finish_framed_body(hdr.is_some(), leftover)?;
if let Some(msg) = pin_fallback
&& !self.pinned_decode_fallback_logged
{
self.pinned_decode_fallback_logged = true;
eprintln!(
"flodl cpu_reduce: rank {} decode staging could not be pinned \
({msg}); reusing pageable staging (H2D writeback degrades to \
a synchronous bounce copy)",
self.rank_id,
);
}
let (_kind, weight) = hdr.expect("finish_framed_body verified Some");
Ok((out, weight, decode_ns))
}
fn wire_dtype_for(&self, kind: RoundKind) -> u8 {
match kind {
RoundKind::Model => self.model_wire_dtype,
RoundKind::Control => DTYPE_F32,
}
}
#[allow(dead_code)]
pub fn all_reduce_tensors(&mut self, tensors: &[&Tensor]) -> Result<Vec<Tensor>> {
Ok(self
.all_reduce_weighted(tensors, RoundKind::Model, 1.0)?
.0)
}
pub fn log_profile_summary(&self) {
if self.prof_count == 0 {
return;
}
let n = self.prof_count as f64;
let ser = self.prof_serialize_ns as f64 / 1e6;
let wire = self.prof_wire_ns as f64 / 1e6;
let de = self.prof_deserialize_ns as f64 / 1e6;
let total = (ser + wire + de).max(1e-9);
let mb = self.prof_bytes as f64 / 1e6;
let wire_s = self.prof_wire_ns as f64 / 1e9;
let mbps = if wire_s > 0.0 { (mb * 2.0) / wire_s } else { 0.0 };
eprintln!(
"[cpu-reduce-prof] rank={} reduces={} | serialize={:.0}ms ({:.0}%) \
wire={:.0}ms ({:.0}%) deserialize={:.0}ms ({:.0}%) | per-reduce \
ser={:.2}ms wire={:.2}ms de={:.2}ms bytes={:.2}MB | wire~{:.1}MB/s(up+down)",
self.rank_id,
self.prof_count,
ser,
100.0 * ser / total,
wire,
100.0 * wire / total,
de,
100.0 * de / total,
ser / n,
wire / n,
de / n,
mb / n,
mbps,
);
}
pub fn broadcast_from_root(
&mut self,
tensors: &[&Tensor],
root: u32,
) -> Result<Vec<Tensor>> {
if root >= self.world_size {
return Err(TensorError::new(&format!(
"cpu_reduce: broadcast root {root} >= world_size {}",
self.world_size,
)));
}
if self.rank_id == root {
Ok(self
.all_reduce_weighted(tensors, RoundKind::Control, 0.0)?
.0)
} else {
Ok(self.stream_zeros_frame(tensors, RoundKind::Control, 0.0)?.0)
}
}
fn stream_zeros_frame(
&mut self,
tensors: &[&Tensor],
kind: RoundKind,
weight: f64,
) -> Result<(Vec<Tensor>, f64)> {
let t0 = Instant::now();
let wire_dtype = self.wire_dtype_for(kind);
let elem = controller::payload_element_size(wire_dtype)? as u64;
let shapes: Vec<Vec<u32>> = tensors
.iter()
.enumerate()
.map(|(i, t)| wire_shape(i, t))
.collect::<Result<_>>()?;
let parts: Vec<controller::PayloadPart<'_>> = tensors
.iter()
.zip(shapes.iter())
.map(|(t, shape)| controller::PayloadPart {
dtype: wire_dtype,
shape,
nbytes: t.numel() as u64 * elem,
})
.collect();
let sent_bytes: u64 = parts.iter().map(|p| p.nbytes).sum();
crate::distributed::relay::mux::write_len_prefix(
&mut self.stream,
controller::round_frame_wire_len(&parts),
)?;
let zeros = vec![0u8; 1 << 20];
controller::write_round_frame_streamed(
&mut self.stream,
kind,
weight,
&parts,
&self.salt,
&mut |ti, tee| {
let mut left = parts[ti].nbytes;
while left > 0 {
let n = left.min(zeros.len() as u64) as usize;
tee.write_all(&zeros[..n])
.map_err(|e| TensorError::new(&e.to_string()))?;
left -= n as u64;
}
Ok(())
},
)?;
let (out, weight, decode_ns) = self.read_reduced_tensors()?;
if self.prof_enabled {
self.prof_wire_ns += t0.elapsed().as_nanos().saturating_sub(decode_ns);
self.prof_deserialize_ns += decode_ns;
self.prof_bytes += sent_bytes;
self.prof_count += 1;
}
Ok((out, weight))
}
pub fn all_reduce_per_rank_f64(&mut self, local: &mut [f64]) -> Result<()> {
let world_size = self.world_size as usize;
if local.len() != world_size {
return Err(TensorError::new(&format!(
"cpu_reduce: all_reduce_per_rank_f64: vector len ({}) must \
equal world_size ({})",
local.len(),
world_size,
)));
}
let vals: Vec<f32> = local.iter().map(|v| *v as f32).collect();
let tensor = Tensor::from_f32(
&vals,
&[world_size as i64],
Device::CPU,
)?;
let mut frame = tensors_to_round_frame(&[&tensor], DTYPE_F32)?;
frame.kind = RoundKind::Control;
let averaged = self.all_reduce(frame)?;
let out = round_frame_to_tensors(&averaged)?;
let avg = out
.first()
.ok_or_else(|| TensorError::new("cpu_reduce: count-gather returned empty frame"))?;
let out = avg.to_f32_vec()?;
for (dst, src) in local.iter_mut().zip(out) {
*dst = src as f64;
}
Ok(())
}
}
fn write_framed_round<W: Write>(
stream: &mut W,
frame: &RoundFrame,
salt: &SessionSalt,
) -> Result<()> {
let parts: Vec<controller::PayloadPart<'_>> = frame
.tensors
.iter()
.map(|t| controller::PayloadPart {
dtype: t.dtype,
shape: &t.shape,
nbytes: t.bytes.len() as u64,
})
.collect();
crate::distributed::relay::mux::write_len_prefix(
stream,
controller::round_frame_wire_len(&parts),
)?;
controller::write_round_frame(stream, frame, salt)
}
fn read_framed_round<R: Read>(stream: &mut R, salt: &SessionSalt) -> Result<Option<RoundFrame>> {
let Some(len) = crate::distributed::relay::mux::read_len_prefix(stream)? else {
return Ok(None);
};
let mut body = stream.take(len as u64);
let frame = controller::read_round_frame(&mut body, salt)?;
finish_framed_body(frame.is_some(), body.limit())?;
Ok(frame)
}
fn finish_framed_body(got_frame: bool, leftover: u64) -> Result<()> {
if !got_frame {
return Err(TensorError::new(
"cpu_reduce: stream ended inside a len-framed RoundFrame body \
(peer died mid-frame, or a zero-length prefix)",
));
}
if leftover != 0 {
return Err(TensorError::new(&format!(
"cpu_reduce: RoundFrame consumed {leftover} bytes fewer than its \
length prefix declared; sender/reader wire drift — stream is \
desynced",
)));
}
Ok(())
}
pub fn tensors_to_round_frame(tensors: &[&Tensor], wire_dtype: u8) -> Result<RoundFrame> {
let wire_tensor_dtype = wire_tensor_dtype(wire_dtype)?;
let mut payloads = Vec::with_capacity(tensors.len());
for (i, t) in tensors.iter().enumerate() {
if !matches!(t.dtype(), DType::Float32 | DType::BFloat16) {
return Err(TensorError::new(&format!(
"cpu_reduce: tensor[{i}] dtype {:?} not supported (only Float32 \
/ BFloat16). Extend cpu_reduce.rs::tensors_to_round_frame and \
the round_frame.rs codec helpers together to add support.",
t.dtype()
)));
}
let shape = wire_shape(i, t)?;
let bytes = if t.dtype() == wire_tensor_dtype {
t.to_blob()?
} else {
t.to_dtype(wire_tensor_dtype)?.to_blob()?
};
payloads.push(TensorPayload {
dtype: wire_dtype,
shape,
bytes,
});
}
Ok(RoundFrame {
tensors: payloads,
kind: RoundKind::Model,
weight: 0.0,
})
}
fn wire_tensor_dtype(wire_dtype: u8) -> Result<DType> {
match wire_dtype {
DTYPE_F32 => Ok(DType::Float32),
DTYPE_BF16 => Ok(DType::BFloat16),
other => Err(TensorError::new(&format!(
"cpu_reduce: unsupported wire dtype tag {other} (0 = f32, 1 = bf16)"
))),
}
}
fn wire_shape(i: usize, t: &Tensor) -> Result<Vec<u32>> {
t.shape()
.iter()
.enumerate()
.map(|(d_idx, d)| {
u32::try_from(*d).map_err(|_| {
TensorError::new(&format!(
"cpu_reduce: tensor[{i}] dim[{d_idx}] = {d} doesn't fit in u32 \
(wire protocol uses u32 shape dims)"
))
})
})
.collect()
}
pub fn round_frame_to_tensors(frame: &RoundFrame) -> Result<Vec<Tensor>> {
let mut out = Vec::with_capacity(frame.tensors.len());
for (i, p) in frame.tensors.iter().enumerate() {
out.push(payload_to_cpu_tensor(i, p)?);
}
Ok(out)
}
fn payload_to_cpu_tensor(i: usize, p: &TensorPayload) -> Result<Tensor> {
let dtype = payload_wire_dtype(i, p)?;
let shape: Vec<i64> = p.shape.iter().map(|&d| d as i64).collect();
let t = Tensor::from_blob(&p.bytes, &shape, dtype, Device::CPU)
.map_err(|e| TensorError::new(&format!("cpu_reduce: payload[{i}]: {e}")))?;
if dtype == DType::Float32 {
Ok(t)
} else {
t.to_dtype(DType::Float32)
}
}
fn payload_wire_dtype(i: usize, p: &TensorPayload) -> Result<DType> {
match p.dtype {
DTYPE_F32 => Ok(DType::Float32),
DTYPE_BF16 => Ok(DType::BFloat16),
other => Err(TensorError::new(&format!(
"cpu_reduce: payload[{i}] unsupported wire dtype tag {other} \
(0 = f32, 1 = bf16)"
))),
}
}
fn decode_into_slot(
i: usize,
p: &TensorPayload,
slot: &mut Vec<Tensor>,
pin_fallback: &mut Option<String>,
) -> Result<Tensor> {
let dtype = payload_wire_dtype(i, p)?;
let shape: Vec<i64> = p.shape.iter().map(|&d| d as i64).collect();
let wire = Tensor::from_blob(&p.bytes, &shape, dtype, Device::CPU)
.map_err(|e| TensorError::new(&format!("cpu_reduce: payload[{i}]: {e}")))?;
if i > slot.len() {
return Err(TensorError::new(&format!(
"cpu_reduce: decode staging skipped an index (payload {i}, \
staged {})",
slot.len(),
)));
}
if i == slot.len() {
slot.push(reused_decode_buffer(&shape, pin_fallback)?);
} else if slot[i].shape() != shape {
slot[i] = reused_decode_buffer(&shape, pin_fallback)?;
}
let dst = &slot[i];
dst.copy_(&wire, false)?;
Ok(dst.clone())
}
fn reused_decode_buffer(
shape: &[i64],
pin_fallback: &mut Option<String>,
) -> Result<Tensor> {
let opts = TensorOptions {
dtype: DType::Float32,
device: Device::CPU,
};
let plain = Tensor::empty(shape, opts)?;
match plain.pin_memory() {
Ok(pinned) => Ok(pinned),
Err(e) => {
if pin_fallback.is_none() {
let msg = e.to_string();
*pin_fallback =
Some(msg.lines().next().unwrap_or("").to_string());
}
Ok(plain)
}
}
}
#[cfg(test)]
#[path = "cpu_reduce_tests.rs"]
mod tests;