Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 60 additions & 8 deletions .github/actions/workflow-build/build-workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import re
import struct
import sys
from pathlib import Path

import yaml

Expand Down Expand Up @@ -1060,21 +1061,22 @@ def natural_sort_key(key):
return workflow_dispatch_groups


def find_workflow_line_number(workflow_name):
def find_workflow_line_number(workflow_name, filename=None):
filename = filename or matrix_yaml["filename"]
regex = re.compile(f"^( )*{workflow_name}:", re.IGNORECASE)
line_number = 0
with open(matrix_yaml["filename"], "r") as f:
with open(filename, "r") as f:
for line in f:
line_number += 1
if regex.match(line):
return line_number
raise Exception(
f"Workflow '{workflow_name}' not found in {matrix_yaml['filename]']} (could not match regex: {regex})"
f"Workflow '{workflow_name}' not found in {filename} (could not match regex: {regex})"
)


def get_matrix_job_origin(matrix_job, workflow_name, workflow_location):
filename = matrix_yaml["filename"]
def get_matrix_job_origin(matrix_job, workflow_name, workflow_location, filename=None):
filename = filename or matrix_yaml["filename"]
original_matrix_job = json.dumps(matrix_job, indent=None, separators=(", ", ": "))
original_matrix_job = original_matrix_job.replace('"', "")
return {
Expand Down Expand Up @@ -1350,9 +1352,9 @@ def parse_workflow_matrix_jobs(workflow_name, filter_projects=None):
workflow_location = (
f"{matrix_yaml['filename']}:{workflow_line_number} (job {idx + 1})"
)
matrix_job["origin"] = get_matrix_job_origin(
matrix_job, workflow_name, workflow_location
)
matrix_job["origin"] = matrix_yaml.get("project_workflow_origins", {}).get(
(workflow_name, idx)
) or get_matrix_job_origin(matrix_job, workflow_name, workflow_location)

# Fill in default values, explode lists.
matrix_jobs = preprocess_matrix_jobs(matrix_jobs, is_exclusion_matrix)
Expand Down Expand Up @@ -1548,6 +1550,53 @@ def print_devcontainer_info(args):
print(json.dumps(devcontainer_json, indent=2))


def load_project_workflows(matrix, matrix_file):
"""Append optional project-owned workflow rows and retain their source locations."""
allowed_workflows = {
"pull_request",
"pull_request_lite",
"pull_request_fork",
"nightly",
"weekly",
}
origins = {}
for project, config in matrix["projects"].items():
workflows_file = config.get("workflows_file")
if workflows_file is None:
continue
path = Path(matrix_file).parent / workflows_file
if not path.exists():
continue
workflows = yaml.safe_load(path.read_text(encoding="utf-8"))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
if not isinstance(workflows, dict):
raise ValueError(f"{path}: expected a mapping of workflows to job lists")
for workflow_name, jobs in workflows.items():
if (
workflow_name not in allowed_workflows
or workflow_name not in matrix["workflows"]
):
raise ValueError(
f"{path}: unsupported project workflow '{workflow_name}'"
)
if not isinstance(jobs, list):
raise ValueError(
f"{path}: workflow '{workflow_name}' must contain a job list"
)
existing_jobs = matrix["workflows"][workflow_name] or []
line_number = find_workflow_line_number(workflow_name, str(path))
for idx, job in enumerate(jobs):
if not isinstance(job, dict) or job.get("project") != project:
raise ValueError(
f"{path}: every job must select only project '{project}'"
)
location = f"{path}:{line_number} (job {idx + 1})"
origins[(workflow_name, len(existing_jobs) + idx)] = (
get_matrix_job_origin(job, workflow_name, location, str(path))
)
matrix["workflows"][workflow_name] = existing_jobs + jobs
return origins


def preprocess_matrix_yaml(matrix):
# Numeric YAML keys lose the spelling of dotted versions.
if not all(isinstance(version, str) for version in matrix["ctk_versions"]):
Expand Down Expand Up @@ -1616,6 +1665,9 @@ def main():
matrix_yaml = yaml.safe_load(f)
matrix_yaml = preprocess_matrix_yaml(matrix_yaml)
matrix_yaml["filename"] = args.matrix_file
matrix_yaml["project_workflow_origins"] = load_project_workflows(
matrix_yaml, args.matrix_file
)

if args.workflows:
print_gha_workflow(args)
Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/ci-workflow-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ jobs:
echo "pr_number=${{ fromJSON(steps.get-pr-info.outputs.pr-info).number }}" | tee -a "${GITHUB_OUTPUT}"
git fetch origin "${BASE_SHA}" -q
echo "merge_base_sha=$(git merge-base "${BASE_SHA}" "${GITHUB_SHA}")" | tee -a "${GITHUB_OUTPUT}"
- name: Inspect compile-time benchmark inputs
id: inspect-compile-time-changes
env:
BASE_SHA: ${{ steps.export-pr-info.outputs.base_sha }}
run: python3 ci/inspect_compile_time_changes.py --refs "${BASE_SHA}" "${GITHUB_SHA}"
- name: Build benchmark dispatch matrix
id: build-bench-matrix
run: |
Expand Down Expand Up @@ -109,6 +114,15 @@ jobs:
exit 0
fi

if [[ "${{ steps.inspect-compile-time-changes.outputs.compile_time_changed }}" != "true" ]]; then
echo "Public headers and compile-time benchmark inputs are unchanged; skipping dispatch."
{
echo "compile_time_enabled=false"
echo 'compile_time_matrix={"include":[]}'
} >> "${GITHUB_OUTPUT}"
exit 0
fi

compile_time_matrix_json="$(./ci/compile_time/parse_matrix.py "ci/matrix.yaml" --workflow pull_request)"
compile_time_enabled="$(
jq -r 'if (.include // []) == [] then "false" else "true" end' \
Expand Down
67 changes: 67 additions & 0 deletions ci/inspect_compile_time_changes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Select PR compile-time benchmarks from their inputs before allocating runners."""

from __future__ import annotations

from typing import Sequence

import inspect_changes

# The benchmark builds public headers, not Python packages or C++ library tests.
# Reuse their ownership from the matrix classifier.
PUBLIC_PROJECTS = ("libcudacxx_public", "cub_public", "thrust_public")
BENCHMARK_INPUTS = (
r"ci/compile_time/",
r"ci/build_compile_time_bench\.sh$",
r"\.github/workflows/compile-time-bench\.yml$",
)


def compile_time_changed(
config: inspect_changes.Config,
dirty_files: Sequence[str],
ignored_files: Sequence[str],
) -> bool:
# Include benchmark inputs ignored by the main matrix, while continuing to
# ignore documentation-only edits.
all_files = [
path for path in (*dirty_files, *ignored_files) if not path.endswith(".md")
]
inputs = inspect_changes.compile_patterns(BENCHMARK_INPUTS)
benchmark_changed = any(
inspect_changes.matches_any(inputs, path) for path in all_files
)
project_files = inspect_changes.build_project_dirty_map(config, dirty_files)

# Preserve the classifier's conservative fallback for unknown infrastructure.
infrastructure_changed = bool(project_files[inspect_changes.CORE_PROJECT_KEY])
public_changed = any(project_files[key] for key in PUBLIC_PROJECTS)
# Package config, install rules, and header-test generation live beside the
# public headers. Their changes also affect compile-time data.
cmake_inputs = inspect_changes.compile_patterns(
(
r"(libcudacxx|cub|thrust|cudax)/(cmake/|CMakeLists\.txt$)",
r"cccl-version\.json$",
)
)
cmake_changed = any(
inspect_changes.matches_any(cmake_inputs, path) for path in all_files
)
return (
infrastructure_changed or public_changed or cmake_changed or benchmark_changed
)


def main(argv: Sequence[str] | None = None) -> int:
args = inspect_changes.parse_args(argv)
config = inspect_changes.load_config(inspect_changes.CONFIG_PATH)
dirty_files, ignored_files = inspect_changes.collect_dirty_files(args, config)
enabled = compile_time_changed(config, dirty_files, ignored_files)
inspect_changes.write_output("compile_time_changed", str(enabled).lower())
return 0


if __name__ == "__main__":
raise SystemExit(main())
8 changes: 8 additions & 0 deletions ci/matrix.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,11 @@ jobs:
test_py_coop_numba_mlir_compile: { name: "Compile cuda.coop Numba-CUDA-MLIR (GPU hidden)", gpu: false, needs: 'build', force_producer_ctk: "pybuild", invoke: { prefix: 'test', args: '-stage numba-mlir-compile'} }
test_py_coop_numba_mlir_runtime: { name: "Test cuda.coop Numba-CUDA-MLIR (L4)", gpu: true, needs: 'build', force_producer_ctk: "pybuild", invoke: { prefix: 'test', args: '-stage numba-mlir-runtime'} }

# Standalone CUTLASS compiler tests build and qualify the runtime wheel.
test_py_coop_cutlass_host: { name: "Test cuda.coop.cutlass (host)", gpu: false, invoke: { prefix: 'test', args: '-stage host'} }
test_py_coop_cutlass_compile: { name: "Compile cuda.coop.cutlass (GPU hidden)", gpu: false, invoke: { prefix: 'test', args: '-stage compile'} }
test_py_coop_cutlass_runtime: { name: "Test cuda.coop.cutlass", gpu: true, needs: 'test_py_coop_cutlass_compile', invoke: { prefix: 'test', args: '-stage runtime'} }

# Run jobs for 'target' project (ci/util/build_and_test_targets.sh):
run_cpu: { gpu: false }
run_gpu: { gpu: true }
Expand Down Expand Up @@ -948,6 +953,9 @@ projects:
test: ['test_py_compute_minimal']
cuda_coop_python:
name: "Python (cuda.coop)"
cuda_coop_cutlass:
name: "Python (cuda.coop.cutlass)"
workflows_file: ../python/cuda_coop_cutlass/ci/matrix.yaml
cccl_c_parallel:
name: 'CCCL C Parallel'
stds: [20]
Expand Down
18 changes: 18 additions & 0 deletions ci/project_files_and_dependencies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,23 @@ projects:
- "^pyproject\\.toml$"
exclude_regexes: []

cuda_coop_cutlass:
name: "cuda.coop.cutlass"
matrix_project: "cuda_coop_cutlass"
lite_dependencies: [libcudacxx_public, cub_public, thrust_public]
full_dependencies: []
include_regexes:
- 'python/cuda_coop_cutlass/'
- 'ci/(build|test)_cuda_coop_cutlass\.sh$'
- 'ci/windows/build_cuda_coop_cutlass\.ps1$'
- 'ci/cuda_coop_cutlass_requirements\.txt$'
- 'ci/validate_cuda_coop_cutlass_artifacts\.py$'
- 'ci/test/test_cuda_coop_cutlass_ci\.py$'
- '^LICENSE$'
- '^cub/LICENSE\.TXT$'
- '^libcudacxx/LICENSE\.TXT$'
- '^thrust/LICENSE$'

packaging:
name: "CCCL Packaging"
matrix_project: "packaging"
Expand All @@ -213,6 +230,7 @@ projects:
- cudax_internal
- cudax_public
full_dependencies: []
exclude_project_files: [cuda_coop_cutlass]
include_regexes:
- "examples/"
- "test/cmake/"
Expand Down
2 changes: 1 addition & 1 deletion ci/test/inspect_changes/core_dirty.output
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
FULL_BUILD=libcudacxx cub thrust cudax cccl_c_parallel cccl_c_parallel_v2 python_v2 python_tsan cccl_c_stf python cuda_coop_python packaging stdpar nvbench_helper nvrtcc tidy
FULL_BUILD=libcudacxx cub thrust cudax cccl_c_parallel cccl_c_parallel_v2 python_v2 python_tsan cccl_c_stf python cuda_coop_python cuda_coop_cutlass packaging stdpar nvbench_helper nvrtcc tidy
LITE_BUILD=
1 change: 1 addition & 0 deletions ci/test/inspect_changes/cuda_coop_cutlass.dirty_files
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
python/cuda_coop_cutlass/cuda/coop/cutlass/_compiler/_runtime.py
2 changes: 2 additions & 0 deletions ci/test/inspect_changes/cuda_coop_cutlass.output
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FULL_BUILD=cuda_coop_cutlass
LITE_BUILD=
2 changes: 1 addition & 1 deletion ci/test/inspect_changes/cuda_coop_python_license.output
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
FULL_BUILD=cuda_coop_python
FULL_BUILD=cuda_coop_python cuda_coop_cutlass
LITE_BUILD=
2 changes: 1 addition & 1 deletion ci/test/inspect_changes/libcudacxx_both.output
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
FULL_BUILD=libcudacxx tidy
LITE_BUILD=cub thrust cudax cccl_c_parallel cccl_c_parallel_v2 python_v2 python_tsan cccl_c_stf python cuda_coop_python packaging stdpar nvbench_helper
LITE_BUILD=cub thrust cudax cccl_c_parallel cccl_c_parallel_v2 python_v2 python_tsan cccl_c_stf python cuda_coop_python cuda_coop_cutlass packaging stdpar nvbench_helper
2 changes: 1 addition & 1 deletion ci/test/inspect_changes/libcudacxx_public_only.output
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
FULL_BUILD=libcudacxx tidy
LITE_BUILD=cub thrust cudax cccl_c_parallel cccl_c_parallel_v2 python_v2 python_tsan cccl_c_stf python cuda_coop_python packaging stdpar nvbench_helper
LITE_BUILD=cub thrust cudax cccl_c_parallel cccl_c_parallel_v2 python_v2 python_tsan cccl_c_stf python cuda_coop_python cuda_coop_cutlass packaging stdpar nvbench_helper
2 changes: 1 addition & 1 deletion ci/test/inspect_changes/libcudacxx_thrust.output
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
FULL_BUILD=libcudacxx thrust tidy
LITE_BUILD=cub cudax cccl_c_parallel cccl_c_parallel_v2 python_v2 python_tsan cccl_c_stf python cuda_coop_python packaging stdpar nvbench_helper
LITE_BUILD=cub cudax cccl_c_parallel cccl_c_parallel_v2 python_v2 python_tsan cccl_c_stf python cuda_coop_python cuda_coop_cutlass packaging stdpar nvbench_helper
Loading
Loading