REP: Topology Aware Schedulling - #66
Conversation
| 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. | ||
|  | ||
|
|
||
| ### Spread Across Availability Zone and Within Rack (Possible Future Steps) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Either way, we should add the first scenario (strict pack in single AZ) as a use-case as it comes up a lot
There was a problem hiding this comment.
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).
| ``` | ||
| 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
@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']
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Is that only failing due to validation though? If we skip validation does the env var render correctly at runtime?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Can we add a section discussing how autoscaler support will be implemented. I'm interested in how we plan to:
- Set default labels for multi-host / topology aware groups on the Ray node and in the autoscaling config.
- Integrate the label domain key into the autoscaler - will this be supported the same as
bundle_label_selector? (i.e. after abundle_label_selectorhas been chosen with a value that's the same across all bundles, does it just follow the same path as though abundle_label_selectorwas provided explicitly?)
Unrelated but I'm also wondering if a future step will include supporting topology_strategy in the fallback_strategy argument.
| 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"}], |
There was a problem hiding this comment.
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.
| 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"}, |
There was a problem hiding this comment.
will topology strategy be a Dict or List of dicts to support hierarchical scheduling?
## 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>
## 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>
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