|
|
@@ -42,6 +42,21 @@ pub(crate) async fn start_cluster(
|
|
|
}
|
|
|
};
|
|
|
let nnodes = nodes.len() as u32;
|
|
|
+ // Renumber to zero-based ranks within the launch set: dist flags
|
|
|
+ // (`--node-rank`), head selection and headless-ness must use the
|
|
|
+ // serve-local index, not the node's global cluster rank. A subset launch
|
|
|
+ // (required_nodes=2 out of a 4-node cluster, dgx3+dgx4 = global ranks
|
|
|
+ // 2/3) otherwise sends `--node-rank 2` with `--nnodes 2` — vLLM rejects
|
|
|
+ // it ("must be between 0 and 1").
|
|
|
+ let launch_nodes: Vec<Node> = nodes
|
|
|
+ .iter()
|
|
|
+ .enumerate()
|
|
|
+ .map(|(i, n)| {
|
|
|
+ let mut n = (*n).clone();
|
|
|
+ n.rank = i as u32;
|
|
|
+ n
|
|
|
+ })
|
|
|
+ .collect();
|
|
|
// A multi-node recipe without an explicit distributed backend would launch
|
|
|
// N INDEPENDENT single-node engines (no dist flags, each loading the full
|
|
|
// checkpoint) — the silent root cause of the glm53-flash-fp4-local-2x
|
|
|
@@ -84,8 +99,8 @@ pub(crate) async fn start_cluster(
|
|
|
let resolved = command::resolve_extra_mounts(config, recipe)?;
|
|
|
command::clear_extra_mounts(config, &nodes, &resolved, opts.clear, opts.dry_run).await?;
|
|
|
let mut handles = Vec::new();
|
|
|
- for node in &nodes {
|
|
|
- let node = (*node).clone();
|
|
|
+ for node in &launch_nodes {
|
|
|
+ let node = node.clone();
|
|
|
let config = config.clone();
|
|
|
let rn = recipe_name.to_string();
|
|
|
let dry = opts.dry_run;
|