use super::{WeightCache, WeightRef, resolve_device};
use crate::compiled::CompiledGraph;
use crate::{Device, Session};
use rlx_driver::ProcessGroup;
use rlx_ir::Graph;
use serde::{Deserialize, Serialize};
const TAG_SPEC: u32 = 40;
const TAG_ACT: u32 = 41;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct StageSpec {
pub graph: Graph,
pub input: String,
pub output: String,
pub weights: Vec<WeightRef>,
pub device: String,
}
impl StageSpec {
pub fn new(
graph: Graph,
input: impl Into<String>,
output: impl Into<String>,
device: impl Into<String>,
) -> Self {
Self {
graph,
input: input.into(),
output: output.into(),
weights: Vec::new(),
device: device.into(),
}
}
pub fn weight(mut self, name: impl Into<String>, uri: impl Into<String>) -> Self {
self.weights.push(WeightRef {
name: name.into(),
uri: uri.into(),
packed: false,
});
self
}
pub fn weight_packed(mut self, name: impl Into<String>, uri: impl Into<String>) -> Self {
self.weights.push(WeightRef {
name: name.into(),
uri: uri.into(),
packed: true,
});
self
}
}
pub fn ship_stage(group: &ProcessGroup, rank: u32, spec: &StageSpec) -> Result<(), String> {
let bytes = serde_json::to_vec(spec).map_err(|e| e.to_string())?;
group
.transport()
.send_bytes(rank, TAG_SPEC, &bytes)
.map_err(|e| format!("ship_stage: {e}"))
}
pub fn send_activation(group: &ProcessGroup, to: u32, data: &[f32]) -> Result<(), String> {
group.send_f32(to, TAG_ACT, data).map_err(|e| e.to_string())
}
pub fn recv_activation(group: &ProcessGroup, from: u32) -> Result<Vec<f32>, String> {
group.recv_f32(from, TAG_ACT).map_err(|e| e.to_string())
}
pub struct WorkerStage {
pub device: Device,
pub nodes: usize,
input: String,
compiled: CompiledGraph,
}
impl WorkerStage {
pub fn run(&mut self, x: &[f32]) -> Vec<f32> {
self.compiled
.run(&[(self.input.as_str(), x)])
.into_iter()
.next()
.unwrap_or_default()
}
pub fn input_name(&self) -> &str {
&self.input
}
}
pub fn recv_stage<F>(group: &ProcessGroup, mut fallback: F) -> Result<WorkerStage, String>
where
F: FnMut(&str) -> Vec<f32>,
{
let bytes = group
.transport()
.recv_bytes(0, TAG_SPEC)
.map_err(|e| format!("recv_stage: {e}"))?;
let spec: StageSpec = serde_json::from_slice(&bytes).map_err(|e| format!("StageSpec: {e}"))?;
let device = resolve_device(&spec.device);
let nodes = spec.graph.len();
let mut compiled = Session::new(device).compile(spec.graph);
let mut cache = WeightCache::new();
for w in &spec.weights {
if w.packed {
let bytes = cache
.bytes(&w.uri)
.map_err(|e| format!("weight {}: {e}", w.name))?;
compiled.set_param_typed(&w.name, &bytes, rlx_ir::DType::U8);
} else {
let vals = cache.f32(&w.uri).unwrap_or_else(|_| fallback(&w.uri));
compiled.set_param(&w.name, &vals);
}
}
Ok(WorkerStage {
device,
nodes,
input: spec.input,
compiled,
})
}
pub fn serve_stage<F>(group: &ProcessGroup, resolve: F) -> Result<Device, String>
where
F: FnMut(&str) -> Vec<f32>,
{
let n = group.world_size();
let rank = group.rank();
let mut stage = recv_stage(group, resolve)?;
let input = recv_activation(group, rank - 1)?;
let out = stage.run(&input);
let next = if rank + 1 < n { rank + 1 } else { 0 };
send_activation(group, next, &out)?;
Ok(stage.device)
}
pub fn serve_stage_uri(group: &ProcessGroup) -> Result<Device, String> {
serve_stage(group, |uri| {
eprintln!("rlx dist: no resolver for weight URI [{uri}]");
Vec::new()
})
}