900 lines
36 KiB
ReStructuredText
900 lines
36 KiB
ReStructuredText
Placement Groups
|
|
================
|
|
|
|
.. _ray-placement-group-doc-ref:
|
|
|
|
Placement groups allow users to atomically reserve groups of resources across multiple nodes, a concept commonly known as gang scheduling.
|
|
After atomically reserving resources, you can use placement groups to schedule Ray tasks and actors packed together for locality (PACK), or spread apart (SPREAD).
|
|
Placement groups are generally used for gang-scheduling actors, but also support tasks.
|
|
|
|
Here are some real-world use cases:
|
|
|
|
- **Distributed Machine Learning Training**: Distributed Training (e.g., :ref:`Ray Train <train-docs>` and :ref:`Ray Tune <tune-main>`) uses the placement group APIs to enable gang scheduling. In these settings, all resources for a trial must be available at the same time. Gang scheduling is a critical technique to enable all-or-nothing scheduling for deep learning training.
|
|
- **Fault tolerance in distributed training**: Placement groups can be used to configure fault tolerance. In Ray Tune, it can be beneficial to pack related resources from a single trial together, so that a node failure impacts a low number of trials. In libraries that support elastic training (e.g., XGBoost-Ray), spreading the resources across multiple nodes can help to ensure that training continues even when a node dies.
|
|
|
|
Key Concepts
|
|
------------
|
|
|
|
Bundles
|
|
~~~~~~~
|
|
|
|
A **bundle** is a collection of "resources." It could be a single resource, ``{"CPU": 1}``, or a group of resources, ``{"CPU": 1, "GPU": 4}``.
|
|
A bundle is a unit of reservation for placement groups. "Scheduling a bundle" means we find a node that fits the bundle and reserve the resources specified by the bundle.
|
|
A bundle must be able to fit on a single node on the Ray cluster. For example, if you have an 8 CPU node and a 1 CPU node and want to schedule a bundle that requires ``{"CPU": 9}``,
|
|
Ray can't schedule the ``{"CPU": 9}``, because there's no single node with 9 CPU's.
|
|
|
|
Placement Group
|
|
~~~~~~~~~~~~~~~
|
|
|
|
A **placement group** reserves the resources from the cluster. Tasks or actors must use the :ref:`PlacementGroupSchedulingStrategy <ray-placement-group-schedule-tasks-actors-ref>` to use the reserved resources.
|
|
|
|
- Ray represents placement groups with a list of bundles. For example, ``{"CPU": 1} * 4`` means you'd like to reserve 4 bundles, each with 1 CPU.
|
|
- Bundles are then placed according to the :ref:`placement strategies <pgroup-strategy>` across nodes on the cluster.
|
|
- After Ray creates the placement group, tasks or actors can be then scheduled according to the placement group and even on individual bundles.
|
|
|
|
Create a Placement Group (Reserve Resources)
|
|
--------------------------------------------
|
|
|
|
You can create a placement group using :func:`ray.util.placement_group`.
|
|
Placement groups take in a list of bundles and a :ref:`placement strategy <pgroup-strategy>`.
|
|
|
|
Bundles are specified by a list of dictionaries, e.g., ``[{"CPU": 1}, {"CPU": 1, "GPU": 1}]``).
|
|
|
|
- ``CPU`` corresponds to ``num_cpus`` as used in :func:`ray.remote <ray.remote>`.
|
|
- ``GPU`` corresponds to ``num_gpus`` as used in :func:`ray.remote <ray.remote>`.
|
|
- ``memory`` corresponds to ``memory`` as used in :func:`ray.remote <ray.remote>`
|
|
- Other resources corresponds to ``resources`` as used in :func:`ray.remote <ray.remote>` (E.g., ``ray.init(resources={"disk": 1})`` can have a bundle of ``{"disk": 1}``).
|
|
|
|
Placement group scheduling is asynchronous. The `ray.util.placement_group` returns immediately.
|
|
|
|
.. tab-set::
|
|
|
|
.. tab-item:: Python
|
|
|
|
.. literalinclude:: ../doc_code/placement_group_example.py
|
|
:language: python
|
|
:start-after: __create_pg_start__
|
|
:end-before: __create_pg_end__
|
|
|
|
|
|
.. tab-item:: Java
|
|
|
|
.. code-block:: java
|
|
|
|
// Initialize Ray.
|
|
Ray.init();
|
|
|
|
// Construct a list of bundles.
|
|
Map<String, Double> bundle = ImmutableMap.of("CPU", 1.0);
|
|
List<Map<String, Double>> bundles = ImmutableList.of(bundle);
|
|
|
|
// Make a creation option with bundles and strategy.
|
|
PlacementGroupCreationOptions options =
|
|
new PlacementGroupCreationOptions.Builder()
|
|
.setBundles(bundles)
|
|
.setStrategy(PlacementStrategy.STRICT_SPREAD)
|
|
.build();
|
|
|
|
PlacementGroup pg = PlacementGroups.createPlacementGroup(options);
|
|
|
|
.. tab-item:: C++
|
|
|
|
.. code-block:: c++
|
|
|
|
// Initialize Ray.
|
|
ray::Init();
|
|
|
|
// Construct a list of bundles.
|
|
std::vector<std::unordered_map<std::string, double>> bundles{{{"CPU", 1.0}}};
|
|
|
|
// Make a creation option with bundles and strategy.
|
|
ray::internal::PlacementGroupCreationOptions options{
|
|
false, "my_pg", bundles, ray::internal::PlacementStrategy::PACK};
|
|
|
|
ray::PlacementGroup pg = ray::CreatePlacementGroup(options);
|
|
|
|
You can block your program until the placement group is ready using one of two APIs:
|
|
|
|
* :func:`ready <ray.util.placement_group.PlacementGroup.ready>`, which is compatible with ``ray.get``
|
|
* :func:`wait <ray.util.placement_group.PlacementGroup.wait>`, which blocks the program until the placement group is ready)
|
|
|
|
.. tab-set::
|
|
|
|
.. tab-item:: Python
|
|
|
|
.. literalinclude:: ../doc_code/placement_group_example.py
|
|
:language: python
|
|
:start-after: __ready_pg_start__
|
|
:end-before: __ready_pg_end__
|
|
|
|
.. tab-item:: Java
|
|
|
|
.. code-block:: java
|
|
|
|
// Wait for the placement group to be ready within the specified time(unit is seconds).
|
|
boolean ready = pg.wait(60);
|
|
Assert.assertTrue(ready);
|
|
|
|
// You can look at placement group states using this API.
|
|
List<PlacementGroup> allPlacementGroup = PlacementGroups.getAllPlacementGroups();
|
|
for (PlacementGroup group: allPlacementGroup) {
|
|
System.out.println(group);
|
|
}
|
|
|
|
.. tab-item:: C++
|
|
|
|
.. code-block:: c++
|
|
|
|
// Wait for the placement group to be ready within the specified time(unit is seconds).
|
|
bool ready = pg.Wait(60);
|
|
assert(ready);
|
|
|
|
// You can look at placement group states using this API.
|
|
std::vector<ray::PlacementGroup> all_placement_group = ray::GetAllPlacementGroups();
|
|
for (const ray::PlacementGroup &group : all_placement_group) {
|
|
std::cout << group.GetName() << std::endl;
|
|
}
|
|
|
|
Let's verify the placement group is successfully created.
|
|
|
|
.. code-block:: bash
|
|
|
|
# This API is only available when you download Ray via `pip install "ray[default]"`
|
|
ray list placement-groups
|
|
|
|
.. code-block:: bash
|
|
|
|
======== List: 2023-04-07 01:15:05.682519 ========
|
|
Stats:
|
|
------------------------------
|
|
Total: 1
|
|
|
|
Table:
|
|
------------------------------
|
|
PLACEMENT_GROUP_ID NAME CREATOR_JOB_ID STATE
|
|
0 3cd6174711f47c14132155039c0501000000 01000000 CREATED
|
|
|
|
The placement group is successfully created. Out of the ``{"CPU": 2, "GPU": 2}`` resources, the placement group reserves ``{"CPU": 1, "GPU": 1}``.
|
|
The reserved resources can only be used when you schedule tasks or actors with a placement group.
|
|
The diagram below demonstrates the "1 CPU and 1 GPU" bundle that the placement group reserved.
|
|
|
|
.. image:: ../images/pg_image_1.png
|
|
:align: center
|
|
|
|
Ray creates placement groups atomically. If a bundle can't fit in any of the current nodes, Ray reserves no resources for the placement group.
|
|
To illustrate this, you can create another placement group with these two bundles ``{"CPU":1}, {"GPU": 2}``.
|
|
|
|
.. tab-set::
|
|
|
|
.. tab-item:: Python
|
|
|
|
.. literalinclude:: ../doc_code/placement_group_example.py
|
|
:language: python
|
|
:start-after: __create_pg_failed_start__
|
|
:end-before: __create_pg_failed_end__
|
|
|
|
You can verify the new placement group is pending creation.
|
|
|
|
.. code-block:: bash
|
|
|
|
# This API is only available when you download Ray via `pip install "ray[default]"`
|
|
ray list placement-groups
|
|
|
|
.. code-block:: bash
|
|
|
|
======== List: 2023-04-07 01:16:23.733410 ========
|
|
Stats:
|
|
------------------------------
|
|
Total: 2
|
|
|
|
Table:
|
|
------------------------------
|
|
PLACEMENT_GROUP_ID NAME CREATOR_JOB_ID STATE
|
|
0 3cd6174711f47c14132155039c0501000000 01000000 CREATED
|
|
1 e1b043bebc751c3081bddc24834d01000000 01000000 PENDING <---- the new placement group.
|
|
|
|
You can also verify that the ``{"CPU": 1, "GPU": 2}`` bundles can't be allocated, using the ``ray status`` CLI command.
|
|
|
|
.. code-block:: bash
|
|
|
|
ray status
|
|
|
|
.. code-block:: bash
|
|
|
|
Resources
|
|
---------------------------------------------------------------
|
|
Usage:
|
|
0.0/2.0 CPU (0.0 used of 1.0 reserved in placement groups)
|
|
0.0/2.0 GPU (0.0 used of 1.0 reserved in placement groups)
|
|
0B/3.46GiB memory
|
|
0B/1.73GiB object_store_memory
|
|
|
|
Demands:
|
|
{'CPU': 1.0} * 1, {'GPU': 2.0} * 1 (PACK): 1+ pending placement groups <--- 1 placement group is pending creation.
|
|
|
|
The current cluster has ``{"CPU": 2, "GPU": 2}``. We already created a ``{"CPU": 1, "GPU": 1}`` bundle, so the cluster only has 1 CPU and 1 GPU left.
|
|
If you try to schedule a placement group with these 2 bundles ``{"CPU": 1}, {"GPU": 2}``, Ray doesn't create the placement group and doesn't reserve any resources,
|
|
including the ``{"CPU": 1}`` bundle.
|
|
|
|
.. image:: ../images/pg_image_2.png
|
|
:align: center
|
|
|
|
When the placement group cannot be scheduled in any way, it is called "infeasible".
|
|
Imagine you schedule ``{"CPU": 4}`` bundle, but you only have a single node with 2 CPUs. There's no way to create this bundle in your cluster.
|
|
The Ray Autoscaler is aware of placement groups, and auto-scales the cluster to ensure pending groups can be placed as needed.
|
|
|
|
If the Autoscaler can't provide resources to schedule a placement group, Ray does *not* print a warning about infeasible groups and tasks and actors that use the groups.
|
|
You can observe the scheduling state of the placement group from the :ref:`dashboard or state APIs <ray-placement-group-observability-ref>`.
|
|
|
|
.. note::
|
|
When a placement group with GPUs is reserved successfully, the bundles are not necessarily ordered by GPU physical rank.
|
|
That is, adjacent bundles don't necessarily map to adjacent physical GPUs.
|
|
|
|
.. _ray-placement-group-schedule-tasks-actors-ref:
|
|
|
|
Schedule Tasks and Actors to Placement Groups (Use Reserved Resources)
|
|
----------------------------------------------------------------------
|
|
|
|
In the previous section, we created a placement group that reserved ``{"CPU": 1, "GPU: 1"}`` from a 2 CPU and 2 GPU node.
|
|
|
|
Now let's schedule an actor to the placement group.
|
|
You can schedule actors or tasks to a placement group using
|
|
:class:`options(scheduling_strategy=PlacementGroupSchedulingStrategy(...)) <ray.util.scheduling_strategies.PlacementGroupSchedulingStrategy>`.
|
|
|
|
.. tab-set::
|
|
|
|
.. tab-item:: Python
|
|
|
|
.. literalinclude:: ../doc_code/placement_group_example.py
|
|
:language: python
|
|
:start-after: __schedule_pg_start__
|
|
:end-before: __schedule_pg_end__
|
|
|
|
.. tab-item:: Java
|
|
|
|
.. code-block:: java
|
|
|
|
public static class Counter {
|
|
private int value;
|
|
|
|
public Counter(int initValue) {
|
|
this.value = initValue;
|
|
}
|
|
|
|
public int getValue() {
|
|
return value;
|
|
}
|
|
|
|
public static String ping() {
|
|
return "pong";
|
|
}
|
|
}
|
|
|
|
// Create GPU actors on a gpu bundle.
|
|
for (int index = 0; index < 1; index++) {
|
|
Ray.actor(Counter::new, 1)
|
|
.setPlacementGroup(pg, 0)
|
|
.remote();
|
|
}
|
|
|
|
.. tab-item:: C++
|
|
|
|
.. code-block:: c++
|
|
|
|
class Counter {
|
|
public:
|
|
Counter(int init_value) : value(init_value){}
|
|
int GetValue() {return value;}
|
|
std::string Ping() {
|
|
return "pong";
|
|
}
|
|
private:
|
|
int value;
|
|
};
|
|
|
|
// Factory function of Counter class.
|
|
static Counter *CreateCounter() {
|
|
return new Counter();
|
|
};
|
|
|
|
RAY_REMOTE(&Counter::Ping, &Counter::GetValue, CreateCounter);
|
|
|
|
// Create GPU actors on a gpu bundle.
|
|
for (int index = 0; index < 1; index++) {
|
|
ray::Actor(CreateCounter)
|
|
.SetPlacementGroup(pg, 0)
|
|
.Remote(1);
|
|
}
|
|
|
|
.. note::
|
|
|
|
By default, Ray actors require 1 logical CPU at schedule time, but after being scheduled, they do not acquire any CPU resources.
|
|
In other words, by default, actors cannot get scheduled on a zero-cpu node, but an infinite number of them can run on any non-zero cpu node.
|
|
Thus, when scheduling an actor with the default resource requirements and a placement group, the placement group has to be created with a bundle containing at least 1 CPU
|
|
(since the actor requires 1 CPU for scheduling). However, after the actor is created, it doesn't consume any placement group resources.
|
|
|
|
To avoid any surprises, always specify resource requirements explicitly for actors. If resources are specified explicitly, they are required both at schedule time and at execution time.
|
|
|
|
The actor is scheduled now! One bundle can be used by multiple tasks and actors (i.e., the bundle to task (or actor) is a one-to-many relationship).
|
|
In this case, since the actor uses 1 CPU, 1 GPU remains from the bundle.
|
|
You can verify this from the CLI command ``ray status``. You can see the 1 CPU is reserved by the placement group, and 1.0 is used (by the actor we created).
|
|
|
|
.. code-block:: bash
|
|
|
|
ray status
|
|
|
|
.. code-block:: bash
|
|
|
|
Resources
|
|
---------------------------------------------------------------
|
|
Usage:
|
|
1.0/2.0 CPU (1.0 used of 1.0 reserved in placement groups) <---
|
|
0.0/2.0 GPU (0.0 used of 1.0 reserved in placement groups)
|
|
0B/4.29GiB memory
|
|
0B/2.00GiB object_store_memory
|
|
|
|
Demands:
|
|
(no resource demands)
|
|
|
|
You can also verify the actor is created using ``ray list actors``.
|
|
|
|
.. code-block:: bash
|
|
|
|
# This API is only available when you download Ray via `pip install "ray[default]"`
|
|
ray list actors --detail
|
|
|
|
.. code-block:: bash
|
|
|
|
- actor_id: b5c990f135a7b32bfbb05e1701000000
|
|
class_name: Actor
|
|
death_cause: null
|
|
is_detached: false
|
|
job_id: '01000000'
|
|
name: ''
|
|
node_id: b552ca3009081c9de857a31e529d248ba051a4d3aeece7135dde8427
|
|
pid: 8795
|
|
placement_group_id: d2e660ac256db230dbe516127c4a01000000 <------
|
|
ray_namespace: e5b19111-306c-4cd8-9e4f-4b13d42dff86
|
|
repr_name: ''
|
|
required_resources:
|
|
CPU_group_d2e660ac256db230dbe516127c4a01000000: 1.0
|
|
serialized_runtime_env: '{}'
|
|
state: ALIVE
|
|
|
|
Since 1 GPU remains, let's create a new actor that requires 1 GPU.
|
|
This time, we also specify the ``placement_group_bundle_index``. Each bundle is given an "index" within the placement group.
|
|
For example, a placement group of 2 bundles ``[{"CPU": 1}, {"GPU": 1}]`` has index 0 bundle ``{"CPU": 1}``
|
|
and index 1 bundle ``{"GPU": 1}``. Since we only have 1 bundle, we only have index 0. If you don't specify a bundle, the actor (or task)
|
|
is scheduled on a random bundle that has unallocated reserved resources.
|
|
|
|
.. tab-set::
|
|
|
|
.. tab-item:: Python
|
|
|
|
.. literalinclude:: ../doc_code/placement_group_example.py
|
|
:language: python
|
|
:start-after: __schedule_pg_3_start__
|
|
:end-before: __schedule_pg_3_end__
|
|
|
|
We succeed to schedule the GPU actor! The below image describes 2 actors scheduled into the placement group.
|
|
|
|
.. image:: ../images/pg_image_3.png
|
|
:align: center
|
|
|
|
You can also verify that the reserved resources are all used, with the ``ray status`` command.
|
|
|
|
.. code-block:: bash
|
|
|
|
ray status
|
|
|
|
.. code-block:: bash
|
|
|
|
Resources
|
|
---------------------------------------------------------------
|
|
Usage:
|
|
1.0/2.0 CPU (1.0 used of 1.0 reserved in placement groups)
|
|
1.0/2.0 GPU (1.0 used of 1.0 reserved in placement groups) <----
|
|
0B/4.29GiB memory
|
|
0B/2.00GiB object_store_memory
|
|
|
|
.. _pgroup-strategy:
|
|
|
|
Placement Strategy
|
|
------------------
|
|
|
|
One of the features the placement group provides is to add placement constraints among bundles.
|
|
|
|
For example, you'd like to pack your bundles to the same
|
|
node or spread out to multiple nodes as much as possible. You can specify the strategy via ``strategy`` argument.
|
|
This way, you can make sure your actors and tasks can be scheduled with certain placement constraints.
|
|
|
|
The example below creates a placement group with 2 bundles with a PACK strategy;
|
|
both bundles have to be created in the same node. Note that it is a soft policy. If the bundles cannot be packed
|
|
into a single node, they are spread to other nodes. If you'd like to avoid the problem, you can instead use `STRICT_PACK`
|
|
policies, which fail to create placement groups if placement requirements cannot be satisfied.
|
|
|
|
.. literalinclude:: ../doc_code/placement_group_example.py
|
|
:language: python
|
|
:start-after: __strategy_pg_start__
|
|
:end-before: __strategy_pg_end__
|
|
|
|
The image below demonstrates the PACK policy. Three of the ``{"CPU": 2}`` bundles are located in the same node.
|
|
|
|
.. image:: ../images/pg_image_4.png
|
|
:align: center
|
|
|
|
The image below demonstrates the SPREAD policy. Each of three of the ``{"CPU": 2}`` bundles are located in three different nodes.
|
|
|
|
.. image:: ../images/pg_image_5.png
|
|
:align: center
|
|
|
|
Ray supports four placement group strategies. The default scheduling policy is ``PACK``.
|
|
|
|
**STRICT_PACK**
|
|
|
|
All bundles must be placed into a single node on the cluster. Use this strategy when you want to maximize the locality.
|
|
|
|
**PACK**
|
|
|
|
All provided bundles are packed onto a single node on a best-effort basis.
|
|
If strict packing is not feasible (i.e., some bundles do not fit on the node), bundles can be placed onto other nodes.
|
|
|
|
**STRICT_SPREAD**
|
|
|
|
Each bundle must be scheduled in a separate node.
|
|
|
|
**SPREAD**
|
|
|
|
Each bundle is spread onto separate nodes on a best-effort basis.
|
|
If strict spreading is not feasible, bundles can be placed on overlapping nodes.
|
|
|
|
Remove Placement Groups (Free Reserved Resources)
|
|
-------------------------------------------------
|
|
|
|
By default, a placement group's lifetime is scoped to the driver that creates placement groups
|
|
(unless you make it a :ref:`detached placement group <placement-group-detached>`). When the placement group is created from
|
|
a :ref:`detached actor <actor-lifetimes>`, the lifetime is scoped to the detached actor.
|
|
In Ray, the driver is the Python script that calls ``ray.init``.
|
|
|
|
Reserved resources (bundles) from the placement group are automatically freed when the driver or detached actor
|
|
that creates placement group exits. To free the reserved resources manually, remove the placement
|
|
group using the :func:`remove_placement_group <ray.util.remove_placement_group>` API (which is also an asynchronous API).
|
|
|
|
.. note::
|
|
|
|
When you remove the placement group, actors or tasks that still use the reserved resources are
|
|
forcefully killed.
|
|
|
|
.. tab-set::
|
|
|
|
.. tab-item:: Python
|
|
|
|
.. literalinclude:: ../doc_code/placement_group_example.py
|
|
:language: python
|
|
:start-after: __remove_pg_start__
|
|
:end-before: __remove_pg_end__
|
|
|
|
.. tab-item:: Java
|
|
|
|
.. code-block:: java
|
|
|
|
PlacementGroups.removePlacementGroup(placementGroup.getId());
|
|
|
|
PlacementGroup removedPlacementGroup = PlacementGroups.getPlacementGroup(placementGroup.getId());
|
|
Assert.assertEquals(removedPlacementGroup.getState(), PlacementGroupState.REMOVED);
|
|
|
|
.. tab-item:: C++
|
|
|
|
.. code-block:: c++
|
|
|
|
ray::RemovePlacementGroup(placement_group.GetID());
|
|
|
|
ray::PlacementGroup removed_placement_group = ray::GetPlacementGroup(placement_group.GetID());
|
|
assert(removed_placement_group.GetState(), ray::PlacementGroupState::REMOVED);
|
|
|
|
.. _ray-placement-group-observability-ref:
|
|
|
|
Observe and Debug Placement Groups
|
|
----------------------------------
|
|
|
|
Ray provides several useful tools to inspect the placement group states and resource usage.
|
|
|
|
- **Ray Status** is a CLI tool for viewing the resource usage and scheduling resource requirements of placement groups.
|
|
- **Ray Dashboard** is a UI tool for inspecting placement group states.
|
|
- **Ray State API** is a CLI for inspecting placement group states.
|
|
|
|
.. tab-set::
|
|
|
|
.. tab-item:: ray status (CLI)
|
|
|
|
The CLI command ``ray status`` provides the autoscaling status of the cluster.
|
|
It provides the "resource demands" from unscheduled placement groups as well as the resource reservation status.
|
|
|
|
.. code-block:: bash
|
|
|
|
Resources
|
|
---------------------------------------------------------------
|
|
Usage:
|
|
1.0/2.0 CPU (1.0 used of 1.0 reserved in placement groups)
|
|
0.0/2.0 GPU (0.0 used of 1.0 reserved in placement groups)
|
|
0B/4.29GiB memory
|
|
0B/2.00GiB object_store_memory
|
|
|
|
.. tab-item:: Dashboard
|
|
|
|
The :ref:`dashboard job view <dash-jobs-view>` provides the placement group table that displays the scheduling state and metadata of the placement group.
|
|
|
|
.. note::
|
|
|
|
Ray dashboard is only available when you install Ray is with ``pip install "ray[default]"``.
|
|
|
|
.. tab-item:: Ray State API
|
|
|
|
:ref:`Ray state API <state-api-overview-ref>` is a CLI tool for inspecting the state of Ray resources (tasks, actors, placement groups, etc.).
|
|
|
|
``ray list placement-groups`` provides the metadata and the scheduling state of the placement group.
|
|
``ray list placement-groups --detail`` provides statistics and scheduling state in a greater detail.
|
|
|
|
.. note::
|
|
|
|
State API is only available when you install Ray is with ``pip install "ray[default]"``
|
|
|
|
Inspect Placement Group Scheduling State
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
With the above tools, you can see the state of the placement group. The definition of states are specified in the following files:
|
|
|
|
- `High level state <https://github.com/ray-project/ray/blob/03a9d2166988b16b7cbf51dac0e6e586455b28d8/src/ray/protobuf/gcs.proto#L579>`_
|
|
- `Details <https://github.com/ray-project/ray/blob/03a9d2166988b16b7cbf51dac0e6e586455b28d8/src/ray/protobuf/gcs.proto#L524>`_
|
|
|
|
.. image:: ../images/pg_image_6.png
|
|
:align: center
|
|
|
|
[Advanced] Child Tasks and Actors
|
|
---------------------------------
|
|
|
|
By default, child actors and tasks don't share the same placement group that the parent uses.
|
|
To automatically schedule child actors or tasks to the same placement group,
|
|
set ``placement_group_capture_child_tasks`` to True.
|
|
|
|
.. tab-set::
|
|
|
|
.. tab-item:: Python
|
|
|
|
.. literalinclude:: ../doc_code/placement_group_capture_child_tasks_example.py
|
|
:language: python
|
|
:start-after: __child_capture_pg_start__
|
|
:end-before: __child_capture_pg_end__
|
|
|
|
.. tab-item:: Java
|
|
|
|
It's not implemented for Java APIs yet.
|
|
|
|
When ``placement_group_capture_child_tasks`` is True, but you don't want to schedule
|
|
child tasks and actors to the same placement group, specify ``PlacementGroupSchedulingStrategy(placement_group=None)``.
|
|
|
|
.. literalinclude:: ../doc_code/placement_group_capture_child_tasks_example.py
|
|
:language: python
|
|
:start-after: __child_capture_disable_pg_start__
|
|
:end-before: __child_capture_disable_pg_end__
|
|
|
|
|
|
[Advanced] Named Placement Group
|
|
--------------------------------
|
|
|
|
Within a :ref:`namespace <namespaces-guide>`, you can *name* a placement group.
|
|
You can use the name of a placement group to retrieve the placement group from any job
|
|
in the Ray cluster, as long as the job is within the same namespace.
|
|
This is useful if you can't directly pass the placement group handle to
|
|
the actor or task that needs it, or if you are trying to
|
|
access a placement group launched by another driver.
|
|
|
|
The placement group is destroyed when the original creation job completes if its
|
|
lifetime isn't `detached`. You can avoid this by using a :ref:`detached placement group <placement-group-detached>`
|
|
|
|
Note that this feature requires that you specify a
|
|
:ref:`namespace <namespaces-guide>` associated with it, or else you can't retrieve the
|
|
placement group across jobs.
|
|
|
|
.. tab-set::
|
|
|
|
.. tab-item:: Python
|
|
|
|
.. literalinclude:: ../doc_code/placement_group_example.py
|
|
:language: python
|
|
:start-after: __get_pg_start__
|
|
:end-before: __get_pg_end__
|
|
|
|
.. tab-item:: Java
|
|
|
|
.. code-block:: java
|
|
|
|
// Create a placement group with a unique name.
|
|
Map<String, Double> bundle = ImmutableMap.of("CPU", 1.0);
|
|
List<Map<String, Double>> bundles = ImmutableList.of(bundle);
|
|
|
|
PlacementGroupCreationOptions options =
|
|
new PlacementGroupCreationOptions.Builder()
|
|
.setBundles(bundles)
|
|
.setStrategy(PlacementStrategy.STRICT_SPREAD)
|
|
.setName("global_name")
|
|
.build();
|
|
|
|
PlacementGroup pg = PlacementGroups.createPlacementGroup(options);
|
|
pg.wait(60);
|
|
|
|
...
|
|
|
|
// Retrieve the placement group later somewhere.
|
|
PlacementGroup group = PlacementGroups.getPlacementGroup("global_name");
|
|
Assert.assertNotNull(group);
|
|
|
|
.. tab-item:: C++
|
|
|
|
.. code-block:: c++
|
|
|
|
// Create a placement group with a globally unique name.
|
|
std::vector<std::unordered_map<std::string, double>> bundles{{{"CPU", 1.0}}};
|
|
|
|
ray::PlacementGroupCreationOptions options{
|
|
true/*global*/, "global_name", bundles, ray::PlacementStrategy::STRICT_SPREAD};
|
|
|
|
ray::PlacementGroup pg = ray::CreatePlacementGroup(options);
|
|
pg.Wait(60);
|
|
|
|
...
|
|
|
|
// Retrieve the placement group later somewhere.
|
|
ray::PlacementGroup group = ray::GetGlobalPlacementGroup("global_name");
|
|
assert(!group.Empty());
|
|
|
|
We also support non-global named placement group in C++, which means that the placement group name is only valid within the job and cannot be accessed from another job.
|
|
|
|
.. code-block:: c++
|
|
|
|
// Create a placement group with a job-scope-unique name.
|
|
std::vector<std::unordered_map<std::string, double>> bundles{{{"CPU", 1.0}}};
|
|
|
|
ray::PlacementGroupCreationOptions options{
|
|
false/*non-global*/, "non_global_name", bundles, ray::PlacementStrategy::STRICT_SPREAD};
|
|
|
|
ray::PlacementGroup pg = ray::CreatePlacementGroup(options);
|
|
pg.Wait(60);
|
|
|
|
...
|
|
|
|
// Retrieve the placement group later somewhere in the same job.
|
|
ray::PlacementGroup group = ray::GetPlacementGroup("non_global_name");
|
|
assert(!group.Empty());
|
|
|
|
.. _placement-group-detached:
|
|
|
|
[Advanced] Detached Placement Group
|
|
-----------------------------------
|
|
|
|
By default, the lifetimes of placement groups belong to the driver and actor.
|
|
|
|
- If the placement group is created from a driver, it is destroyed when the driver is terminated.
|
|
- If it is created from a detached actor, it is killed when the detached actor is killed.
|
|
|
|
To keep the placement group alive regardless of its job or detached actor, specify
|
|
`lifetime="detached"`. For example:
|
|
|
|
.. tab-set::
|
|
|
|
.. tab-item:: Python
|
|
|
|
.. literalinclude:: ../doc_code/placement_group_example.py
|
|
:language: python
|
|
:start-after: __detached_pg_start__
|
|
:end-before: __detached_pg_end__
|
|
|
|
.. tab-item:: Java
|
|
|
|
The lifetime argument isn't implemented for Java APIs yet.
|
|
|
|
Let's terminate the current script and start a new Python script. Call ``ray list placement-groups``, and you can see the placement group is not removed.
|
|
|
|
Note that Ray decouples the lifetime option and the name option. If you only specify
|
|
the name without specifying ``lifetime="detached"``, then you can only retrieve the placement group
|
|
while the driver where you created the placement group is still running.
|
|
It's recommended to always specify the name when creating the detached placement group. If you don't,
|
|
there is no way to retrieve the placement group from another process, and there is no way
|
|
to kill it once you exit the driver script that created the placement group.
|
|
|
|
|
|
.. _ray-placement-group-ft-ref:
|
|
|
|
[Advanced] Fault Tolerance
|
|
--------------------------
|
|
|
|
Rescheduling Bundles on a Dead Node
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
If nodes that contain some bundles of a placement group die, Ray tries to reschedule the lost bundles on different nodes.
|
|
This means that the initial creation of placement group is "atomic," but after the initial creation,
|
|
there could be partial placement groups. Actors or tasks running on bundles on the remaining live nodes continue to run.
|
|
Note that rescheduling bundles have higher scheduling priority than other placement group scheduling.
|
|
|
|
Provide Resources for Partially Lost Bundles
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
If there aren't enough resources to schedule the partially lost bundles,
|
|
the placement group waits, assuming the Ray Autoscaler starts a new node to satisfy the resource requirements.
|
|
If the autoscaler can't provide additional resources or if you're not using the autoscaler,
|
|
the placement group remains in the partially created state indefinitely.
|
|
|
|
Fault Tolerance of Actors and Tasks that Use the Bundle
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Ray reschedules Actors and tasks that use the bundle (reserved resources) based on their :ref:`fault tolerant policy <fault-tolerance>` once Ray recovers the bundle.
|
|
|
|
.. _pgroup-topology-strategy:
|
|
|
|
[Alpha] Topology aware scheduling
|
|
---------------------------------
|
|
|
|
.. warning::
|
|
|
|
Topology aware scheduling is an **alpha** feature. It's actively being iterated on and
|
|
the API surface may change. Ray currently only supports defining one topology label and
|
|
one node level strategy (described below). For topology labels, Ray currently supports
|
|
only ``STRICT_PACK``. Support for additional strategies and multi-level topologies is planned.
|
|
|
|
Why topology aware scheduling?
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
The placement strategies above (PACK, STRICT_PACK, SPREAD, STRICT_SPREAD) operate purely on a
|
|
per-node basis. For multi-node GPU domains such as GB200 or GB300 NVL racks where nodes share
|
|
fast interconnects, there's no native way to ensure all bundles land within the same GPU
|
|
domain.
|
|
|
|
For example, consider a cluster with 2 racks of 18 nodes each, where each node has
|
|
``{"GPU": 4, "CPU": 2}``. You want to schedule ``[{"GPU": 4, "CPU": 2}] * 18`` within a single rack:
|
|
|
|
- **STRICT_PACK** tries to place all 18 bundles onto a single *node*, which is infeasible because
|
|
a single node only has 4 GPUs and 2 CPUs.
|
|
- **PACK** spreads bundles across nodes but it has no concept of racks and bundles may land on nodes
|
|
across *both* racks.
|
|
|
|
You could work around this with static :ref:`label selectors <labels>` (such as
|
|
``bundle_label_selector=[{"my_custom_gpu_domain_label": "rack-1"}] * 18``), but that approach doesn't support
|
|
fault tolerance. If all nodes in ``rack-1`` go down, the placement group can't automatically move
|
|
to a different rack. Furthermore, you have to manually specify a domain when you really just want
|
|
any domain and this becomes cumbersome if you have many GPU domains.
|
|
|
|
Topology aware scheduling currently solves this by letting you express a topology strategy for the placement
|
|
group, which allows specifying a topology label within the cluster. Ray picks a value for this topology label
|
|
that can satisfy all bundles (for example, a specific rack) and then applies your node-level strategy
|
|
within that value.
|
|
|
|
How it works
|
|
~~~~~~~~~~~~
|
|
|
|
Pass ``topology_strategy=`` to :func:`ray.util.placement_group` to enable topology-aware
|
|
placement. The argument is a dict that maps a label key to the placement strategy used at that level.
|
|
|
|
Currently, ``topology_strategy`` is a dictionary that may contain up to two keys:
|
|
|
|
- The special key ``ray.io/node-id`` sets the **node-level** strategy and accepts any value
|
|
in ``{"PACK", "STRICT_PACK", "SPREAD", "STRICT_SPREAD"}``. If you omit it, the node-level
|
|
strategy defaults to ``PACK``.
|
|
- Any other key is a **topology label** that nodes have set (via ``ray start --labels`` or
|
|
your cluster configuration). Only ``STRICT_PACK`` is supported for these labels today.
|
|
|
|
.. code-block:: python
|
|
|
|
from ray.util.placement_group import placement_group
|
|
|
|
bundles = [{"GPU": 4, "CPU": 2}] * 18
|
|
|
|
pg = placement_group(
|
|
bundles=bundles,
|
|
topology_strategy={"ray.io/gpu-domain": "STRICT_PACK"},
|
|
)
|
|
|
|
ray.get(pg.ready())
|
|
|
|
With this, Ray accomplishes the following:
|
|
|
|
1. Groups candidate nodes by the value of the topology label you named
|
|
(``ray.io/gpu-domain`` in the example above).
|
|
2. Selects a value for that label that can satisfy all bundles.
|
|
3. Applies the node-level scheduling strategy within the selected value.
|
|
|
|
Here is a following example to STRICT_SPREAD bundles across distinct nodes while still
|
|
STRICT_PACKing them onto a single rack (``ray.io/gpu-domain`` is each rack's **topology label**):
|
|
|
|
.. code-block:: python
|
|
|
|
pg = placement_group(
|
|
bundles=[{"CPU": 1}] * 4,
|
|
topology_strategy={
|
|
"ray.io/node-id": "STRICT_SPREAD",
|
|
"ray.io/gpu-domain": "STRICT_PACK",
|
|
},
|
|
)
|
|
|
|
``topology_strategy`` is mutually exclusive with the ``strategy=`` parameter, and passing both
|
|
raises ``ValueError``. To override the default node-level strategy alongside a topology label,
|
|
put the node-level strategy under ``ray.io/node-id`` in the same dict, as shown above.
|
|
|
|
.. note::
|
|
|
|
Ray doesn't automatically set topology labels such as ``ray.io/gpu-domain`` on nodes.
|
|
Configure these labels through ``ray start --labels`` or your cluster configuration.
|
|
For example:
|
|
|
|
.. code-block:: bash
|
|
|
|
ray start --labels="ray.io/gpu-domain=rack-1"
|
|
|
|
Fault tolerance
|
|
~~~~~~~~~~~~~~~
|
|
|
|
Topology aware scheduling improves on static label selectors by providing automatic
|
|
fault tolerance at the topology-label level:
|
|
|
|
- **Partial failure** (some nodes within the selected value of the topology label die):
|
|
Ray reschedules the lost bundles onto surviving nodes **within the same value** (for
|
|
example, the same rack). Actors and tasks on the remaining bundles keep running. If
|
|
the selected value doesn't have enough resources to reschedule the lost bundles,
|
|
those bundles stay infeasible and queued until resources free up in the same value.
|
|
To force the placement group onto a different value, call
|
|
:func:`ray.util.remove_placement_group <ray.util.remove_placement_group>` and create
|
|
a new one. Removing the placement group forcefully kills every actor and task still
|
|
using its bundles and doesn't restart them, so you must re-create them yourself on
|
|
the new placement group.
|
|
- **Total failure** (all nodes with the selected value die): Ray clears the topology
|
|
assignment and reschedules the entire placement group onto a different value.
|
|
|
|
Observability
|
|
~~~~~~~~~~~~~
|
|
|
|
You can inspect topology-aware placement groups using the existing placement group
|
|
observability tools:
|
|
|
|
- **Dashboard**: The placement group table shows a ``Topology`` column, which displays the
|
|
strategy you requested and the value Ray selected for each topology label.
|
|
- **State API**: ``ray list placement-groups --detail`` returns the requested strategy in
|
|
``topology_strategy`` and the value Ray selected in ``topology_assignments``.
|
|
|
|
The following ``ray list placement-groups --detail`` output shows the two topology fields,
|
|
``topology_strategy`` and ``topology_assignments``, populated for a placement group that
|
|
packs onto a single ``ray.io/gpu-domain``:
|
|
|
|
.. code-block:: yaml
|
|
|
|
- placement_group_id: 237f47c3235ac1a96ad423c3f74501000000
|
|
name: gpu-domain-pg
|
|
state: CREATED
|
|
bundles:
|
|
- bundle_id:
|
|
placement_group_id: 237f47c3235ac1a96ad423c3f74501000000
|
|
bundle_index: 0
|
|
unit_resources:
|
|
CPU: 1.0
|
|
node_id: 0fd7eecf6335633ba39ab66f5a26b18eeb35c70c15a9563a29ee2bce
|
|
- bundle_id:
|
|
placement_group_id: 237f47c3235ac1a96ad423c3f74501000000
|
|
bundle_index: 1
|
|
unit_resources:
|
|
CPU: 1.0
|
|
node_id: 0fd7eecf6335633ba39ab66f5a26b18eeb35c70c15a9563a29ee2bce
|
|
is_detached: false
|
|
stats: ...
|
|
topology_strategy:
|
|
- entries:
|
|
ray.io/gpu-domain: STRICT_PACK
|
|
topology_assignments:
|
|
- assignments:
|
|
ray.io/gpu-domain: rack-2
|
|
|
|
For placement groups that don't use topology-aware scheduling, ``topology_strategy`` and
|
|
``topology_assignments`` are both empty lists. Both fields appear only when you pass
|
|
``--detail``.
|
|
|
|
API Reference
|
|
-------------
|
|
:ref:`Placement Group API reference <ray-placement-group-ref>`
|