Skip to content

REP: Topology Aware Schedulling - #66

Open
aaronscalene wants to merge 12 commits into
ray-project:mainfrom
aaronscalene:main
Open

REP: Topology Aware Schedulling#66
aaronscalene wants to merge 12 commits into
ray-project:mainfrom
aaronscalene:main

Conversation

@aaronscalene

Copy link
Copy Markdown

This REP proposes topology aware scheduling for placement groups. This allows functionality such as rack aware placement group scheduling, enabling rack aware fault tolerance. Motivated by new GB300 racks that are connected through the NVLink domain, necessitating topology aware scheduling to take advantage of faster memory bandwidth.

Requested reviewers: @edoakes @MengjinYan @Sparks0219

Comment thread reps/2026-06-18-topology-strategy/2026-05-18-topology-strategy.md Outdated
Comment thread reps/2026-06-18-topology-strategy/2026-05-18-topology-strategy.md
Comment thread reps/2026-06-18-topology-strategy/2026-05-18-topology-strategy.md Outdated
Comment thread reps/2026-06-18-topology-strategy/2026-05-18-topology-strategy.md Outdated
Comment thread reps/2026-06-18-topology-strategy/2026-05-18-topology-strategy.md Outdated
Let us say one node within this placement fails, since our placement group is not completely unplaced as in the previous example, we will still try to reschedule the unplaced bundle to no avail, marking this placement group as infeasible.
![](image5.png)

### Spread Across Availability Zone and Within Rack (Possible Future Steps)

@andrewsykim andrewsykim May 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For AZ topology scheduling, I think the two main scenarios are something like:

  • For training, STRICT_PACK in single AZ to avoid cross AZ network transfer (incurs additional cost)
  • For inference: STRICT_SPREAD across AZs for better fault tolerance

The inference case has a bit more nuance in that if you're doing multi-host serving, you want each group of nodes to be STRICT_PACK but each "replica" of the model to be STRICT_SPREAD. This is maybe irrelevant if we always run a placement group per model replica?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way, we should add the first scenario (strict pack in single AZ) as a use-case as it comes up a lot

@aaronscalene aaronscalene May 28, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, I added an example of Strick Pack AZ + Strict Pack Rack hierarchical topology. For your inference case, I am not entirely sure about the model replica implementation, but I believe what you can do would be to define a group of bundles for each replica, and have a hierarchical scheduling to STRICT_SPREAD each group of bundles across AZs. You can then probably target each group of bundles using their bundle ids (probably should discuss more on this).

