Cluster bootstrap, fleets, and placement¶
This document covers how a container joins the cluster, how its fleet role determines its placement, and how parallelism requests are validated against the available topology before any GPU is claimed.
Bootstrap¶
Every container declares its role through DOCKYARD_FLEET_ROLE and calls
init_ray() (cluster/bootstrap.py). There are two operating modes:
Data-centre —
RAY_ADDRESSis set; the process attaches to the existing cluster unconditionally.Local-dev —
RAY_ADDRESSis absent; a local cluster is reused or started, keyed byCUDA_VISIBLE_DEVICESso a second process with the same device set reuses the cluster without a cold start.
init_ray() forwards the full process environment to every Ray worker, so
image-level settings (DOCKYARD_FLEET_ROLE, NCCL_SOCKET_IFNAME,
CUDA_DEVICE_ORDER, TOKENIZERS_PARALLELISM, …) are visible inside remote
actors without extra configuration. get_fleet_role() validates the role
against the three known values and raises if it is missing or unrecognized.
Fleet specifications¶
A fleet’s topology is captured by an immutable FleetSpec (cluster/fleet.py),
built from environment variables via FleetSpec.from_env():
Variable |
Meaning |
|---|---|
|
|
|
Node count, or concurrent episode slots for sandbox (required). |
|
GPUs per node; must be |
|
Placement override ( |
|
CPUs allocated per bundle. |
FleetSpec.__post_init__ enforces the invariants — notably that the sandbox
fleet is CPU-only (gpus_per_node == 0) and the GPU fleets are not.
build_cluster(spec) dispatches to a per-role builder that returns a
RayVirtualCluster. Call cluster._init_placement_groups() before constructing
any workers.
Placement strategy per fleet¶
Each fleet has a default placement strategy chosen for its collective pattern:
Fleet |
Strategy |
Rationale |
|---|---|---|
trainer |
|
Each node gets its own placement group, so intra-node NVLink is fully available for tensor-parallel traffic while inter-node RDMA carries pipeline/data-parallel collectives. |
inference |
|
Keeps an engine’s tensor-parallel ranks together on one node. When TP exceeds |
sandbox |
|
Episode slots distribute evenly across CPU nodes; claiming no GPU resources, sandbox actors never compete with the inference fleet for devices. |
Independent of the fleet-level strategy, the per-node sub-groups inside a
RayVirtualCluster always use STRICT_PACK (distributed/virtual_cluster.py):
one strict-pack group per logical node guarantees every bundle of that group
lands on the same physical node, preserving NVLink locality.
NVLink-segment placement¶
STRICT_PACK keeps a bundle on one node, but a multi-node collective group (a
tensor-parallel replica spanning nodes, or a dedicated teacher fleet) also wants
its nodes to share one NVLink switch fabric rather than scatter across the
cluster. A FleetSpec can request this with segment_size (default None —
topology-agnostic placement, unchanged behaviour).
ray.sub registers two custom Ray resources per node: nvlink_domain_<UUID>
(the NVLink fabric, parsed from nvidia-smi) and topo_rank (the SLURM
topological rank). get_ray_cluster_topology() reads them into
node_id → (nvlink_domain, topo_rank), and select_segment_nodes(topology, segment_size, num_nodes) greedily takes complete segment_size-node segments
from each NVLink domain, in topological order, until num_nodes are chosen —
returning the selected nodes plus the remainder. segment_size must divide
num_nodes; if not enough complete segments can be formed it raises rather than
spill a segment across fabrics.
Nodes with no topology resources collapse into a single unknown pseudo-domain
and are excluded from segment selection (they fall through to the remainder).
Selecting them would emit a placement constraint naming a Ray resource ray.sub
never registered, so the bundle could never schedule — excluding them pins
segments only to real, registered NVLink domains. This underpins topology-aware
teacher placement for on-policy distillation
and any TP replica that must span nodes.
The selection/sort logic is pure (CPU-tested); the ray.nodes() read, the
domain-pinned placement, and the ray.sub topology probe are cluster-bound and
hardware-deferred.
Pre-flight validation¶
cluster/placement.py is pure computation — it creates no Ray objects, so it
can run before init_ray() for pre-flight checks. A ParallelismSpec
(tensor / pipeline / data / expert parallel sizes) is validated against the
fleet topology before placement groups exist, catching impossible
configurations (e.g. an expert-parallel size that is not a multiple of the
tensor-parallel size, or a model-parallel size that does not fit the node) at
launch rather than mid-run.
A model replica consumes tensor_parallel_size × pipeline_parallel_size GPUs;
when that exceeds gpus_per_node, the placement helpers signal that a unified
cross-node placement group is required rather than per-node groups.
The virtual cluster¶
RayVirtualCluster (distributed/virtual_cluster.py) is the object the
worker-group builders consume. It owns the placement groups and exposes them via
get_placement_groups(). Its constructor takes a bundle_ct_per_node_list (the
per-node bundle counts), use_gpus, num_gpus_per_node, and the fleet’s
placement_group_strategy; the per-node sub-groups it creates are always
STRICT_PACK regardless of that top-level strategy.