Repository navigation
fix(rccl): fix ASAN GPU target selection, and propagate ASAN to rocSHMEM #12314
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
e8feaa6
fix(rccl): fix ASAN GPU target selection, and propagate ASAN to rocSHMEM
lfmeadow 82d941c
fix(rocshmem): apply the ASAN target policy before target-ID validation
lfmeadow 5ad863a
fix(rocshmem): carry ASAN's feature suffixes into the defaults path too
lfmeadow c8060eb
fix(rocshmem): stop the ASAN policy from feeding on its own output
lfmeadow 0d178bf
fix(rocshmem): instrument the device bitcode under ASAN
lfmeadow f197e41
fix(rocshmem): refresh BITCODE_GPU_ARCHS, move the policy to a module
lfmeadow dba3612
fix(rocshmem): run the asan pass on the device bitcode
lfmeadow 45c37bd
docs(rocshmem): describe the ASAN target policy in DEBUG.md
lfmeadow 3a16cf8
build(rccl): shorten the rocSHMEM options-stamp comment
lfmeadow 6b04310
build(rocshmem): fail an ASAN configure that has no supported GPU tar…
lfmeadow File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| # GPU target selection for device AddressSanitizer builds. Device ASAN needs | ||
| # xnack, which gfx9 must request explicitly, gfx1250 has always on (and rejects | ||
| # the suffix), and no other family has. Cases pinned in | ||
| # tests/test_asan_gpu_targets.py; projects/rocshmem repeats the policy inline. | ||
|
|
||
| # Parts where xnack is always on, so naming it is an error. | ||
| set(_RCCL_ASAN_XNACK_ALWAYS_ON gfx1250 gfx1250-strict) | ||
|
|
||
| # rccl_asan_adjust_gpu_targets(OUT_VAR gfx942 gfx1100 gfx1250) | ||
| # -> OUT_VAR = gfx942:xnack+;gfx1250 | ||
| # Uninstrumentable targets are dropped rather than left to produce an | ||
| # uninstrumented image inside an ASAN build. | ||
| function(rccl_asan_adjust_gpu_targets OUT_VAR) | ||
| set(_result "") | ||
| set(_dropped "") | ||
|
|
||
| foreach(_target IN LISTS ARGN) | ||
| if(_target STREQUAL "") | ||
| continue() | ||
| endif() | ||
|
|
||
| # A requested suffix says what the caller wants, not what the part | ||
| # supports, so classify on the bare name. | ||
| string(REGEX REPLACE ":.*" "" _base "${_target}") | ||
| # list(FIND) not IN_LIST: the test loads this via cmake -P, where CMP0057 | ||
| # is unset. | ||
| list(FIND _RCCL_ASAN_XNACK_ALWAYS_ON "${_base}" _always_on) | ||
|
|
||
| # Drop any xnack term the caller wrote before deciding what to do with it: | ||
| # it may be ":xnack-", which contradicts the build. Other feature terms | ||
| # (e.g. ":sramecc+") are part of the requested target and are kept; xnack | ||
| # sorts last, so re-appending it keeps the target ID canonical. | ||
| string(REGEX REPLACE ":xnack[+-]" "" _without_xnack "${_target}") | ||
|
|
||
| if(_base MATCHES "^gfx9") | ||
| list(APPEND _result "${_without_xnack}:xnack+") | ||
| elseif(NOT _always_on EQUAL -1) | ||
| # Always on here, so naming xnack at all is an invalid target ID. | ||
| list(APPEND _result "${_without_xnack}") | ||
| else() | ||
| list(APPEND _dropped "${_target}") | ||
| endif() | ||
| endforeach() | ||
|
|
||
| # Both xnack spellings of one arch normalise together, and a duplicate | ||
| # becomes a second add_library() in DeviceLinker.cmake. | ||
| if(_result) | ||
| list(REMOVE_DUPLICATES _result) | ||
| endif() | ||
|
|
||
| if(_dropped) | ||
| message(STATUS | ||
| "ASAN: dropping GPU target(s) with no xnack support: ${_dropped}") | ||
| endif() | ||
| if(NOT _result) | ||
| message(FATAL_ERROR | ||
| "ASAN requires a GPU target that supports xnack (gfx9* with :xnack+, " | ||
| "or gfx1250 where it is always on). None of the requested GPU targets " | ||
| "do: ${ARGN}") | ||
| endif() | ||
|
|
||
| set(${OUT_VAR} "${_result}" PARENT_SCOPE) | ||
| endfunction() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
159 changes: 159 additions & 0 deletions
159
projects/rccl/tools/scripts/test_runner/tests/test_asan_gpu_targets.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| """Guards the ASAN GPU-target policy in cmake/AsanGpuTargets.cmake. | ||
|
|
||
| Each case pins one family's observed clang behaviour, so reclassifying a | ||
| family fails here rather than producing an ASAN build with a silently | ||
| uninstrumented device image. Driven through `cmake -P`, like | ||
| test_device_coverage_cmake.py. | ||
| """ | ||
|
|
||
| import shutil | ||
| import subprocess | ||
| import tempfile | ||
| import unittest | ||
| from pathlib import Path | ||
|
|
||
|
|
||
| RCCL_ROOT = Path(__file__).resolve().parents[4] | ||
| MODULE = RCCL_ROOT / "cmake" / "AsanGpuTargets.cmake" | ||
| # The same policy, duplicated because rccl builds rocSHMEM as a separate cmake | ||
| # process. Drift between the two shows up only as librocshmem.a built for a | ||
| # different arch set than the librccl.so it links into, so run the cases | ||
| # against both. | ||
| ROCSHMEM_MODULE = (RCCL_ROOT.parent / "rocshmem" / "cmake" / "AsanGpuTargets.cmake") | ||
|
|
||
|
|
||
| @unittest.skipUnless(shutil.which("cmake"), "cmake not available on PATH") | ||
| class AsanGpuTargetsTest(unittest.TestCase): | ||
|
Copilot marked this conversation as resolved.
Resolved
|
||
| module = MODULE | ||
| function = "rccl_asan_adjust_gpu_targets" | ||
|
|
||
| def adjust(self, targets): | ||
| """Run rccl_asan_adjust_gpu_targets over the given targets. | ||
|
|
||
| Returns (result_list, status_messages). Raises on FATAL_ERROR. | ||
| """ | ||
| quoted = " ".join(f'"{t}"' for t in targets) | ||
| with tempfile.TemporaryDirectory() as temp_dir: | ||
| out_file = Path(temp_dir) / "out.txt" | ||
| script = Path(temp_dir) / "probe.cmake" | ||
| script.write_text( | ||
| f'include("{self.module}")\n' | ||
| f"{self.function}(RESULT {quoted})\n" | ||
| f'file(WRITE "{out_file}" "${{RESULT}}")\n' | ||
| ) | ||
| completed = subprocess.run( | ||
| ["cmake", "-P", str(script)], | ||
| capture_output=True, text=True) | ||
| if completed.returncode != 0: | ||
| raise AssertionError(completed.stderr.strip()) | ||
| result = out_file.read_text() | ||
| # cmake -P sends STATUS to stdout and WARNING/FATAL_ERROR to stderr. | ||
| # Both, so the tests below do not pin the policy to a message level. | ||
| return ([t for t in result.split(";") if t], | ||
| completed.stdout + completed.stderr) | ||
|
|
||
| def test_gfx9_gets_xnack_plus_requested(self): | ||
| """A bare gfx9 name compiles with the sanitizer ignored.""" | ||
| result, _ = self.adjust(["gfx90a", "gfx942", "gfx950"]) | ||
|
|
||
| self.assertEqual(result, | ||
| ["gfx90a:xnack+", "gfx942:xnack+", "gfx950:xnack+"]) | ||
|
|
||
| def test_gfx9_already_requesting_xnack_plus_is_unchanged(self): | ||
| result, _ = self.adjust(["gfx942:xnack+"]) | ||
|
|
||
| self.assertEqual(result, ["gfx942:xnack+"]) | ||
|
|
||
| def test_gfx9_requesting_xnack_minus_is_corrected(self): | ||
| """The old substring test for ":xnack+" produced gfx942:xnack-:xnack+.""" | ||
| result, _ = self.adjust(["gfx942:xnack-"]) | ||
|
|
||
| self.assertEqual(result, ["gfx942:xnack+"]) | ||
|
|
||
| def test_gfx9_keeps_its_other_target_features(self): | ||
| """Only the xnack term is ours to rewrite; sramecc+ is what was asked for.""" | ||
| result, _ = self.adjust(["gfx942:sramecc+:xnack-"]) | ||
|
|
||
| self.assertEqual(result, ["gfx942:sramecc+:xnack+"]) | ||
|
|
||
| def test_both_xnack_variants_of_one_arch_collapse_to_one_entry(self): | ||
| """A duplicate becomes a second add_library() in DeviceLinker.cmake.""" | ||
| result, _ = self.adjust(["gfx942:xnack+", "gfx942:xnack-"]) | ||
|
|
||
| self.assertEqual(result, ["gfx942:xnack+"]) | ||
|
|
||
| def test_gfx1250_is_kept_untouched(self): | ||
| result, _ = self.adjust(["gfx1250"]) | ||
|
|
||
| self.assertEqual(result, ["gfx1250"]) | ||
|
lfmeadow marked this conversation as resolved.
Resolved
|
||
|
|
||
| def test_gfx1250_strict_is_kept_untouched(self): | ||
| """The other entry in _RCCL_ASAN_XNACK_ALWAYS_ON, and the one no other | ||
| case reaches: drop it from that list and every test here stays green | ||
| while the target silently moves to the drop arm.""" | ||
| result, _ = self.adjust(["gfx1250-strict", "gfx1250-strict:xnack+"]) | ||
|
|
||
| self.assertEqual(result, ["gfx1250-strict"]) | ||
|
|
||
| def test_gfx1250_loses_an_explicitly_requested_xnack(self): | ||
| """clang rejects 'gfx1250:xnack+' outright: xnack is not selectable there.""" | ||
| result, _ = self.adjust(["gfx1250:xnack+"]) | ||
|
|
||
| self.assertEqual(result, ["gfx1250"]) | ||
|
|
||
| def test_gfx1250_with_and_without_xnack_collapse_to_one_entry(self): | ||
| result, _ = self.adjust(["gfx1250", "gfx1250:xnack+"]) | ||
|
|
||
| self.assertEqual(result, ["gfx1250"]) | ||
|
|
||
| def test_archs_without_xnack_are_dropped(self): | ||
| result, messages = self.adjust(["gfx942", "gfx1100", "gfx1201"]) | ||
|
|
||
| self.assertEqual(result, ["gfx942:xnack+"]) | ||
| self.assertIn("gfx1100", messages) | ||
| self.assertIn("gfx1201", messages) | ||
|
lfmeadow marked this conversation as resolved.
Resolved
|
||
|
|
||
| def test_full_default_target_list(self): | ||
| """Matches the old effective set, except gfx1250 is no longer lost. | ||
|
|
||
| The input is a copy of DEFAULT_GPUS in the root CMakeLists.txt and has | ||
| to be updated by hand when a family is added there. | ||
| """ | ||
| result, _ = self.adjust([ | ||
| "gfx906", "gfx908", "gfx90a", "gfx942", "gfx950", | ||
| "gfx1030", "gfx1100", "gfx1101", "gfx1102", "gfx1151", | ||
| "gfx1200", "gfx1201", "gfx1250"]) | ||
|
|
||
| self.assertEqual(result, [ | ||
| "gfx906:xnack+", "gfx908:xnack+", "gfx90a:xnack+", | ||
| "gfx942:xnack+", "gfx950:xnack+", "gfx1250"]) | ||
|
|
||
| def test_empty_result_is_fatal(self): | ||
| with self.assertRaises(AssertionError) as cm: | ||
| self.adjust(["gfx1100"]) | ||
|
|
||
| self.assertIn("ASAN requires a GPU target that supports xnack", | ||
| str(cm.exception)) | ||
|
|
||
|
|
||
| @unittest.skipUnless(shutil.which("cmake"), "cmake not available on PATH") | ||
| @unittest.skipUnless(ROCSHMEM_MODULE.exists(), "projects/rocshmem not in this checkout") | ||
| class RocshmemAsanGpuTargetsTest(AsanGpuTargetsTest): | ||
|
lfmeadow marked this conversation as resolved.
Resolved
|
||
| """Every case above, run against the rocSHMEM copy of the policy.""" | ||
|
|
||
| module = ROCSHMEM_MODULE | ||
| function = "rocshmem_asan_adjust_gpu_targets" | ||
|
|
||
| def test_full_default_target_list(self): | ||
| """rocSHMEM's DEFAULT_GPUS differs from rccl's; see its CMakeLists.txt.""" | ||
| result, _ = self.adjust([ | ||
| "gfx90a", "gfx1100", "gfx1201", "gfx942", "gfx950", | ||
| "gfx1250", "gfx1250-strict"]) | ||
|
|
||
| self.assertEqual(result, [ | ||
| "gfx90a:xnack+", "gfx942:xnack+", "gfx950:xnack+", | ||
| "gfx1250", "gfx1250-strict"]) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.