Comment thread reps/2026-06-18-topology-strategy/2026-05-18-topology-strategy.md
```
ray start --head --labels="rack_id=1"
ray start --labels="rack_id=1" # rack 1 nodes
ray start --labels="rack_id=2" # rack 2 nodes

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth nothing that in KubeRay we started to add better metadata for multi-host scenarios. Specifically if you set replicas > 0 and numOfHosts > 1, we set the following labels on every Pod:

ray.io/worker-group-replica-index=<replica index>
ray.io/replica-host-index=<host-index>

So in the NVL72 case, you set numOfHosts=18 and each "replica" is a rack. Then rack_id can just inherit whatever value is used for ray.io/worker-group-replica-index. This is a QoL improvement to the cluster operator because they don't need to manually set unique labels for every rack.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ryanaoleary do you know if it's possible now to set Ray labels based on Pod labels?

For example something like:

  workerGroupSpecs:
  - replicas: 1
    minReplicas: 1
    maxReplicas: 10
    numOfHosts: 18
    groupName: gb200
    labels:
      rack-id: ${WORKER_GROUP_REPLICA_INDEX}
    rayStartParams: {}
    template:
      spec:
        containers:
        - name: ray-worker
          image: rayproject/ray:nightly
          resources:
            limits:
              cpu: "2"
              memory: "4Gi"
            requests:
              cpu: "2"
              memory: "4Gi"
        env:
        - name: WORKER_GROUP_REPLICA_INDEX
          valueFrom:
            fieldRef:
              fieldPath: metadata.labels['worker-group-replica-index']

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above example fails with:

Warning  InvalidRayClusterSpec  13s   raycluster-controller  The RayCluster spec is invalid default/raycluster-manual-label-test: invalid label value for key 'rack-id' in gb200 group: '${WORKER_GROUP_REPLICA_INDEX}', error: a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyValue',  or 'my_value',  or '12345', regex used for validation is '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?')

I think it's not currently supported, if we wanted the label to be set by default when the env var is present we could add it here: https://github.com/ray-project/ray/blob/7ddc3faa761ab533eaff081be4db7dcea683ea56/python/ray/_private/resource_and_label_spec.py#L271. We do currently set a label based on the TPU_NAME var only for TPU, that serves a similar function (unique per multi-host replica): https://github.com/ray-project/ray/blob/7ddc3faa761ab533eaff081be4db7dcea683ea56/python/ray/_private/accelerators/tpu.py#L750.

I think we should set Ray node labels by default for k8s labels on the pod with the prefix ray.io/. I'll put out this change since we previously talked about adding this support.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that only failing due to validation though? If we skip validation does the env var render correctly at runtime?

@ryanaoleary ryanaoleary Jun 13, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry missed this comment - tested with skipping the KubeRay validation (specifically: validateRayGroupLabels)and it fails validation at the kube-apiserver level when trying to create the Pod:

3s          Normal    CreatedService            raycluster/raycluster-manual-label-test       Created service default/raycluster-manual-label-test-head-svc
3s          Normal    CreatedService            raycluster/raycluster-manual-label-test       Created service default/raycluster-manual-label-test-headless
1s          Warning   FailedToCreateWorkerPod   raycluster/raycluster-manual-label-test       Failed to create worker Pod for the cluster default/raycluster-manual-label-test, Pod "raycluster-manual-label-test-gb200-worker-jlxz8" is invalid
: metadata.labels: Invalid value: "${WORKER_GROUP_REPLICA_INDEX}": a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyValue',  or 'my_value',  or '12345', regex used for validation is '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?')

I'm not sure if we can skip/disable the above validation.

it works if we overwrite the rayStartCommand though and skip the KubeRay validation:

Amended worker group spec to:

  workerGroupSpecs:
  - replicas: 1
    minReplicas: 1
    maxReplicas: 10
    numOfHosts: 18
    groupName: gb200
    labels: {}
    rayStartParams: {}
    template:
      metadata:
        annotations:
          ray.io/overwrite-container-cmd: "true"
      spec:
        containers:
        - name: ray-worker
          image: rayproject/ray:nightly
          
          command: ["/bin/bash", "-c"]
          # Inject the downward API variable natively into the ray start arguments
          args: ["ulimit -n 65536; $KUBERAY_GEN_RAY_START_CMD --labels=\"rack-id=${WORKER_GROUP_REPLICA_INDEX}\""]
          env:
          - name: WORKER_GROUP_REPLICA_INDEX
            valueFrom:
              fieldRef:
                fieldPath: metadata.labels['ray.io/worker-group-replica-index']

Ran this test on the worker group with the above spec:

import ray
ray.init()
print([n.get('Labels', {}).get('rack-id') for n in ray.nodes()])

labels set on the nodes:

['0', '0', None, '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0']


In the first strategy, both groups of bundles have to be in the same availability zone. However, these groups of bundles can be on the same / different racks. The bundles themselves within each group must be spread on different nodes of the rack. In the second strategy, both groups of bundles have to be in the same availability zone AND same rack. However, these groups of bundles have to then be spread across different nodes of that rack.

## Compatibility, Deprecation, and Migration Plan

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a section discussing how autoscaler support will be implemented. I'm interested in how we plan to:

  1. Set default labels for multi-host / topology aware groups on the Ray node and in the autoscaling config.
  2. Integrate the label domain key into the autoscaler - will this be supported the same as bundle_label_selector? (i.e. after a bundle_label_selector has been chosen with a value that's the same across all bundles, does it just follow the same path as though a bundle_label_selector was provided explicitly?)

Unrelated but I'm also wondering if a future step will include supporting topology_strategy in the fallback_strategy argument.

Comment thread reps/2026-06-18-topology-strategy/2026-05-18-topology-strategy.md Outdated
pg = ray.util.placement_group(
bundles = [{"CPU": 2, "GPU": 4}] * 16,
# NEW FIELD
topology_strategy = [{"ray.io/node-id": "STRICT_PACK", "rack_id" : "STRICT_PACK"}],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would likely be easier to use if we put the topology strategy itself in the bundle then have a way to have topology labels for a group of bundles.

Signed-off-by: aaronscalene <aaron.li@anyscale.com>
pg = ray.util.placement_group(
bundles = [{"CPU": 2, "GPU": 4}] * 16,
# NEW FIELD
topology_strategy = {"ray.io/node-id": "STRICT_PACK", "rack_id" : "STRICT_PACK"},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will topology strategy be a Dict or List of dicts to support hierarchical scheduling?

@ryanaoleary ryanaoleary left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with a few clarifying questions worth resolving, happy to help with implementing/reviewing this soon - especially pieces like SPREAD and hierarchical scheduling support

MengjinYan pushed a commit to ray-project/ray that referenced this pull request Jun 26, 2026
## Description
This is directly related to [this
REP](ray-project/enhancements#66). This commit
introduces topology aware scheduling for the public API, providing users
a public-facing API to use as opposed to the code added in #61442. For
more information, please read the REP and the private facing API.

## Considerations
### API Validation
Currently, our API will only support one layer of topology aware
scheduling. Furthermore, we currently only support at max one node level
placement strategy and label aware placement strategy. We also only
support STRICT_PACK currently on label aware placement strategies. Thus,
our `_validate_topology_strategy` will mainly focus on ensuring that
users are only allowed to pass in these inputs.

### Node Level Placement Strategy
Whenever `topology_strategy` is defined, we will not allow users to
specify `strategy` and instead only be able to specify strategy through
the label `ray.io/node-id`. We enforce this through our validation as
well. However, currently, this `ray.io/node-id` is just extracted from
the user input and piped as the `strategy` input anyways into the code.
In the future, we will have to consider a more robust way to pipe this
input through once we decided to support multiple levels as well as
multiple labels to be scheduled on per level.

### Label Locality Private Functionality
Currently, we support `bundle_label_selectors` to be specified with
GB200 / 300 to implicitly call into the same topology aware code. Within
this, we are deprecating this functionality in support of this PR, which
supports a more general version. Although we considered that this will
deprecate parts of the API we support, we decided to continue with this
more general version since we believe that having one path is more
intuitive for future users. Furthermore, since our initial feature is
just an alpha feature, we are comfortable with removing it.

## Testing
New unit tests added, and existing unit tests are translated over from
GB200 / 300 support.

The new dashboard UI looks like below:
<img width="1610" height="108" alt="image"
src="https://github.com/user-attachments/assets/0b442cfc-6ff1-4765-9423-279a1a6aa264"
/>

The new `ray list placement-groups --detail`:
```
-   placement_group_id: 94cbd842f8deeee2167e746f1cc101000000
    name: ''
    creator_job_id: '01000000'
    state: CREATED
    ...
    topology_strategy:
    -   entries:
            ray.io/gpu-domain: STRICT_PACK
    topology_assignments:
    -   assignments:
            ray.io/gpu-domain: rack-1
...
```

---------

Signed-off-by: aaron.li <aaron.li@anyscale.com>
Signed-off-by: aaronscalene <aaron.li@anyscale.com>
Co-authored-by: Aaron <35825663+aarli421@users.noreply.github.com>
limarkdcunha pushed a commit to limarkdcunha/ray that referenced this pull request Jun 30, 2026
## Description
This is directly related to [this
REP](ray-project/enhancements#66). This commit
introduces topology aware scheduling for the public API, providing users
a public-facing API to use as opposed to the code added in ray-project#61442. For
more information, please read the REP and the private facing API.

## Considerations
### API Validation
Currently, our API will only support one layer of topology aware
scheduling. Furthermore, we currently only support at max one node level
placement strategy and label aware placement strategy. We also only
support STRICT_PACK currently on label aware placement strategies. Thus,
our `_validate_topology_strategy` will mainly focus on ensuring that
users are only allowed to pass in these inputs.

### Node Level Placement Strategy
Whenever `topology_strategy` is defined, we will not allow users to
specify `strategy` and instead only be able to specify strategy through
the label `ray.io/node-id`. We enforce this through our validation as
well. However, currently, this `ray.io/node-id` is just extracted from
the user input and piped as the `strategy` input anyways into the code.
In the future, we will have to consider a more robust way to pipe this
input through once we decided to support multiple levels as well as
multiple labels to be scheduled on per level.

### Label Locality Private Functionality
Currently, we support `bundle_label_selectors` to be specified with
GB200 / 300 to implicitly call into the same topology aware code. Within
this, we are deprecating this functionality in support of this PR, which
supports a more general version. Although we considered that this will
deprecate parts of the API we support, we decided to continue with this
more general version since we believe that having one path is more
intuitive for future users. Furthermore, since our initial feature is
just an alpha feature, we are comfortable with removing it.

## Testing
New unit tests added, and existing unit tests are translated over from
GB200 / 300 support.

The new dashboard UI looks like below:
<img width="1610" height="108" alt="image"
src="https://github.com/user-attachments/assets/0b442cfc-6ff1-4765-9423-279a1a6aa264"
/>

The new `ray list placement-groups --detail`:
```
-   placement_group_id: 94cbd842f8deeee2167e746f1cc101000000
    name: ''
    creator_job_id: '01000000'
    state: CREATED
    ...
    topology_strategy:
    -   entries:
            ray.io/gpu-domain: STRICT_PACK
    topology_assignments:
    -   assignments:
            ray.io/gpu-domain: rack-1
...
```

---------

Signed-off-by: aaron.li <aaron.li@anyscale.com>
Signed-off-by: aaronscalene <aaron.li@anyscale.com>
Co-authored-by: Aaron <35825663+aarli421@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants