fix(rocm): resolve the build architecture in one place, not three - #289
fix(rocm): resolve the build architecture in one place, not three#289demandal25 wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR centralizes ROCm target-architecture resolution into hip_utils.resolve_target_archs() so all build/validation paths (JIT validation, CompilationContext, and AOT packaging) agree on the same --offload-arch set—fixing a prior CDNA4 mismatch where validation could check gfx942 while compilation targeted gfx950.
Changes:
- Added
resolve_target_archs()and routedvalidate_rocm_arch,validate_flashinfer_rocm_arch,CompilationContext, andaot_hipthrough it. - Removed
CompilationContext._auto_detect_archsand switched detection to the shared resolver. - Updated/added tests to cover resolution branches and to assert validator/compile-context agreement.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
flashinfer/hip_utils.py |
Introduces the unified architecture resolver and wires validators through it. |
flashinfer/compilation_context_hip.py |
Uses the unified resolver to avoid divergence between compilation and validation. |
flashinfer/aot_hip.py |
Resolves+publishes the target arch list once, then validates via CompilationContext. |
tests/rocm_tests/test_hip_utils.py |
Adds targeted tests for the new resolver and updates the prior “defaults to gfx942” assertion. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…t test hermetic Addresses both review comments on #289. - resolve_target_archs() returned the caller/env string verbatim. Now that it is the single source of truth that is a hard failure, not untidiness: the validators split on "," only and match tokens against FLASHINFER_SUPPORTED_ROCM_ARCHS verbatim, so FLASHINFER_ROCM_ARCH_LIST=gfx950:sramecc+ -> ['gfx950:sramecc+'] unsupported FLASHINFER_ROCM_ARCH_LIST=gfx942;gfx950 -> ['gfx942;gfx950'] unsupported FLASHINFER_ROCM_ARCH_LIST=gfx942,,gfx942 -> ['gfx942','','gfx942'] unsupported '' and validate_flashinfer_rocm_arch raises "does not support any of the requested ROCm architectures". ';' matters specifically because jit/aiter_source.py already documents it for this same variable, and aot_hip.py writes this resolver's output back into that env var -- so the two consumers were disagreeing about their own input format. _canonical_arch_list normalizes syntax only: accepts ',' or ';', strips qualifiers via normalize_arch, drops empties, dedupes preserving first-seen order. Unknown architectures pass through so the validators can still report them; dropping one here would turn a clear error into a build that quietly targets less than was asked for. A value that normalizes away entirely (";;") falls through to detection rather than returning "". - test_agrees_with_the_compilation_context compared a _FakeCppExt-fed validator against a CompilationContext that validates against the *real* torch, so the assertion depended on the installed wheel. Both sides now see the same view. Verified rather than assumed, by simulating an arch-specific wheel (_get_rocm_arch_flags -> gfx942 only) via a pytest plugin: before: RuntimeError: PyTorch does not support the following architectures: --offload-arch=gfx950 -> FAILED after: passes The wheel on this box is a fat build advertising gfx950, which is why the fragility was latent here. 11 tests added for the canonicalization. gfx942 behaviour is unchanged.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Suppressed comments (1)
flashinfer/aot_hip.py:231
- compile_and_package_modules() sets FLASHINFER_ROCM_ARCH_LIST before validating it via CompilationContext(). If CompilationContext() raises (e.g. unsupported ROCm version / arch), the process environment is left mutated to an invalid value, which can affect subsequent calls in the same process.
rocm_arch_list = resolve_target_archs()
os.environ["FLASHINFER_ROCM_ARCH_LIST"] = rocm_arch_list
CompilationContext() # validates the resolved list, raising on a bad one
if verbose:
print(f"Target ROCm architectures: {rocm_arch_list}")
…ment Addresses the suppressed comment in Copilot review 4984984345 on #289. compile_and_package_modules set FLASHINFER_ROCM_ARCH_LIST and then validated it, so a CompilationContext() raise left the variable set for whatever ran next in the same process. Publishing it is deliberate -- the AITER shim reads the build architecture from there (jit/aiter_source.py) and an AOT build has no other channel -- but it is a side effect that outlives the call, so it should only happen once the list is known good. Reordering is behaviour-preserving on success: CompilationContext() re-resolves through resolve_target_archs() and nothing can change between the two calls, so it validates exactly the list published afterwards. On the failure path the environment is now left as it was found. One correction to the review's wording: the leaked value is not "invalid" -- resolve_target_archs() returns a canonical list. It is a *valid* list that failed validation against this ROCm version or PyTorch build, which is a different thing and is why the leak is subtle rather than obvious. The regression test was wrong on its first attempt and is worth flagging: it seeded FLASHINFER_ROCM_ARCH_LIST with the value the resolver would return, so the buggy write was a no-op and the test passed with the bug present. It now starts from the variable unset with detection patched, and was verified to fail without the reorder: AssertionError: assert 'FLASHINFER_ROCM_ARCH_LIST' not in environ({... 'FLASHINFER_ROCM_ARCH_LIST': 'gfx950'})
|
Suppressed comment in review 4984984345 — accepted, fixed in 4d1335d. A My first regression test was vacuous (it seeded the variable with the value the resolver returns, so the buggy write was a no-op); rewritten and A/B-ed against the unfixed code. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (1)
flashinfer/aot_hip.py:234
- The comment says the arch list is “resolved once, then validated”, but
CompilationContext()re-resolves viaresolve_target_archs()(with the env var still unset), so this can re-run detection (extrarocminfosubprocess + duplicate warning) and the validation isn’t guaranteed to be against the exactrocm_arch_listyou just computed. Consider temporarily settingFLASHINFER_ROCM_ARCH_LISTtorocm_arch_listjust for the validation call, and restoring it on failure; on success you can leave it set to the resolved value.
# the inputs cannot change in between, so it validates exactly the list
# published below -- the order costs nothing and stops a raise here from
# leaving FLASHINFER_ROCM_ARCH_LIST set for whatever runs next in-process.
rocm_arch_list = resolve_target_archs()
CompilationContext() # validates the resolved list, raising on a bad one
… failure Addresses the suppressed comment in Copilot review 4985096141 on #289, which caught a regression introduced by the previous commit. Validating before publishing made CompilationContext() re-resolve from an unset environment. rocminfo_gpu_agents() is not cached -- only get_supported_device_indices and get_physical_card_device_indices are -- so that is a second detection pass, and on a GPU-less host a second "no supported AMD GPU detected" warning. Measured: two resolves emit two warnings. Publishing first and restoring in an except clause gets both properties at once: validation sees exactly the list that was resolved (one detection, one warning), and a raise leaves the variable as it was found, including the case where it was previously unset. The test now pins both halves, since the leak fix alone passed without the single-resolution property: _Boom records what os.environ held when validation ran, so a return to validate-then-publish fails on `_Boom.seen == "gfx950"` rather than quietly reintroducing the duplicate detection.
|
Suppressed comment in review 4985096141 — accepted, fixed in 6e8773c. This was a regression I introduced in 4d1335d, not pre-existing. Both halves verified: |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (2)
flashinfer/aot_hip.py:247
- compile_and_package_modules() publishes the resolved arch list to FLASHINFER_ROCM_ARCH_LIST, but CompilationContext() may silently filter that list (unsupported by ROCm version / unsupported by FlashInfer) without raising. If that happens, the env var can still contain unsupported entries (and even keep them first), which can cause the AITER shim (jit/aiter_source.py) to build for a different/invalid architecture than the kernels being packaged. Update the env var to the validated set after CompilationContext succeeds so the process-global side effect reflects what will actually be compiled.
rocm_arch_list = resolve_target_archs()
previous_arch_list = os.environ.get("FLASHINFER_ROCM_ARCH_LIST")
os.environ["FLASHINFER_ROCM_ARCH_LIST"] = rocm_arch_list
try:
CompilationContext() # validates the resolved list, raising on a bad one
except BaseException:
if previous_arch_list is None:
os.environ.pop("FLASHINFER_ROCM_ARCH_LIST", None)
else:
os.environ["FLASHINFER_ROCM_ARCH_LIST"] = previous_arch_list
raise
tests/rocm_tests/test_aot_hip.py:198
- This test creates a temporary build_dir via tempfile.mkdtemp() but never cleans it up, which can leak disk space across repeated test runs. Mirror test_compile_and_package_minimal() by deleting the directory in a finally block.
with pytest.raises(RuntimeError, match="not recognized"):
aot_hip.compile_and_package_modules(
out_dir=None,
build_dir=Path(tempfile.mkdtemp()),
project_root=Path(__file__).parent.parent,
config={
"fa2_head_dim": [(128, 128)],
"f16_dtype": [torch.float16],
"use_sliding_window": [False],
"use_logits_soft_cap": [False],
},
verbose=False,
skip_prebuilt=True,
)
Addresses both suppressed comments in Copilot review 4985222187 on #289. - Validation filters rather than raises. An architecture FlashInfer cannot serve is dropped with a warnings.warn and the build continues, provided at least one survives. Measured: validate_flashinfer_rocm_arch("gfx900,gfx942") -> no exception, arch_flags ['--offload-arch=gfx942'], set {'gfx942'} so FLASHINFER_ROCM_ARCH_LIST could advertise gfx900 while the packaged kernels were compiled only for gfx942. The AITER shim resolves its own build target from that variable (jit/aiter_source.py), so it would build for an architecture nothing else in the package targets -- the precise divergence this PR exists to remove, reintroduced one layer up. The resolved list is still published before validation, so CompilationContext does not re-run detection; the validated list replaces it afterwards. Taken from arch_flags rather than TARGET_ROCM_ARCHS because the latter is a set and order is meaningful, both on the hipcc command line and to AITER. - The new failure-path test leaked a tempfile.mkdtemp() directory. Switched to the tmp_path fixture, which pytest cleans up, rather than adding a finally block. Verified the guard is real, not vacuous -- without the republish: - gfx950 + gfx900,gfx950
|
Suppressed comments in review 4985222187 — both accepted, fixed in 7bdc2c9. The first is the best catch on this PR.
Both new tests were A/B-ed against the unfixed code. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Suppressed comments (4)
flashinfer/hip_utils.py:98
- This set comprehension includes every physical supported agent reported by
rocminfo, even after the process has been restricted withHIP_VISIBLE_DEVICES. The repository documents thatrocminfoignores that variable (tests/rocm_tests/conftest.py:97-102); on a mixed gfx942/gfx950 host withHIP_VISIBLE_DEVICESselecting gfx950 and a gfx950-only PyTorch wheel, this resolves to both architectures andvalidate_flashinfer_rocm_archthen fails because PyTorch lacks gfx942. The resolver needs to honor the visible/current device when one is selected, or explicitly require an override for this case.
detected = sorted(
{
arch
for arch, _ in rocminfo_gpu_agents()
if arch in FLASHINFER_SUPPORTED_ROCM_ARCHS
}
)
flashinfer/hip_utils.py:107
- When
FLASHINFER_ROCM_ARCH_LISTis set to a non-empty value that normalizes away (for example";;"or whitespace), this warning says the variable is "unset" even though it was supplied. That misdirects the operator toward setting a variable that is already present; report that it is unset or contains no usable architecture instead.
logger.warning(
"No supported AMD GPU detected and FLASHINFER_ROCM_ARCH_LIST is unset; "
"building for every supported architecture (%s). This is slower than "
"targeting one. Set FLASHINFER_ROCM_ARCH_LIST to the architecture you "
"are building for.",
tests/rocm_tests/test_hip_utils.py:345
- This new test covers
validate_rocm_arch, but the existingTestValidateFlashinferRocmArch.test_defaults_to_gfx942_when_no_env_no_argstill mocksvalidate_rocm_archand asserts the old hard-codedgfx942. A regression invalidate_flashinfer_rocm_arch(arch_list=None)resolving the wrong target would therefore still pass. Update that wrapper test to stubrocminfo_gpu_agentsand assert the detected target instead.
def test_falls_back_to_the_running_device_not_a_hard_coded_arch(self, monkeypatch):
"""With no argument and no env var, follow the hardware.
This used to assert ``== "gfx942"``, encoding the literal that made
``validate_flashinfer_rocm_arch(arch_list=None)`` answer ``gfx942`` on a
gfx950 device while CompilationContext compiled for gfx950. A test that
pins a wrong constant is how the constant survives, so it is now pinned
to the detected architecture instead.
flashinfer/hip_utils.py:98
- When no environment override is present, this path starts a new
rocminfosubprocess on everyresolve_target_archs()call. The removed_auto_detect_archs()used the cachedget_supported_device_indices(), but JIT setup constructsCompilationContextmore than once andgen_jit_spec()revalidates for each operation, so a normal multi-op process now repeatedly pays this probe (including its 10-second timeout) and can emit the fallback warning repeatedly. Cache the hardware probe for the process or reuse the already-resolved list while preserving invalidation when visibility changes.
detected = sorted(
{
arch
for arch, _ in rocminfo_gpu_agents()
if arch in FLASHINFER_SUPPORTED_ROCM_ARCHS
}
)
…d a stale test Addresses three of the six findings in Copilot review 4985367370 on #289. The other three are design-level and tracked separately. - rocminfo_gpu_agents() is now cached. gen_jit_spec() calls check_rocm_arch() for every module it builds, and that reaches the probe through resolve_target_archs(), so a multi-op process was paying one rocminfo subprocess -- with a 10 s timeout -- per operation, and a GPU-less host repeated the fallback warning each time. The _auto_detect_archs() this PR removed avoided that by going through the already-cached get_supported_device_indices(), so this is a regression the PR introduced. Caching the probe itself also makes that function's "rocminfo is invoked at most once per process" docstring true globally rather than only on its path. TestGetSupportedDeviceIndices already cleared the derived cache per test; it now clears this one too. Without that the first test in the class pinned the probe and the remaining five asserted against it instead of their own patched subprocess output -- which is how they failed when the cache was added. - The GPU-less fallback warning said FLASHINFER_ROCM_ARCH_LIST "is unset" even when it was set to something that normalized away (";;", whitespace), which is reachable through the fall-through added earlier in this branch. It now distinguishes unset from "names no architecture", so the operator is not sent looking for a variable that is already there. - test_defaults_to_gfx942_when_no_env_no_arg stubbed validate_rocm_arch to return "gfx942" whatever it was handed, so the resolved list never reached the assertion: the hard-coded default this PR exists to remove could have come back with the test still green. The stub now echoes its argument and the detected architecture is what is checked.
|
Review 4985367370 — 4 suppressed comments; 3 fixed in b2f586d, 1 deferred.
164 passed; |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (8)
flashinfer/aot_hip.py:235
- The no-environment behavior changed here, but
amd-flashinfer-jit-cache/README.md:35-40still says that AOT always compiles gfx942 and that the environment default is gfx942. A gfx950 host now follows detection, while a GPU-less host targets every supported architecture, so the package build instructions are inaccurate. Update that documentation (and the stale JIT default comment) with the centralized resolver behavior.
rocm_arch_list = resolve_target_archs()
flashinfer/aot_hip.py:260
contextis used only to validate and derive the environment value; the actual HIP flags later come from the module-globaljit.core.current_compilation_context(jit/core.py:406), which was initialized whenflashinfer.jitwas first imported. If this API is called after changingFLASHINFER_ROCM_ARCH_LIST(or called twice with different targets),contextcan validate/republishgfx950while the kernels still compile with stalegfx942flags; AITER then follows the republished value and no longer matches the packaged kernels. The active JIT context must be rebuilt/updated for this target before generating specs, or target changes must be rejected.
rocm_arch_list = ",".join(
flag.removeprefix("--offload-arch=") for flag in context.arch_flags
)
os.environ["FLASHINFER_ROCM_ARCH_LIST"] = rocm_arch_list
flashinfer/hip_utils.py:98
- The deleted
_auto_detect_archs()caught all probe failures and fell back, but this new direct call is not protected.rocminfo_gpu_agents()only convertsFileNotFoundErrorand timeouts to an empty result, so an unavailable executable that raises anotherOSError(for examplePermissionError) now aborts resolution instead of reaching the documented GPU-less fallback. Catch the probe'sOSErrorhere or broaden the helper's failure handling.
detected = sorted(
{
arch
for arch, _ in rocminfo_gpu_agents()
if arch in FLASHINFER_SUPPORTED_ROCM_ARCHS
}
)
flashinfer/hip_utils.py:64
- The no-argument behavior documented by this new resolver is now detection/env dependent (and can fall back to both supported architectures), but
flashinfer/jit/core.py:148still saysarch_list=None“defaults to gfx942”. That comment is on a call site now routed through this resolver, so it should be updated in this change to avoid documenting the behavior this PR removes.
Step 4 replaces a hard-coded ``"gfx942"`` that three call sites reached
independently. On a CDNA4 host that literal was not a conservative default
but a wrong answer: ``validate_flashinfer_rocm_arch(arch_list=None)``
returned ``{"gfx942"}`` on a gfx950 device while ``CompilationContext``
compiled for gfx950, so the check that exists to catch "your PyTorch was not
built for this architecture" was validating an architecture nobody was
building for. Vacuous on a PyTorch carrying both; a spurious hard failure on
an arch-specific build that carries only gfx950.
tests/rocm_tests/test_aot_hip.py:213
- This new test hard-codes
gfx950but runsCompilationContextagainst the real PyTorch extension flags. On a supported gfx942 host with an arch-specific PyTorch build, validation filtersgfx900and then raises because PyTorch does not advertisegfx950, so the test fails before checking the AOT environment behavior. Stub_get_rocm_arch_flags(as the resolver-agreement test does) or choose the host's target so this test is hermetic.
monkeypatch.setenv("FLASHINFER_ROCM_ARCH_LIST", "gfx900,gfx950")
flashinfer/aot_hip.py:231
- The explanation here is now stale:
rocminfo_gpu_agents()is decorated withfunctools.cache, and publishing the environment before constructingCompilationContextmeans its resolver takes the just-published value rather than performing a second detection pass. Please update the comment so it does not claim an uncached probe or a second warning.
# Publish first so CompilationContext() -- which resolves through
# resolve_target_archs() itself -- validates exactly this list rather than
# repeating the work. rocminfo_gpu_agents() is not cached, so simply
# validating before publishing would re-run detection and, on a GPU-less
# host, emit the "no supported AMD GPU detected" warning a second time.
flashinfer/hip_utils.py:505
- The new
@functools.cachemakes this probe process-wide, but the docstring below still saysNot cached: the caller decidesand describes callers paying for a fresh subprocess. That is now false and can lead callers to reason incorrectly about cache invalidation; update the stale sentence to document the shared cache and how it is cleared.
Cached for the process. ``gen_jit_spec`` calls ``check_rocm_arch()`` for
every operation it builds, and that reaches here through
``resolve_target_archs()``, so without this a multi-op process pays one
``rocminfo`` subprocess -- and its 10-second timeout -- per module, and a
GPU-less host repeats the fallback warning each time. The prior
tests/rocm_tests/test_aot_hip.py:196
- This rationale is stale after
rocminfo_gpu_agentsbecame cached in the same change:CompilationContextwill not perform a second rocminfo subprocess merely because it resolves again. Keep the assertion, but describe the actual invariant—that validation must receive the exact resolved list rather than re-reading an unset environment variable—so the test does not document behavior that no longer exists.
# Validation must see the resolved list, not an unset variable. Otherwise
# CompilationContext re-resolves from scratch -- rocminfo_gpu_agents() is
# not cached, so that is a second detection pass and, on a GPU-less host, a
# second "no supported AMD GPU detected" warning.
… failure handling Addresses seven of the eight suppressed comments in Copilot review 4985855053 on #289. Four of them are defects the previous commit introduced. Self-inflicted by adding @functools.cache to rocminfo_gpu_agents: - The docstring still carried "Not cached: the caller decides" three paragraphs below the new "Cached for the process". Removed. - aot_hip's rationale for publishing before validating said it avoided "a second detection pass". True when written, false once the probe was cached. Rewritten to the reason that survives: the two agree by construction rather than by coincidence. - The same stale reasoning in the test comment, likewise rewritten. Also self-inflicted, and the same bug flagged earlier on a different test: - test_environment_gets_the_validated_list_not_the_resolved_one hard-coded gfx950 and validated against the real PyTorch, so on an arch-specific gfx942 wheel it would fail for reasons unrelated to the republish. Stubs _get_rocm_arch_flags now. Verified with a plugin simulating a gfx942-only wheel: this test, test_failed_validation_leaves_the_environment_alone and test_agrees_with_the_compilation_context all pass under it. Pre-existing, surfaced by this PR changing the behaviour they describe: - jit/core.py's "defaults to gfx942" comment and amd-flashinfer-jit-cache's README both still documented the hard-coded default this PR removes. - rocminfo_gpu_agents caught only FileNotFoundError and TimeoutExpired, so a present-but-unexecutable rocminfo (PermissionError) aborted resolution instead of reaching the documented GPU-less fallback. The removed _auto_detect_archs() swallowed these; catching OSError restores that. The eighth -- the module-global JIT compilation context being a different object from the one validated here -- is deferred with the other two design-level findings; see the thread replies.
|
Review 4985855053 — 8 suppressed; 7 fixed in b3fd023, 1 deferred. Four were defects the previous commit introduced. Adding Three were pre-existing and surfaced because this PR changes what they describe: Deferred: the module-global JIT context ( 164 passed; |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.
Suppressed comments (4)
flashinfer/hip_utils.py:341
validate_rocm_archonly consultsresolve_target_archswhenarch_listisNone, so an explicit caller value bypasses the new canonicalization. For example,validate_rocm_arch("gfx950:sramecc+")reaches the compatibility matrix with the qualifier and fails even thoughresolve_target_archsaccepts and normalizes caller-supplied lists. Resolve unconditionally here so explicit and implicit inputs use the same path.
if arch_list is None:
arch_list = resolve_target_archs()
flashinfer/hip_utils.py:425
- The wrapper has the same bypass: when a caller supplies
arch_list, it passes the raw value straight tovalidate_rocm_archinstead of using the centralized resolver. Thusvalidate_flashinfer_rocm_arch("gfx942;gfx950")still treats the semicolon list as one invalid token, while the resolver's documented canonical path handles it. Resolve the argument unconditionally before validation.
# Get architecture list from parameter, env var, or default
if arch_list is None:
arch_list = resolve_target_archs()
tests/rocm_tests/test_aot_hip.py:214
- This test depends on the host's real ROCm version. On the supported ROCm 6.4/6.3 path (
_ROCM_ARCH_GROUPSabove), neithergfx900norgfx950is supported, soCompilationContext()raises before the republished environment is checked instead of filtering togfx950. Stubget_system_rocm_versionto a 7.x version here, as the architecture tests do, so the test is portable across the supported ROCm matrix.
monkeypatch.setenv("FLASHINFER_ROCM_ARCH_LIST", "gfx900,gfx950")
flashinfer/hip_utils.py:117
- On a GPU-less host with no environment override, this warning is emitted on every
resolve_target_archs()call; cachingrocminfo_gpu_agents()does not cache this fallback branch.gen_jit_spec()callscheck_rocm_arch()for each operation, so one JIT generation can flood stderr/logs with the same warning. Guard the fallback warning (or cache the no-device resolution while still invalidating it when the environment changes) so the operator is notified once.
logger.warning(
"No supported AMD GPU detected and %s; building for every supported "
"architecture (%s). This is slower than targeting one. Set "
"FLASHINFER_ROCM_ARCH_LIST to the architecture you are building for.",
how,
fallback,
)
…rn once Addresses all four suppressed comments in Copilot review 4985982606 on #289. Three continue findings the previous commits fixed only partway. - The validators consulted resolve_target_archs() only when arch_list was None, so an explicit caller bypassed the canonicalization added earlier in this branch: validate_rocm_arch("gfx950:sramecc+") reached the compatibility matrix with the qualifier attached and failed on a value the resolver handles, and validate_flashinfer_rocm_arch("gfx942;gfx950") still saw one invalid token. Both now call resolve_target_archs(arch_list) unconditionally, which is what "single source of truth" was supposed to mean -- the previous form routed only half the callers through it. - Caching rocminfo_gpu_agents stopped the repeated subprocess but not the repeated warning, which lives past the cached call. gen_jit_spec() validates per module, so one JIT generation printed the GPU-less fallback warning once per operation. Measured: 5 resolves emitted 5 warnings, now 1. Keyed on the message so a genuine change of cause -- unset becoming set-but-empty -- is still reported. - test_environment_gets_the_validated_list_not_the_resolved_one also depended on the host's ROCm version: the 6.3/6.4 compatibility path supports neither gfx900 nor gfx950, so CompilationContext would raise before the republished value was checked. Now stubs get_system_rocm_version alongside the PyTorch flags. That is the third environment dependency found in these tests; the pattern is that anything reaching CompilationContext needs both stubs.
|
Suppressed comments from review 4996642977 — all four read, three fixed in c5e5bea.
Separately: I had pushed the redesign without running the quality gate's code review. Running it found eight defects, four of mine, two reproduced on-box — all fixed in c5e5bea. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
flashinfer/jit/core.py:183
- Because this is a process-global lazy singleton, two concurrent first accesses can both observe
_ctx is NonewhileCompilationContext()is doing its probe and validation. That runs the supposedly one-time resolution more than once (and the last assignment wins if the device/environment changes), so the proxy is not actually a once-per-process context. Guard the check-and-construct with a lock or another thread-safe lazy-initialization primitive.
if self._ctx is None:
self._ctx = CompilationContext()
|
Suppressed comment from review 4997466925 — accepted, fixed in 19fc3e5.
Separately, I had pushed c5e5bea without the self code-review the quality gate requires. Running it on 6e58e0d..HEAD found five more, two serious — both mine, both fixed in cd390d5:
Plus a docstring that overclaimed, dead 259 tests pass serially and at |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
flashinfer/jit/aiter_source.py:88
- The cache key omits the value that this function uses to choose the result:
_detected_device_arch()at lines 97-99. With both gfx942 and gfx950 in the resolved list, the first call on a gfx942 device caches gfx942; aftertorch.cuda.set_device()switches to gfx950, the resolved string is unchanged and this cache still selects gfx942, so the AITER shim/link path can be reused on the wrong GPU. Include the current device architecture in the selection key or do not cache this device-dependent choice.
@functools.lru_cache(maxsize=None)
def _select_build_arch(resolved: str) -> str:
"""Pick one architecture out of the shared resolver's answer.
Candidates come from ``validate_flashinfer_rocm_arch``, not from splitting
the resolved string. Both of its filters -- the ROCm compatibility matrix
and the FlashInfer supported list -- *drop* an architecture rather than
raising, so re-deriving either one here is how the shim and the kernels came
to disagree: on ROCm 6.4 with "gfx950,gfx942" the JIT narrowed to gfx942
while this picked gfx950. Reusing the validator makes that unrepresentable
instead of merely currently-absent.
Keyed on the resolved string rather than ``maxsize=1``, so a changed target
re-selects, and each distinct list warns once rather than once per module.
flashinfer/hip_utils.py:161
visible_gpu_agents()has already established that the process has no visible device, but this unconditional torch fallback can resurrect one if the HIP runtime was initialized beforeHIP_VISIBLE_DEVICES/CUDA_VISIBLE_DEVICESwas changed. For example, withHIP_VISIBLE_DEVICES=-1(or an empty/invalid selector), a previously initializedtorch.cudacan still report its devices, causing the resolver to return a target instead of raising. Make the torch fallback honor explicit visibility (at least the hide-all/invalid forms), and cover that path without stubbing_torch_detected_archs.
detected = _torch_detected_archs()
if detected:
return ",".join(detected)
flashinfer/jit/core.py:196
- This lazy proxy is now resolved after
jit/env.pyhas already fixedFLASHINFER_WORKSPACE_DIRfrom the import-time current device. On a mixed host,tests/conftest.pycan pin the worker to gfx950 after that import, so the first access here emits gfx950 flags into a workspace keyed as gfx942; the same mismatch occurs if the target/device changes after import. That lets a cache directory reuse or publish binaries under the wrong architecture. The workspace key must be deferred/refreshed together with the realized context (or this deferral must wait until workspace selection is aligned).
current_compilation_context = _LazyCompilationContext()
|
Suppressed comments from review 4997770801 — all three read; one fixed, two answered.
Process note: this round's fixes were themselves self-reviewed before pushing, which is how the two corrections above were caught rather than shipped. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
flashinfer/jit/aiter_source.py:257
- This resolves the target a second time after
_aiter_libs_dir()has already derived the cache tag andlib_path. The outer resolver is no longer cached and rereadsFLASHINFER_ROCM_ARCH_LIST, so if the target changes between these calls, the path can be for architecture A whileGPU_ARCHSbuilds architecture B; B is then copied into A's cache directory and linked through A's RUNPATH. Resolve once and pass the same architecture/tag through both path construction and the AITER build environment.
gpu_archs = resolve_aiter_build_arch()
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.
Suppressed comments (3)
flashinfer/jit/aiter_source.py:90
- On a host where
rocminfois unavailable or times out but the documented_torch_detected_archs()fallback succeeds, this call still retries the 10-secondrocminfoprobe on every AITER module:rocminfo_gpu_agents()deliberately does not cache empty results, andresolve_target_archs()runs it before the torch fallback.aiter_jitspec_flags()is invoked independently by the activation, norm, and rope AITER generators, so one JIT setup can pay this delay several times. Cache/share the resolved target (or otherwise cache a failed probe separately from the initial worker-discovery path) before resolving per module.
return _select_build_arch(resolve_target_archs(), _detected_device_arch())
flashinfer/jit/aiter_source.py:107
- This device-aware cache is bypassed by the normal AITER entry points:
get_silu_and_mul_aiter_module,get_rope_aiter_module,get_norm_aiter_module, andget_batch_decode_aiter_moduleare all@functools.cached only by their shape/dtype arguments (for example,activation.py:38anddecode_rocm.py:300). After the first module is built on gfx942, switching the current device to gfx950 returns the already-loaded shim without callingresolve_aiter_build_arch()again, so the old architecture-specific library/RUNPATH is still used. Include the device/architecture in those public cache keys or invalidate those module caches when the device changes.
Keyed on (target list, running device) rather than ``maxsize=1``, so a
change to either re-selects, and each distinct pair warns once rather than
once per module.
flashinfer/jit/env.py:292
_arch_cache_key()is only evaluated whilejit.envis imported, before_LazyCompilationContextresolves the target. On a GPU-less import with no env this therefore fixesFLASHINFER_WORKSPACE_DIRto.../noarch; settingFLASHINFER_ROCM_ARCH_LIST=gfx950before the first JIT build still compiles into that directory, and a later gfx942 build can reuse the samebuild.ninja/.so. Defer workspace selection until the target is realized, or re-key/invalidate the workspace when the context resolves.
arch = _arch_cache_key(os.environ.get("FLASHINFER_ROCM_ARCH_LIST", ""))
|
Suppressed comments from review 4998975841.
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
flashinfer/jit/aiter_source.py:114
- This candidate list is validated only against ROCm and FlashInfer because no PyTorch extension module is supplied. In the normal AITER path,
aiter_jitspec_flags()runs beforegen_jit_spec(), so an explicit/multi-architecture target missing from the installed PyTorch can spend time building an AITER shim and only then fail when the lazy compilation context performs the PyTorch check. Select from the shared context's already-validated flags, or perform the PyTorch validation beforeensure_aiter_lib().
arch_flags, _ = validate_flashinfer_rocm_arch(arch_list=resolved)
candidates = [f.removeprefix("--offload-arch=") for f in arch_flags]
flashinfer/aot_hip.py:198
- This validates with a new
CompilationContext, but the generated specs do not use this object:gen_jit_spec()later realizescurrent_compilation_contextand reads its flags. AOT therefore resolves/validates twice, and the two objects can disagree if visibility, device, or the environment changes between these calls. Realize the shared proxy here so the early check and the context that emits--offload-archare the same object.
from .compilation_context_hip import CompilationContext
context = CompilationContext() # raises if nothing in the list is usable
flashinfer/jit/aiter_source.py:90
_select_build_archis cached, but its arguments are evaluated first, so everyresolve_aiter_build_arch()call still invokesresolve_target_archs(). Whenrocminfois unavailable or times out,rocminfo_gpu_agents()deliberately does not cache the empty result; consequently each AITER module retries a 10-second probe even though_torch_detected_archs()already supplied the target. On the documented slim-image/torch-fallback path, this can add tens of seconds to one import/build. Cache the resolved probe/result for the operation (while preserving the intentional retry semantics for the initial visibility setup), or otherwise avoid re-probing for each AITER module.
return _select_build_arch(resolve_target_archs(), _detected_device_arch())
Three call sites independently answered "what architecture are we building
for", and on CDNA4 they disagreed. Measured on a gfx950 host with
FLASHINFER_ROCM_ARCH_LIST unset, before this change:
ACTUAL device arch gfx950
validate_flashinfer_rocm_arch(arch_list=None) ['gfx942'] <-- wrong
CompilationContext().TARGET_ROCM_ARCHS ['gfx950']
resolve_aiter_build_arch() gfx950
So the JIT validated gfx942 while compiling for gfx950. The check exists to
catch "your PyTorch was not built for this architecture", and it was asking
about an architecture nobody was building for: vacuous on a PyTorch carrying
both, and a spurious hard failure on an arch-specific build carrying only
gfx950. The cause was `os.environ.get("FLASHINFER_ROCM_ARCH_LIST", "gfx942")`
reached from two functions in hip_utils, plus two more `return "gfx942"` lines
in CompilationContext._auto_detect_archs.
Add hip_utils.resolve_target_archs() -- explicit argument, then the env var,
then the architectures actually present, then every supported architecture with
a warning -- and route validate_rocm_arch, validate_flashinfer_rocm_arch,
CompilationContext and aot_hip through it. _auto_detect_archs goes away; it was
private and had no other caller.
The last-resort fallback changes from "gfx942" to every supported architecture.
On a GPU-less build host the old literal was not conservative, it was a guess
that silently produced a gfx942-only artifact; a fat build is slower but
correct wherever it lands, and the warning says how to make it cheap again.
Detection uses rocminfo rather than torch.cuda, so the resolver adds no torch
dependency to a module that must stay importable without one.
test_defaults_to_gfx942_when_no_env_and_no_arg asserted the wrong constant --
which is how the constant survived -- and now pins the detected architecture.
Measured after, same host and script: all four rows report gfx950. Also
verified FLASHINFER_ROCM_ARCH_LIST=gfx942 is still honoured on a gfx950 box
(cross-compiling stays possible), and that a container with no /dev/kfd
resolves to "gfx942,gfx950" with the warning and without importing torch.
169 passed across test_hip_utils.py, test_aiter_build_arch_hip.py and
test_arch_caps_hip.py on gfx950.
Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 0ec49b0)
…t test hermetic Addresses both review comments on #289. - resolve_target_archs() returned the caller/env string verbatim. Now that it is the single source of truth that is a hard failure, not untidiness: the validators split on "," only and match tokens against FLASHINFER_SUPPORTED_ROCM_ARCHS verbatim, so FLASHINFER_ROCM_ARCH_LIST=gfx950:sramecc+ -> ['gfx950:sramecc+'] unsupported FLASHINFER_ROCM_ARCH_LIST=gfx942;gfx950 -> ['gfx942;gfx950'] unsupported FLASHINFER_ROCM_ARCH_LIST=gfx942,,gfx942 -> ['gfx942','','gfx942'] unsupported '' and validate_flashinfer_rocm_arch raises "does not support any of the requested ROCm architectures". ';' matters specifically because jit/aiter_source.py already documents it for this same variable, and aot_hip.py writes this resolver's output back into that env var -- so the two consumers were disagreeing about their own input format. _canonical_arch_list normalizes syntax only: accepts ',' or ';', strips qualifiers via normalize_arch, drops empties, dedupes preserving first-seen order. Unknown architectures pass through so the validators can still report them; dropping one here would turn a clear error into a build that quietly targets less than was asked for. A value that normalizes away entirely (";;") falls through to detection rather than returning "". - test_agrees_with_the_compilation_context compared a _FakeCppExt-fed validator against a CompilationContext that validates against the *real* torch, so the assertion depended on the installed wheel. Both sides now see the same view. Verified rather than assumed, by simulating an arch-specific wheel (_get_rocm_arch_flags -> gfx942 only) via a pytest plugin: before: RuntimeError: PyTorch does not support the following architectures: --offload-arch=gfx950 -> FAILED after: passes The wheel on this box is a fat build advertising gfx950, which is why the fragility was latent here. 11 tests added for the canonicalization. gfx942 behaviour is unchanged. (cherry picked from commit 1937e0c)
…ment Addresses the suppressed comment in Copilot review 4984984345 on #289. compile_and_package_modules set FLASHINFER_ROCM_ARCH_LIST and then validated it, so a CompilationContext() raise left the variable set for whatever ran next in the same process. Publishing it is deliberate -- the AITER shim reads the build architecture from there (jit/aiter_source.py) and an AOT build has no other channel -- but it is a side effect that outlives the call, so it should only happen once the list is known good. Reordering is behaviour-preserving on success: CompilationContext() re-resolves through resolve_target_archs() and nothing can change between the two calls, so it validates exactly the list published afterwards. On the failure path the environment is now left as it was found. One correction to the review's wording: the leaked value is not "invalid" -- resolve_target_archs() returns a canonical list. It is a *valid* list that failed validation against this ROCm version or PyTorch build, which is a different thing and is why the leak is subtle rather than obvious. The regression test was wrong on its first attempt and is worth flagging: it seeded FLASHINFER_ROCM_ARCH_LIST with the value the resolver would return, so the buggy write was a no-op and the test passed with the bug present. It now starts from the variable unset with detection patched, and was verified to fail without the reorder: AssertionError: assert 'FLASHINFER_ROCM_ARCH_LIST' not in environ({... 'FLASHINFER_ROCM_ARCH_LIST': 'gfx950'})
…ction cache
Two defects found reviewing the branch before proposing it for merge.
**The widened fallback was wrong.** Resolving to every supported architecture
when no GPU is visible looked like the safe answer and is not. `aot_hip`
publishes the resolved list into FLASHINFER_ROCM_ARCH_LIST, and
`resolve_aiter_build_arch()` returns `env_archs[0]` when no device is visible --
so "gfx942,gfx950" ships gfx950 HIP kernels beside a gfx942-only AITER shim,
which faults on a gfx950 card. Today both sides independently fall back to
gfx942 and therefore agree; widening only one of them is a regression this
change would have introduced.
Keeping the existing default preserves that agreement and still fixes what this
function exists for: the *disagreement* between the validator and the compiler
on a host where a GPU is visible. Whether a GPU-less host should raise, build
fat, or teach the shim to follow the list is a genuine question with its own
blast radius -- it breaks build hosts that work today -- and belongs in its own
change. A test now pins the fallback equal to aiter_source's
_DEFAULT_BUILD_ARCH so the two cannot drift apart silently.
**The refactor dropped a cache.** `CompilationContext._auto_detect_archs`
reached rocminfo through `get_supported_device_indices`, which is
`@functools.cache`d, so the subprocess ran once per process.
`resolve_target_archs` called the uncached `rocminfo_gpu_agents` directly
("Not cached: the caller decides") from four call sites, with CompilationContext
constructed in five places -- re-running rocminfo, timeout included, on every
construction. Detection is cached again, with an autouse fixture clearing it so
the tests that patch the probe stay order-independent.
Co-Authored-By: Claude <noreply@anthropic.com>
e372913 to
a26a790
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (6)
flashinfer/aot_hip.py:234
CompilationContext()can validate a narrowed target list, but this publishes the pre-validation string. For example, on ROCm 6.4gfx950,gfx942is narrowed togfx942;resolve_aiter_build_arch()then sees the published fat list and can select gfx950 on a gfx950 device while the FlashInfer kernels target only gfx942. Publish the validated flags/targets from the context so the AITER shim receives the same effective architecture set.
rocm_arch_list = resolve_target_archs()
CompilationContext() # validates the resolved list, raising on a bad one
os.environ["FLASHINFER_ROCM_ARCH_LIST"] = rocm_arch_list
flashinfer/aot_hip.py:233
CompilationContext()is invoked only afterFLASHINFER_WORKSPACE_BASEand thejit_envworkspace paths have been mutated above. If this validation raises, the process retains the failed AOTbuild_diras its global JIT workspace, so subsequent builds or retries use the wrong directory. Move resolution/validation before those global overrides and before importing the JIT modules.
CompilationContext() # validates the resolved list, raising on a bad one
flashinfer/hip_utils.py:45
rocminfo_gpu_agents()enumerates every physical GPU and does not applyHIP_VISIBLE_DEVICES. Because this new cache is used byCompilationContextduring import, a worker pinned to gfx950 on a mixed gfx942/gfx950 host still resolves both targets; an arch-specific PyTorch build can then fail validation, while a fat build compiles code for an invisible GPU. Filter the probe by the process-visible device selection before caching, or defer resolution until the visibility pin is established.
for arch, _ in rocminfo_gpu_agents()
if arch in FLASHINFER_SUPPORTED_ROCM_ARCHS
flashinfer/hip_utils.py:143
- When the environment is present but normalizes entirely away (
";;"or whitespace)—a case this resolver deliberately supports—this branch is taken even though the warning saysFLASHINFER_ROCM_ARCH_LISTis unset. The message should say that no usable target was provided (or identify the invalid value), otherwise it misdiagnoses the operator's configuration.
logger.warning(
"No supported AMD GPU detected and FLASHINFER_ROCM_ARCH_LIST is unset; "
"falling back to %s. Set FLASHINFER_ROCM_ARCH_LIST to the architecture "
"you are building for -- otherwise the result will not run on %s.",
flashinfer/hip_utils.py:44
- The removed
_auto_detect_archs()caught probe failures and used the fallback, but this new wrapper callsrocminfo_gpu_agents()without protection. That helper only handlesFileNotFoundErrorandTimeoutExpired, so aPermissionError/otherOSErrorfromsubprocess.runnow abortsCompilationContext(and potentially package import) instead of taking the documented fallback path. Catch probe failures here and return an empty tuple.
for arch, _ in rocminfo_gpu_agents()
tests/rocm_tests/test_hip_utils.py:384
_detected_supported_archsis process-cached, but the cache-clearing fixture is scoped only toTestResolveTargetArchs. If the resolver ran during package import or an earlier test, patchingrocminfo_gpu_agentshere is ignored; running this test alone (or after another module) on a gfx942 host can still return gfx942 and fail the new gfx950 assertion. Clear this cache before and after the patched call, or use a module-scoped fixture.
patch(
"flashinfer.hip_utils.rocminfo_gpu_agents",
return_value=(("gfx950", "AMD Instinct MI350X"),),
|
The six Copilot comments above predate the scope reduction (they were written 2026-08-20/21; the branch was rewritten on 08-22) and none apply to the current diff. Verified rather than assumed:
One is now inverted by the rewrite: the The current diff is 5 files / +414 −57, confined to To be clear about what is being set aside rather than dismissed: the |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Suppressed comments (3)
flashinfer/hip_utils.py:143
- When the variable is set to a non-empty value that normalizes away (for example
" ; , ", which this PR explicitly supports as a fallback case), this branch still logs thatFLASHINFER_ROCM_ARCH_LISTis unset. That makes the warning misleading and hides the invalid/empty configuration; distinguish unset from an empty-after-normalization value or describe both cases.
"No supported AMD GPU detected and FLASHINFER_ROCM_ARCH_LIST is unset; "
"falling back to %s. Set FLASHINFER_ROCM_ARCH_LIST to the architecture "
"you are building for -- otherwise the result will not run on %s.",
flashinfer/hip_utils.py:45
- This cache is independent of the existing
get_supported_device_indices()cache, so it does not prevent duplicate probes.tests/conftest.pycallsget_physical_card_device_indices()(which invokesrocminfo_gpu_agents()), and the new resolver invokes the uncached probe again in the same worker; with the 10-second subprocess timeout this doubles startup/probe cost. Share a cached raw-agent result between these helpers instead of caching only the derived architecture set here.
return tuple(
sorted(
{
arch
for arch, _ in rocminfo_gpu_agents()
if arch in FLASHINFER_SUPPORTED_ROCM_ARCHS
tests/rocm_tests/test_aot_hip.py:174
- This test is vacuous against the parent implementation: when the variable is unset, the old
compile_and_package_modulesbranch also constructedCompilationContext()before assigningFLASHINFER_ROCM_ARCH_LIST, so patching that constructor to raise already left the environment untouched. Replace this with an assertion that distinguishes the new resolver/publication behavior (and covers the validated target), otherwise a regression in the claimed ordering can pass this test.
monkeypatch.setattr("flashinfer.compilation_context_hip.CompilationContext", _Boom)
with pytest.raises(RuntimeError, match="not recognized"):
aot_hip.compile_and_package_modules(
out_dir=None,
| rocm_arch_list = resolve_target_archs() | ||
| CompilationContext() # validates the resolved list, raising on a bad one | ||
| os.environ["FLASHINFER_ROCM_ARCH_LIST"] = rocm_arch_list |
| for arch, _ in rocminfo_gpu_agents() | ||
| if arch in FLASHINFER_SUPPORTED_ROCM_ARCHS |
| with ( | ||
| self._patch_rocm_version("7.1.0"), | ||
| patch( | ||
| "flashinfer.hip_utils.rocminfo_gpu_agents", | ||
| return_value=(("gfx950", "AMD Instinct MI350X"),), | ||
| ), | ||
| ): | ||
| assert validate_rocm_arch(arch_list=None) == "gfx950" |
The bug
Three call sites independently answered "which GPU architecture are we compiling for", and on CDNA4 they disagreed. Measured on a gfx950 host with
FLASHINFER_ROCM_ARCH_LISTunset:The JIT validated gfx942 while compiling gfx950. That check exists to catch "your PyTorch was not built for this architecture", so pointed at the wrong architecture it fails both ways: vacuous on a fat PyTorch that carries both (it passes, having checked something you are not using), and a spurious hard failure on an arch-specific build carrying only gfx950 — refusing a setup that works.
The cause was
os.environ.get("FLASHINFER_ROCM_ARCH_LIST", "gfx942")reached from two functions inhip_utils, plus two morereturn "gfx942"lines inCompilationContext._auto_detect_archs. gfx942 was the one architecture where the literal happened to be right, which is why CDNA3 never noticed. A unit test pinned the wrong constant (test_defaults_to_gfx942_when_no_env_and_no_arg), which is how it survived review for so long.The fix
hip_utils.resolve_target_archs()— explicit argument →FLASHINFER_ROCM_ARCH_LIST→ architectures actually present →gfx942with a warning.validate_rocm_arch,validate_flashinfer_rocm_arch,CompilationContextandaot_hipall route through it._auto_detect_archsis deleted (private, no other caller, and the holder of the remaining literals).The GPU-less fallback stays on
gfx942— the pre-existing default — so this changes nothing on hosts where no GPU is visible.aot_hippublishes the resolved list intoFLASHINFER_ROCM_ARCH_LISTandresolve_aiter_build_arch()reads it back, so the two must agree about that default or the AITER shim is built for a different architecture than the kernels it ships beside. A test pins_GPULESS_FALLBACK_ARCH == aiter_source._DEFAULT_BUILD_ARCHso they cannot drift apart silently.Detection uses
rocminfo, nottorch.cuda, so the resolver adds no torch dependency to a module that must stay importable without one — the hardware-less conformance job from #287 loads it.Also included, both small and both fixing real failures:
_canonical_arch_list— the validators split on,only and match tokens verbatim, soFLASHINFER_ROCM_ARCH_LIST=gfx942;gfx950became one token and raised "does not support any of the requested ROCm architectures".;is worth accepting becausejit/aiter_source.pyalready documents it for this same variable — the two consumers were disagreeing about the format of their own env var. Qualifiers (gfx950:sramecc+), empty tokens and duplicates are handled too. Syntax only: unknown architectures pass through so the validators can still report them.aot_hipnow validates the resolved list before writing it to the environment, so a failed build leaves the environment as it found it.Test plan
gfx950on the gfx950 host — the measurement at the top, re-run.FLASHINFER_ROCM_ARCH_LIST=gfx942on a gfx950 box still resolves togfx942; cross-compiling stays possible./dev/kfdresolves togfx942with the warning, without importing torch.CompilationContextis constructed from five places, and the code this replaces reached rocminfo through the cachedget_supported_device_indices. Calling the uncached probe directly re-ran the subprocess, timeout included, on every construction. Covered by a test.test_defaults_to_gfx942_when_no_env_and_no_argnow pins the detected architecture instead of the literal.test_hip_utils.py,test_aot_hip.py,test_aiter_build_arch_hip.py,test_arch_caps_hip.pyon gfx950.pre-commit runclean on all changed files.amd-integration, same image both sides, so architecture is the only variable.