From a31e7c18878f022cb1d56b7afeb62a9ea070755d Mon Sep 17 00:00:00 2001 From: Jacob Szwejbka Date: Fri, 30 Oct 2026 06:52:20 -0700 Subject: [PATCH 1/3] Fix release CI test isolation, linkage checks and runner refresh --- .ci/scripts/tests/test_filter_cuda_matrix.py | 2 ++ .ci/scripts/wheel/test_shared_libraries.py | 1 + .github/workflows/pull.yml | 8 +++++++- .../release-runner-groups-refresh.yml | 14 +++++++++++++- backends/aoti/common_shims_slim.cpp | 5 +++++ backends/aoti/common_shims_slim.h | 2 ++ backends/cuda/runtime/aoti_cuda_shims.lib | Bin 43802 -> 44642 bytes backends/cuda/runtime/aoti_cuda_shims.lib.md | 7 +++++-- 8 files changed, 35 insertions(+), 4 deletions(-) diff --git a/.ci/scripts/tests/test_filter_cuda_matrix.py b/.ci/scripts/tests/test_filter_cuda_matrix.py index 87cace023e4..33ba959a4f1 100644 --- a/.ci/scripts/tests/test_filter_cuda_matrix.py +++ b/.ci/scripts/tests/test_filter_cuda_matrix.py @@ -327,6 +327,8 @@ def test_supported_toolkits_select_the_matching_torch_index(self): INSTALL_UTILS.platform, "system", return_value="Linux" ), mock.patch.object( INSTALL_UTILS.subprocess, "run", return_value=detected + ), mock.patch.dict( + os.environ, {"CMAKE_ARGS": "-DEXECUTORCH_BUILD_CUDA=ON"} ): self.assertEqual( INSTALL_UTILS.determine_torch_url(base_url), diff --git a/.ci/scripts/wheel/test_shared_libraries.py b/.ci/scripts/wheel/test_shared_libraries.py index a1d8c791b83..55120809626 100644 --- a/.ci/scripts/wheel/test_shared_libraries.py +++ b/.ci/scripts/wheel/test_shared_libraries.py @@ -92,6 +92,7 @@ _AOTI_SHIM_SYMBOLS = ( "aoti_torch_empty_strided", "aoti_torch_delete_tensor_object", + "aoti_torch_dtype_element_size", "executorch::backends::cuda::CUDAStreamGuard::create", # From the CUDA sources rather than the C++ ones. The build drops every .cu file when no working # compiler is found, and the library is still produced from its .cpp sources, so a check that diff --git a/.github/workflows/pull.yml b/.github/workflows/pull.yml index dca55ee5d24..29e9c2cd339 100644 --- a/.github/workflows/pull.yml +++ b/.github/workflows/pull.yml @@ -213,7 +213,13 @@ jobs: EXECUTORCH_PYBIND_TEST_BFLOAT16_CHANNELS_LAST_PTE=cmake-out-no-aten/add_bfloat16_channels_last.pte \ PYTHONPATH=cmake-out-no-aten:.. \ python extension/pybindings/test/test_pybindings_no_aten.py - if ldd cmake-out-no-aten/_C*.so | grep -Eiq 'torch|aten|c10'; then + NO_ATEN_LINKS="$(ldd cmake-out-no-aten/_C*.so)" + printf '%s\n' "$NO_ATEN_LINKS" + if grep -q 'not found' <<<"$NO_ATEN_LINKS"; then + echo "torch-free pybindings have a missing runtime dependency" + exit 1 + fi + if grep -Eiq '^[[:space:]]*(lib(torch|aten|c10)[^[:space:]/]*\.so|[^[:space:]]*/lib(torch|aten|c10)[^[:space:]/]*\.so)' <<<"$NO_ATEN_LINKS"; then echo "torch-free pybindings unexpectedly link against ATen" exit 1 fi diff --git a/.github/workflows/release-runner-groups-refresh.yml b/.github/workflows/release-runner-groups-refresh.yml index 59cd8871899..a7631683abd 100644 --- a/.github/workflows/release-runner-groups-refresh.yml +++ b/.github/workflows/release-runner-groups-refresh.yml @@ -23,8 +23,20 @@ concurrency: cancel-in-progress: true jobs: + dispatch: + if: github.repository_owner == 'pytorch' && github.event_name == 'push' + runs-on: ubuntu-latest + permissions: + actions: write + steps: + - name: Refresh runner groups from protected main + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: gh workflow run release-runner-groups-refresh.yml --ref main -f apply=true + reconcile: - if: github.repository_owner == 'pytorch' + if: github.repository_owner == 'pytorch' && github.event_name != 'push' runs-on: ubuntu-latest environment: ${{ github.event_name != 'pull_request' && 'runner-group' || '' }} steps: diff --git a/backends/aoti/common_shims_slim.cpp b/backends/aoti/common_shims_slim.cpp index d9e255bf220..fa94c55455f 100644 --- a/backends/aoti/common_shims_slim.cpp +++ b/backends/aoti/common_shims_slim.cpp @@ -164,6 +164,11 @@ int32_t aoti_torch_dtype_bool() { return 11; // ScalarType::Bool } +size_t aoti_torch_dtype_element_size(int32_t dtype) { + using namespace executorch::backends::aoti::slim::c10; + return elementSize(static_cast(dtype)); +} + // ============================================================ // Device Type Constants - Implementations // ============================================================ diff --git a/backends/aoti/common_shims_slim.h b/backends/aoti/common_shims_slim.h index 0d60f578605..29b7fa9b3d7 100644 --- a/backends/aoti/common_shims_slim.h +++ b/backends/aoti/common_shims_slim.h @@ -87,6 +87,8 @@ AOTI_SHIM_EXPORT int32_t aoti_torch_dtype_int8(); AOTI_SHIM_EXPORT int32_t aoti_torch_dtype_uint8(); AOTI_SHIM_EXPORT int32_t aoti_torch_dtype_bool(); +AOTI_SHIM_EXPORT size_t aoti_torch_dtype_element_size(int32_t dtype); + // ============================================================ // Device Type Constants - Declarations // ============================================================ diff --git a/backends/cuda/runtime/aoti_cuda_shims.lib b/backends/cuda/runtime/aoti_cuda_shims.lib index c0d61c611008c3897350d7b0950a8e0229607ba3..9a8acd416c5e60bc9c922c97433a3950fb540419 100644 GIT binary patch delta 1622 zcmZXSdraF^6vxkPq+?KsqXi0NP(%%GT_3FND8fMZ2r9~|bt2Ayj8Rs)BQCXU;h}5_x84I%qE}o-rsqD&mH<+ zb!Sf1=WKOkWEcX!(g*;k0?_#Z;;%>&#{mQ@+a-o<0KpS~NuIg`AV?pUtZo7jn5re# zQUHOiNV2W~K#*mVe@LGH4nVN!Q%OEgEO@C=^2$~Kf$K$yI|D%QhDP${ zJpjSBE0U5<06|%~#Nz-ER6HiBYQ4dS<|sZ%O$C6Ujynn73zsx0ooBIlSE=!K?1rQ8ulN`$g5M-TH0|XL ztLrQli`|O5qxICGOJXdKMq{t5SRT5P63}1grdfR>zmhBIw)nFqeQI(9| zwl;LMtY17n{@mixzae;(z0Yh8gwDHpGcQz!LI(?$^i^B9*q12heO1KJy!)o`7oq7z zfzYuEC(RTO$t(%@#c8h1+YJo=ao!EcrJ%1=yHxp4lCPpM+l;q&{%=K#E1T(0#f5h) zm{KFs@FF-UlT#t%J6D9Sn=BENCeE zIp{XGBaWV|v@=XqtazwOd=~rUJ>x{RmKJubW$52!#*{KyRADJNx=T-2tGVIJT5ed< z&Bppf>en^vKo5uUhr!i%^rMC5K^Jn+gAZO+*OOM0?CYiyD90 zA4b2n^1!ZCsr2%x%?#ffF+Z}{xiiri9Th>wVKd*m?~zGV7Qn)&So-}8ALP$* z6BhbJ`rvzGRvZ~mqUkaIl5TuxMo+q|t*9~puSKn-V-tMH!{^rwoc)DIS(BY;IA>#P U>Gg9CrpmPAx6aixb-qmXAJoVr@&Et; delta 1575 zcmZY8YfO`86ae7!Hd_bI5@{POmuWH18OFZ0SgtBlqmE8?31bdMH;1lBSrt%JhNHj$ zsYeae!w#*%lvww5^a8sEwr^%h(Hd0i>62vsbPGNINgE z(q;h3UCef+07&I(jr8_aw)X^pRMk|XzNz+ES+#mdIxx)+jRHvZA$GJ1K=N;AEozu;F_E(#07z8lZXcuAV-M znSWH5nL`6{c0JuOCkvb!j}=ZVx1``q-XmmB7}nv78L{Xs^5BYz$Iz9OMCFOE@x#&) z53NZ#z(FuQ9(}3Bq+eMWeL%j{A+6ql`RAO}u-YHZhL2Y~6*(@OS<`C5Sqi4#(T;iO zo~=y_9P~-XloojCR^~Ad)bVI0r-1|6>B{=ZSTg9(e;}ye{6J8$H4?}MInPBKIPiz} zon(D}|3b&V3wbsSrhDkym-vIpTlAxLglj&3)wlRTPf4VSx*)QYO>)*1@X)3=4+-3R z@aOP?<@zjT#TZj=qQCZR6j-s_hQXSJWPfcj{<6o^7ptcm73&1$c+4VNDU*{G3G|^i zOOU?up-jM=Kt5llz#CN#yz8C~m#GurPG17$Roev)@3Uj5h7)oU*>82ViTMuN45!k{IG(9igphVb{;X{8%rz{ zY$z0Xzg|Jpds>n|x2_=`yZy1$+~^cId(?(s74U)5k?@MKqR~hN%`SnT`E5Ae!c%L> ztY!$Kkc5W*QG}6&TZRN9$Sc>MaWYo$y3(Ic_p6 zrBmm&3;aE#;BW5erF05l%}@f>4XPLZZor1_4lb4#aSz5(#&EX4^Fs%w3cHy4CUNGR?sFA)OQSZ6&l7gG*Ele+Y3X)Bpeg diff --git a/backends/cuda/runtime/aoti_cuda_shims.lib.md b/backends/cuda/runtime/aoti_cuda_shims.lib.md index af2fb65ca13..39458f379de 100644 --- a/backends/cuda/runtime/aoti_cuda_shims.lib.md +++ b/backends/cuda/runtime/aoti_cuda_shims.lib.md @@ -10,12 +10,12 @@ pin moves and the generated wrapper starts calling something new: ``` nm --defined-only aoti_cuda_shims.lib \ - | sed -n 's/.* T _\?\(aoti_torch_[a-z_0-9]*\)$/\1/p' \ + | sed -n 's/.* T _\?\(aoti_torch_[A-Za-z_0-9]*\)$/\1/p' \ | sort -u > exports.txt # add the new names to exports.txt, then { echo 'LIBRARY aoti_cuda_shims.dll'; echo 'EXPORTS'; sed 's/^/ /' exports.txt; } \ > aoti_cuda_shims.def -x86_64-w64-mingw32-dlltool -d aoti_cuda_shims.def -l aoti_cuda_shims.lib \ +x86_64-w64-mingw32-dlltool -m i386:x86-64 -d aoti_cuda_shims.def -l aoti_cuda_shims.lib \ --dllname aoti_cuda_shims.dll # zero the archive metadata so the file is reproducible mkdir extract && cd extract && x86_64-w64-mingw32-ar x ../aoti_cuda_shims.lib \ @@ -27,6 +27,9 @@ The archive has to be built at a fresh path. Updating it in place keeps the reve member order the generator produced, so the same export list would not give the same bytes twice. +Use the relative output name shown above in each build directory. The generator embeds +the output name in its object names, so absolute temporary paths change the bytes. + Then check the result is a superset of what it replaced and that no member carries a timestamp, since this file ships in the wheel and a stamped one makes two builds of the same source differ. From a6100bb5173ee4dcc00843428573c29bac9c9659 Mon Sep 17 00:00:00 2001 From: Jacob Szwejbka Date: Fri, 30 Oct 2026 07:25:11 -0700 Subject: [PATCH 2/3] Restrict ROCm INT6 autotuning to verified eight-row tiles --- backends/cuda/tests/test_int6_large_quantized_gemm.py | 5 +++-- backends/cuda/triton/kernels/int6_quantized_gemm.py | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/backends/cuda/tests/test_int6_large_quantized_gemm.py b/backends/cuda/tests/test_int6_large_quantized_gemm.py index bb762b24e0c..ff8e96c6e8e 100644 --- a/backends/cuda/tests/test_int6_large_quantized_gemm.py +++ b/backends/cuda/tests/test_int6_large_quantized_gemm.py @@ -76,8 +76,9 @@ def test_buckets_and_generic_candidate_counts(self): for bucket, expected in expected_counts.items(): configs = tile_autotune_configs(bucket) self.assertEqual(len(configs), expected) - self.assertEqual(len(legal_configs(bucket, 256)), expected // 3) - self.assertEqual(len(legal_configs(bucket, 5376)), expected) + legal_expected = 18 if torch.version.hip is not None else expected + self.assertEqual(len(legal_configs(bucket, 256)), legal_expected // 3) + self.assertEqual(len(legal_configs(bucket, 5376)), legal_expected) self.assertTrue(hasattr(torch.ops.triton, f"int6_quantized_gemm_m{bucket}")) @unittest.skipUnless(torch.cuda.is_available(), "CUDA required") diff --git a/backends/cuda/triton/kernels/int6_quantized_gemm.py b/backends/cuda/triton/kernels/int6_quantized_gemm.py index 5ddd6fe8ed3..bf170e56570 100644 --- a/backends/cuda/triton/kernels/int6_quantized_gemm.py +++ b/backends/cuda/triton/kernels/int6_quantized_gemm.py @@ -1239,6 +1239,10 @@ def _tile_main_loop_trips(args) -> int: def _prune_tiles(configs, named_args, **kwargs): + if torch.version.hip is not None: + # Larger M tiles can access invalid memory on masked tails with ROCm + # 7.14. Keep the eight-row path for the release. + configs = [config for config in configs if config.kwargs["BLOCK_M"] == 8] return prune_by_main_loop_trips( _tile_main_loop_trips, configs, named_args, **kwargs ) From d3add7b1fe83acaf82e266b27a3db983cd09b17f Mon Sep 17 00:00:00 2001 From: Jacob Szwejbka Date: Fri, 30 Oct 2026 11:01:47 -0700 Subject: [PATCH 3/3] Fix the ROCm AOTI pointwise export on gfx950 Compile the Triton kernels during AOTI lowering: with autotune_at_compile_time OFF, AOTInductor also generates JIT code and the JIT code fails to run on gfx950 with the pinned ROCm 7.14 toolchain, so lowering never completes. Verify the generated code object with the bare architecture name instead of the full amdgcn triple, which the toolchain does not embed in the program or the hsaco files, and require a compiled hsaco for the device. A diagnostic run confirmed the .pte and every hsaco carry bare gfx950 while the 256-byte .ptd carries neither. --- examples/cuda/scripts/export_amd_pointwise.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/examples/cuda/scripts/export_amd_pointwise.py b/examples/cuda/scripts/export_amd_pointwise.py index 67c07da7a0e..d701a29e722 100644 --- a/examples/cuda/scripts/export_amd_pointwise.py +++ b/examples/cuda/scripts/export_amd_pointwise.py @@ -66,7 +66,11 @@ def main() -> None: CompileSpec("target_device", f"cuda:{device_index}".encode()), CompileSpec("triton_kernel_mode", b"ON"), CompileSpec("max_autotune", b"OFF"), - CompileSpec("autotune_at_compile_time", b"OFF"), + # Compile the Triton kernels during AOTI lowering: with + # autotune_at_compile_time OFF, AOTInductor also generates JIT code + # and the JIT code fails to run on gfx950 with the pinned + # ROCm 7.14 toolchain, so lowering never completes. + CompileSpec("autotune_at_compile_time", b"ON"), ] edge_program = to_edge_transform_and_lower( exported_program, @@ -106,10 +110,20 @@ def main() -> None: if not ptd_path.is_file(): raise RuntimeError(f"expected AOTI data file was not created: {ptd_path}") - # Verify the embedded code object matches the active device. - triple = f"amdgcn-amd-amdhsa--{arch}".encode() - if triple not in pte_path.read_bytes(): + # Verify the generated code object targets the active device. The + # toolchain embeds the bare architecture name, not the full amdgcn + # triple, in the program and in the hsaco files, so check for that + # and require a compiled hsaco for the device. + arch_marker = arch.encode() + if arch_marker not in pte_path.read_bytes(): raise RuntimeError(f"{pte_path} embeds no code object for {arch}") + hsaco_paths = [ + hsaco_path + for hsaco_path in cache_dir.rglob("*.hsaco") + if arch_marker in hsaco_path.read_bytes() + ] + if not hsaco_paths: + raise RuntimeError(f"no hsaco code object for {arch} under {cache_dir}") print(f"ROCm: {torch.version.hip}") print(f"Compile GPU: cuda:{device_index} {device.name} ({device.gcnArchName})") @@ -119,6 +133,9 @@ def main() -> None: print("Triton sources:") for source_path in triton_sources: print(f" {source_path}") + print("Code objects:") + for hsaco_path in hsaco_paths: + print(f" {hsaco_path}") if __name__ == "__main__":