blob: 0d2a094e6a143dbb3843b12d4d955649c2168136 [file] [edit]
############################################################################
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
############################################################################
"""Test per-device typed shard counts in gres.conf (Type without File)."""
import re
import pytest
import atf
node = "node1"
@pytest.fixture(scope="module", autouse=True)
def setup():
atf.require_version(
(26, 11),
component="sbin/slurmd",
reason="Issue 51032: per-device typed shard counts were added in 26.11",
)
atf.require_config_parameter("SelectType", "select/cons_tres")
atf.require_config_parameter("SelectTypeParameters", "CR_Core_Memory")
atf.require_config_parameter_includes("GresTypes", "gpu")
atf.require_config_parameter_includes("GresTypes", "shard")
for tty_num in range(6):
atf.require_tty(tty_num)
# Three GPU models: 2x a100 (32 shards each), 2x l40s (16 each), and
# 2x h100 with no shard config, to check an unsharded model does not drain
atf.require_config_parameter(
"Name",
"gpu Type=a100 File=/dev/tty[0-1]"
"\nName=gpu Type=l40s File=/dev/tty[2-3]"
"\nName=gpu Type=h100 File=/dev/tty[4-5]"
"\nName=shard Type=a100 Count=32"
"\nName=shard Type=l40s Count=16",
source="gres",
)
atf.require_nodes(
1,
[
("Gres", "gpu:a100:2,gpu:l40s:2,gpu:h100:2,shard:a100:64,shard:l40s:32"),
("CPUs", 4),
("RealMemory", 100),
],
)
atf.require_slurm_running()
def test_node_registers_typed_totals():
"""The node registers the per-type shard totals (per-device Count times
the number of devices of that type)."""
output = atf.run_command_output(f"scontrol show node {node}", fatal=True)
gres = re.search(r"Gres=(\S*)", output).group(1)
assert "shard:a100:64" in gres, f"Expected shard:a100:64 in Gres: {gres}"
assert "shard:l40s:32" in gres, f"Expected shard:l40s:32 in Gres: {gres}"
def test_per_device_counts():
"""Each a100 device holds 32 shards: two concurrent jobs asking for 32
a100 shards each must both run (one per device), since shards must fit
a single device. Before 26.11 typed shard lines without File were
rejected at slurmd startup."""
# 33 shards fit the 64 free a100 total but not one 32-shard device
result = atf.run_command(
f"srun -w {node} --gres=shard:a100:33 --mem=10 -n1 -t1 --immediate=5 true",
xfail=True,
)
assert result["exit_code"] != 0, "33 shards must not fit a single a100 device"
job_ids = []
for i in range(2):
job_ids.append(
atf.submit_job_sbatch(
f"-w {node} --gres=shard:a100:32 -n1 -t2 --mem=10 "
f"-o shard_a_{i}.out --wrap 'echo SHARDS=$SLURM_SHARDS_ON_NODE; sleep infinity'",
fatal=True,
)
)
for job_id in job_ids:
atf.wait_for_job_state(job_id, "RUNNING", fatal=True)
# Both a100 devices must be exactly full: allocations may not span devices
output = atf.run_command_output(f"scontrol show node -d {node}", fatal=True)
assert "shard:a100:64(32/32,32/32)" in output, f"Unexpected GresUsed: {output}"
# An l40s device holds 16 shards, so 16 fit alongside the a100 jobs
job_b = atf.submit_job_sbatch(
f"-w {node} --gres=shard:l40s:16 -n1 -t2 --mem=10 "
"-o shard_b.out --wrap 'echo SHARDS=$SLURM_SHARDS_ON_NODE; sleep infinity'",
fatal=True,
)
atf.wait_for_job_state(job_b, "RUNNING", fatal=True)
for i in range(2):
atf.assert_file_contents(f"shard_a_{i}.out", "SHARDS=32", contains=True)
atf.assert_file_contents("shard_b.out", "SHARDS=16", contains=True)
def test_unsharded_gpu_type_does_not_drain():
"""A gpu model with no shard config (h100) must not drain the node.
The drain triggers when: (1) gres.conf defines h100 gpus, (2) it has no
Name=shard Type=h100 line, and (3) another model uses a typed no-File shard
line (Name=shard Type=a100). slurmd must not then report a shard:h100:0 that
the controller rejects as 'reported but not configured'."""
output = atf.run_command_output(f"scontrol show node {node}", fatal=True)
state = re.search(r"State=(\S+)", output).group(1)
assert "DRAIN" not in state, f"node drained: {output}"
assert "INVALID_REG" not in state, f"invalid registration: {output}"
assert "shard:h100" not in output, f"phantom shard:h100 present: {output}"