Skip to content

Commit bb25ce0

Browse files
committed
Split automatically detected GPUs into gpus/nvidia and gpus/amd
1 parent 91f8991 commit bb25ce0

10 files changed

Lines changed: 90 additions & 62 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
$ hq worker start --resource="res1=[foo, bar]"
1313
```
1414

15+
* HyperQueue now provides built-in support for AMD GPUs. For this reason, the default name of GPU
16+
resources that are automatically detected on a worker has been changed from `gpus` to `gpus/nvidia`
17+
for NVIDIA GPUs. AMD GPUs are now autodetected as `gpus/amd`. In the future, we intend to create a way
18+
to ask for any GPU resource (e.g. `--resource=gpus=2`), regardless of its type.
19+
1520
* AMD GPUs are now automatically detected in workers from the environment variable `ROCR_VISIBLE_DEVICES`
1621
or `HIP_VISIBLE_DEVICES`.
1722

crates/hyperqueue/src/worker/hwdetect.rs

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use tako::format_comma_delimited;
1212
use tako::internal::has_unique_elements;
1313
use tako::resources::{
1414
ResourceDescriptorItem, ResourceDescriptorKind, ResourceIndex, ResourceLabel,
15-
GPU_RESOURCE_NAME, MEM_RESOURCE_NAME,
15+
AMD_GPU_RESOURCE_NAME, MEM_RESOURCE_NAME, NVIDIA_GPU_RESOURCE_NAME,
1616
};
1717

1818
pub fn detect_cpus() -> anyhow::Result<ResourceDescriptorKind> {
@@ -57,17 +57,17 @@ pub fn detect_additional_resources(items: &mut Vec<ResourceDescriptorItem>) -> a
5757
let has_resource =
5858
|items: &[ResourceDescriptorItem], name: &str| items.iter().any(|x| x.name == name);
5959

60-
if !has_resource(items, GPU_RESOURCE_NAME) {
61-
if let Some(gpus) = detect_gpus_from_env() {
60+
if !has_resource(items, NVIDIA_GPU_RESOURCE_NAME) {
61+
if let Some(detected) = detect_gpus_from_env() {
6262
items.push(ResourceDescriptorItem {
63-
name: GPU_RESOURCE_NAME.to_string(),
64-
kind: gpus,
63+
name: detected.resource_name.to_string(),
64+
kind: detected.resource,
6565
});
66-
} else if let Ok(count) = read_linux_gpu_count() {
66+
} else if let Ok(count) = read_nvidia_linux_gpu_count() {
6767
if count > 0 {
6868
log::info!("Detected {} GPUs from procs", count);
6969
items.push(ResourceDescriptorItem {
70-
name: GPU_RESOURCE_NAME.to_string(),
70+
name: NVIDIA_GPU_RESOURCE_NAME.to_string(),
7171
kind: ResourceDescriptorKind::simple_indices(count as u32),
7272
});
7373
}
@@ -86,15 +86,20 @@ pub fn detect_additional_resources(items: &mut Vec<ResourceDescriptorItem>) -> a
8686
Ok(())
8787
}
8888

89-
pub const GPU_ENV_KEYS: &[&str; 3] = &[
90-
"CUDA_VISIBLE_DEVICES",
91-
"HIP_VISIBLE_DEVICES",
92-
"ROCR_VISIBLE_DEVICES",
89+
pub const GPU_ENV_KEYS: &[(&str, &str); 3] = &[
90+
("CUDA_VISIBLE_DEVICES", NVIDIA_GPU_RESOURCE_NAME),
91+
("HIP_VISIBLE_DEVICES", AMD_GPU_RESOURCE_NAME),
92+
("ROCR_VISIBLE_DEVICES", AMD_GPU_RESOURCE_NAME),
9393
];
9494

95+
struct DetectedGpu {
96+
resource_name: &'static str,
97+
resource: ResourceDescriptorKind,
98+
}
99+
95100
/// Tries to detect available GPUs from one of the `GPU_ENV_KEYS` environment variables.
96-
fn detect_gpus_from_env() -> Option<ResourceDescriptorKind> {
97-
GPU_ENV_KEYS.iter().find_map(|env_key| {
101+
fn detect_gpus_from_env() -> Option<DetectedGpu> {
102+
for (env_key, resource_name) in GPU_ENV_KEYS {
98103
if let Ok(devices_str) = std::env::var(env_key) {
99104
if let Ok(devices) = parse_comma_separated_values(&devices_str) {
100105
log::info!(
@@ -108,15 +113,18 @@ fn detect_gpus_from_env() -> Option<ResourceDescriptorKind> {
108113

109114
let list =
110115
ResourceDescriptorKind::list(devices).expect("List values were not unique");
111-
return Some(list);
116+
return Some(DetectedGpu {
117+
resource_name,
118+
resource: list,
119+
});
112120
}
113121
}
114-
None
115-
})
122+
}
123+
None
116124
}
117125

118126
/// Try to find out how many Nvidia GPUs are available on the current node.
119-
fn read_linux_gpu_count() -> anyhow::Result<usize> {
127+
fn read_nvidia_linux_gpu_count() -> anyhow::Result<usize> {
120128
Ok(std::fs::read_dir("/proc/driver/nvidia/gpus")?.count())
121129
}
122130

crates/hyperqueue/src/worker/start.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ use crate::common::placeholders::{
3131
use crate::common::utils::fs::{bytes_to_path, is_implicit_path, path_has_extension};
3232
use crate::transfer::messages::{PinMode, TaskBody};
3333
use crate::transfer::stream::ChannelId;
34-
use crate::worker::hwdetect::GPU_ENV_KEYS;
3534
use crate::worker::streamer::StreamSender;
3635
use crate::worker::streamer::StreamerRef;
3736
use crate::{JobId, JobTaskId};
3837
use serde::{Deserialize, Serialize};
3938
use tako::comm::serialize;
4039
use tako::program::{ProgramDefinition, StdioDef};
4140
use tako::resources::{
42-
Allocation, ResourceAllocation, CPU_RESOURCE_ID, CPU_RESOURCE_NAME, GPU_RESOURCE_NAME,
41+
Allocation, ResourceAllocation, AMD_GPU_RESOURCE_NAME, CPU_RESOURCE_ID, CPU_RESOURCE_NAME,
42+
NVIDIA_GPU_RESOURCE_NAME,
4343
};
4444

4545
const MAX_CUSTOM_ERROR_LENGTH: usize = 2048; // 2KiB
@@ -244,16 +244,21 @@ fn insert_resources_into_env(ctx: &LaunchContext, program: &mut ProgramDefinitio
244244
);
245245
}
246246
}
247-
if resource_name == GPU_RESOURCE_NAME {
248-
/* Extra variables for GPUS */
249-
for &key in GPU_ENV_KEYS {
250-
program.env.insert(key.into(), labels.clone().into());
251-
}
247+
if resource_name == NVIDIA_GPU_RESOURCE_NAME {
248+
/* Extra variables for Nvidia GPUS */
249+
program
250+
.env
251+
.insert("CUDA_VISIBLE_DEVICES".into(), labels.clone().into());
252252
program
253253
.env
254254
.insert("CUDA_DEVICE_ORDER".into(), "PCI_BUS_ID".into());
255255
}
256-
256+
if resource_name == AMD_GPU_RESOURCE_NAME {
257+
/* Extra variable for AMD GPUS */
258+
program
259+
.env
260+
.insert("ROCR_VISIBLE_DEVICES".into(), labels.clone().into());
261+
}
257262
program.env.insert(
258263
format!("HQ_RESOURCE_VALUES_{resource_name}").into(),
259264
labels.into(),

crates/tako/benches/benchmarks/worker.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use tako::resources::{
1313
AllocationRequest, ResourceDescriptor, ResourceRequest, ResourceRequestEntry, TimeRequest,
1414
};
1515
use tako::resources::{
16-
ResourceDescriptorItem, ResourceDescriptorKind, CPU_RESOURCE_NAME, GPU_RESOURCE_NAME,
16+
ResourceDescriptorItem, ResourceDescriptorKind, CPU_RESOURCE_NAME, NVIDIA_GPU_RESOURCE_NAME,
1717
};
1818
use tako::ItemId;
1919
use tokio::sync::mpsc::unbounded_channel;
@@ -170,7 +170,7 @@ fn create_resource_queue(num_cpus: u32) -> ResourceWaitQueue {
170170
kind: ResourceDescriptorKind::simple_indices(num_cpus),
171171
},
172172
ResourceDescriptorItem {
173-
name: GPU_RESOURCE_NAME.to_string(),
173+
name: NVIDIA_GPU_RESOURCE_NAME.to_string(),
174174
kind: ResourceDescriptorKind::simple_indices(8),
175175
},
176176
]);

crates/tako/src/internal/common/resources/map.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use crate::internal::common::Map;
44
pub const CPU_RESOURCE_ID: ResourceId = ResourceId(0);
55

66
pub const CPU_RESOURCE_NAME: &str = "cpus";
7-
pub const GPU_RESOURCE_NAME: &str = "gpus";
7+
pub const NVIDIA_GPU_RESOURCE_NAME: &str = "gpus/nvidia";
8+
pub const AMD_GPU_RESOURCE_NAME: &str = "gpus/amd";
89
pub const MEM_RESOURCE_NAME: &str = "mem";
910

1011
#[derive(Debug)]

crates/tako/src/internal/common/resources/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ pub use allocation::{Allocation, AllocationValue, ResourceAllocation, ResourceAl
99
pub use descriptor::{
1010
DescriptorError, ResourceDescriptor, ResourceDescriptorItem, ResourceDescriptorKind,
1111
};
12-
pub use map::{CPU_RESOURCE_ID, CPU_RESOURCE_NAME, GPU_RESOURCE_NAME, MEM_RESOURCE_NAME};
12+
pub use map::{
13+
AMD_GPU_RESOURCE_NAME, CPU_RESOURCE_ID, CPU_RESOURCE_NAME, MEM_RESOURCE_NAME,
14+
NVIDIA_GPU_RESOURCE_NAME,
15+
};
1316
pub use request::{
1417
AllocationRequest, ResourceRequest, ResourceRequestEntries, ResourceRequestEntry, TimeRequest,
1518
};

crates/tako/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ pub mod resources {
3131
Allocation, AllocationRequest, AllocationValue, NumOfNodes, ResourceAllocation,
3232
ResourceAmount, ResourceDescriptor, ResourceDescriptorItem, ResourceDescriptorKind,
3333
ResourceIndex, ResourceLabel, ResourceRequest, ResourceRequestEntries,
34-
ResourceRequestEntry, TimeRequest, CPU_RESOURCE_ID, CPU_RESOURCE_NAME, GPU_RESOURCE_NAME,
35-
MEM_RESOURCE_NAME,
34+
ResourceRequestEntry, TimeRequest, AMD_GPU_RESOURCE_NAME, CPU_RESOURCE_ID,
35+
CPU_RESOURCE_NAME, MEM_RESOURCE_NAME, NVIDIA_GPU_RESOURCE_NAME,
3636
};
3737

3838
pub use crate::internal::common::resources::map::ResourceMap;

docs/deployment/allocation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,13 @@ creating a new allocation queue:
7474
=== "PBS"
7575

7676
```bash
77-
$ hq alloc add pbs --time-limit 1h --cpus 4x4 --resource "gpus=range(1-2)" -- -qqprod -AAccount1
77+
$ hq alloc add pbs --time-limit 1h --cpus 4x4 --resource "gpus/nvidia=range(1-2)" -- -qqprod -AAccount1
7878
```
7979

8080
=== "Slurm"
8181

8282
``` bash
83-
$ hq alloc add slurm --time-limit 1h --cpus 4x4 --resource "gpus=range(1-2)" -- --partition=p1
83+
$ hq alloc add slurm --time-limit 1h --cpus 4x4 --resource "gpus/nvidia=range(1-2)" -- --partition=p1
8484
```
8585

8686
If you do not pass any resources, they will be detected automatically (same as it works with

docs/jobs/resources.md

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,19 @@ The following resources are detected automatically if a resource of a given name
9090

9191
* CPUs are automatically detected as resource named "cpus" (more in [CPU resources](cresources.md)).
9292

93-
* GPUs that are available when a worker is started are automatically detected under the resource
94-
name `gpus`. You can use the environment variables `CUDA_VISIBLE_DEVICES`, `HIP_VISIBLE_DEVICES` or
95-
`ROCR_VISIBLE_DEVICES` when starting a worker to override the list of available GPUs:
93+
* GPUs that are available when a worker is started are automatically detected under the following
94+
resource names:
95+
- NVIDIA GPUs are stored the under resource name `gpus/nvidia`. These GPUs are detected from the
96+
environment variable `CUDA_VISIBLE_DEVICES` or from the `procfs` filesystem.
97+
- AMD GPUs are stored under the resource name `gpus/amd`. These GPUs are detected from the environment
98+
variables `ROCR_VISIBLE_DEVICES` or `HIP_VISIBLE_DEVICES`.
99+
100+
You can set these environment variables when starting a worker to override the list of available GPUs:
96101

97-
```bash
98-
$ CUDA_VISIBLE_DEVICES=2,3 hq worker start
99-
```
102+
```bash
103+
$ CUDA_VISIBLE_DEVICES=2,3 hq worker start
104+
# The worker will have resource gpus/nvidia=[2,3]
105+
```
100106

101107
* RAM of the node is detected as resource "mem" in bytes.
102108

@@ -111,7 +117,6 @@ The automatic detection of resources can be disabled by argument ``--no-detect-r
111117
It disables detection of resources other than "cpus";
112118
if resource "cpus" are not explicitly defined, it will always be detected.
113119

114-
115120
## Resource request
116121

117122
When you submit a job, you can define a **resource requests** with the `--resource` flag:
@@ -133,14 +138,14 @@ task requests.
133138

134139
For example, let's say that a worker has an indexed pool of GPUs:
135140
```bash
136-
$ hq worker start --resource "gpus=range(1-3)"
141+
$ hq worker start --resource "gpus/nvidia=range(1-3)"
137142
```
138143
And we create two jobs, each with a single task. The first job wants 1 GPU, the second one wants
139144
two GPUs.
140145
141146
```bash
142-
$ hq submit --resource gpus=1 ...
143-
$ hq submit --resource gpus=2 ...
147+
$ hq submit --resource gpus/nvidia=1 ...
148+
$ hq submit --resource gpus/nvidia=2 ...
144149
```
145150
146151
Then the first job can be allocated e.g. the GPU `2` and the second job can be allocated the GPUs
@@ -191,16 +196,13 @@ each resource request named `<NAME>`:
191196
* `HQ_RESOURCE_VALUES_<NAME>` contains the specific resource values allocated for the task as a
192197
comma-separated list. This variable is only filled for indexed resource pool.
193198
194-
!!! tip
195-
196-
HQ has a special case for a resource named `gpus`. For that resource, it will also pass the following
197-
environment variables to the spawned task:
198-
199-
* `CUDA_DEVICE_ORDER` set to the value `PCI_BUS_ID`
200-
* `CUDA_VISIBLE_DEVICES` set to the same value as `HQ_RESOURCE_VALUES_gpus`
201-
* `HIP_VISIBLE_DEVICES` set to the same value as `HQ_RESOURCE_VALUES_gpus`
202-
* `ROCR_VISIBLE_DEVICES` set to the same value as `HQ_RESOURCE_VALUES_gpus`
199+
HQ also sets additional environment variables for various resources with special names:
203200
201+
- For the resource `gpus/nvidia`, HQ will set:
202+
- `CUDA_VISIBLE_DEVICES` to the same value as `HQ_RESOURCE_VALUES_gpus/nvidia`
203+
- `CUDA_DEVICE_ORDER` to `PCI_BUS_ID`
204+
- For the resource `gpus/amd`, HQ will set:
205+
- `ROCR_VISIBLE_DEVICES` and `HIP_VISIBLE_DEVICES` to the same value as `HQ_RESOURCE_VALUES_gpus/amd`
204206
205207
## Resource requests and job arrays
206208

tests/test_resources.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -171,44 +171,48 @@ def test_worker_resource_hwdetect_mem(hq_env: HqEnv):
171171

172172
def test_worker_set_gpu_env_for_task(hq_env: HqEnv):
173173
hq_env.start_server()
174-
hq_env.start_worker(args=["--resource", "gpus=[0,1]"])
174+
hq_env.start_worker(args=["--resource", "gpus/nvidia=[0,1]"])
175175
hq_env.command(
176176
[
177177
"submit",
178178
"--resource",
179-
"gpus=2",
179+
"gpus/nvidia=2",
180180
"--",
181181
"bash",
182182
"-c",
183183
"""
184184
echo $CUDA_VISIBLE_DEVICES
185-
echo $HIP_VISIBLE_DEVICES
186-
echo $ROCR_VISIBLE_DEVICES
187185
""",
188186
]
189187
)
190188
wait_for_job_state(hq_env, 1, "FINISHED")
191189
assert list(
192190
set(int(v) for v in line.split(","))
193191
for line in read_file(default_task_output()).splitlines()
194-
) == [{0, 1}, {0, 1}, {0, 1}]
192+
) == [{0, 1}]
195193

196194

197195
@pytest.mark.parametrize(
198-
"env_key", ("CUDA_VISIBLE_DEVICES", "HIP_VISIBLE_DEVICES", "ROCR_VISIBLE_DEVICES")
196+
"env_and_res",
197+
(
198+
("CUDA_VISIBLE_DEVICES", "gpus/nvidia"),
199+
("HIP_VISIBLE_DEVICES", "gpus/amd"),
200+
("ROCR_VISIBLE_DEVICES", "gpus/amd"),
201+
),
199202
)
200-
def test_worker_detect_gpus_from_env(hq_env: HqEnv, env_key: str):
203+
def test_worker_detect_gpus_from_env(hq_env: HqEnv, env_and_res: str):
204+
env, resource = env_and_res
201205
hq_env.start_server()
202-
resources = hq_env.command(["worker", "hwdetect"], env={env_key: "1,3"})
203-
assert "gpus: [1,3]" in resources
206+
resources = hq_env.command(["worker", "hwdetect"], env={env: "1,3"})
207+
assert f"{resource}: [1,3]" in resources
204208

205209

206210
def test_worker_detect_uuid_gpus_from_env(hq_env: HqEnv):
207211
hq_env.start_server()
208212
resources = hq_env.command(
209213
["worker", "hwdetect"], env={"CUDA_VISIBLE_DEVICES": "foo,bar"}
210214
)
211-
assert "gpus: [foo,bar]" in resources
215+
assert "gpus/nvidia: [foo,bar]" in resources
212216

213217

214218
def test_task_info_resources(hq_env: HqEnv):

0 commit comments

Comments
 (0)