Skip to content

fix(rdc): apply a GPU group's config setting to every GPU or none - #14075

Draft
adam360x wants to merge 2 commits into
users/adam360x/stack-base/rdc-configfrom
users/adam360x/rdc-config
Draft

adam360x wants to merge 2 commits into
users/adam360x/stack-base/rdc-configfrom
users/adam360x/rdc-config

Conversation

@adam360x

@adam360x adam360x commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #14041 and #14051 as a draft: its base branch, users/adam360x/stack-base/rdc-config, merges them, so this PR shows only its own commits. When they merge, this PR moves to develop, where RDC CI runs, and becomes ready for review.

F12 (#14072) also defines get_processor_handle_from_id() in tests/race, as a weak stand-in, so this PR's definition is the one linked when both are in, whichever lands first.

Motivation

rdc_config_set() read its status uninitialized for a GPU group with no GPU, so the setting was cached and reported as set, or failed, by chance. A setting type other than the three in rdc_config_type_t reached no GPU but was cached and reported as set, and a client could cache as many as it liked. A power limit whose microwatts did not fit in 64 bits wrapped around to another limit, which the GPUs took. All three are now RDC_ST_BAD_PARAMETER, before any GPU is touched; the type is compared as an integer, since loading a value outside the enum through it is undefined.

Technical Details

When one GPU of a group rejected a setting, the GPUs before it kept the new value, which RDC neither tracked nor cleared. They now get the group's previous setting of that type back, or their default. rdc_config_clear() stopped at the first GPU or setting it could not reset and dropped the group's settings anyway; it now resets every GPU and setting it can, then reports the failure.

A clear that removed the last setting joined the monitoring thread outside the lock while a concurrent set could start a new thread in the same std::thread: a data race, and std::terminate while the old thread was still joinable. The monitoring thread now runs from the first setting until the object is destroyed, waits without waking up while no setting is cached, and set, clear and each monitoring pass hold the lock throughout.

The monitoring thread read the groups' GPUs into one structure for all groups and passes, so a group lookup that succeeded without filling it reused another group's GPUs. It compared the power cap in whole watts converted through an int, which re-applied caps above 2147 W every minute and missed changes of less than a watt, and it read every setting of a GPU and skipped the rest after any failed read. It now reads each group afresh, compares microwatts, and checks only the cached settings, each on its own. A group that reports more GPUs than rdc_group_info_t holds is an error instead of a read past the array, and an empty frequency table no longer makes rdc_config_clear() read far outside it.

Issue Tracking

ISSUE ID : #14107

Part of a series of RDC fixes, which #14107 lists. No JIRA ticket.

Test Plan

Tests: rdc_config_tests compiles RdcConfigSettingsImpl.cc with fakes for the group table and amd-smi, and a second constructor sets the monitoring period to 10 ms; the uninitialized cases fill the stack with 0xA5 first, behind a check that the fill shows. rdc_race_tests sets, reads and clears several groups from several threads under ThreadSanitizer, and test_fake_gpu_config.py drives embedded RDC against the fake amd-smi. Without the fix, 17 of the 20 gtests fail (the round trip passes either way, and the two oversized-table cases fail only under AddressSanitizer), the race test ends in std::terminate in every run, and the 6 fake-GPU tests fail.

Test Result

GPU-less container, as CI runs the tests: Fake-GPU suite: 136 passed, 25 skipped. tests/ci: 93 passed, 6 skipped.

Submission Checklist

rdc_config_set() read its status uninitialized for a GPU group with no
GPU, so the setting was cached and reported as set, or failed, by
chance. A setting type other than the three in rdc_config_type_t
reached no GPU but was cached and reported as set, and a client could
cache as many as it liked. A power limit whose microwatts did not fit
in 64 bits wrapped around to another limit, which the GPUs took. All
three are now RDC_ST_BAD_PARAMETER, before any GPU is touched; the type
is compared as an integer, since loading a value outside the enum
through it is undefined.

When one GPU of a group rejected a setting, the GPUs before it kept the
new value, which RDC neither tracked nor cleared. They now get the
group's previous setting of that type back, or their default.
rdc_config_clear() stopped at the first GPU or setting it could not
reset and dropped the group's settings anyway; it now resets every GPU
and setting it can, then reports the failure.

A clear that removed the last setting joined the monitoring thread
outside the lock while a concurrent set could start a new thread in the
same std::thread: a data race, and std::terminate while the old thread
was still joinable. The monitoring thread now runs from the first
setting until the object is destroyed, waits without waking up while no
setting is cached, and set, clear and each monitoring pass hold the
lock throughout.

The monitoring thread read the groups' GPUs into one structure for all
groups and passes, so a group lookup that succeeded without filling it
reused another group's GPUs. It compared the power cap in whole watts
converted through an int, which re-applied caps above 2147 W every
minute and missed changes of less than a watt, and it read every
setting of a GPU and skipped the rest after any failed read. It now
reads each group afresh, compares microwatts, and checks only the
cached settings, each on its own. A group that reports more GPUs than
rdc_group_info_t holds is an error instead of a read past the array,
and an empty frequency table no longer makes rdc_config_clear() read
far outside it.

Tests: rdc_config_tests compiles RdcConfigSettingsImpl.cc with fakes for
the group table and amd-smi, and a second constructor sets the
monitoring period to 10 ms; the uninitialized cases fill the stack with
0xA5 first, behind a check that the fill shows. rdc_race_tests sets,
reads and clears several groups from several threads under
ThreadSanitizer, and test_fake_gpu_config.py drives embedded RDC
against the fake amd-smi. Without the fix, 17 of the 20 gtests fail
(the round trip passes either way, and the two oversized-table cases
fail only under AddressSanitizer), the race test ends in std::terminate
in every run, and the 6 fake-GPU tests fail.

Signed-off-by: Adam360x <Adam.pryor@amd.com>
The config race test asked any other race test that defines
get_processor_handle_from_id() to drop its copy. The policy race test
keeps a weak one, which links with this test's definition, so say that
a weak copy works too.

Signed-off-by: Adam360x <Adam.pryor@amd.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant