Skip to content

Commit fb740c9

Browse files
authored
Merge branch 'main' into nvbug6084457
2 parents 9cd877c + 355fcaa commit fb740c9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+1967
-634
lines changed

.coveragerc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2-
# SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE
2+
# SPDX-License-Identifier: Apache-2.0
33

44
[paths]
55
source =

.github/workflows/build-wheel.yml

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,24 @@ jobs:
4444
with:
4545
fetch-depth: 0
4646

47-
# The env vars ACTIONS_CACHE_SERVICE_V2, ACTIONS_RESULTS_URL, and ACTIONS_RUNTIME_TOKEN
48-
# are exposed by this action.
49-
- name: Enable sccache
50-
uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # 0.0.9
51-
with:
52-
disable_annotations: 'true'
47+
- name: Install latest rapidsai/sccache
48+
if: ${{ startsWith(inputs.host-platform, 'linux') }}
49+
run: |
50+
curl -fsSL "https://github.com/rapidsai/sccache/releases/latest/download/sccache-$(uname -m)-unknown-linux-musl.tar.gz" \
51+
| sudo tar -C /usr/local/bin -xvzf - --wildcards --strip-components=1 -x '*/sccache'
52+
echo "SCCACHE_PATH=/usr/local/bin/sccache" >> "$GITHUB_ENV"
53+
echo "SCCACHE_GHA_USE_PREPROCESSOR_CACHE_MODE=true" >> "$GITHUB_ENV"
5354
5455
# xref: https://github.com/orgs/community/discussions/42856#discussioncomment-7678867
5556
- name: Adding addtional GHA cache-related env vars
5657
uses: actions/github-script@v8
5758
with:
5859
script: |
59-
core.exportVariable('ACTIONS_CACHE_URL', process.env['ACTIONS_CACHE_URL'])
60-
core.exportVariable('ACTIONS_RUNTIME_URL', process.env['ACTIONS_RUNTIME_URL'])
60+
core.exportVariable('ACTIONS_CACHE_SERVICE_V2', 'on');
61+
core.exportVariable('ACTIONS_CACHE_URL', process.env['ACTIONS_CACHE_URL'] || '');
62+
core.exportVariable('ACTIONS_RESULTS_URL', process.env['ACTIONS_RESULTS_URL'] || '');
63+
core.exportVariable('ACTIONS_RUNTIME_URL', process.env['ACTIONS_RUNTIME_URL'] || '');
64+
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN'] || '');
6165
6266
- name: Setup proxy cache
6367
uses: nv-gha-runners/setup-proxy-cache@main
@@ -176,14 +180,15 @@ jobs:
176180
ACTIONS_RESULTS_URL=${{ env.ACTIONS_RESULTS_URL }}
177181
ACTIONS_CACHE_URL=${{ env.ACTIONS_CACHE_URL }}
178182
ACTIONS_CACHE_SERVICE_V2=${{ env.ACTIONS_CACHE_SERVICE_V2 }}
183+
SCCACHE_GHA_USE_PREPROCESSOR_CACHE_MODE=${{ env.SCCACHE_GHA_USE_PREPROCESSOR_CACHE_MODE }}
179184
SCCACHE_DIR=/host/${{ env.SCCACHE_DIR }}
180185
SCCACHE_CACHE_SIZE=${{ env.SCCACHE_CACHE_SIZE }}
181186
CIBW_ENVIRONMENT_WINDOWS: >
182187
CUDA_PATH="$(cygpath -w ${{ env.CUDA_PATH }})"
183188
CUDA_PYTHON_PARALLEL_LEVEL=${{ env.CUDA_PYTHON_PARALLEL_LEVEL }}
184189
# check cache stats before leaving cibuildwheel
185190
CIBW_BEFORE_TEST_LINUX: >
186-
"/host/${{ env.SCCACHE_PATH }}" --show-stats &&
191+
"/host/${{ env.SCCACHE_PATH }}" --show-adv-stats &&
187192
"/host/${{ env.SCCACHE_PATH }}" --show-stats --stats-format=json > /host/${{ github.workspace }}/sccache_bindings.json
188193
# force the test stage to be run (so that before-test is not skipped)
189194
# TODO: we might want to think twice on adding this, it does a lot of
@@ -241,6 +246,7 @@ jobs:
241246
ACTIONS_RESULTS_URL=${{ env.ACTIONS_RESULTS_URL }}
242247
ACTIONS_CACHE_URL=${{ env.ACTIONS_CACHE_URL }}
243248
ACTIONS_CACHE_SERVICE_V2=${{ env.ACTIONS_CACHE_SERVICE_V2 }}
249+
SCCACHE_GHA_USE_PREPROCESSOR_CACHE_MODE=${{ env.SCCACHE_GHA_USE_PREPROCESSOR_CACHE_MODE }}
244250
SCCACHE_DIR=/host/${{ env.SCCACHE_DIR }}
245251
SCCACHE_CACHE_SIZE=${{ env.SCCACHE_CACHE_SIZE }}
246252
CIBW_ENVIRONMENT_WINDOWS: >
@@ -250,7 +256,7 @@ jobs:
250256
PIP_FIND_LINKS="$(cygpath -w ${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }})"
251257
# check cache stats before leaving cibuildwheel
252258
CIBW_BEFORE_TEST_LINUX: >
253-
"/host${{ env.SCCACHE_PATH }}" --show-stats &&
259+
"/host${{ env.SCCACHE_PATH }}" --show-adv-stats &&
254260
"/host${{ env.SCCACHE_PATH }}" --show-stats --stats-format=json > /host/${{ github.workspace }}/sccache_core.json
255261
# force the test stage to be run (so that before-test is not skipped)
256262
# TODO: we might want to think twice on adding this, it does a lot of
@@ -429,6 +435,7 @@ jobs:
429435
ACTIONS_RESULTS_URL=${{ env.ACTIONS_RESULTS_URL }}
430436
ACTIONS_CACHE_URL=${{ env.ACTIONS_CACHE_URL }}
431437
ACTIONS_CACHE_SERVICE_V2=${{ env.ACTIONS_CACHE_SERVICE_V2 }}
438+
SCCACHE_GHA_USE_PREPROCESSOR_CACHE_MODE=${{ env.SCCACHE_GHA_USE_PREPROCESSOR_CACHE_MODE }}
432439
SCCACHE_DIR=/host/${{ env.SCCACHE_DIR }}
433440
SCCACHE_CACHE_SIZE=${{ env.SCCACHE_CACHE_SIZE }}
434441
CIBW_ENVIRONMENT_WINDOWS: >
@@ -438,7 +445,7 @@ jobs:
438445
PIP_FIND_LINKS="$(cygpath -w ${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }})"
439446
# check cache stats before leaving cibuildwheel
440447
CIBW_BEFORE_TEST_LINUX: >
441-
"/host${{ env.SCCACHE_PATH }}" --show-stats &&
448+
"/host${{ env.SCCACHE_PATH }}" --show-adv-stats &&
442449
"/host${{ env.SCCACHE_PATH }}" --show-stats --stats-format=json > /host/${{ github.workspace }}/sccache_core_prev.json
443450
# force the test stage to be run (so that before-test is not skipped)
444451
# TODO: we might want to think twice on adding this, it does a lot of

.github/workflows/ci.yml

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,159 @@ jobs:
7171
echo "skip=${skip}" >> "$GITHUB_OUTPUT"
7272
echo "doc_only=${doc_only}" >> "$GITHUB_OUTPUT"
7373
74+
# Detect which top-level modules were touched by the PR so downstream build
75+
# and test jobs can avoid rebuilding/retesting modules unaffected by the
76+
# change. See issue #299.
77+
#
78+
# Dependency graph (verified in pyproject.toml files):
79+
# cuda_pathfinder -> (no internal deps)
80+
# cuda_bindings -> cuda_pathfinder
81+
# cuda_core -> cuda_pathfinder, cuda_bindings
82+
# cuda_python -> cuda_bindings (meta package)
83+
#
84+
# A change to cuda_pathfinder (or shared infra) forces a rebuild of every
85+
# downstream module. A change to cuda_bindings forces rebuild of cuda_core.
86+
# A change to cuda_core alone skips rebuilding/retesting cuda_bindings.
87+
# On push to main, tag refs, schedule, or workflow_dispatch events we
88+
# unconditionally run everything because there is no meaningful "changed
89+
# paths" baseline for those events.
90+
detect-changes:
91+
runs-on: ubuntu-latest
92+
outputs:
93+
bindings: ${{ steps.compose.outputs.bindings }}
94+
core: ${{ steps.compose.outputs.core }}
95+
pathfinder: ${{ steps.compose.outputs.pathfinder }}
96+
python_meta: ${{ steps.compose.outputs.python_meta }}
97+
test_helpers: ${{ steps.compose.outputs.test_helpers }}
98+
shared: ${{ steps.compose.outputs.shared }}
99+
build_bindings: ${{ steps.compose.outputs.build_bindings }}
100+
build_core: ${{ steps.compose.outputs.build_core }}
101+
build_pathfinder: ${{ steps.compose.outputs.build_pathfinder }}
102+
test_bindings: ${{ steps.compose.outputs.test_bindings }}
103+
test_core: ${{ steps.compose.outputs.test_core }}
104+
test_pathfinder: ${{ steps.compose.outputs.test_pathfinder }}
105+
steps:
106+
- name: Checkout repository
107+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
108+
with:
109+
fetch-depth: 0
110+
111+
# copy-pr-bot pushes every PR (whether it targets main or a backport
112+
# branch such as 12.9.x) to pull-request/<N>, so the base branch
113+
# cannot be inferred from github.ref_name. Look it up via the
114+
# upstream PR metadata so the diff below is rooted at the right place.
115+
- name: Resolve PR base branch
116+
id: pr-info
117+
if: ${{ startsWith(github.ref_name, 'pull-request/') }}
118+
uses: nv-gha-runners/get-pr-info@main
119+
120+
- name: Detect changed paths
121+
id: filter
122+
if: ${{ startsWith(github.ref_name, 'pull-request/') }}
123+
env:
124+
# GitHub Actions evaluates step-level `env:` expressions eagerly —
125+
# the step's `if:` gate does NOT short-circuit them. On non-PR
126+
# events (push/tag/schedule), `pr-info` is skipped and its outputs
127+
# are empty strings, so `fromJSON('')` would raise a template error
128+
# and fail the step despite `if:` being false. Guard the
129+
# `fromJSON` call with a short-circuit so the expression resolves
130+
# to an empty string on non-PR events; the step is still gated
131+
# off by `if:`, so `BASE_REF` is never consumed there.
132+
BASE_REF: ${{ steps.pr-info.outputs.pr-info && fromJSON(steps.pr-info.outputs.pr-info).base.ref || '' }}
133+
run: |
134+
# Diff against the merge base with the PR's actual target branch.
135+
# Uses merge-base so diverged branches only show files changed on
136+
# the PR side, not upstream commits.
137+
if [[ -z "${BASE_REF}" ]]; then
138+
echo "Could not resolve PR base branch from get-pr-info output" >&2
139+
exit 1
140+
fi
141+
base=$(git merge-base HEAD "origin/${BASE_REF}")
142+
changed=$(git diff --name-only "$base"...HEAD)
143+
144+
has_match() {
145+
grep -qE "$1" <<< "$changed" && echo true || echo false
146+
}
147+
148+
{
149+
echo "bindings=$(has_match '^cuda_bindings/')"
150+
echo "core=$(has_match '^cuda_core/')"
151+
echo "pathfinder=$(has_match '^cuda_pathfinder/')"
152+
echo "python_meta=$(has_match '^cuda_python/')"
153+
echo "test_helpers=$(has_match '^cuda_python_test_helpers/')"
154+
echo "shared=$(has_match '^(\.github/|ci/|scripts/|toolshed/|conftest\.py$|pyproject\.toml$|pixi\.(toml|lock)$|pytest\.ini$|ruff\.toml$)')"
155+
} >> "$GITHUB_OUTPUT"
156+
157+
- name: Compose gating outputs
158+
id: compose
159+
env:
160+
IS_PR: ${{ startsWith(github.ref_name, 'pull-request/') }}
161+
BINDINGS: ${{ steps.filter.outputs.bindings || 'false' }}
162+
CORE: ${{ steps.filter.outputs.core || 'false' }}
163+
PATHFINDER: ${{ steps.filter.outputs.pathfinder || 'false' }}
164+
PYTHON_META: ${{ steps.filter.outputs.python_meta || 'false' }}
165+
TEST_HELPERS: ${{ steps.filter.outputs.test_helpers || 'false' }}
166+
SHARED: ${{ steps.filter.outputs.shared || 'false' }}
167+
run: |
168+
set -euxo pipefail
169+
# Non-PR events (push to main, tag push, schedule, workflow_dispatch)
170+
# always exercise the full pipeline because there is no baseline for
171+
# a meaningful diff.
172+
if [[ "${IS_PR}" != "true" ]]; then
173+
bindings=true
174+
core=true
175+
pathfinder=true
176+
python_meta=true
177+
test_helpers=true
178+
shared=true
179+
else
180+
bindings="${BINDINGS}"
181+
core="${CORE}"
182+
pathfinder="${PATHFINDER}"
183+
python_meta="${PYTHON_META}"
184+
test_helpers="${TEST_HELPERS}"
185+
shared="${SHARED}"
186+
fi
187+
188+
or_flag() {
189+
for v in "$@"; do
190+
if [[ "${v}" == "true" ]]; then
191+
echo "true"
192+
return
193+
fi
194+
done
195+
echo "false"
196+
}
197+
198+
# Build gating: pathfinder change forces rebuild of bindings and
199+
# core; bindings change forces rebuild of core. shared changes force
200+
# a full rebuild.
201+
build_pathfinder="$(or_flag "${shared}" "${pathfinder}")"
202+
build_bindings="$(or_flag "${shared}" "${pathfinder}" "${bindings}")"
203+
build_core="$(or_flag "${shared}" "${pathfinder}" "${bindings}" "${core}")"
204+
205+
# Test gating: tests for a module must run whenever that module, any
206+
# of its runtime dependencies, the shared test helper package, or
207+
# shared infra changes. pathfinder tests are cheap and always run.
208+
test_pathfinder=true
209+
test_bindings="$(or_flag "${shared}" "${pathfinder}" "${bindings}" "${test_helpers}")"
210+
test_core="$(or_flag "${shared}" "${pathfinder}" "${bindings}" "${core}" "${test_helpers}")"
211+
212+
{
213+
echo "bindings=${bindings}"
214+
echo "core=${core}"
215+
echo "pathfinder=${pathfinder}"
216+
echo "python_meta=${python_meta}"
217+
echo "test_helpers=${test_helpers}"
218+
echo "shared=${shared}"
219+
echo "build_bindings=${build_bindings}"
220+
echo "build_core=${build_core}"
221+
echo "build_pathfinder=${build_pathfinder}"
222+
echo "test_bindings=${test_bindings}"
223+
echo "test_core=${test_core}"
224+
echo "test_pathfinder=${test_pathfinder}"
225+
} >> "$GITHUB_OUTPUT"
226+
74227
# NOTE: Build jobs are intentionally split by platform rather than using a single
75228
# matrix. This allows each test job to depend only on its corresponding build,
76229
# so faster platforms can proceed through build & test without waiting for slower
@@ -151,6 +304,7 @@ jobs:
151304
needs:
152305
- ci-vars
153306
- should-skip
307+
- detect-changes
154308
- build-linux-64
155309
secrets: inherit
156310
uses: ./.github/workflows/test-wheel-linux.yml
@@ -159,6 +313,7 @@ jobs:
159313
host-platform: ${{ matrix.host-platform }}
160314
build-ctk-ver: ${{ needs.ci-vars.outputs.CUDA_BUILD_VER }}
161315
nruns: ${{ (github.event_name == 'schedule' && 100) || 1}}
316+
skip-bindings-test: ${{ !fromJSON(needs.detect-changes.outputs.test_bindings) }}
162317

163318
# See test-linux-64 for why test jobs are split by platform.
164319
test-linux-aarch64:
@@ -174,6 +329,7 @@ jobs:
174329
needs:
175330
- ci-vars
176331
- should-skip
332+
- detect-changes
177333
- build-linux-aarch64
178334
secrets: inherit
179335
uses: ./.github/workflows/test-wheel-linux.yml
@@ -182,6 +338,7 @@ jobs:
182338
host-platform: ${{ matrix.host-platform }}
183339
build-ctk-ver: ${{ needs.ci-vars.outputs.CUDA_BUILD_VER }}
184340
nruns: ${{ (github.event_name == 'schedule' && 100) || 1}}
341+
skip-bindings-test: ${{ !fromJSON(needs.detect-changes.outputs.test_bindings) }}
185342

186343
# See test-linux-64 for why test jobs are split by platform.
187344
test-windows:
@@ -197,6 +354,7 @@ jobs:
197354
needs:
198355
- ci-vars
199356
- should-skip
357+
- detect-changes
200358
- build-windows
201359
secrets: inherit
202360
uses: ./.github/workflows/test-wheel-windows.yml
@@ -205,6 +363,7 @@ jobs:
205363
host-platform: ${{ matrix.host-platform }}
206364
build-ctk-ver: ${{ needs.ci-vars.outputs.CUDA_BUILD_VER }}
207365
nruns: ${{ (github.event_name == 'schedule' && 100) || 1}}
366+
skip-bindings-test: ${{ !fromJSON(needs.detect-changes.outputs.test_bindings) }}
208367

209368
doc:
210369
name: Docs
@@ -228,6 +387,7 @@ jobs:
228387
runs-on: ubuntu-latest
229388
needs:
230389
- should-skip
390+
- detect-changes
231391
- test-linux-64
232392
- test-linux-aarch64
233393
- test-windows
@@ -254,7 +414,16 @@ jobs:
254414
#
255415
# Note: When [doc-only] is in PR title, test jobs are intentionally
256416
# skipped and should not cause failure.
417+
#
418+
# detect-changes gates whether heavy test matrices run at all; if it
419+
# does not succeed, downstream test jobs are skipped rather than
420+
# failed, which would otherwise go unnoticed here. Require its
421+
# success explicitly so a broken gating step cannot masquerade as a
422+
# green CI run.
257423
doc_only=${{ needs.should-skip.outputs.doc-only }}
424+
if ${{ needs.detect-changes.result != 'success' }}; then
425+
exit 1
426+
fi
258427
if ${{ needs.doc.result == 'cancelled' || needs.doc.result == 'failure' }}; then
259428
exit 1
260429
fi

.github/workflows/test-wheel-linux.yml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ on:
2222
nruns:
2323
type: number
2424
default: 1
25+
# When true, cuda.bindings tests (and the Cython tests that depend on
26+
# them) are skipped even when CTK majors match. Callers set this based
27+
# on the output of the detect-changes job in ci.yml so PRs that only
28+
# touch unrelated modules avoid the expensive bindings test suite.
29+
skip-bindings-test:
30+
type: boolean
31+
default: false
2532

2633
defaults:
2734
run:
@@ -113,6 +120,7 @@ jobs:
113120
LOCAL_CTK: ${{ matrix.LOCAL_CTK }}
114121
PY_VER: ${{ matrix.PY_VER }}
115122
SHA: ${{ github.sha }}
123+
SKIP_BINDINGS_TEST_OVERRIDE: ${{ inputs.skip-bindings-test && '1' || '0' }}
116124
run: ./ci/tools/env-vars test
117125

118126
- name: Download cuda-pathfinder build artifacts
@@ -122,21 +130,21 @@ jobs:
122130
path: ./cuda_pathfinder
123131

124132
- name: Download cuda-python build artifacts
125-
if: ${{ env.SKIP_CUDA_BINDINGS_TEST == '0'}}
133+
if: ${{ env.USE_BACKPORT_BINDINGS == '0' }}
126134
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
127135
with:
128136
name: cuda-python-wheel
129137
path: .
130138

131139
- name: Download cuda.bindings build artifacts
132-
if: ${{ env.SKIP_CUDA_BINDINGS_TEST == '0'}}
140+
if: ${{ env.USE_BACKPORT_BINDINGS == '0' }}
133141
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
134142
with:
135143
name: ${{ env.CUDA_BINDINGS_ARTIFACT_NAME }}
136144
path: ${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }}
137145

138146
- name: Download cuda-python & cuda.bindings build artifacts from the prior branch
139-
if: ${{ env.SKIP_CUDA_BINDINGS_TEST == '1'}}
147+
if: ${{ env.USE_BACKPORT_BINDINGS == '1' }}
140148
env:
141149
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
142150
run: |
@@ -266,7 +274,7 @@ jobs:
266274
if: ${{ env.SKIP_CUDA_BINDINGS_TEST == '0' }}
267275
run: |
268276
pip install pyperf
269-
pushd cuda_bindings/benchmarks
277+
pushd benchmarks/cuda_bindings
270278
python run_pyperf.py --fast --min-time 1
271279
popd
272280

0 commit comments

Comments
 (0)