diff --git a/dashboard/src/routes/+page.svelte b/dashboard/src/routes/+page.svelte index b8a5c577..df1163f8 100644 --- a/dashboard/src/routes/+page.svelte +++ b/dashboard/src/routes/+page.svelte @@ -643,7 +643,12 @@ model_id: modelId, sharding: preview?.sharding ?? selectedSharding, instance_meta: preview?.instance_meta ?? selectedInstanceType, - min_nodes: Math.max(selectedMinNodes, nodeFilter.size), + min_nodes: Math.max( + selectedMinNodes, + nodeFilter.size, + // RDMA requires at least 2 nodes + (preview?.instance_meta ?? selectedInstanceType) === "MlxJaccl" ? 2 : 1, + ), node_ids: nodeFilter.size > 0 ? Array.from(nodeFilter) : undefined, }), }); diff --git a/src/exo/master/placement.py b/src/exo/master/placement.py index a2b5201b..7a8efb78 100644 --- a/src/exo/master/placement.py +++ b/src/exo/master/placement.py @@ -63,7 +63,14 @@ def place_instance( required_nodes: set[NodeId] | None = None, ) -> dict[InstanceId, Instance]: cycles = topology.get_cycles() - candidate_cycles = list(filter(lambda it: len(it) >= command.min_nodes, cycles)) + # RDMA (MlxJaccl) requires at least 2 nodes + effective_min_nodes = max( + command.min_nodes, + 2 if command.instance_meta == InstanceMeta.MlxJaccl else 1, + ) + candidate_cycles = list( + filter(lambda it: len(it) >= effective_min_nodes, cycles) + ) # Filter to cycles containing all required nodes (subset matching) if required_nodes: