feat(cccl): integrate missing CCCL directories — python/, ci/, .agent/, docs/, test/
Sparse-checkout from NVIDIA/cccl main branch to complete cccl_upstream: Added: - python/cuda_cccl/ (226 files) — Python bindings for device-level algorithms Critical for muh toolchain: cuda.compute.reduce_into, scan, radix_sort, etc. Includes 204 .py files with full test coverage for all 27 algorithms - ci/ (163 files) — Build/test infrastructure build_cub.sh, test_cub.sh, build_and_test_targets.sh, matrix.yaml Directly maps to our [INFRA-CI] and [INFRA-BUILD] items - .agent/skills/ (7 files) — NVIDIA's own agent skills for CCCL cccl-style/SKILL.md, cccl-test/SKILL.md, sass-diff/SKILL.md - docs/ (491 files) — Official CCCL documentation CI references, CMake guides, Python compute docs, libcudacxx PTX docs - test/ (12 files) — Top-level integration tests (cuda_smoke, stdpar) - Root configs: .clang-format, .clang-tidy, CONTRIBUTING.md, pyproject.toml - CLAUDE.md symlink → AGENTS.md (NVIDIA's standard) cccl_upstream now mirrors full NVIDIA/cccl structure: Before: 42M (cub + thrust + libcudacxx + cudax + c + examples + benchmarks) After: 53M (+python +ci +docs +.agent +test +configs) This completes the CCCL base needed for: - [muh-bench] items: ci/util/build_and_test_targets.sh for targeted builds - [CCCL-verify] items: python/cuda_cccl/tests/ as reference implementations - [CCCL-test] items: ci/test_cub.sh, ci/test_thrust.sh - Agent workflow: .agent/skills/ for consistent style and test patterns
This commit is contained in:
1
cccl_upstream/ci/CMakeLists.txt
Normal file
1
cccl_upstream/ci/CMakeLists.txt
Normal file
@@ -0,0 +1 @@
|
||||
add_subdirectory("test")
|
||||
70
cccl_upstream/ci/bench.template.yaml
Normal file
70
cccl_upstream/ci/bench.template.yaml
Normal file
@@ -0,0 +1,70 @@
|
||||
# # CCCL PR benchmark request config.
|
||||
#
|
||||
# ## Overview:
|
||||
#
|
||||
# This file is used to request benchmark comparisons in PR CI.
|
||||
#
|
||||
# This file must match ci/bench.template.yaml to merge.
|
||||
# CI branch protections will fail if they differ. Reset before merging.
|
||||
#
|
||||
# To update the defaults (e.g. new GPU pools), modify both this file and
|
||||
# ci/bench.template.yaml together in the same PR.
|
||||
#
|
||||
# !! Strongly consider appending the following to your **commit messages** while benchmarking. !!
|
||||
# This prevents wasteful non-benchmark CI jobs if they are not needed.
|
||||
#
|
||||
# [bench-only]
|
||||
#
|
||||
# To skip compile-time benchmark telemetry on unrelated changes, use:
|
||||
#
|
||||
# [skip-compile-time-bench]
|
||||
#
|
||||
# ## Quick start:
|
||||
#
|
||||
# 1. Add one or more benchmark regexes under benchmarks.filters.cub and/or
|
||||
# benchmarks.filters.python.
|
||||
# 2. Enable at least one GPU by uncommenting or adding entries in benchmarks.gpus.
|
||||
# 3. Push and inspect the dispatched benchmark jobs/artifacts.
|
||||
# 4. Remove/reset benchmark-request edits before final merge.
|
||||
|
||||
benchmarks:
|
||||
|
||||
# Benchmark filters grouped by project.
|
||||
filters:
|
||||
# CUB C++ benchmark filters (regex matched against ninja target names).
|
||||
cub:
|
||||
# Examples:
|
||||
# - '^cub\.bench\.for_each\.base'
|
||||
# - '^cub\.bench\.reduce\.(sum|min)\.'
|
||||
|
||||
# Python benchmark filters (regex matched against paths under benchmarks/).
|
||||
python:
|
||||
# Examples:
|
||||
# - 'compute/reduce/sum\.py'
|
||||
# - 'compute/transform/.*\.py'
|
||||
|
||||
# Select GPUs. These are limited and shared, be intentional and conservative.
|
||||
gpus:
|
||||
# - "t4" # sm_75, 16 GB
|
||||
# - "rtx2080" # sm_75, 8 GB
|
||||
# - "rtxa6000" # sm_86, 48 GB
|
||||
# - "l4" # sm_89, 24 GB
|
||||
# - "rtx4090" # sm_89, 24 GB
|
||||
# - "h100" # sm_90, 80 GB
|
||||
# - "rtxpro6000" # sm_120
|
||||
|
||||
# Extra .devcontainer/launch.sh -d args
|
||||
# launch_args: "--cuda 13.3 --host gcc14"
|
||||
launch_args: "" # Latest nvcc + gcc
|
||||
|
||||
# Advanced:
|
||||
base_ref: "origin/main"
|
||||
test_ref: "HEAD"
|
||||
arch: "native"
|
||||
nvbench_args: >-
|
||||
--timeout 30
|
||||
--skip-time 15e-6
|
||||
--stopping-criterion entropy
|
||||
--throttle-threshold 90
|
||||
--throttle-recovery-delay 0.15
|
||||
nvbench_compare_args: ""
|
||||
70
cccl_upstream/ci/bench.yaml
Normal file
70
cccl_upstream/ci/bench.yaml
Normal file
@@ -0,0 +1,70 @@
|
||||
# # CCCL PR benchmark request config.
|
||||
#
|
||||
# ## Overview:
|
||||
#
|
||||
# This file is used to request benchmark comparisons in PR CI.
|
||||
#
|
||||
# This file must match ci/bench.template.yaml to merge.
|
||||
# CI branch protections will fail if they differ. Reset before merging.
|
||||
#
|
||||
# To update the defaults (e.g. new GPU pools), modify both this file and
|
||||
# ci/bench.template.yaml together in the same PR.
|
||||
#
|
||||
# !! Strongly consider appending the following to your **commit messages** while benchmarking. !!
|
||||
# This prevents wasteful non-benchmark CI jobs if they are not needed.
|
||||
#
|
||||
# [bench-only]
|
||||
#
|
||||
# To skip compile-time benchmark telemetry on unrelated changes, use:
|
||||
#
|
||||
# [skip-compile-time-bench]
|
||||
#
|
||||
# ## Quick start:
|
||||
#
|
||||
# 1. Add one or more benchmark regexes under benchmarks.filters.cub and/or
|
||||
# benchmarks.filters.python.
|
||||
# 2. Enable at least one GPU by uncommenting or adding entries in benchmarks.gpus.
|
||||
# 3. Push and inspect the dispatched benchmark jobs/artifacts.
|
||||
# 4. Remove/reset benchmark-request edits before final merge.
|
||||
|
||||
benchmarks:
|
||||
|
||||
# Benchmark filters grouped by project.
|
||||
filters:
|
||||
# CUB C++ benchmark filters (regex matched against ninja target names).
|
||||
cub:
|
||||
# Examples:
|
||||
# - '^cub\.bench\.for_each\.base'
|
||||
# - '^cub\.bench\.reduce\.(sum|min)\.'
|
||||
|
||||
# Python benchmark filters (regex matched against paths under benchmarks/).
|
||||
python:
|
||||
# Examples:
|
||||
# - 'compute/reduce/sum\.py'
|
||||
# - 'compute/transform/.*\.py'
|
||||
|
||||
# Select GPUs. These are limited and shared, be intentional and conservative.
|
||||
gpus:
|
||||
# - "t4" # sm_75, 16 GB
|
||||
# - "rtx2080" # sm_75, 8 GB
|
||||
# - "rtxa6000" # sm_86, 48 GB
|
||||
# - "l4" # sm_89, 24 GB
|
||||
# - "rtx4090" # sm_89, 24 GB
|
||||
# - "h100" # sm_90, 80 GB
|
||||
# - "rtxpro6000" # sm_120
|
||||
|
||||
# Extra .devcontainer/launch.sh -d args
|
||||
# launch_args: "--cuda 13.3 --host gcc14"
|
||||
launch_args: "" # Latest nvcc + gcc
|
||||
|
||||
# Advanced:
|
||||
base_ref: "origin/main"
|
||||
test_ref: "HEAD"
|
||||
arch: "native"
|
||||
nvbench_args: >-
|
||||
--timeout 30
|
||||
--skip-time 15e-6
|
||||
--stopping-criterion entropy
|
||||
--throttle-threshold 90
|
||||
--throttle-recovery-delay 0.15
|
||||
nvbench_compare_args: ""
|
||||
82
cccl_upstream/ci/bench/README.md
Normal file
82
cccl_upstream/ci/bench/README.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Benchmark Compare Scripts
|
||||
|
||||
This directory contains the scripts used by `.github/workflows/bench.yml` to compare benchmark results between two code states.
|
||||
|
||||
## Scripts
|
||||
|
||||
- `ci/bench/bench.sh`: CI-oriented wrapper that calls `ci/bench/compare_git_refs.sh`.
|
||||
- `ci/bench/compare_git_refs.sh`: checks out `<base-ref>` and `<test-ref>` in temporary worktrees, then forwards all remaining args to `ci/bench/compare_paths.sh`.
|
||||
- `ci/bench/compare_paths.sh`: configures/builds/runs CUB benchmarks and/or Python benchmarks in two source trees and runs comparison tools on produced JSON outputs.
|
||||
- `ci/bench/parse_bench_matrix.sh`: parses `ci/bench.yaml` and emits a dispatch matrix JSON object for `.github/workflows/bench.yml`.
|
||||
|
||||
## Usage
|
||||
|
||||
Compare CUB benchmarks between two refs:
|
||||
|
||||
```bash
|
||||
"./ci/bench/bench.sh" "origin/main" "HEAD" \
|
||||
--cub-filter "^cub\\.bench\\.copy\\.memcpy\\.base$"
|
||||
```
|
||||
|
||||
Compare Python benchmarks between two refs:
|
||||
|
||||
```bash
|
||||
"./ci/bench/bench.sh" "origin/main" "HEAD" \
|
||||
--python-filter "compute/reduce/sum\\.py"
|
||||
```
|
||||
|
||||
Run both CUB and Python benchmarks:
|
||||
|
||||
```bash
|
||||
"./ci/bench/bench.sh" \
|
||||
"origin/main" \
|
||||
"HEAD" \
|
||||
--arch "native" \
|
||||
--nvbench-args "..." \
|
||||
--cub-filter "^cub\\.bench\\.reduce\\..*$" \
|
||||
--python-filter "compute/reduce/sum\\.py"
|
||||
```
|
||||
|
||||
Compare already checked-out trees:
|
||||
|
||||
```bash
|
||||
"./ci/bench/compare_paths.sh" \
|
||||
"/path/to/base/cccl" \
|
||||
"/path/to/test/cccl" \
|
||||
--arch "native" \
|
||||
--cub-filter "^cub\\.bench\\.copy\\.memcpy\\.base$" \
|
||||
--python-filter "compute/transform/.*\\.py"
|
||||
```
|
||||
|
||||
## Workflow Inputs
|
||||
|
||||
In `.github/workflows/bench.yml`:
|
||||
|
||||
- If `raw_args` is non-empty, it is parsed and passed directly to `ci/bench/bench.sh`.
|
||||
- Otherwise, args are assembled from `base_ref`, `test_ref`, `arch`, `cub_filters`, `python_filters`, `nvbench_args`, and `nvbench_compare_args`.
|
||||
- CUB filters are passed as `--cub-filter` flags. Python filters are passed as `--python-filter` flags.
|
||||
- Malformed quoted input (for example unmatched quotes) fails the workflow step.
|
||||
|
||||
## Python Benchmarks
|
||||
|
||||
Python benchmarks live under `python/cuda_cccl/benchmarks/` and use `cuda.bench` (the Python nvbench bindings). Each benchmark script outputs nvbench-compatible JSON.
|
||||
|
||||
For Python benchmarks, `compare_paths.sh`:
|
||||
|
||||
1. Creates isolated virtual environments for base and test trees.
|
||||
2. Installs `cuda-cccl[bench-cuXX]` (editable, from each worktree), which pulls in `cuda-bench`, `cupy`, and all other benchmark dependencies.
|
||||
3. Runs matching benchmark scripts in each venv.
|
||||
4. Compares results using `nvbench-compare` (installed with `cuda-bench`).
|
||||
|
||||
Python filters are regex patterns matched against relative paths under `python/cuda_cccl/benchmarks/`, for example:
|
||||
- `compute/reduce/sum\.py` — single benchmark
|
||||
- `compute/transform/.*\.py` — all transform benchmarks
|
||||
|
||||
## Artifacts
|
||||
|
||||
`compare_paths.sh` writes a run directory under `${CCCL_BENCH_ARTIFACT_ROOT:-$(pwd)/bench-artifacts}` containing:
|
||||
|
||||
- per-target JSON and markdown outputs for base/test runs,
|
||||
- grouped build logs (`build.base.log`, `build.test.log`), per-target run logs, and per-target compare logs (`compare.<target>.log`),
|
||||
- Python venv setup logs (`py.venv.base.log`, `py.venv.test.log`),
|
||||
- `summary.md` with run metadata and per-target collapsible full compare reports.
|
||||
26
cccl_upstream/ci/bench/bench.sh
Executable file
26
cccl_upstream/ci/bench/bench.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
die() {
|
||||
local message="$1"
|
||||
local code="${2:-2}"
|
||||
echo "${message}" >&2
|
||||
exit "${code}"
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 <base-ref> <test-ref> [compare_paths args...]
|
||||
|
||||
Wrapper for ci/bench/compare_git_refs.sh.
|
||||
EOF
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
bench_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
"${bench_dir}/compare_git_refs.sh" "$@"
|
||||
137
cccl_upstream/ci/bench/compare_git_refs.sh
Executable file
137
cccl_upstream/ci/bench/compare_git_refs.sh
Executable file
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
die() {
|
||||
local message="$1"
|
||||
local code="${2:-2}"
|
||||
echo "${message}" >&2
|
||||
exit "${code}"
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 <base-ref> <test-ref> [compare_paths args...]
|
||||
|
||||
Compare benchmark performance between two git refs from the current CCCL repo.
|
||||
Each ref is checked out in an isolated worktree and compared via compare_paths.sh.
|
||||
EOF
|
||||
}
|
||||
|
||||
display_label_for_ref() {
|
||||
local ref="$1"
|
||||
local short_sha="$2"
|
||||
if [[ "${ref}" =~ ^[0-9a-fA-F]{7,40}$ ]]; then
|
||||
printf "%s" "${short_sha}"
|
||||
return 0
|
||||
fi
|
||||
if [[ "${ref}" == "HEAD" ]]; then
|
||||
printf "%s" "${short_sha}"
|
||||
return 0
|
||||
fi
|
||||
printf "%s" "${ref}"
|
||||
}
|
||||
|
||||
resolve_ref_to_commit() {
|
||||
local repo_root="$1"
|
||||
local ref="$2"
|
||||
local remote=""
|
||||
local branch=""
|
||||
local alternate_ref=""
|
||||
|
||||
if git -C "${repo_root}" rev-parse --verify "${ref}^{commit}" >/dev/null 2>&1; then
|
||||
git -C "${repo_root}" rev-parse --verify "${ref}^{commit}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "${ref}" =~ ^([^/]+)/(.+)$ ]] && git -C "${repo_root}" remote get-url "${BASH_REMATCH[1]}" >/dev/null 2>&1; then
|
||||
remote="${BASH_REMATCH[1]}"
|
||||
branch="${BASH_REMATCH[2]}"
|
||||
git -C "${repo_root}" fetch --no-tags "${remote}" \
|
||||
"+refs/heads/${branch}:refs/remotes/${remote}/${branch}" >/dev/null 2>&1 || true
|
||||
elif [[ "${ref}" != refs/* ]]; then
|
||||
# Try unqualified refs as origin branches/tags.
|
||||
git -C "${repo_root}" fetch --no-tags origin \
|
||||
"+refs/heads/${ref}:refs/remotes/origin/${ref}" >/dev/null 2>&1 || true
|
||||
git -C "${repo_root}" fetch --no-tags origin \
|
||||
"refs/tags/${ref}:refs/tags/${ref}" >/dev/null 2>&1 || true
|
||||
alternate_ref="origin/${ref}"
|
||||
fi
|
||||
|
||||
# Final best-effort fetch for raw refs (e.g. refs/pull/* or specific SHAs).
|
||||
git -C "${repo_root}" fetch --no-tags origin "${ref}" >/dev/null 2>&1 || true
|
||||
|
||||
if git -C "${repo_root}" rev-parse --verify "${ref}^{commit}" >/dev/null 2>&1; then
|
||||
git -C "${repo_root}" rev-parse --verify "${ref}^{commit}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n "${alternate_ref}" ]] && git -C "${repo_root}" rev-parse --verify "${alternate_ref}^{commit}" >/dev/null 2>&1; then
|
||||
git -C "${repo_root}" rev-parse --verify "${alternate_ref}^{commit}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$#" -lt 2 ]]; then
|
||||
usage
|
||||
exit 2
|
||||
fi
|
||||
|
||||
base_ref="$1"
|
||||
test_ref="$2"
|
||||
shift 2
|
||||
compare_paths_args=("$@")
|
||||
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
repo_root="$(cd "${ci_dir}/../.." && pwd)"
|
||||
|
||||
if [[ ! -f "${repo_root}/cccl-version.json" ]]; then
|
||||
die "This script must run from a CCCL checkout."
|
||||
fi
|
||||
|
||||
if ! base_commit="$(resolve_ref_to_commit "${repo_root}" "${base_ref}")"; then
|
||||
die "Unable to resolve base ref: ${base_ref}"
|
||||
fi
|
||||
if ! test_commit="$(resolve_ref_to_commit "${repo_root}" "${test_ref}")"; then
|
||||
die "Unable to resolve test ref: ${test_ref}"
|
||||
fi
|
||||
|
||||
base_short_sha="$(git -C "${repo_root}" rev-parse --short=12 "${base_commit}")"
|
||||
test_short_sha="$(git -C "${repo_root}" rev-parse --short=12 "${test_commit}")"
|
||||
base_label="$(display_label_for_ref "${base_ref}" "${base_short_sha}")"
|
||||
test_label="$(display_label_for_ref "${test_ref}" "${test_short_sha}")"
|
||||
|
||||
worktree_root="$(mktemp -d "/tmp/cccl-bench-worktrees-XXXXXX")"
|
||||
base_path="${worktree_root}/base"
|
||||
test_path="${worktree_root}/test"
|
||||
|
||||
cleanup() {
|
||||
git -C "${repo_root}" worktree remove --force "${base_path}" >/dev/null 2>&1 || true
|
||||
git -C "${repo_root}" worktree remove --force "${test_path}" >/dev/null 2>&1 || true
|
||||
rm -rf "${worktree_root}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "Creating worktree for base ref: ${base_ref}"
|
||||
git -C "${repo_root}" worktree add --detach "${base_path}" "${base_commit}" >/dev/null
|
||||
|
||||
echo "Creating worktree for test ref: ${test_ref}"
|
||||
git -C "${repo_root}" worktree add --detach "${test_path}" "${test_commit}" >/dev/null
|
||||
|
||||
compare_cmd=(
|
||||
"${ci_dir}/compare_paths.sh"
|
||||
"${base_path}"
|
||||
"${test_path}"
|
||||
"${compare_paths_args[@]}"
|
||||
)
|
||||
|
||||
CCCL_BENCH_BASE_LABEL="${base_label}" \
|
||||
CCCL_BENCH_TEST_LABEL="${test_label}" \
|
||||
"${compare_cmd[@]}"
|
||||
1048
cccl_upstream/ci/bench/compare_paths.sh
Executable file
1048
cccl_upstream/ci/bench/compare_paths.sh
Executable file
File diff suppressed because it is too large
Load Diff
80
cccl_upstream/ci/bench/parse_bench_matrix.sh
Executable file
80
cccl_upstream/ci/bench/parse_bench_matrix.sh
Executable file
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
die() {
|
||||
local message="$1"
|
||||
local code="${2:-2}"
|
||||
echo "${message}" >&2
|
||||
exit "${code}"
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 [bench-yaml-path]
|
||||
|
||||
Parse ci/bench.yaml and emit a GitHub Actions strategy matrix JSON object:
|
||||
{"include":[...]}
|
||||
|
||||
Each include entry maps one enabled GPU to a benchmark workflow invocation.
|
||||
EOF
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
bench_yaml_path="${1:-ci/bench.yaml}"
|
||||
if [[ ! -f "${bench_yaml_path}" ]]; then
|
||||
die "Benchmark config file not found: ${bench_yaml_path}"
|
||||
fi
|
||||
|
||||
command -v "yq" >/dev/null 2>&1 || die "'yq' is required to parse ${bench_yaml_path}."
|
||||
command -v "jq" >/dev/null 2>&1 || die "'jq' is required to build the dispatch matrix."
|
||||
|
||||
if ! bench_cfg_json="$(yq -o=json '.benchmarks // {}' "${bench_yaml_path}" 2>&1)"; then
|
||||
die "Failed to parse ${bench_yaml_path} as YAML: ${bench_cfg_json}"
|
||||
fi
|
||||
|
||||
# Extract CUB and Python filter arrays (default to empty arrays).
|
||||
cub_filters_json="$(jq -c '.filters.cub // []' <<<"${bench_cfg_json}")"
|
||||
python_filters_json="$(jq -c '.filters.python // []' <<<"${bench_cfg_json}")"
|
||||
|
||||
has_cub_filters="$(jq -e 'type == "array" and length > 0 and all(.[]; type == "string")' <<<"${cub_filters_json}" >/dev/null 2>&1 && echo true || echo false)"
|
||||
has_python_filters="$(jq -e 'type == "array" and length > 0 and all(.[]; type == "string")' <<<"${python_filters_json}" >/dev/null 2>&1 && echo true || echo false)"
|
||||
|
||||
if [[ "${has_cub_filters}" != "true" && "${has_python_filters}" != "true" ]]; then
|
||||
die "${bench_yaml_path} must define at least one string entry in benchmarks.filters.cub or benchmarks.filters.python."
|
||||
fi
|
||||
|
||||
cub_filters_arg=""
|
||||
if [[ "${has_cub_filters}" == "true" ]]; then
|
||||
cub_filters_arg="$(jq -r '.filters.cub | map(@sh) | join(" ")' <<<"${bench_cfg_json}")"
|
||||
fi
|
||||
|
||||
python_filters_arg=""
|
||||
if [[ "${has_python_filters}" == "true" ]]; then
|
||||
python_filters_arg="$(jq -r '.filters.python | map(@sh) | join(" ")' <<<"${bench_cfg_json}")"
|
||||
fi
|
||||
|
||||
jq -cn \
|
||||
--argjson cfg "${bench_cfg_json}" \
|
||||
--arg cub_filters "${cub_filters_arg}" \
|
||||
--arg python_filters "${python_filters_arg}" \
|
||||
'{
|
||||
"include": [
|
||||
($cfg.gpus // [])[] as $gpu
|
||||
| {
|
||||
"gpu": $gpu,
|
||||
"launch_args": ($cfg.launch_args // ""),
|
||||
"arch": ($cfg.arch // "native"),
|
||||
"base_ref": ($cfg.base_ref // "origin/main"),
|
||||
"test_ref": ($cfg.test_ref // "HEAD"),
|
||||
"cub_filters": $cub_filters,
|
||||
"python_filters": $python_filters,
|
||||
"nvbench_args": ($cfg.nvbench_args // ""),
|
||||
"nvbench_compare_args": ($cfg.nvbench_compare_args // "")
|
||||
}
|
||||
]
|
||||
}'
|
||||
16
cccl_upstream/ci/build_cccl_c_parallel.sh
Executable file
16
cccl_upstream/ci/build_cccl_c_parallel.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck source=ci/build_common.sh
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/build_common.sh"
|
||||
|
||||
print_environment_details
|
||||
|
||||
PRESET="cccl-c-parallel"
|
||||
|
||||
CMAKE_OPTIONS=("-DCMAKE_CXX_STANDARD=${CXX_STANDARD}" "-DCMAKE_CUDA_STANDARD=${CXX_STANDARD}")
|
||||
|
||||
configure_and_build_preset "CCCL C Parallel Library" "$PRESET" "${CMAKE_OPTIONS[@]}"
|
||||
|
||||
print_time_summary
|
||||
16
cccl_upstream/ci/build_cccl_c_stf.sh
Executable file
16
cccl_upstream/ci/build_cccl_c_stf.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck source=ci/build_common.sh
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/build_common.sh"
|
||||
|
||||
print_environment_details
|
||||
|
||||
PRESET="cccl-c-stf"
|
||||
|
||||
CMAKE_OPTIONS=("-DCMAKE_CXX_STANDARD=${CXX_STANDARD}" "-DCMAKE_CUDA_STANDARD=${CXX_STANDARD}")
|
||||
|
||||
configure_and_build_preset "CCCL C CUDASTF Library" "$PRESET" "${CMAKE_OPTIONS[@]}"
|
||||
|
||||
print_time_summary
|
||||
501
cccl_upstream/ci/build_common.sh
Executable file
501
cccl_upstream/ci/build_common.sh
Executable file
@@ -0,0 +1,501 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
echo "This script must be sourced, not executed directly." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure the script is being executed in its containing directory
|
||||
cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )";
|
||||
|
||||
# Script defaults
|
||||
VERBOSE=${VERBOSE:-}
|
||||
HOST_COMPILER=${CXX:-g++} # $CXX if set, otherwise `g++`
|
||||
CXX_STANDARD=17
|
||||
CUDA_COMPILER=${CUDACXX:-nvcc} # $CUDACXX if set, otherwise `nvcc`
|
||||
CUDA_ARCHS= # Empty, use presets by default.
|
||||
GLOBAL_CMAKE_OPTIONS=()
|
||||
DISABLE_CUB_BENCHMARKS= # Enable to force-disable building CUB benchmarks.
|
||||
PEDANTIC=${PEDANTIC:-} # Enable strict warnings. Default: on in CI, off locally.
|
||||
CONFIGURE_ONLY=false
|
||||
CTEST_PARALLEL_LEVEL=1
|
||||
|
||||
# Check if the correct number of arguments has been provided
|
||||
function usage {
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo
|
||||
echo "The PARALLEL_LEVEL environment variable controls the amount of build parallelism. Default is the number of cores minus one."
|
||||
echo
|
||||
echo "Options:"
|
||||
echo " -v/-verbose: enable shell echo for debugging"
|
||||
echo " -configure: Only run cmake to configure, do not build or test."
|
||||
echo " -cuda: CUDA compiler (Defaults to \$CUDACXX if set, otherwise nvcc)"
|
||||
echo " -cxx: Host compiler (Defaults to \$CXX if set, otherwise g++)"
|
||||
echo " -std: CUDA/C++ standard (Defaults to 17)"
|
||||
echo " -arch: Target CUDA arches, e.g. \"60-real;70;80-virtual\" (Defaults to value in presets file)"
|
||||
echo " --test-par: CTest parallel level (Defaults to 1)"
|
||||
echo " -pedantic/--pedantic: Enable strict warnings-as-errors and expose CCCL header warnings (default in CI)"
|
||||
echo " -cmake-options: Additional options to pass to CMake"
|
||||
echo
|
||||
echo "Examples:"
|
||||
echo " $ PARALLEL_LEVEL=8 $0"
|
||||
echo " $ PARALLEL_LEVEL=8 $0 -cxx g++-9"
|
||||
echo " $ $0 -cxx clang++-8"
|
||||
echo " $ $0 -configure -arch 80"
|
||||
echo " $ $0 -cxx g++-8 -std 14 -arch 80-real -v -cuda /usr/local/bin/nvcc"
|
||||
echo " $ $0 -cmake-options \"-DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS=-Wfatal-errors\""
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check for required dependencies
|
||||
function check_required_dependencies() {
|
||||
local missing_deps=()
|
||||
|
||||
# Check for essential tools
|
||||
local required_tools=("cmake" "git" "jq" "ninja" "nproc")
|
||||
for tool in "${required_tools[@]}"; do
|
||||
command -v "$tool" &>/dev/null || missing_deps+=("$tool")
|
||||
done
|
||||
|
||||
if [[ ${#missing_deps[@]} -ne 0 ]]; then
|
||||
echo "❌ Error: Missing required dependencies:" >&2
|
||||
printf " • %s\n" "${missing_deps[@]}" >&2
|
||||
echo >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Parse options
|
||||
|
||||
# Copy the args into a temporary array, since we will modify them and
|
||||
# the parent script may still need them.
|
||||
args=("$@")
|
||||
while [[ "${#args[@]}" -ne 0 ]]; do
|
||||
case "${args[0]}" in
|
||||
-v | --verbose | -verbose) VERBOSE=1; args=("${args[@]:1}");;
|
||||
-configure) CONFIGURE_ONLY=true; args=("${args[@]:1}");;
|
||||
-cxx) HOST_COMPILER="${args[1]}"; args=("${args[@]:2}");;
|
||||
-std) CXX_STANDARD="${args[1]}"; args=("${args[@]:2}");;
|
||||
-cuda) CUDA_COMPILER="${args[1]}"; args=("${args[@]:2}");;
|
||||
-arch) CUDA_ARCHS="${args[1]}"; args=("${args[@]:2}");;
|
||||
--test-par) CTEST_PARALLEL_LEVEL="${args[1]}"; args=("${args[@]:2}");;
|
||||
-pedantic | --pedantic) PEDANTIC=1; args=("${args[@]:1}");;
|
||||
-disable-benchmarks) export DISABLE_CUB_BENCHMARKS=1; args=("${args[@]:1}");;
|
||||
-cmake-options)
|
||||
if [[ -n "${args[1]}" ]]; then
|
||||
IFS=' ' read -ra split_args <<< "${args[1]}"
|
||||
GLOBAL_CMAKE_OPTIONS+=("${split_args[@]}")
|
||||
args=("${args[@]:2}")
|
||||
else
|
||||
echo "Error: No arguments provided for -cmake-options"
|
||||
usage
|
||||
# usage will exit 1 for us, so below exit 1 is unreachable, but it does not
|
||||
# hurt and guards against changes in usage.
|
||||
# shellcheck disable=SC2317
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
-h | -help | --help) usage ;;
|
||||
*) echo "Unrecognized option: ${args[0]}"; usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Convert to full paths and validate compilers exist:
|
||||
function validate_and_resolve_compiler() {
|
||||
local compiler_name="$1"
|
||||
local compiler_var="$2"
|
||||
local compiler_path
|
||||
|
||||
compiler_path=$(command -v "${compiler_var}" 2>/dev/null)
|
||||
if [[ -z "$compiler_path" ]]; then
|
||||
echo "❌ Error: ${compiler_name} '${compiler_var}' not found in PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$compiler_path"
|
||||
}
|
||||
|
||||
HOST_COMPILER=$(validate_and_resolve_compiler "Host compiler" "${HOST_COMPILER}")
|
||||
CUDA_COMPILER=$(validate_and_resolve_compiler "CUDA compiler" "${CUDA_COMPILER}")
|
||||
|
||||
if [[ "$(basename "$CUDA_COMPILER")" == nvcc* ]]; then
|
||||
NVCC_VERSION=$("$CUDA_COMPILER" --version | grep "release" | sed 's/.*, V//')
|
||||
# Verify that we have an X.Y.Z version in case the output format changes:
|
||||
if ! [[ "$NVCC_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "❌ Error: Detected nvcc version is not a valid X.Y.Z triple: '$NVCC_VERSION'" >&2
|
||||
echo "$CUDA_COMPILER --version" >&2 || :
|
||||
$CUDA_COMPILER --version >&2 || :
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
if [[ -n "${CUDA_ARCHS}" ]]; then
|
||||
GLOBAL_CMAKE_OPTIONS+=("-DCMAKE_CUDA_ARCHITECTURES=${CUDA_ARCHS}")
|
||||
fi
|
||||
|
||||
# Default to pedantic mode in CI
|
||||
if [[ -z "${PEDANTIC}" && -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
PEDANTIC=1
|
||||
fi
|
||||
|
||||
if [[ -n "${PEDANTIC}" ]]; then
|
||||
GLOBAL_CMAKE_OPTIONS+=("-DCCCL_ENABLE_WERROR=ON" "-DCCCL_ENABLE_PRAGMA_SYSTEM_HEADER=OFF")
|
||||
else
|
||||
GLOBAL_CMAKE_OPTIONS+=("-DCCCL_ENABLE_WERROR=OFF" "-DCCCL_ENABLE_PRAGMA_SYSTEM_HEADER=ON")
|
||||
fi
|
||||
|
||||
if [[ -n "$VERBOSE" ]]; then
|
||||
set -x
|
||||
fi
|
||||
|
||||
# Check for required dependencies
|
||||
check_required_dependencies
|
||||
|
||||
# Begin processing unsets after option parsing
|
||||
set -u
|
||||
|
||||
N_CPUS="$(nproc --all --ignore=1)"
|
||||
readonly N_CPUS
|
||||
readonly PARALLEL_LEVEL="${PARALLEL_LEVEL:=${N_CPUS}}"
|
||||
|
||||
if [[ -z ${CCCL_BUILD_INFIX+x} ]]; then
|
||||
CCCL_BUILD_INFIX=""
|
||||
fi
|
||||
|
||||
mkdir -p ../build
|
||||
# Absolute path to cccl/build
|
||||
BUILD_ROOT=$(cd "../build" && pwd)
|
||||
|
||||
# Absolute path to per-devcontainer build directory
|
||||
BUILD_DIR="$BUILD_ROOT/$CCCL_BUILD_INFIX"
|
||||
|
||||
# The most recent devcontainer build dir will always be symlinked to cccl/build/latest
|
||||
mkdir -p "$BUILD_DIR"
|
||||
rm -f "$BUILD_ROOT"/latest
|
||||
ln -sf "$BUILD_DIR" "$BUILD_ROOT"/latest
|
||||
|
||||
# The more recent preset build dir will always be symlinked to:
|
||||
# cccl/preset-latest
|
||||
function symlink_latest_preset {
|
||||
local PRESET=$1
|
||||
mkdir -p "$BUILD_DIR/$PRESET"
|
||||
rm -f "$BUILD_ROOT/preset-latest"
|
||||
ln -sf "$BUILD_DIR/$PRESET" "$BUILD_ROOT/preset-latest"
|
||||
}
|
||||
|
||||
# Now that BUILD_DIR exists, use readlink to canonicalize the path:
|
||||
BUILD_DIR=$(readlink -f "${BUILD_DIR}")
|
||||
|
||||
# Prepare environment for CMake:
|
||||
export CMAKE_BUILD_PARALLEL_LEVEL="$((PARALLEL_LEVEL > N_CPUS ? N_CPUS : PARALLEL_LEVEL))"
|
||||
|
||||
export CTEST_PARALLEL_LEVEL
|
||||
export CXX="${HOST_COMPILER}"
|
||||
export CUDACXX="${CUDA_COMPILER}"
|
||||
export CUDAHOSTCXX="${HOST_COMPILER}"
|
||||
export CXX_STANDARD
|
||||
|
||||
# shellcheck source=ci/pretty_printing.sh
|
||||
source ./pretty_printing.sh
|
||||
|
||||
# Kill any build / test steps that exceed this time, otherwise CI jobs may be
|
||||
# killed by GHA before they can upload logs / artifacts needed to reproduce the timeout.
|
||||
# Only applies when running inside GitHub Actions.
|
||||
# Note that this is per-build/test limit, not a total timeout for the entire job.
|
||||
: "${CCCL_CI_COMMAND_TIMEOUT:=5.5h}"
|
||||
|
||||
print_environment_details() {
|
||||
begin_group "⚙️ Environment Details"
|
||||
|
||||
echo "free -h:"
|
||||
free -h || :
|
||||
|
||||
echo "nproc=$(nproc || :)"
|
||||
|
||||
echo "pwd=$(pwd)"
|
||||
|
||||
print_var_values \
|
||||
BUILD_DIR \
|
||||
CXX_STANDARD \
|
||||
CXX \
|
||||
CUDACXX \
|
||||
CUDAHOSTCXX \
|
||||
NVCC_VERSION \
|
||||
CMAKE_BUILD_PARALLEL_LEVEL \
|
||||
CTEST_PARALLEL_LEVEL \
|
||||
CCCL_CI_COMMAND_TIMEOUT \
|
||||
CCCL_CUDA_EXTENDED \
|
||||
CCCL_BUILD_INFIX \
|
||||
PEDANTIC \
|
||||
GLOBAL_CMAKE_OPTIONS \
|
||||
TBB_ROOT
|
||||
|
||||
echo "Current commit is:"
|
||||
git log -1 --format=short || echo "Not a repository"
|
||||
|
||||
if command -v nvidia-smi &> /dev/null; then
|
||||
nvidia-smi
|
||||
else
|
||||
echo "nvidia-smi not found"
|
||||
fi
|
||||
|
||||
if command -v sccache &> /dev/null; then
|
||||
sccache --version
|
||||
else
|
||||
echo "sccache not found"
|
||||
fi
|
||||
|
||||
if command -v cmake &> /dev/null; then
|
||||
cmake --version
|
||||
else
|
||||
echo "cmake not found"
|
||||
fi
|
||||
|
||||
if command -v ctest &> /dev/null; then
|
||||
ctest --version
|
||||
else
|
||||
echo "ctest not found"
|
||||
fi
|
||||
|
||||
end_group "⚙️ Environment Details"
|
||||
}
|
||||
|
||||
run_ci_timed_command() {
|
||||
local group_name="${1:-}"
|
||||
shift
|
||||
local -a command=("$@")
|
||||
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
if [[ -n "${CCCL_CI_COMMAND_TIMEOUT}" && "${CCCL_CI_COMMAND_TIMEOUT}" != "0" ]]; then
|
||||
if command -v timeout &> /dev/null; then
|
||||
run_command "${group_name}" timeout "${CCCL_CI_COMMAND_TIMEOUT}" "${command[@]}"
|
||||
return $?
|
||||
fi
|
||||
echo "Warning: timeout not found; running without CI timeout." >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
run_command "${group_name}" "${command[@]}"
|
||||
}
|
||||
|
||||
fail_if_no_gpu() {
|
||||
if ! nvidia-smi &> /dev/null; then
|
||||
echo "Error: No NVIDIA GPU detected. Please ensure you have an NVIDIA GPU installed and the drivers are properly configured." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function cccl_configure_preset_for_test() {
|
||||
local test_preset=$1
|
||||
local presets_file="${BUILD_ROOT}/../CMakePresets.json"
|
||||
local configure_preset
|
||||
|
||||
if [[ ! -f "${presets_file}" ]]; then
|
||||
echo "Error: CMakePresets.json not found: ${presets_file}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
configure_preset=$(
|
||||
jq -r --arg t "${test_preset}" '
|
||||
.testPresets[] | select(.name == $t) | .configurePreset // empty
|
||||
' "${presets_file}"
|
||||
)
|
||||
|
||||
if [[ -z "${configure_preset}" ]]; then
|
||||
configure_preset="${test_preset}"
|
||||
fi
|
||||
|
||||
echo "${configure_preset}"
|
||||
}
|
||||
|
||||
function cccl_smoke_tests_enabled() {
|
||||
local configure_preset=$1
|
||||
local cache_file="${BUILD_DIR}/${configure_preset}/CMakeCache.txt"
|
||||
|
||||
[[ -f "${cache_file}" ]] \
|
||||
&& grep -q '^CCCL_ENABLE_CUDA_SMOKE_TESTS:BOOL=ON' "${cache_file}"
|
||||
}
|
||||
|
||||
function run_cuda_smoke_test() {
|
||||
local BUILD_NAME=$1
|
||||
local test_preset=$2
|
||||
|
||||
local configure_preset
|
||||
configure_preset="$(cccl_configure_preset_for_test "${test_preset}")"
|
||||
local smoke_bin="${BUILD_DIR}/${configure_preset}/bin/cccl.test.cuda_runtime_smoke"
|
||||
|
||||
if [[ -x "${smoke_bin}" ]]; then
|
||||
run_ci_timed_command "CUDA smoke ${BUILD_NAME}" "${smoke_bin}" || return $?
|
||||
elif cccl_smoke_tests_enabled "${configure_preset}"; then
|
||||
echo "Error: CCCL_ENABLE_CUDA_SMOKE_TESTS=ON but smoke binary not found: ${smoke_bin}" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
function print_test_time_summary()
|
||||
{
|
||||
ctest_log=${1}
|
||||
|
||||
if [[ -f "${ctest_log}" ]]; then
|
||||
begin_group "⏱️ Longest Test Steps"
|
||||
# Only print the full output in CI:
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
cmake -DLOGFILE="${ctest_log}" -P ../cmake/PrintCTestRunTimes.cmake
|
||||
else
|
||||
# `|| :` to avoid `set -o pipefail` from triggering when `head` closes the pipe before `cmake` finishes.
|
||||
# Otherwise the script will exit early with status 141 (SIGPIPE).
|
||||
cmake -DLOGFILE="${ctest_log}" -P ../cmake/PrintCTestRunTimes.cmake | head -n 15 || :
|
||||
fi
|
||||
end_group "⏱️ Longest Test Steps"
|
||||
fi
|
||||
}
|
||||
|
||||
function configure_preset()
|
||||
{
|
||||
local BUILD_NAME=$1
|
||||
local PRESET=$2
|
||||
shift 2
|
||||
local CMAKE_OPTIONS=("$@")
|
||||
|
||||
local GROUP_NAME="🛠️ CMake Configure ${BUILD_NAME}"
|
||||
|
||||
symlink_latest_preset "$PRESET"
|
||||
|
||||
pushd .. > /dev/null
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
# Retry 5 times with 30 seconds between attempts to try to WAR network issues during CPM fetch on CI runners:
|
||||
export RUN_COMMAND_RETRY_PARAMS=(5 30)
|
||||
fi
|
||||
status=0
|
||||
SCCACHE_NO_DIST_COMPILE=1 run_command "$GROUP_NAME" cmake --preset="$PRESET" --log-level=VERBOSE "${CMAKE_OPTIONS[@]}" "${GLOBAL_CMAKE_OPTIONS[@]}" || status=$?
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
unset RUN_COMMAND_RETRY_PARAMS
|
||||
fi
|
||||
popd > /dev/null
|
||||
|
||||
if $CONFIGURE_ONLY; then
|
||||
echo "${BUILD_NAME} configuration complete:"
|
||||
echo " Exit code: ${status}"
|
||||
echo " CMake Preset: ${PRESET}"
|
||||
echo " CMake Options: ${CMAKE_OPTIONS[*]}"
|
||||
echo " Build Directory: ${BUILD_DIR}/${PRESET}"
|
||||
exit "$status"
|
||||
fi
|
||||
|
||||
return "$status"
|
||||
}
|
||||
|
||||
function build_preset() {
|
||||
local BUILD_NAME=$1
|
||||
local PRESET=$2
|
||||
# shellcheck disable=SC2034
|
||||
local green="1;32"
|
||||
# shellcheck disable=SC2034
|
||||
local red="1;31"
|
||||
local GROUP_NAME="🏗️ Build ${BUILD_NAME}"
|
||||
shift 2
|
||||
local BUILD_COMMANDS=("$@")
|
||||
|
||||
symlink_latest_preset "$PRESET"
|
||||
|
||||
if $CONFIGURE_ONLY; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local preset_dir="${BUILD_DIR}/${PRESET}"
|
||||
local sccache_json="${preset_dir}/sccache_stats.json"
|
||||
local memmon_log="${preset_dir}/memmon.log"
|
||||
|
||||
sccache -z > /dev/null || :
|
||||
|
||||
# Track memory usage on CI:
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" || -n "${MEMMON:-}" ]]; then
|
||||
util/memmon.sh --start \
|
||||
--log-threshold "${MEMMON_LOG_THRESHOLD:-2}" \
|
||||
--print-threshold "${MEMMON_PRINT_THRESHOLD:-5}" \
|
||||
--log-file "$memmon_log" \
|
||||
--poll "${MEMMON_POLL_INTERVAL:-5}" \
|
||||
|| :
|
||||
fi
|
||||
|
||||
pushd .. > /dev/null
|
||||
status=0
|
||||
run_ci_timed_command "$GROUP_NAME" cmake --build --parallel "$PARALLEL_LEVEL" --preset="$PRESET" ${VERBOSE:+-v} "${BUILD_COMMANDS[@]}" || status=$?
|
||||
popd > /dev/null
|
||||
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" || -n "${MEMMON:-}" ]]; then
|
||||
util/memmon.sh --stop || :
|
||||
run_command "📝 Memory Monitor Log" head -n20 "$memmon_log" || :
|
||||
fi
|
||||
|
||||
# Only print detailed stats in actions workflow
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
sccache --show-adv-stats --stats-format=json > "${sccache_json}" || :
|
||||
run_command "📊 sccache stats" sccache --show-adv-stats || :
|
||||
|
||||
begin_group "🥷 ninja build times"
|
||||
echo "The \"weighted\" time is the elapsed time of each build step divided by the number
|
||||
of tasks that were running in parallel. This makes it an excellent approximation
|
||||
of how \"important\" a slow step was. A link that is entirely or mostly serialized
|
||||
will have a weighted time that is the same or similar to its elapsed time. A
|
||||
compile that runs in parallel with 999 other compiles will have a weighted time
|
||||
that is tiny."
|
||||
./ninja_summary.py -C "${BUILD_DIR}"/"${PRESET}" || echo "Warning: ninja_summary.py failed to execute properly."
|
||||
end_group
|
||||
else
|
||||
sccache -s || :
|
||||
fi
|
||||
|
||||
return "$status"
|
||||
}
|
||||
|
||||
function test_preset()
|
||||
{
|
||||
local BUILD_NAME=$1
|
||||
local PRESET=$2
|
||||
local GPU_REQUIRED=${3:-true}
|
||||
|
||||
symlink_latest_preset "$PRESET"
|
||||
|
||||
if $CONFIGURE_ONLY; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if $GPU_REQUIRED; then
|
||||
fail_if_no_gpu
|
||||
if [[ -z "${CCCL_SKIP_CI_SMOKE:-}" ]]; then
|
||||
run_cuda_smoke_test "${BUILD_NAME}" "${PRESET}" || return $?
|
||||
fi
|
||||
fi
|
||||
|
||||
local GROUP_NAME="🚀 Test ${BUILD_NAME}"
|
||||
|
||||
local preset_dir="${BUILD_DIR}/${PRESET}"
|
||||
local ctest_log="${preset_dir}/ctest.log"
|
||||
|
||||
pushd .. > /dev/null
|
||||
status=0
|
||||
run_ci_timed_command "$GROUP_NAME" ctest --output-on-failure --output-log "${ctest_log}" --preset="$PRESET" || status=$?
|
||||
popd > /dev/null
|
||||
|
||||
print_test_time_summary "${ctest_log}"
|
||||
|
||||
return "$status"
|
||||
}
|
||||
|
||||
function configure_and_build_preset()
|
||||
{
|
||||
local BUILD_NAME=$1
|
||||
local PRESET=$2
|
||||
shift 2
|
||||
local CMAKE_OPTIONS=("$@")
|
||||
|
||||
configure_preset "$BUILD_NAME" "$PRESET" "${CMAKE_OPTIONS[@]}"
|
||||
|
||||
if ! $CONFIGURE_ONLY; then
|
||||
build_preset "$BUILD_NAME" "$PRESET"
|
||||
fi
|
||||
}
|
||||
370
cccl_upstream/ci/build_compile_time_bench.sh
Executable file
370
cccl_upstream/ci/build_compile_time_bench.sh
Executable file
@@ -0,0 +1,370 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
repo_root="$(cd "${ci_dir}/.." && pwd)"
|
||||
tool_dir="${repo_root}/ci/compile_time"
|
||||
|
||||
default_preset="all-dev"
|
||||
preset="${default_preset}"
|
||||
skip_configure=0
|
||||
skip_build=0
|
||||
prepare_perfetto=1
|
||||
run_ctadvisor=0
|
||||
write_tu_csv=1
|
||||
explicit_tu_csv=0
|
||||
tu_csv=""
|
||||
perfetto_output_dir=""
|
||||
baseline_ref=""
|
||||
baseline_worktree=""
|
||||
max_detail_len=180
|
||||
cloc_processes=0
|
||||
|
||||
declare -a build_targets=()
|
||||
declare -a event_args=()
|
||||
declare -a common_args=()
|
||||
declare -a default_build_targets=(
|
||||
"cub.headers.base"
|
||||
"thrust.cpp.cuda.headers.base"
|
||||
"libcudacxx.test.public_headers"
|
||||
)
|
||||
declare -a bench_cmake_args=(
|
||||
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
|
||||
"-DCMAKE_CUDA_COMPILER_LAUNCHER="
|
||||
"-DCMAKE_CXX_COMPILER_LAUNCHER="
|
||||
"-DCCCL_ENABLE_TESTING=OFF"
|
||||
"-DCCCL_ENABLE_EXAMPLES=OFF"
|
||||
"-DCCCL_ENABLE_BENCHMARKS=OFF"
|
||||
"-DCCCL_ENABLE_C_PARALLEL=OFF"
|
||||
"-DCCCL_ENABLE_C_EXPERIMENTAL_STF=OFF"
|
||||
"-DCUB_ENABLE_TESTING=OFF"
|
||||
"-DCUB_ENABLE_EXAMPLES=OFF"
|
||||
"-DTHRUST_ENABLE_TESTING=OFF"
|
||||
"-DTHRUST_ENABLE_EXAMPLES=OFF"
|
||||
"-DTHRUST_MULTICONFIG_WORKLOAD=SMALL"
|
||||
"-DTHRUST_MULTICONFIG_ENABLE_SYSTEM_OMP=OFF"
|
||||
"-DTHRUST_MULTICONFIG_ENABLE_SYSTEM_TBB=OFF"
|
||||
"-Dcudax_ENABLE_TESTING=OFF"
|
||||
"-Dcudax_ENABLE_EXAMPLES=OFF"
|
||||
"-Dcudax_ENABLE_CUDASTF=OFF"
|
||||
"-Dcudax_ENABLE_CUFILE=OFF"
|
||||
"-DCCCL_COMPILE_TIME_SAVE_PREPROCESSED_TUS=ON"
|
||||
"-DCCCL_COMPILE_TIME_GENERATE_DEVICE_TIME_TRACES=ON"
|
||||
)
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: ci/build_compile_time_bench.sh [options] [-- <event summary args>]
|
||||
|
||||
Build options:
|
||||
-preset <name> CMake configure preset (default: all-dev)
|
||||
-cmake-options <args> Extra CMake configure options handled by ci/build_common.sh
|
||||
-target <name> Build target; repeatable
|
||||
(default: public include-check target set)
|
||||
-baseline-ref <commit-ish> Build this commit-ish as a comparison baseline
|
||||
-skip-configure Do not run cmake configure
|
||||
-skip-build Do not run cmake --build
|
||||
-cuda, -cxx, -std, -arch Common compiler/standard/arch options from ci/build_common.sh
|
||||
|
||||
Summary options:
|
||||
-tu-csv <path> Generated-TU summary CSV
|
||||
(default: <preset-build-dir>/compile_time/tu_summary.csv)
|
||||
-no-tu-csv Do not write the generated-TU summary CSV
|
||||
-prepare-perfetto Prepare Perfetto-friendly trace copies (default)
|
||||
-no-prepare-perfetto Skip Perfetto trace preparation
|
||||
-perfetto-output <path> Perfetto trace output directory
|
||||
(default: <preset-build-dir>/compile_time/perfetto_traces)
|
||||
-max-detail-len <n> Max promoted detail length for Perfetto traces (default: 180)
|
||||
-cloc-processes <n> cloc process count for generated-TU summary CSV
|
||||
-ctadvisor Print ctadvisor report for raw traces
|
||||
|
||||
Event summary args:
|
||||
Arguments after '--' are forwarded to ci/compile_time/summarize_events.py
|
||||
after the raw trace directory. If omitted, the default summary is:
|
||||
-f file-processing -e -n 15
|
||||
Pass --slices <json-file> after '--' to emit multiple event report slices
|
||||
and an event_reports/summary.json manifest.
|
||||
With -baseline-ref, comparison-only options such as --threshold <seconds>
|
||||
may also be passed after '--'. Baseline raw traces are preserved under
|
||||
<preset-build-dir>/compile_time/baseline_raw_traces.
|
||||
|
||||
Examples:
|
||||
ci/build_compile_time_bench.sh
|
||||
ci/build_compile_time_bench.sh -target cudax.headers.basic.no_stf -- -f scanning-function-body -i -n 20
|
||||
ci/build_compile_time_bench.sh -preset cub -target cub.headers.base -- -f template-instantiation -e -n 15
|
||||
ci/build_compile_time_bench.sh -baseline-ref origin/main -- -f file-processing -e --threshold 0.001
|
||||
EOF
|
||||
}
|
||||
|
||||
status() { echo "[compile-time-bench] $*" >&2; }
|
||||
|
||||
require_command() {
|
||||
local command_name="$1"
|
||||
command -v "${command_name}" >/dev/null \
|
||||
|| { echo "error: ${command_name} not found" >&2; exit 1; }
|
||||
}
|
||||
|
||||
cleanup_baseline_worktree() {
|
||||
if [[ -n "${baseline_worktree}" && -d "${baseline_worktree}" ]]; then
|
||||
git -C "${repo_root}" worktree remove --force "${baseline_worktree}" >/dev/null 2>&1 \
|
||||
|| rm -rf "${baseline_worktree}"
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup_baseline_worktree_and_exit() {
|
||||
local exit_code="$1"
|
||||
trap - EXIT HUP INT TERM
|
||||
cleanup_baseline_worktree
|
||||
exit "${exit_code}"
|
||||
}
|
||||
|
||||
install_baseline_cleanup_traps() {
|
||||
trap cleanup_baseline_worktree EXIT
|
||||
trap 'cleanup_baseline_worktree_and_exit 129' HUP
|
||||
trap 'cleanup_baseline_worktree_and_exit 130' INT
|
||||
trap 'cleanup_baseline_worktree_and_exit 143' TERM
|
||||
}
|
||||
|
||||
overlay_current_bench_file() {
|
||||
local rel_path="$1"
|
||||
mkdir -p "$(dirname "${baseline_worktree}/${rel_path}")"
|
||||
rm -rf "${baseline_worktree:?}/${rel_path}"
|
||||
ln -s "${repo_root}/${rel_path}" "${baseline_worktree}/${rel_path}"
|
||||
}
|
||||
|
||||
overlay_current_bench_logic() {
|
||||
overlay_current_bench_file "ci"
|
||||
overlay_current_bench_file "CMakePresets.json"
|
||||
overlay_current_bench_file "cmake/CCCLGenerateHeaderTests.cmake"
|
||||
}
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
-h|-help|--help) usage; exit 0 ;;
|
||||
*) ;;
|
||||
esac
|
||||
done
|
||||
|
||||
new_args="$("${ci_dir}/util/extract_switches.sh" \
|
||||
-skip-configure \
|
||||
-skip-build \
|
||||
-no-tu-csv \
|
||||
-prepare-perfetto \
|
||||
-no-prepare-perfetto \
|
||||
-ctadvisor \
|
||||
-- "$@")"
|
||||
|
||||
declare -a new_args="(${new_args})"
|
||||
set -- "${new_args[@]}"
|
||||
while true; do
|
||||
case "$1" in
|
||||
-skip-configure) skip_configure=1; shift ;;
|
||||
-skip-build) skip_build=1; shift ;;
|
||||
-no-tu-csv) write_tu_csv=0; shift ;;
|
||||
-prepare-perfetto) prepare_perfetto=1; shift ;;
|
||||
-no-prepare-perfetto) prepare_perfetto=0; shift ;;
|
||||
-ctadvisor) run_ctadvisor=1; shift ;;
|
||||
--) shift; break ;;
|
||||
*) echo "Unknown argument: $1" >&2; usage; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
-preset) preset="$2"; shift 2 ;;
|
||||
-target) build_targets+=("$2"); shift 2 ;;
|
||||
-baseline-ref) baseline_ref="$2"; shift 2 ;;
|
||||
-tu-csv) explicit_tu_csv=1; write_tu_csv=1; tu_csv="$2"; shift 2 ;;
|
||||
-perfetto-output) perfetto_output_dir="$2"; shift 2 ;;
|
||||
-max-detail-len) max_detail_len="$2"; shift 2 ;;
|
||||
-cloc-processes) cloc_processes="$2"; shift 2 ;;
|
||||
--) shift; event_args+=("$@"); break ;;
|
||||
*) common_args+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
set -- "${common_args[@]}"
|
||||
# shellcheck source=ci/build_common.sh
|
||||
source "${ci_dir}/build_common.sh"
|
||||
|
||||
current_build_root="${BUILD_ROOT}"
|
||||
current_build_dir="${BUILD_DIR}"
|
||||
|
||||
build_root_for_source() {
|
||||
local source_root="$1"
|
||||
mkdir -p "${source_root}/build"
|
||||
(cd "${source_root}/build" && pwd)
|
||||
}
|
||||
|
||||
build_dir_for_source() {
|
||||
local source_root="$1"
|
||||
local build_root="$2"
|
||||
local build_dir="${build_root}/${CCCL_BUILD_INFIX}"
|
||||
mkdir -p "${build_dir}"
|
||||
readlink -f "${build_dir}"
|
||||
}
|
||||
|
||||
run_bench_build() {
|
||||
local source_root="$1"
|
||||
local build_name="$2"
|
||||
local build_root="$3"
|
||||
local build_dir="$4"
|
||||
|
||||
(
|
||||
# build_common.sh keeps the active build tree in these globals; override
|
||||
# them only inside this subshell so current-tree state is restored after
|
||||
# each build.
|
||||
# shellcheck disable=SC2030
|
||||
BUILD_ROOT="${build_root}"
|
||||
# shellcheck disable=SC2030
|
||||
BUILD_DIR="${build_dir}"
|
||||
cd "${source_root}/ci"
|
||||
|
||||
if (( ! skip_configure )); then
|
||||
status "Configuring preset '${preset}' with compile-time bench instrumentation (${build_name})..."
|
||||
configure_preset "${build_name}" "${preset}" "${bench_cmake_args[@]}"
|
||||
fi
|
||||
|
||||
if (( ! skip_build )); then
|
||||
status "Building target(s) (${build_name}): ${build_targets[*]}"
|
||||
build_preset "${build_name}" "${preset}" --target "${build_targets[@]}"
|
||||
fi
|
||||
)
|
||||
}
|
||||
|
||||
prepare_perfetto_traces() {
|
||||
local input_dir="$1"
|
||||
local output_dir="$2"
|
||||
local trace_repo_root="$3"
|
||||
local label="$4"
|
||||
|
||||
status "Preparing Perfetto trace copies (${label})..."
|
||||
rm -rf "${output_dir}"
|
||||
"${tool_dir}/prepare_traces.py" \
|
||||
--input "${input_dir}" \
|
||||
--output "${output_dir}" \
|
||||
--repo-root "${trace_repo_root}" \
|
||||
--max-detail-len "${max_detail_len}"
|
||||
status "Perfetto traces (${label}): ${output_dir}"
|
||||
}
|
||||
|
||||
if ((${#build_targets[@]} == 0)); then
|
||||
build_targets=("${default_build_targets[@]}")
|
||||
fi
|
||||
|
||||
preset_build_dir="${current_build_dir}/${preset}"
|
||||
baseline_trace_dir=""
|
||||
|
||||
if [[ -n "${baseline_ref}" ]]; then
|
||||
baseline_commit="$(git -C "${repo_root}" rev-parse --verify "${baseline_ref}^{commit}")"
|
||||
baseline_worktree="$(mktemp -d "${TMPDIR:-/tmp}/cccl-compile-time-baseline.XXXXXX")"
|
||||
rmdir "${baseline_worktree}"
|
||||
install_baseline_cleanup_traps
|
||||
status "Creating baseline worktree for ${baseline_ref} (${baseline_commit})..."
|
||||
git -C "${repo_root}" worktree add --detach "${baseline_worktree}" "${baseline_commit}" >/dev/null
|
||||
overlay_current_bench_logic
|
||||
|
||||
baseline_build_root="$(build_root_for_source "${baseline_worktree}")"
|
||||
baseline_build_dir="$(build_dir_for_source "${baseline_worktree}" "${baseline_build_root}")"
|
||||
baseline_trace_dir="${baseline_build_dir}/${preset}/compile_time/raw_traces"
|
||||
fi
|
||||
|
||||
report_root="${preset_build_dir}/compile_time"
|
||||
trace_dir="${report_root}/raw_traces"
|
||||
baseline_artifact_trace_dir="${report_root}/baseline_raw_traces"
|
||||
event_output_dir="${report_root}/event_reports"
|
||||
tu_csv="${tu_csv:-${report_root}/tu_summary.csv}"
|
||||
perfetto_output_dir="${perfetto_output_dir:-${report_root}/perfetto_traces}"
|
||||
if [[ -n "${baseline_ref}" && "${explicit_tu_csv}" -eq 0 ]]; then
|
||||
write_tu_csv=0
|
||||
fi
|
||||
|
||||
require_command python3
|
||||
if (( write_tu_csv )); then
|
||||
require_command cloc
|
||||
fi
|
||||
if (( run_ctadvisor )); then
|
||||
require_command ctadvisor
|
||||
fi
|
||||
|
||||
run_bench_build "${repo_root}" "Compile-time Bench (current)" "${current_build_root}" "${current_build_dir}"
|
||||
if $CONFIGURE_ONLY; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -n "${baseline_ref}" ]]; then
|
||||
run_bench_build \
|
||||
"${baseline_worktree}" \
|
||||
"Compile-time Bench (baseline)" \
|
||||
"${baseline_build_root}" \
|
||||
"${baseline_build_dir}"
|
||||
fi
|
||||
|
||||
shopt -s nullglob globstar
|
||||
trace_paths=("${trace_dir}"/**/*.json)
|
||||
(( ${#trace_paths[@]} > 0 )) \
|
||||
|| { echo "error: no device-time-trace JSON files found under ${trace_dir}" >&2; exit 1; }
|
||||
if [[ -n "${baseline_ref}" ]]; then
|
||||
baseline_trace_paths=("${baseline_trace_dir}"/**/*.json)
|
||||
(( ${#baseline_trace_paths[@]} > 0 )) \
|
||||
|| { echo "error: no device-time-trace JSON files found under ${baseline_trace_dir}" >&2; exit 1; }
|
||||
|
||||
status "Copying baseline raw traces to ${baseline_artifact_trace_dir}..."
|
||||
rm -rf "${baseline_artifact_trace_dir}"
|
||||
mkdir -p "${baseline_artifact_trace_dir}"
|
||||
cp -a "${baseline_trace_dir}/." "${baseline_artifact_trace_dir}/"
|
||||
fi
|
||||
|
||||
if (( prepare_perfetto )); then
|
||||
if [[ -n "${baseline_ref}" ]]; then
|
||||
rm -rf "${perfetto_output_dir}"
|
||||
prepare_perfetto_traces \
|
||||
"${trace_dir}" \
|
||||
"${perfetto_output_dir}/current" \
|
||||
"${repo_root}" \
|
||||
"current"
|
||||
prepare_perfetto_traces \
|
||||
"${baseline_trace_dir}" \
|
||||
"${perfetto_output_dir}/baseline" \
|
||||
"${baseline_worktree}" \
|
||||
"baseline"
|
||||
else
|
||||
prepare_perfetto_traces \
|
||||
"${trace_dir}" \
|
||||
"${perfetto_output_dir}" \
|
||||
"${repo_root}" \
|
||||
"current"
|
||||
fi
|
||||
fi
|
||||
|
||||
if (( write_tu_csv )); then
|
||||
status "Writing generated-TU summary CSV..."
|
||||
"${tool_dir}/summarize_tus.py" \
|
||||
--build-dir "${preset_build_dir}" \
|
||||
--output-csv "${tu_csv}" \
|
||||
--cloc-processes "${cloc_processes}"
|
||||
status "Generated-TU summary CSV: ${tu_csv}"
|
||||
fi
|
||||
|
||||
declare -a summary_args=("${event_args[@]}")
|
||||
if ((${#summary_args[@]} == 0)); then
|
||||
summary_args=(-f file-processing -e -n 15)
|
||||
fi
|
||||
summary_args+=(-o "${event_output_dir}")
|
||||
if [[ -n "${baseline_ref}" ]]; then
|
||||
summary_args+=(
|
||||
--baseline-dir "${baseline_trace_dir}"
|
||||
--baseline-repo-root "${baseline_worktree}"
|
||||
)
|
||||
fi
|
||||
status "Writing event summary..."
|
||||
"${tool_dir}/summarize_events.py" "${trace_dir}" "${summary_args[@]}"
|
||||
|
||||
if (( run_ctadvisor )); then
|
||||
status "Running ctadvisor over ${#trace_paths[@]} trace(s)..."
|
||||
ctadvisor \
|
||||
--trace-file-path "${trace_dir}" \
|
||||
--header-advisor-entries 20 \
|
||||
--thread-number "$(nproc --all --ignore=2)"
|
||||
fi
|
||||
111
cccl_upstream/ci/build_cub.sh
Executable file
111
cccl_upstream/ci/build_cub.sh
Executable file
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
NO_LID=false
|
||||
LID0=false
|
||||
LID1=false
|
||||
LID2=false
|
||||
ARTIFACT_TAGS=()
|
||||
|
||||
ci_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
|
||||
new_args="$("${ci_dir}/util/extract_switches.sh" \
|
||||
-no-lid \
|
||||
-lid0 \
|
||||
-lid1 \
|
||||
-lid2 \
|
||||
-- "$@")"
|
||||
|
||||
declare -a new_args="(${new_args})"
|
||||
set -- "${new_args[@]}"
|
||||
while true; do
|
||||
case "$1" in
|
||||
-no-lid)
|
||||
ARTIFACT_TAGS+=("no_lid")
|
||||
NO_LID=true
|
||||
shift
|
||||
;;
|
||||
-lid0)
|
||||
ARTIFACT_TAGS+=("lid_0")
|
||||
LID0=true
|
||||
shift
|
||||
;;
|
||||
-lid1)
|
||||
ARTIFACT_TAGS+=("lid_1")
|
||||
LID1=true
|
||||
shift
|
||||
;;
|
||||
-lid2)
|
||||
ARTIFACT_TAGS+=("lid_2")
|
||||
LID2=true
|
||||
shift
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# shellcheck source=ci/build_common.sh
|
||||
source "${ci_dir}/build_common.sh"
|
||||
|
||||
print_environment_details
|
||||
|
||||
ENABLE_CCCL_BENCHMARKS="false"
|
||||
ENABLE_CUB_RDC="false"
|
||||
|
||||
if [[ "$CUDA_COMPILER" == *nvcc* ]]; then
|
||||
ENABLE_CUB_RDC="true"
|
||||
NVCC_VERSION=$($CUDA_COMPILER --version | grep release | awk '{print $6}' | cut -c2-)
|
||||
if [[ -n "${DISABLE_CUB_BENCHMARKS}" ]]; then
|
||||
echo "Benchmarks have been forcefully disabled."
|
||||
else
|
||||
ENABLE_CCCL_BENCHMARKS="true"
|
||||
echo "nvcc version is $NVCC_VERSION. Building CUB benchmarks."
|
||||
fi
|
||||
else
|
||||
echo "Not building with NVCC, disabling RDC and benchmarks."
|
||||
fi
|
||||
|
||||
if [[ "$HOST_COMPILER" == *icpc* || "$HOST_COMPILER" == *nvhpc* ]]; then
|
||||
ENABLE_CCCL_BENCHMARKS="false"
|
||||
fi
|
||||
|
||||
PRESET="cub"
|
||||
if $NO_LID; then
|
||||
PRESET="cub-nolid"
|
||||
elif $LID0; then
|
||||
PRESET="cub-lid0"
|
||||
elif $LID1; then
|
||||
PRESET="cub-lid1"
|
||||
elif $LID2; then
|
||||
PRESET="cub-lid2"
|
||||
fi
|
||||
|
||||
CMAKE_OPTIONS=(
|
||||
"-DCMAKE_CXX_STANDARD=$CXX_STANDARD"
|
||||
"-DCMAKE_CUDA_STANDARD=$CXX_STANDARD"
|
||||
"-DCCCL_ENABLE_BENCHMARKS=$ENABLE_CCCL_BENCHMARKS"
|
||||
"-DCUB_ENABLE_RDC_TESTS=$ENABLE_CUB_RDC"
|
||||
)
|
||||
|
||||
configure_and_build_preset "CUB" "$PRESET" "${CMAKE_OPTIONS[@]}"
|
||||
|
||||
# Create test artifacts:
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
if [[ ${#ARTIFACT_TAGS[@]} -gt 0 ]]; then
|
||||
run_command "📦 Packaging test artifacts" \
|
||||
"${ci_dir}/upload_cub_test_artifacts.sh" \
|
||||
"${ARTIFACT_TAGS[@]}"
|
||||
else
|
||||
run_command "📦 Packaging test artifacts" "${ci_dir}/upload_cub_test_artifacts.sh"
|
||||
fi
|
||||
fi
|
||||
|
||||
print_time_summary
|
||||
184
cccl_upstream/ci/build_cuda_cccl_python.sh
Executable file
184
cccl_upstream/ci/build_cuda_cccl_python.sh
Executable file
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
usage="Usage: $0 -py-version <python_version> [additional options...]"
|
||||
|
||||
# shellcheck source=ci/util/python/common_arg_parser.sh
|
||||
source "$ci_dir/util/python/common_arg_parser.sh"
|
||||
parse_python_args "$@"
|
||||
|
||||
# Check if py_version was provided (this script requires it)
|
||||
require_py_version "$usage" || exit 1
|
||||
|
||||
echo "Docker socket: " "$(ls /var/run/docker.sock)"
|
||||
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
# Prepare mount points etc for getting artifacts in/out of the container.
|
||||
# shellcheck source=ci/util/artifacts/common.sh
|
||||
source "$ci_dir/util/artifacts/common.sh"
|
||||
# Note that these mounts use the runner (not the devcontainer) filesystem for
|
||||
# source directories because of docker-out-of-docker quirks.
|
||||
# The workflow-job GH actions make sure that they exist before running any
|
||||
# scripts.
|
||||
action_mounts=(
|
||||
--mount "type=bind,source=${ARTIFACT_ARCHIVES},target=${ARTIFACT_ARCHIVES}"
|
||||
--mount "type=bind,source=${ARTIFACT_UPLOAD_STAGE},target=${ARTIFACT_UPLOAD_STAGE}"
|
||||
)
|
||||
else
|
||||
# If not running in GitHub Actions, we don't need to set up artifact mounts.
|
||||
action_mounts=()
|
||||
fi
|
||||
|
||||
# cuda_cccl must be built in a container that can produce manylinux wheels,
|
||||
# and has the CUDA toolkit installed. We use the rapidsai/ci-wheel image for this.
|
||||
# We build separate wheels using separate containers for each CUDA version,
|
||||
# then merge them into a single wheel.
|
||||
|
||||
readonly cuda12_version=12.9.1
|
||||
readonly cuda13_version=13.1.1
|
||||
readonly devcontainer_version=26.04
|
||||
readonly devcontainer_distro=rockylinux8
|
||||
# Use a baseline Python tag for the rapidsai ci-wheel image. The requested
|
||||
# py_version is installed inside the container by setup_python_env (uv).
|
||||
# Pinning the image tag avoids relying on a per-py_version image being
|
||||
# published (e.g. py3.14 images may not yet exist).
|
||||
readonly devcontainer_python_version=3.10
|
||||
|
||||
if [[ "$(uname -m)" == "aarch64" ]]; then
|
||||
cuda12_image="rapidsai/ci-wheel:${devcontainer_version}-cuda${cuda12_version}-${devcontainer_distro}-py${devcontainer_python_version}-arm64"
|
||||
cuda13_image="rapidsai/ci-wheel:${devcontainer_version}-cuda${cuda13_version}-${devcontainer_distro}-py${devcontainer_python_version}-arm64"
|
||||
else
|
||||
cuda12_image="rapidsai/ci-wheel:${devcontainer_version}-cuda${cuda12_version}-${devcontainer_distro}-py${devcontainer_python_version}"
|
||||
cuda13_image="rapidsai/ci-wheel:${devcontainer_version}-cuda${cuda13_version}-${devcontainer_distro}-py${devcontainer_python_version}"
|
||||
fi
|
||||
# shellcheck disable=SC2034
|
||||
readonly cuda12_image
|
||||
# shellcheck disable=SC2034
|
||||
readonly cuda13_image
|
||||
|
||||
mkdir -p wheelhouse
|
||||
|
||||
# Shared caches across the cu12 + cu13 wheel builds. Both jobs compile an
|
||||
# identical LLVM/clang tree (LLVM has no CUDA dep), so a shared ccache cuts
|
||||
# the second build's LLVM phase from ~10 min to under 2 min; a shared CPM
|
||||
# source cache skips the second LLVM git clone entirely.
|
||||
#
|
||||
# The `mkdir`s run inside the (dev)container where only the container-side
|
||||
# paths are visible. The docker bind-mount uses the host-side paths
|
||||
# (${HOST_WORKSPACE}) since the inner docker daemon is the host's.
|
||||
mkdir -p ./.ccache ./.cpm-cache
|
||||
host_ccache_dir="${HOST_WORKSPACE:?}/.ccache"
|
||||
host_cpm_cache_dir="${HOST_WORKSPACE:?}/.cpm-cache"
|
||||
|
||||
for ctk in 12 13; do
|
||||
image="cuda${ctk}_image"
|
||||
image="${!image}"
|
||||
echo "::group::⚒️ Building CUDA $ctk wheel on $image"
|
||||
(
|
||||
set -x
|
||||
docker pull "$image"
|
||||
docker run --rm -i \
|
||||
--workdir /workspace/python/cuda_cccl \
|
||||
--mount "type=bind,source=${HOST_WORKSPACE:?},target=/workspace/" \
|
||||
--mount "type=bind,source=${host_ccache_dir},target=/root/.ccache" \
|
||||
--mount "type=bind,source=${host_cpm_cache_dir},target=/root/.cpm-cache" \
|
||||
"${action_mounts[@]}" \
|
||||
--env "py_version=${py_version}" \
|
||||
--env "GITHUB_ACTIONS=${GITHUB_ACTIONS:-}" \
|
||||
--env "GITHUB_RUN_ID=${GITHUB_RUN_ID:-}" \
|
||||
--env "JOB_ID=${JOB_ID:-}" \
|
||||
--env "CCCL_PYTHON_USE_V2=${CCCL_PYTHON_USE_V2:-}" \
|
||||
--env "CCCL_C_PARALLEL_SANITIZE_THREAD=${CCCL_C_PARALLEL_SANITIZE_THREAD:-}" \
|
||||
--env "CCACHE_DIR=/root/.ccache" \
|
||||
--env "CPM_SOURCE_CACHE=/root/.cpm-cache" \
|
||||
"$image" \
|
||||
/workspace/ci/build_cuda_cccl_wheel.sh
|
||||
# Prevent GHA runners from exhausting available storage with leftover images:
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
docker rmi -f "$image"
|
||||
fi
|
||||
)
|
||||
echo "::endgroup::"
|
||||
done
|
||||
|
||||
echo "Merging CUDA wheels..."
|
||||
|
||||
# Set up a Python environment for the merge/repair steps.
|
||||
source "$ci_dir/pyenv_helper.sh"
|
||||
setup_python_env "${py_version}"
|
||||
|
||||
# Needed for unpacking and repacking wheels.
|
||||
python -m pip install wheel
|
||||
|
||||
# Find the built wheels
|
||||
cu12_wheel=$(find wheelhouse -name "*cu12*.whl" | head -1)
|
||||
cu13_wheel=$(find wheelhouse -name "*cu13*.whl" | head -1)
|
||||
|
||||
if [[ -z "$cu12_wheel" ]]; then
|
||||
echo "Error: CUDA 12 wheel not found in wheelhouse/"
|
||||
ls -la wheelhouse/
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$cu13_wheel" ]]; then
|
||||
echo "Error: CUDA 13 wheel not found in wheelhouse/"
|
||||
ls -la wheelhouse/
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found CUDA 12 wheel: $cu12_wheel"
|
||||
echo "Found CUDA 13 wheel: $cu13_wheel"
|
||||
|
||||
# Merge the wheels
|
||||
python python/cuda_cccl/merge_cuda_wheels.py "$cu12_wheel" "$cu13_wheel" --output-dir wheelhouse_merged
|
||||
|
||||
# A ThreadSanitizer wheel links libtsan; keep it external (excluded) so it is
|
||||
# NOT bundled -- the TSan test lane LD_PRELOADs the runner's matching libtsan
|
||||
# instead. Harmless for normal builds (the .so has no libtsan dependency).
|
||||
tsan_exclude=()
|
||||
if [[ "${CCCL_C_PARALLEL_SANITIZE_THREAD:-}" =~ ^(1|true|TRUE|on|ON)$ ]]; then
|
||||
tsan_exclude=(--exclude 'libtsan.so.2')
|
||||
fi
|
||||
|
||||
# Install auditwheel and repair the merged wheel
|
||||
python -m pip install patchelf auditwheel
|
||||
for wheel in wheelhouse_merged/cuda_cccl-*.whl; do
|
||||
echo "Repairing merged wheel: $wheel"
|
||||
python -m auditwheel repair \
|
||||
--exclude 'libnvrtc.so.12' \
|
||||
--exclude 'libnvrtc.so.13' \
|
||||
--exclude 'libnvJitLink.so.12' \
|
||||
--exclude 'libnvJitLink.so.13' \
|
||||
--exclude 'libcudart.so.12' \
|
||||
--exclude 'libcudart.so.13' \
|
||||
--exclude 'libcuda.so.1' \
|
||||
"${tsan_exclude[@]}" \
|
||||
"$wheel" \
|
||||
--wheel-dir wheelhouse_final
|
||||
done
|
||||
|
||||
# Clean up intermediate files and move only the final merged wheel to wheelhouse
|
||||
rm -rf wheelhouse/* # Clean existing wheelhouse
|
||||
mkdir -p wheelhouse
|
||||
|
||||
# Move only the final repaired merged wheel
|
||||
if ls wheelhouse_final/cuda_cccl-*.whl 1> /dev/null 2>&1; then
|
||||
mv wheelhouse_final/cuda_cccl-*.whl wheelhouse/
|
||||
echo "Final merged wheel moved to wheelhouse"
|
||||
else
|
||||
echo "No final repaired wheel found, moving unrepaired merged wheel"
|
||||
mv wheelhouse_merged/cuda_cccl-*.whl wheelhouse/
|
||||
fi
|
||||
|
||||
# Clean up temporary directories
|
||||
rm -rf wheelhouse_merged wheelhouse_final
|
||||
|
||||
echo "Final wheels in wheelhouse:"
|
||||
ls -la wheelhouse/
|
||||
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
wheel_artifact_name="$(ci/util/workflow/get_wheel_artifact_name.sh)"
|
||||
ci/util/artifacts/upload.sh "$wheel_artifact_name" 'wheelhouse/.*'
|
||||
fi
|
||||
12
cccl_upstream/ci/build_cuda_cccl_python_tsan.sh
Executable file
12
cccl_upstream/ci/build_cuda_cccl_python_tsan.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# Thin wrapper around build_cuda_cccl_python.sh that builds the cuda_cccl wheel
|
||||
# with ThreadSanitizer instrumentation on the c.parallel (v1) host code, for the
|
||||
# free-threaded (3.14t) TSan nightly lane. The shared build script honors
|
||||
# CCCL_C_PARALLEL_SANITIZE_THREAD (passes -DCCCL_C_PARALLEL_SANITIZE_THREAD=ON to
|
||||
# the wheel build and --exclude libtsan from the auditwheel repair).
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
export CCCL_C_PARALLEL_SANITIZE_THREAD=1
|
||||
"$ci_dir/build_cuda_cccl_python.sh" "$@"
|
||||
10
cccl_upstream/ci/build_cuda_cccl_python_v2.sh
Executable file
10
cccl_upstream/ci/build_cuda_cccl_python_v2.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# Thin wrapper around build_cuda_cccl_python.sh that builds the cuda_cccl
|
||||
# wheel against the HostJIT-based cccl.c.parallel.v2 library instead of the
|
||||
# legacy NVRTC v1 library. The shared build script honors CCCL_PYTHON_USE_V2.
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
export CCCL_PYTHON_USE_V2=1
|
||||
exec "$ci_dir/build_cuda_cccl_python.sh" "$@"
|
||||
130
cccl_upstream/ci/build_cuda_cccl_wheel.sh
Executable file
130
cccl_upstream/ci/build_cuda_cccl_wheel.sh
Executable file
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Target script for `docker run` command in build_cuda_cccl_python.sh
|
||||
# The /workspace pathnames are hard-wired here.
|
||||
|
||||
# Toolchain version, pinned in one place. Every gcc-toolset package name and the
|
||||
# /opt/rh enable path below derive from it — the -fsanitize=thread libtsan must
|
||||
# come from the same toolset as the compiler, so these cannot drift apart.
|
||||
readonly gcc_toolset_version=13
|
||||
|
||||
# Install the GCC toolset (needed for the build) and ccache (shared between
|
||||
# cu12 and cu13 builds via /root/.ccache bind-mount from the host).
|
||||
/workspace/ci/util/retry.sh 5 30 dnf -y install \
|
||||
"gcc-toolset-${gcc_toolset_version}-gcc" "gcc-toolset-${gcc_toolset_version}-gcc-c++" ccache
|
||||
|
||||
# ThreadSanitizer builds link libcccl.c.parallel.so with -fsanitize=thread, which
|
||||
# the linker resolves via the toolset's libtsan.so. That runtime lives in a
|
||||
# separate package not pulled in by -gcc-c++, so install it for the TSan lane.
|
||||
if [[ "${CCCL_C_PARALLEL_SANITIZE_THREAD:-}" =~ ^(1|true|TRUE|on|ON)$ ]]; then
|
||||
/workspace/ci/util/retry.sh 5 30 dnf -y install "gcc-toolset-${gcc_toolset_version}-libtsan-devel"
|
||||
fi
|
||||
|
||||
# When the caller bind-mounts a ccache dir, wire it through to CMake. This
|
||||
# transparently caches every compile, so the second wheel build (cu13 after
|
||||
# cu12, or vice versa) reuses the entire LLVM/clang object tree.
|
||||
if [[ -n "${CCACHE_DIR:-}" ]]; then
|
||||
export CMAKE_C_COMPILER_LAUNCHER=ccache
|
||||
export CMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||
export CMAKE_CUDA_COMPILER_LAUNCHER=ccache
|
||||
echo "ccache enabled: CCACHE_DIR=${CCACHE_DIR}"
|
||||
ccache --version 2>&1 | head -1 || true
|
||||
ccache --show-stats 2>&1 | head -5 || true
|
||||
fi
|
||||
echo -e "#!/usr/bin/env bash\nsource /opt/rh/gcc-toolset-${gcc_toolset_version}/enable" >/etc/profile.d/enable_devtools.sh
|
||||
# shellcheck disable=SC1091
|
||||
source /etc/profile.d/enable_devtools.sh
|
||||
|
||||
# Check what's available
|
||||
command -v gcc
|
||||
gcc --version
|
||||
command -v nvcc
|
||||
nvcc --version
|
||||
|
||||
# Set up Python environment
|
||||
# shellcheck source=ci/pyenv_helper.sh
|
||||
source /workspace/ci/pyenv_helper.sh
|
||||
# shellcheck disable=SC2154
|
||||
setup_python_env "${py_version}"
|
||||
command -v python
|
||||
python --version
|
||||
echo "Done setting up python env"
|
||||
|
||||
# Figure out the version to use for the package, we need repo history
|
||||
if "$(git rev-parse --is-shallow-repository)"; then
|
||||
git fetch --unshallow
|
||||
fi
|
||||
export PACKAGE_VERSION_PREFIX="0.1."
|
||||
package_version=$(/workspace/ci/generate_version.sh)
|
||||
echo "Using package version ${package_version}"
|
||||
# Override the version used by setuptools_scm to the custom version
|
||||
export SETUPTOOLS_SCM_PRETEND_VERSION_FOR_CUDA_CCCL="${package_version}"
|
||||
|
||||
cd /workspace/python/cuda_cccl
|
||||
|
||||
# Determine CUDA version from nvcc
|
||||
cuda_version=$(nvcc --version | grep -oP 'release \K[0-9]+\.[0-9]+' | cut -d. -f1)
|
||||
echo "Detected CUDA version: ${cuda_version}"
|
||||
|
||||
# Configure compilers:
|
||||
CXX="$(command -v g++)"
|
||||
export CXX
|
||||
CUDACXX="$(command -v nvcc)"
|
||||
export CUDACXX
|
||||
CUDAHOSTCXX="$(command -v g++)"
|
||||
export CUDAHOSTCXX
|
||||
|
||||
# When CCCL_PYTHON_USE_V2 is set (=1/true/on), build the wheel against the
|
||||
# HostJIT-based cccl.c.parallel.v2 library instead of the default v1.
|
||||
if [[ "${CCCL_PYTHON_USE_V2:-}" =~ ^(1|true|TRUE|on|ON)$ ]]; then
|
||||
export CMAKE_ARGS="${CMAKE_ARGS:-} -DCCCL_PYTHON_USE_V2=ON"
|
||||
echo "Building wheel with CCCL v2 backend: CMAKE_ARGS=${CMAKE_ARGS}"
|
||||
|
||||
# v2's hostjit links against libnvJitLink and libnvfatbin, which aren't in
|
||||
# the base rapidsai/ci-wheel image. Install the matching CTK devel packages
|
||||
# so CMake's FindCUDAToolkit picks them up. nvcc is on PATH; derive the
|
||||
# version (e.g. "13-0") from it.
|
||||
ctk_pkg_ver=$(nvcc --version 2>/dev/null \
|
||||
| grep -oP 'release \K[0-9]+\.[0-9]+' | tr '.' '-')
|
||||
if [[ -n "${ctk_pkg_ver}" ]]; then
|
||||
echo "Installing libnvjitlink-devel-${ctk_pkg_ver} libnvfatbin-devel-${ctk_pkg_ver}..."
|
||||
/workspace/ci/util/retry.sh 5 30 dnf -y install \
|
||||
"libnvjitlink-devel-${ctk_pkg_ver}" \
|
||||
"libnvfatbin-devel-${ctk_pkg_ver}"
|
||||
else
|
||||
echo "WARNING: could not derive CTK version from nvcc; skipping nvJitLink/nvfatbin install"
|
||||
fi
|
||||
|
||||
# FindCUDAToolkit learned about CUDA::nvfatbin only in CMake 3.27. The base
|
||||
# rapidsai/ci-wheel image ships an older CMake; install a newer one into
|
||||
# the active venv so scikit-build-core picks it up over the system cmake.
|
||||
echo "Pinning cmake>=3.27 for FindCUDAToolkit nvfatbin support..."
|
||||
python -m pip install --upgrade 'cmake>=3.27'
|
||||
fi
|
||||
|
||||
# When CCCL_C_PARALLEL_SANITIZE_THREAD is set (=1/true/on), instrument the
|
||||
# c.parallel (v1) host code with ThreadSanitizer for the free-threaded TSan
|
||||
# nightly lane. Host-only; the libtsan runtime stays external (the shared
|
||||
# build_cuda_cccl_python.sh --excludes it from auditwheel).
|
||||
if [[ "${CCCL_C_PARALLEL_SANITIZE_THREAD:-}" =~ ^(1|true|TRUE|on|ON)$ ]]; then
|
||||
export CMAKE_ARGS="${CMAKE_ARGS:-} -DCCCL_C_PARALLEL_SANITIZE_THREAD=ON"
|
||||
echo "Building wheel with ThreadSanitizer-instrumented c.parallel: CMAKE_ARGS=${CMAKE_ARGS}"
|
||||
fi
|
||||
|
||||
# Build the wheel
|
||||
python -m pip wheel --no-deps --verbose --wheel-dir dist .
|
||||
|
||||
# Rename wheel to include CUDA version suffix
|
||||
for wheel in dist/cuda_cccl-*.whl; do
|
||||
if [[ -f "$wheel" ]]; then
|
||||
base_name=$(basename "$wheel" .whl)
|
||||
new_name="${base_name}.cu${cuda_version}.whl"
|
||||
mv "$wheel" "dist/${new_name}"
|
||||
echo "Renamed wheel to: ${new_name}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Move wheel to output directory
|
||||
mkdir -p /workspace/wheelhouse
|
||||
mv dist/cuda_cccl-*.cu*.whl /workspace/wheelhouse/
|
||||
32
cccl_upstream/ci/build_cudax.sh
Executable file
32
cccl_upstream/ci/build_cudax.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck source=ci/build_common.sh
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/build_common.sh"
|
||||
|
||||
print_environment_details
|
||||
|
||||
# If the cudax_ENABLE_CUFILE variable is specified, we don't modify it. Otherwise if we got an nvcc binary, check the
|
||||
# nvcc version and if it's less than 12.9, disable the cuFile support. NVHPC Toolkit doesn't come with cuFile, too, so
|
||||
# we don't enable cuFile support if the host compiler is nvc++.
|
||||
if [[ -z "${cudax_ENABLE_CUFILE:-}" ]]; then
|
||||
cudax_ENABLE_CUFILE="false"
|
||||
if [[ -n "${NVCC_VERSION:-}" ]] && [[ "$(basename "${HOST_COMPILER}")" != "nvc++" ]]; then
|
||||
if util/version_compare.sh "${NVCC_VERSION}" ge 12.9; then
|
||||
cudax_ENABLE_CUFILE="true"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
PRESET="cudax"
|
||||
|
||||
CMAKE_OPTIONS=(
|
||||
"-Dcudax_ENABLE_CUFILE=${cudax_ENABLE_CUFILE}"
|
||||
"-DCMAKE_CXX_STANDARD=${CXX_STANDARD}"
|
||||
"-DCMAKE_CUDA_STANDARD=${CXX_STANDARD}"
|
||||
)
|
||||
|
||||
configure_and_build_preset "CUDA Experimental" "$PRESET" "${CMAKE_OPTIONS[@]}"
|
||||
|
||||
print_time_summary
|
||||
27
cccl_upstream/ci/build_libcudacxx.sh
Executable file
27
cccl_upstream/ci/build_libcudacxx.sh
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
|
||||
# shellcheck source=ci/build_common.sh
|
||||
source "${ci_dir}/build_common.sh"
|
||||
|
||||
print_environment_details
|
||||
|
||||
PRESET="libcudacxx"
|
||||
CMAKE_OPTIONS=("-DCMAKE_CXX_STANDARD=${CXX_STANDARD}" "-DCMAKE_CUDA_STANDARD=${CXX_STANDARD}")
|
||||
|
||||
upload_test_artifacts=false
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]] && "${ci_dir}/util/workflow/has_consumers.sh"; then
|
||||
upload_test_artifacts=true
|
||||
export LIT_OPTS="${LIT_OPTS:+${LIT_OPTS} }-Dtest_executable_mode=build"
|
||||
fi
|
||||
|
||||
configure_and_build_preset libcudacxx "$PRESET" "${CMAKE_OPTIONS[@]}"
|
||||
|
||||
if $upload_test_artifacts; then
|
||||
run_command "📦 Packaging test artifacts" "${ci_dir}/upload_libcudacxx_test_artifacts.sh"
|
||||
fi
|
||||
|
||||
print_time_summary
|
||||
37
cccl_upstream/ci/build_stdpar.sh
Executable file
37
cccl_upstream/ci/build_stdpar.sh
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Ensure the script is being executed in the root cccl directory:
|
||||
cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/..";
|
||||
|
||||
# Get the current CCCL info:
|
||||
readonly cccl_repo="${PWD}"
|
||||
readonly workdir="${cccl_repo}/test/stdpar"
|
||||
|
||||
CXX_STANDARD=17
|
||||
|
||||
args=("$@")
|
||||
while [[ "${#args[@]}" -ne 0 ]]; do
|
||||
case "${args[0]}" in
|
||||
-std) CXX_STANDARD="${args[1]}"; args=("${args[@]:2}");;
|
||||
*) echo "Unrecognized option: ${args[0]}"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
mkdir -p "${workdir}"
|
||||
cd "${workdir}"
|
||||
|
||||
# Configure and build
|
||||
rm -rf build
|
||||
|
||||
cmake -B build -S . -G Ninja \
|
||||
-DCMAKE_CXX_STANDARD="${CXX_STANDARD}" \
|
||||
`# Explicitly compile for hopper since the CI machine does not have a gpu:` \
|
||||
-DCMAKE_CXX_FLAGS="-gpu=cc90"
|
||||
|
||||
# Disabled because `cmake --build -j ""` is invalid, but so is
|
||||
# `cmake --build -j8`. CMake expects a space between `-j` and
|
||||
# the numeric argument, or no argument at all.
|
||||
# shellcheck disable=SC2086
|
||||
cmake --build build -j ${PARALLEL_LEVEL:-}
|
||||
21
cccl_upstream/ci/build_thrust.sh
Executable file
21
cccl_upstream/ci/build_thrust.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck source=ci/build_common.sh
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/build_common.sh"
|
||||
|
||||
print_environment_details
|
||||
|
||||
PRESET="thrust"
|
||||
|
||||
CMAKE_OPTIONS=("-DCMAKE_CXX_STANDARD=$CXX_STANDARD" "-DCMAKE_CUDA_STANDARD=$CXX_STANDARD")
|
||||
|
||||
configure_and_build_preset "Thrust" "$PRESET" "${CMAKE_OPTIONS[@]}"
|
||||
|
||||
# Create test artifacts:
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
run_command "📦 Packaging test artifacts" /home/coder/cccl/ci/upload_thrust_test_artifacts.sh
|
||||
fi
|
||||
|
||||
print_time_summary
|
||||
32
cccl_upstream/ci/build_tidy.sh
Executable file
32
cccl_upstream/ci/build_tidy.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/build_common.sh"
|
||||
|
||||
print_environment_details
|
||||
|
||||
BUILD_NAME="clang-tidy"
|
||||
PRESET="all-tidy"
|
||||
CMAKE_OPTIONS=("-DCMAKE_CXX_STANDARD=${CXX_STANDARD}" "-DCMAKE_CUDA_STANDARD=${CXX_STANDARD}")
|
||||
# Clang does not understand -G, passed by all-dev-debug which all-tidy derives from
|
||||
CMAKE_OPTIONS+=("-DCMAKE_CUDA_FLAGS=")
|
||||
# TODO(jfaibussowit)
|
||||
#
|
||||
# STF seems to trip clang-cuda up pretty heavily. It's unclear whether this is because STF
|
||||
# hasn't been compiled against clang-cuda before or whether it's an issue with clang-cuda
|
||||
# itself.
|
||||
CMAKE_OPTIONS+=("-Dcudax_ENABLE_CUDASTF=OFF")
|
||||
CMAKE_OPTIONS+=("-Dcudax_ENABLE_PLACES=OFF")
|
||||
|
||||
# todo(dabayer): Re-enable OpenMP thrust builds for clang-tidy.
|
||||
CMAKE_OPTIONS+=("-DTHRUST_MULTICONFIG_ENABLE_SYSTEM_OMP=OFF")
|
||||
|
||||
# Cannot use configure_and_build_preset because that does not allow us to pass additional
|
||||
# arguments to the build command.
|
||||
configure_preset "${BUILD_NAME}" "${PRESET}" "${CMAKE_OPTIONS[@]}"
|
||||
# Keep going after errors, we want CI to unearth all clang-tidy errors in one go
|
||||
BUILD_OPTIONS=(-- -k 0)
|
||||
build_preset "${BUILD_NAME}" "${PRESET}" "${BUILD_OPTIONS[@]}"
|
||||
|
||||
print_time_summary
|
||||
77
cccl_upstream/ci/compile_time/README.md
Normal file
77
cccl_upstream/ci/compile_time/README.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Compile-time benchmark CI contracts
|
||||
|
||||
The compile-time benchmark CI flow is configured from `ci/matrix.yaml` under
|
||||
`compile_time.pull_request`.
|
||||
|
||||
## Matrix schema
|
||||
|
||||
Each config is a GitHub Actions matrix entry:
|
||||
|
||||
```yaml
|
||||
compile_time:
|
||||
pull_request:
|
||||
- id: public-headers-gcc13
|
||||
name: Public headers compile-time bench
|
||||
gpu: rtx2080
|
||||
launch_args: "--cuda 13.3 --host gcc13"
|
||||
baseline_ref: origin/main
|
||||
preset: all-dev
|
||||
targets:
|
||||
- cub.headers.base
|
||||
args: "-arch native"
|
||||
slices:
|
||||
- id: total-compilation
|
||||
title: TU total compilation
|
||||
filter: total-compilation
|
||||
timing: inclusive
|
||||
sort: total
|
||||
top: 15
|
||||
threshold: 0.001
|
||||
```
|
||||
|
||||
Required config fields are `id`, `name`, `gpu`, `launch_args`,
|
||||
`baseline_ref`, `preset`, `targets`, and `slices`. `args`, `comment`, and
|
||||
`artifact_retention_days` are optional.
|
||||
|
||||
Required slice fields are `id`, `title`, `filter`, `timing`, `sort`, `top`, and
|
||||
`threshold`. Slice `children` may be used to group nested report sections in the
|
||||
PR comment. Empty slice sections are omitted recursively by the renderer unless
|
||||
the summary manifest carries warnings for that slice.
|
||||
|
||||
`ci/compile_time/parse_matrix.py ci/matrix.yaml --workflow pull_request` emits
|
||||
the GitHub Actions matrix JSON. Missing or empty `compile_time.pull_request`
|
||||
emits `{"include":[]}`.
|
||||
|
||||
In baseline comparisons, `threshold` is measured against the total selected
|
||||
inclusive/exclusive impact across all matched traces. The per-side reports still
|
||||
use `sort` for their own top-N ordering; comparison worse/better tables always
|
||||
rank by total impact so a change repeated across many traces is not hidden by a
|
||||
larger single-trace movement.
|
||||
|
||||
## Report contract
|
||||
|
||||
`summarize_events.py --slices <json>` writes per-slice CSVs under
|
||||
`event_reports/<slice-id>/` and writes a normalized `event_reports/summary.json`
|
||||
manifest. The manifest is the renderer contract; CSVs are human artifacts.
|
||||
Configured slices that match no events, have no matching trace files, or have no
|
||||
comparable event keys record warnings in the manifest so reporting failures are
|
||||
not presented as ordinary no-regression results.
|
||||
|
||||
In comparison mode, the wrapper preserves:
|
||||
|
||||
- current raw traces: `compile_time/raw_traces`
|
||||
- baseline raw traces: `compile_time/baseline_raw_traces`
|
||||
- Perfetto copies: `compile_time/perfetto_traces/current` and
|
||||
`compile_time/perfetto_traces/baseline`
|
||||
|
||||
## PR comments
|
||||
|
||||
`render_pr_comment.py` reads `summary.json`, config metadata, and an artifacts
|
||||
URL, then writes the sticky PR comment body. Regressions and improvements are
|
||||
rendered in separate `<details>` blocks and are never mixed in one table.
|
||||
Warnings are rendered separately and keep their slice visible even when there
|
||||
are no regression/improvement rows.
|
||||
|
||||
The reusable workflow uses the sticky-comment header
|
||||
`compile-time-bench-<config-id>` with `hide_and_recreate: true`, so previous
|
||||
comments for the same config are archived as outdated.
|
||||
333
cccl_upstream/ci/compile_time/analytics.ipynb
Normal file
333
cccl_upstream/ci/compile_time/analytics.ipynb
Normal file
@@ -0,0 +1,333 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "903704f7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Compile-Time Analytics\n",
|
||||
"\n",
|
||||
"This notebook helps you:\n",
|
||||
"\n",
|
||||
"1. Run `ci/build_compile_time_bench.sh` from the repo root.\n",
|
||||
"2. Load and inspect an all-header processing CSV.\n",
|
||||
"3. Explore high-impact headers by TU coverage and average processing time.\n",
|
||||
"4. Build combined ranking scores to identify optimization targets.\n",
|
||||
"\n",
|
||||
"Expected CSV columns include:\n",
|
||||
"- `header_path`\n",
|
||||
"- `include_tu_count`\n",
|
||||
"- `avg_process_time_s`\n",
|
||||
"- `total_process_time_s`\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ad58db28",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%pip install pandas matplotlib plotly nbformat"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "16ba6808",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import shlex\n",
|
||||
"import sys\n",
|
||||
"from pathlib import Path\n",
|
||||
"\n",
|
||||
"import pandas as pd\n",
|
||||
"\n",
|
||||
"pd.set_option(\"display.max_colwidth\", 160)\n",
|
||||
"pd.set_option(\"display.width\", 200)\n",
|
||||
"pd.set_option(\"display.max_columns\", 20)\n",
|
||||
"\n",
|
||||
"REPO_ROOT = Path.cwd()\n",
|
||||
"while REPO_ROOT != REPO_ROOT.parent and not (REPO_ROOT / \".git\").exists():\n",
|
||||
" REPO_ROOT = REPO_ROOT.parent\n",
|
||||
"\n",
|
||||
"if not (REPO_ROOT / \".git\").exists():\n",
|
||||
" raise RuntimeError(\n",
|
||||
" \"Could not locate repo root (.git). Start notebook from inside the CCCL repo.\"\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"print(f\"Repo root: {REPO_ROOT}\")\n",
|
||||
"print(f\"Python executable: {sys.executable}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "373652c8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# --- Run build_compile_time_bench.sh (file-processing mode) ---\n",
|
||||
"import subprocess\n",
|
||||
"from pathlib import Path\n",
|
||||
"\n",
|
||||
"if \"REPO_ROOT\" not in globals():\n",
|
||||
" REPO_ROOT = Path.cwd()\n",
|
||||
" while REPO_ROOT != REPO_ROOT.parent and not (REPO_ROOT / \".git\").exists():\n",
|
||||
" REPO_ROOT = REPO_ROOT.parent\n",
|
||||
"\n",
|
||||
"output_csv = Path(os.environ.get(\"COMPILE_TIME_CSV\", \"/tmp/compile_time.csv\"))\n",
|
||||
"cmd = [\n",
|
||||
" \"bash\",\n",
|
||||
" str(REPO_ROOT / \"ci\" / \"build_compile_time_bench.sh\"),\n",
|
||||
" *shlex.split(os.environ.get(\"COMPILE_TIME_BUILD_ARGS\", \"\")),\n",
|
||||
" \"--\",\n",
|
||||
" \"-f\",\n",
|
||||
" \"file-processing\",\n",
|
||||
" \"-e\",\n",
|
||||
" \"-n\",\n",
|
||||
" os.environ.get(\"COMPILE_TIME_TOP_N\", \"5000\"),\n",
|
||||
" \"--sort\",\n",
|
||||
" \"total\",\n",
|
||||
" \"--output-csv\",\n",
|
||||
" str(output_csv),\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"print(\"Command:\")\n",
|
||||
"print(\" \" + \" \".join(shlex.quote(x) for x in cmd))\n",
|
||||
"\n",
|
||||
"subprocess.run(cmd, cwd=REPO_ROOT, check=True)\n",
|
||||
"print(f\"\\nWrote: {output_csv}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "12ad4c5c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# --- Load CSV ---\n",
|
||||
"csv_path = Path(os.environ.get(\"COMPILE_TIME_CSV\", \"/tmp/compile_time.csv\"))\n",
|
||||
"if not csv_path.exists():\n",
|
||||
" raise FileNotFoundError(f\"Missing CSV: {csv_path}. Run the script first.\")\n",
|
||||
"\n",
|
||||
"df = pd.read_csv(csv_path)\n",
|
||||
"\n",
|
||||
"event_summary_cols = {\n",
|
||||
" \"event_name\",\n",
|
||||
" \"event_key\",\n",
|
||||
" \"root_tu_count\",\n",
|
||||
" \"selected_avg_per_root_tu_s\",\n",
|
||||
" \"selected_total_s\",\n",
|
||||
"}\n",
|
||||
"required_cols = [\n",
|
||||
" \"header_path\",\n",
|
||||
" \"include_tu_count\",\n",
|
||||
" \"avg_process_time_s\",\n",
|
||||
" \"total_process_time_s\",\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"if event_summary_cols.issubset(df.columns):\n",
|
||||
" df[\"header_path\"] = df[\"event_key\"]\n",
|
||||
" df[\"include_tu_count\"] = df[\"root_tu_count\"]\n",
|
||||
" if (\n",
|
||||
" \"avg_inclusive_per_root_tu_s\" in df.columns\n",
|
||||
" and \"total_inclusive_s\" in df.columns\n",
|
||||
" ):\n",
|
||||
" df[\"avg_process_time_s\"] = df[\"avg_inclusive_per_root_tu_s\"]\n",
|
||||
" df[\"total_process_time_s\"] = df[\"total_inclusive_s\"]\n",
|
||||
" else:\n",
|
||||
" df[\"avg_process_time_s\"] = df[\"selected_avg_per_root_tu_s\"]\n",
|
||||
" df[\"total_process_time_s\"] = df[\"selected_total_s\"]\n",
|
||||
"\n",
|
||||
"missing = [c for c in required_cols if c not in df.columns]\n",
|
||||
"if missing:\n",
|
||||
" raise ValueError(\n",
|
||||
" f\"CSV is not all-header processing output. Missing columns: {missing}. \"\n",
|
||||
" \"Run the script cell above to regenerate /tmp/compile_time.csv.\"\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"for col in [\"include_tu_count\", \"avg_process_time_s\", \"total_process_time_s\"]:\n",
|
||||
" df[col] = pd.to_numeric(df[col], errors=\"coerce\").fillna(0)\n",
|
||||
"\n",
|
||||
"print(f\"Rows: {len(df):,}\")\n",
|
||||
"print(f\"Columns: {list(df.columns)}\")\n",
|
||||
"df.head(5)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1a9b36a8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# --- Quick top-N views ---\n",
|
||||
"TOP_N = 5\n",
|
||||
"\n",
|
||||
"print(\"Top by avg_process_time_s\")\n",
|
||||
"display(\n",
|
||||
" df.nlargest(TOP_N, \"avg_process_time_s\")[\n",
|
||||
" [\n",
|
||||
" \"header_path\",\n",
|
||||
" \"avg_process_time_s\",\n",
|
||||
" \"include_tu_count\",\n",
|
||||
" \"total_process_time_s\",\n",
|
||||
" ]\n",
|
||||
" ]\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"print(\"\\nTop by include_tu_count\")\n",
|
||||
"display(\n",
|
||||
" df.nlargest(TOP_N, \"include_tu_count\")[\n",
|
||||
" [\n",
|
||||
" \"header_path\",\n",
|
||||
" \"include_tu_count\",\n",
|
||||
" \"avg_process_time_s\",\n",
|
||||
" \"total_process_time_s\",\n",
|
||||
" ]\n",
|
||||
" ]\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"print(\"\\nTop by impact score (include_tu_count * avg_process_time_s)\")\n",
|
||||
"df_score = df.copy()\n",
|
||||
"df_score[\"impact_score\"] = df_score[\"include_tu_count\"] * df_score[\"avg_process_time_s\"]\n",
|
||||
"display(\n",
|
||||
" df_score.nlargest(TOP_N, \"impact_score\")[\n",
|
||||
" [\n",
|
||||
" \"header_path\",\n",
|
||||
" \"impact_score\",\n",
|
||||
" \"include_tu_count\",\n",
|
||||
" \"avg_process_time_s\",\n",
|
||||
" \"total_process_time_s\",\n",
|
||||
" ]\n",
|
||||
" ]\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "174eabd4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### How to read this plot\n",
|
||||
"\n",
|
||||
"- Each point is one header seen in all-mode profiling.\n",
|
||||
"- **X axis (`include_tu_count`)**: how many generated public-header TUs include this header at least once.\n",
|
||||
"- **Y axis (`avg_process_time_s`)**: average time spent processing that header per including TU.\n",
|
||||
"- Headers near the **upper-right** are usually the best optimization candidates because they are both widespread and expensive per include."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9ddc0725",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# --- Interactive scatter (hover shows header name) ---\n",
|
||||
"\n",
|
||||
"import plotly.express as px\n",
|
||||
"\n",
|
||||
"plot_df = df.copy()\n",
|
||||
"\n",
|
||||
"total_headers = len(plot_df)\n",
|
||||
"total_public_headers = int(plot_df[\"include_tu_count\"].max())\n",
|
||||
"\n",
|
||||
"fig = px.scatter(\n",
|
||||
" plot_df,\n",
|
||||
" x=\"include_tu_count\",\n",
|
||||
" y=\"avg_process_time_s\",\n",
|
||||
" hover_name=\"header_path\",\n",
|
||||
" hover_data={\n",
|
||||
" \"include_tu_count\": True,\n",
|
||||
" \"avg_process_time_s\": \":.6f\",\n",
|
||||
" \"total_process_time_s\": \":.3f\",\n",
|
||||
" \"header_path\": False,\n",
|
||||
" },\n",
|
||||
" opacity=0.6,\n",
|
||||
" title=(\n",
|
||||
" f\"Header include count vs avg processing time ({total_headers:,} total headers; \"\n",
|
||||
" f\"coverage measured across {total_public_headers} public headers)\"\n",
|
||||
" ),\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"fig.update_layout(\n",
|
||||
" xaxis_title=(\n",
|
||||
" \"TU coverage: number of public headers that include this header \"\n",
|
||||
" f\"(out of {total_public_headers})\"\n",
|
||||
" ),\n",
|
||||
" yaxis_title=\"Average processing time per including TU (seconds)\",\n",
|
||||
" height=900,\n",
|
||||
")\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f429b934",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# --- Optional: run ctadvisor and parse expensive headers ---\n",
|
||||
"run_ctadvisor = os.environ.get(\"COMPILE_TIME_RUN_CTADVISOR\") == \"1\"\n",
|
||||
"CTADVISOR_ENTRIES = 10\n",
|
||||
"CTADVISOR_THREADS = os.cpu_count() or 8\n",
|
||||
"\n",
|
||||
"trace_root = (\n",
|
||||
" REPO_ROOT\n",
|
||||
" / \"build\"\n",
|
||||
" / os.environ.get(\"CCCL_BUILD_INFIX\", \"cuda13.1-gcc14\")\n",
|
||||
" / os.environ.get(\"CCCL_COMPILE_TIME_PRESET\", \"all-dev\")\n",
|
||||
" / \"compile_time\"\n",
|
||||
" / \"raw_traces\"\n",
|
||||
")\n",
|
||||
"ctadvisor_cmd = [\n",
|
||||
" \"ctadvisor\",\n",
|
||||
" \"--trace-file-path\",\n",
|
||||
" str(trace_root),\n",
|
||||
" \"--header-advisor-entries\",\n",
|
||||
" str(CTADVISOR_ENTRIES),\n",
|
||||
" \"--thread-number\",\n",
|
||||
" str(CTADVISOR_THREADS),\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"if run_ctadvisor:\n",
|
||||
" print(\"Command:\")\n",
|
||||
" print(\" \" + \" \".join(shlex.quote(x) for x in ctadvisor_cmd))\n",
|
||||
"\n",
|
||||
" result = subprocess.run(\n",
|
||||
" ctadvisor_cmd, cwd=REPO_ROOT, check=True, capture_output=True, text=True\n",
|
||||
" )\n",
|
||||
" print(result.stdout)\n",
|
||||
"else:\n",
|
||||
" print(\"Skipping ctadvisor. Set COMPILE_TIME_RUN_CTADVISOR=1 to run it.\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "cccl",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
272
cccl_upstream/ci/compile_time/parse_matrix.py
Executable file
272
cccl_upstream/ci/compile_time/parse_matrix.py
Executable file
@@ -0,0 +1,272 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except ModuleNotFoundError:
|
||||
yaml = None
|
||||
YAML_ERROR_TYPES: tuple[type[BaseException], ...] = ()
|
||||
else:
|
||||
YAML_ERROR_TYPES = (yaml.YAMLError,)
|
||||
|
||||
ID_RE = re.compile(r"^[a-z0-9][a-z0-9_.-]*$")
|
||||
TIMINGS = {"inclusive", "exclusive"}
|
||||
SORTS = {"total", "avg", "avg-root-tu", "max"}
|
||||
|
||||
|
||||
def die(message: str) -> None:
|
||||
print(f"error: {message}", file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
|
||||
|
||||
def require_mapping(value: Any, where: str) -> dict[str, Any]:
|
||||
if not isinstance(value, dict):
|
||||
die(f"{where} must be a mapping")
|
||||
return value
|
||||
|
||||
|
||||
def require_field(mapping: dict[str, Any], field: str, where: str) -> Any:
|
||||
if field not in mapping:
|
||||
die(f"{where} is missing required field '{field}'")
|
||||
return mapping[field]
|
||||
|
||||
|
||||
def require_string(value: Any, where: str, *, nonempty: bool = True) -> str:
|
||||
if not isinstance(value, str):
|
||||
die(f"{where} must be a string")
|
||||
if nonempty and not value:
|
||||
die(f"{where} must be non-empty")
|
||||
return value
|
||||
|
||||
|
||||
def require_id(value: Any, where: str) -> str:
|
||||
text = require_string(value, where)
|
||||
if not ID_RE.fullmatch(text):
|
||||
die(f"{where} must match {ID_RE.pattern}")
|
||||
return text
|
||||
|
||||
|
||||
def require_string_list(value: Any, where: str) -> list[str]:
|
||||
if not isinstance(value, list) or not value:
|
||||
die(f"{where} must be a non-empty list")
|
||||
strings: list[str] = []
|
||||
for index, item in enumerate(value):
|
||||
strings.append(require_string(item, f"{where}[{index}]"))
|
||||
return strings
|
||||
|
||||
|
||||
def require_bool(value: Any, where: str) -> bool:
|
||||
if not isinstance(value, bool):
|
||||
die(f"{where} must be a boolean")
|
||||
return value
|
||||
|
||||
|
||||
def require_positive_int(value: Any, where: str) -> int:
|
||||
if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
|
||||
die(f"{where} must be a positive integer")
|
||||
return value
|
||||
|
||||
|
||||
def validate_slice(
|
||||
slice_data: Any,
|
||||
*,
|
||||
where: str,
|
||||
seen_ids: set[str],
|
||||
) -> dict[str, Any]:
|
||||
data = require_mapping(slice_data, where)
|
||||
slice_id = require_id(require_field(data, "id", where), f"{where}.id")
|
||||
if slice_id in seen_ids:
|
||||
die(f"duplicate slice id '{slice_id}' in {where}")
|
||||
seen_ids.add(slice_id)
|
||||
|
||||
title = require_string(require_field(data, "title", where), f"{where}.title")
|
||||
filter_name = require_string(
|
||||
require_field(data, "filter", where), f"{where}.filter"
|
||||
)
|
||||
timing = require_string(require_field(data, "timing", where), f"{where}.timing")
|
||||
if timing not in TIMINGS:
|
||||
die(f"{where}.timing must be one of {sorted(TIMINGS)}")
|
||||
sort = require_string(require_field(data, "sort", where), f"{where}.sort")
|
||||
if sort not in SORTS:
|
||||
die(f"{where}.sort must be one of {sorted(SORTS)}")
|
||||
|
||||
top = require_field(data, "top", where)
|
||||
if isinstance(top, bool) or not isinstance(top, int) or top <= 0:
|
||||
die(f"{where}.top must be a positive integer")
|
||||
threshold = require_field(data, "threshold", where)
|
||||
if (
|
||||
isinstance(threshold, bool)
|
||||
or not isinstance(threshold, (int, float))
|
||||
or threshold < 0
|
||||
):
|
||||
die(f"{where}.threshold must be a non-negative number")
|
||||
|
||||
result: dict[str, Any] = {
|
||||
"id": slice_id,
|
||||
"title": title,
|
||||
"filter": filter_name,
|
||||
"timing": timing,
|
||||
"sort": sort,
|
||||
"top": top,
|
||||
"threshold": threshold,
|
||||
}
|
||||
for optional in ("scope_filter", "exclusive_scope"):
|
||||
if optional in data:
|
||||
result[optional] = require_string(
|
||||
data[optional], f"{where}.{optional}", nonempty=False
|
||||
)
|
||||
|
||||
children = data.get("children", [])
|
||||
if not isinstance(children, list):
|
||||
die(f"{where}.children must be a list")
|
||||
if children:
|
||||
result["children"] = [
|
||||
validate_slice(
|
||||
child,
|
||||
where=f"{where}.children[{index}]",
|
||||
seen_ids=seen_ids,
|
||||
)
|
||||
for index, child in enumerate(children)
|
||||
]
|
||||
return result
|
||||
|
||||
|
||||
def validate_config(
|
||||
config_data: Any, *, where: str, seen_ids: set[str]
|
||||
) -> dict[str, Any]:
|
||||
data = require_mapping(config_data, where)
|
||||
config_id = require_id(require_field(data, "id", where), f"{where}.id")
|
||||
if config_id in seen_ids:
|
||||
die(f"duplicate compile_time config id '{config_id}'")
|
||||
seen_ids.add(config_id)
|
||||
|
||||
targets = require_string_list(
|
||||
require_field(data, "targets", where), f"{where}.targets"
|
||||
)
|
||||
slices = require_field(data, "slices", where)
|
||||
if not isinstance(slices, list) or not slices:
|
||||
die(f"{where}.slices must be a non-empty list")
|
||||
|
||||
slice_ids: set[str] = set()
|
||||
normalized_slices = [
|
||||
validate_slice(
|
||||
slice_data,
|
||||
where=f"{where}.slices[{index}]",
|
||||
seen_ids=slice_ids,
|
||||
)
|
||||
for index, slice_data in enumerate(slices)
|
||||
]
|
||||
|
||||
return {
|
||||
"id": config_id,
|
||||
"name": require_string(require_field(data, "name", where), f"{where}.name"),
|
||||
"gpu": require_string(require_field(data, "gpu", where), f"{where}.gpu"),
|
||||
"launch_args": require_string(
|
||||
require_field(data, "launch_args", where), f"{where}.launch_args"
|
||||
),
|
||||
"baseline_ref": require_string(
|
||||
require_field(data, "baseline_ref", where), f"{where}.baseline_ref"
|
||||
),
|
||||
"preset": require_string(
|
||||
require_field(data, "preset", where), f"{where}.preset"
|
||||
),
|
||||
"targets": targets,
|
||||
"args": require_string(data.get("args", ""), f"{where}.args", nonempty=False),
|
||||
"comment": require_bool(data.get("comment", True), f"{where}.comment"),
|
||||
"artifact_retention_days": require_positive_int(
|
||||
data.get("artifact_retention_days", 14),
|
||||
f"{where}.artifact_retention_days",
|
||||
),
|
||||
"slices": normalized_slices,
|
||||
}
|
||||
|
||||
|
||||
def matrix_entry(config: dict[str, Any]) -> dict[str, Any]:
|
||||
config_id = config["id"]
|
||||
return {
|
||||
"id": config_id,
|
||||
"name": config["name"],
|
||||
"gpu": config["gpu"],
|
||||
"launch_args": config["launch_args"],
|
||||
"baseline_ref": config["baseline_ref"],
|
||||
"preset": config["preset"],
|
||||
"targets_json": json.dumps(config["targets"], separators=(",", ":")),
|
||||
"args": config["args"],
|
||||
"slices_json": json.dumps({"slices": config["slices"]}, separators=(",", ":")),
|
||||
"comment": str(config["comment"]).lower(),
|
||||
"artifact_retention_days": config["artifact_retention_days"],
|
||||
"comment_header": f"compile-time-bench-{config_id}",
|
||||
}
|
||||
|
||||
|
||||
def parse_matrix(path: Path, workflow: str) -> dict[str, Any]:
|
||||
try:
|
||||
if yaml is not None:
|
||||
with path.open(encoding="utf-8") as f:
|
||||
matrix = yaml.safe_load(f) or {}
|
||||
else:
|
||||
completed = subprocess.run(
|
||||
["yq", "-o=json", ".", path.as_posix()],
|
||||
check=True,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
matrix = json.loads(completed.stdout or "{}")
|
||||
except OSError as e:
|
||||
die(f"failed to read {path}: {e}")
|
||||
except subprocess.CalledProcessError as e:
|
||||
die(f"failed to parse {path} with yq: {e.stderr.strip()}")
|
||||
except json.JSONDecodeError as e:
|
||||
die(f"failed to decode {path} as JSON: {e}")
|
||||
except YAML_ERROR_TYPES as e:
|
||||
die(f"failed to parse {path}: {e}")
|
||||
|
||||
compile_time = matrix.get("compile_time")
|
||||
if compile_time is None:
|
||||
return {"include": []}
|
||||
compile_time = require_mapping(compile_time, "compile_time")
|
||||
configs = compile_time.get(workflow, [])
|
||||
if configs is None:
|
||||
configs = []
|
||||
if not isinstance(configs, list):
|
||||
die(f"compile_time.{workflow} must be a list")
|
||||
if not configs:
|
||||
return {"include": []}
|
||||
|
||||
seen_ids: set[str] = set()
|
||||
return {
|
||||
"include": [
|
||||
matrix_entry(
|
||||
validate_config(
|
||||
config,
|
||||
where=f"compile_time.{workflow}[{index}]",
|
||||
seen_ids=seen_ids,
|
||||
)
|
||||
)
|
||||
for index, config in enumerate(configs)
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Parse ci/matrix.yaml compile_time entries for GitHub Actions."
|
||||
)
|
||||
parser.add_argument("matrix_yaml", type=Path)
|
||||
parser.add_argument("--workflow", default="pull_request")
|
||||
args = parser.parse_args()
|
||||
|
||||
json.dump(parse_matrix(args.matrix_yaml, args.workflow), sys.stdout)
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
167
cccl_upstream/ci/compile_time/prepare_traces.py
Executable file
167
cccl_upstream/ci/compile_time/prepare_traces.py
Executable file
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
DETAIL_EVENT_NAMES = {
|
||||
"Code Generation Function",
|
||||
"CodeGen Function",
|
||||
"ExecuteCompiler",
|
||||
"Frontend",
|
||||
"Instantiating Template Class",
|
||||
"Instantiating Template Function",
|
||||
"InstantiateClass",
|
||||
"InstantiateFunction",
|
||||
"OptFunction",
|
||||
"ParseClass",
|
||||
"PerformPendingInstantiations",
|
||||
"Processing Header File",
|
||||
"RunPass",
|
||||
"Scanning Function Body",
|
||||
"Source",
|
||||
}
|
||||
|
||||
DETAIL_PREFIXES_TO_STRIP = (
|
||||
"libcudacxx/include/",
|
||||
"cudax/include/",
|
||||
"c/parallel/include/",
|
||||
)
|
||||
|
||||
DETAIL_PREFIXES_TO_COLLAPSE = (
|
||||
("cub/cub/", "cub/"),
|
||||
("thrust/thrust/", "thrust/"),
|
||||
)
|
||||
|
||||
|
||||
def normalize_detail(detail: str, repo_root: Path) -> str:
|
||||
detail_path = Path(detail)
|
||||
if detail_path.is_absolute():
|
||||
try:
|
||||
rel = detail_path.resolve(strict=False).relative_to(repo_root)
|
||||
detail = rel.as_posix()
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
for prefix in DETAIL_PREFIXES_TO_STRIP:
|
||||
if detail.startswith(prefix):
|
||||
detail = detail[len(prefix) :]
|
||||
break
|
||||
|
||||
for prefix, replacement in DETAIL_PREFIXES_TO_COLLAPSE:
|
||||
if detail.startswith(prefix):
|
||||
detail = replacement + detail[len(prefix) :]
|
||||
break
|
||||
|
||||
return detail
|
||||
|
||||
|
||||
def display_detail(detail: str, repo_root: Path, max_detail_len: int | None) -> str:
|
||||
detail = normalize_detail(detail, repo_root)
|
||||
if (
|
||||
max_detail_len is not None
|
||||
and max_detail_len > 0
|
||||
and len(detail) > max_detail_len
|
||||
):
|
||||
return detail[: max_detail_len - 1] + "..."
|
||||
return detail
|
||||
|
||||
|
||||
def rewrite_event_name(
|
||||
event: dict, repo_root: Path, max_detail_len: int | None
|
||||
) -> bool:
|
||||
name = event.get("name")
|
||||
if name not in DETAIL_EVENT_NAMES:
|
||||
return False
|
||||
|
||||
args = event.get("args")
|
||||
if not isinstance(args, dict):
|
||||
return False
|
||||
|
||||
detail = args.get("detail")
|
||||
if not detail:
|
||||
return False
|
||||
|
||||
args.setdefault("original_name", name)
|
||||
event["name"] = f"{name}: {display_detail(str(detail), repo_root, max_detail_len)}"
|
||||
return True
|
||||
|
||||
|
||||
def prepare_trace(
|
||||
input_path: Path, output_path: Path, repo_root: Path, max_detail_len: int | None
|
||||
) -> int:
|
||||
with input_path.open(encoding="utf-8") as f:
|
||||
trace = json.load(f)
|
||||
|
||||
rewritten = 0
|
||||
for event in trace.get("traceEvents", []):
|
||||
if rewrite_event_name(event, repo_root, max_detail_len):
|
||||
rewritten += 1
|
||||
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with output_path.open("w", encoding="utf-8") as f:
|
||||
json.dump(trace, f, separators=(",", ":"))
|
||||
|
||||
return rewritten
|
||||
|
||||
|
||||
def iter_input_traces(input_path: Path) -> list[Path]:
|
||||
if input_path.is_file():
|
||||
return [input_path]
|
||||
return sorted(input_path.rglob("*.json"))
|
||||
|
||||
|
||||
def output_path_for(input_trace: Path, input_root: Path, output_path: Path) -> Path:
|
||||
if input_root.is_file():
|
||||
if output_path.is_dir() or not output_path.suffix:
|
||||
return output_path / f"{input_trace.stem}.perfetto.json"
|
||||
return output_path
|
||||
|
||||
rel = input_trace.relative_to(input_root)
|
||||
return output_path / rel.parent / f"{rel.stem}.perfetto.json"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Prepare NVCC device-time-trace JSON files for Perfetto by promoting args.detail into event names."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--input", required=True, type=Path, help="Input trace JSON file or directory"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output", required=True, type=Path, help="Output trace JSON file or directory"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--repo-root", default=Path(__file__).resolve().parents[2], type=Path
|
||||
)
|
||||
parser.add_argument(
|
||||
"--max-detail-len",
|
||||
default=0,
|
||||
type=int,
|
||||
help="Truncate promoted detail text to this many characters; 0 keeps full details",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
input_path = args.input.resolve(strict=False)
|
||||
output_path = args.output.resolve(strict=False)
|
||||
repo_root = args.repo_root.resolve(strict=False)
|
||||
max_detail_len = args.max_detail_len if args.max_detail_len > 0 else None
|
||||
|
||||
traces = iter_input_traces(input_path)
|
||||
if not traces:
|
||||
raise SystemExit(f"no JSON traces found under {args.input}")
|
||||
|
||||
total_rewritten = 0
|
||||
for trace_path in traces:
|
||||
total_rewritten += prepare_trace(
|
||||
trace_path,
|
||||
output_path_for(trace_path, input_path, output_path),
|
||||
repo_root,
|
||||
max_detail_len,
|
||||
)
|
||||
|
||||
print(f"prepared {len(traces)} trace(s); renamed {total_rewritten} event(s)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
246
cccl_upstream/ci/compile_time/render_pr_comment.py
Executable file
246
cccl_upstream/ci/compile_time/render_pr_comment.py
Executable file
@@ -0,0 +1,246 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
def load_json(path: Path) -> dict[str, Any]:
|
||||
with path.open(encoding="utf-8") as f:
|
||||
payload = json.load(f)
|
||||
if not isinstance(payload, dict):
|
||||
raise SystemExit(f"{path} must contain a JSON object")
|
||||
return payload
|
||||
|
||||
|
||||
def md_escape(value: object) -> str:
|
||||
text = str(value)
|
||||
return (
|
||||
text.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("|", "\\|")
|
||||
.replace("\n", " ")
|
||||
)
|
||||
|
||||
|
||||
def md_code_span(value: object) -> str:
|
||||
text = str(value).replace("\n", " ")
|
||||
max_backtick_run = 0
|
||||
current_backtick_run = 0
|
||||
for char in text:
|
||||
if char == "`":
|
||||
current_backtick_run += 1
|
||||
max_backtick_run = max(max_backtick_run, current_backtick_run)
|
||||
else:
|
||||
current_backtick_run = 0
|
||||
delimiter = "`" * (max_backtick_run + 1)
|
||||
if text.startswith("`") or text.endswith("`"):
|
||||
text = f" {text} "
|
||||
return f"{delimiter}{text}{delimiter}"
|
||||
|
||||
|
||||
def render_event_name(row: dict[str, Any]) -> str:
|
||||
event_name = row.get("event_name", "")
|
||||
event_key = row.get("event_key", "")
|
||||
if event_key:
|
||||
return f"{md_escape(event_name)}: {md_code_span(event_key)}"
|
||||
return md_escape(event_name)
|
||||
|
||||
|
||||
def render_rows(rows: list[dict[str, Any]], *, direction: str) -> str:
|
||||
delta_heading = (
|
||||
"Regression impact" if direction == "worse" else "Improvement impact"
|
||||
)
|
||||
lines = [
|
||||
f"| Rank | {delta_heading} | Selected Δ | Baseline | Current | Event | Matched traces |",
|
||||
"| ---: | ---: | ---: | ---: | ---: | --- | ---: |",
|
||||
]
|
||||
for row in rows:
|
||||
lines.append(
|
||||
"| {rank} | `{impact}` | `{selected_delta}` | `{baseline}` | `{current}` | {event} | {traces} |".format(
|
||||
rank=md_escape(row.get("rank", "")),
|
||||
impact=md_escape(row.get("impact_magnitude_s", "")),
|
||||
selected_delta=md_escape(row.get("selected_delta_s", "")),
|
||||
baseline=md_escape(row.get("baseline_selected_s", "")),
|
||||
current=md_escape(row.get("current_selected_s", "")),
|
||||
event=render_event_name(row),
|
||||
traces=md_escape(row.get("matched_trace_count", "")),
|
||||
)
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def render_direction_details(
|
||||
slice_title: str,
|
||||
direction: str,
|
||||
rows: list[dict[str, Any]],
|
||||
) -> str:
|
||||
if not rows:
|
||||
return ""
|
||||
label = "Regressions" if direction == "worse" else "Improvements"
|
||||
icon = "🔴" if direction == "worse" else "🟢"
|
||||
return "\n".join(
|
||||
[
|
||||
"<details>",
|
||||
f"<summary><strong>{icon} {md_escape(slice_title)} — {label}</strong></summary>",
|
||||
"",
|
||||
render_rows(rows, direction=direction),
|
||||
"",
|
||||
"</details>",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def render_warning_details(slice_title: str, warnings: list[Any]) -> str:
|
||||
if not warnings:
|
||||
return ""
|
||||
lines = [
|
||||
"<details open>",
|
||||
f"<summary><strong>⚠️ {md_escape(slice_title)} — Warnings</strong></summary>",
|
||||
"",
|
||||
]
|
||||
lines.extend(f"- {md_escape(warning)}" for warning in warnings)
|
||||
lines.extend(["", "</details>"])
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def render_slice(slice_data: dict[str, Any], *, level: int = 3) -> str:
|
||||
comparison = slice_data.get("comparison", {})
|
||||
worse_rows = comparison.get("worse", {}).get("rows", [])
|
||||
better_rows = comparison.get("better", {}).get("rows", [])
|
||||
warnings = slice_data.get("warnings", [])
|
||||
child_sections = [
|
||||
rendered
|
||||
for child in slice_data.get("children", [])
|
||||
if (rendered := render_slice(child, level=level + 1))
|
||||
]
|
||||
direct_sections = [
|
||||
section
|
||||
for section in (
|
||||
render_warning_details(slice_data.get("title", "Slice"), warnings),
|
||||
render_direction_details(
|
||||
slice_data.get("title", "Slice"), "worse", worse_rows
|
||||
),
|
||||
render_direction_details(
|
||||
slice_data.get("title", "Slice"), "better", better_rows
|
||||
),
|
||||
)
|
||||
if section
|
||||
]
|
||||
if not direct_sections and not child_sections:
|
||||
return ""
|
||||
|
||||
heading_prefix = "#" * min(level, 6)
|
||||
subtitle = (
|
||||
f"`-f {slice_data.get('filter', '')}` "
|
||||
f"`{slice_data.get('timing', '')}` "
|
||||
f"`--sort {slice_data.get('sort', '')}`"
|
||||
)
|
||||
lines = [
|
||||
f"{heading_prefix} {md_escape(slice_data.get('title', 'Slice'))}",
|
||||
"",
|
||||
subtitle,
|
||||
"",
|
||||
]
|
||||
lines.extend(join_sections(direct_sections))
|
||||
if child_sections:
|
||||
lines.extend(["", *join_sections(child_sections)])
|
||||
return "\n".join(lines).strip()
|
||||
|
||||
|
||||
def join_sections(sections: list[str]) -> list[str]:
|
||||
lines: list[str] = []
|
||||
for section in sections:
|
||||
if lines:
|
||||
lines.append("")
|
||||
lines.append(section)
|
||||
return lines
|
||||
|
||||
|
||||
def count_rows(slice_data: dict[str, Any], direction: str) -> int:
|
||||
comparison = slice_data.get("comparison", {})
|
||||
total = len(comparison.get(direction, {}).get("rows", []))
|
||||
return total + sum(
|
||||
count_rows(child, direction) for child in slice_data.get("children", [])
|
||||
)
|
||||
|
||||
|
||||
def count_warnings(slice_data: dict[str, Any]) -> int:
|
||||
return len(slice_data.get("warnings", [])) + sum(
|
||||
count_warnings(child) for child in slice_data.get("children", [])
|
||||
)
|
||||
|
||||
|
||||
def render_comment(
|
||||
summary: dict[str, Any],
|
||||
config: dict[str, Any],
|
||||
*,
|
||||
artifacts_url: str,
|
||||
) -> str:
|
||||
config_id = str(config["id"])
|
||||
slices = summary.get("slices", [])
|
||||
sections = [
|
||||
section for slice_data in slices if (section := render_slice(slice_data))
|
||||
]
|
||||
worse_count = sum(count_rows(slice_data, "worse") for slice_data in slices)
|
||||
better_count = sum(count_rows(slice_data, "better") for slice_data in slices)
|
||||
warning_count = sum(count_warnings(slice_data) for slice_data in slices)
|
||||
result = (
|
||||
f"**Result:** {worse_count} regression row(s), "
|
||||
f"{better_count} improvement row(s) above threshold."
|
||||
)
|
||||
if warning_count:
|
||||
result += f" {warning_count} warning(s)."
|
||||
|
||||
lines = [
|
||||
f"<!-- cccl-compile-time-bench: {md_escape(config_id)} -->",
|
||||
f"## ⏱️ CCCL compile-time benchmark comparison: {md_escape(config.get('name', config_id))}",
|
||||
"",
|
||||
result,
|
||||
"",
|
||||
"| Run | Value |",
|
||||
"| --- | --- |",
|
||||
f"| Config | {md_code_span(config_id)} |",
|
||||
f"| Baseline | {md_code_span(config.get('baseline_ref', ''))} |",
|
||||
f"| Preset | {md_code_span(config.get('preset', ''))} |",
|
||||
f"| Targets | {md_code_span(', '.join(config.get('targets', [])))} |",
|
||||
f"| GPU / launch args | {md_code_span(config.get('gpu', ''))} / {md_code_span(config.get('launch_args', ''))} |",
|
||||
"",
|
||||
f"**Artifacts:** [reports and traces]({artifacts_url})",
|
||||
"",
|
||||
]
|
||||
if sections:
|
||||
lines.extend(join_sections(sections))
|
||||
else:
|
||||
lines.append(
|
||||
"No compile-time benchmark changes exceeded the configured thresholds."
|
||||
)
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Render a GitHub PR comment from compile-time report JSON."
|
||||
)
|
||||
parser.add_argument("--summary", type=Path, required=True)
|
||||
parser.add_argument("--config", type=Path, required=True)
|
||||
parser.add_argument("--artifacts-url", required=True)
|
||||
parser.add_argument("-o", "--output", type=Path)
|
||||
args = parser.parse_args()
|
||||
|
||||
comment = render_comment(
|
||||
load_json(args.summary),
|
||||
load_json(args.config),
|
||||
artifacts_url=args.artifacts_url,
|
||||
)
|
||||
if args.output:
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_text(comment, encoding="utf-8")
|
||||
else:
|
||||
print(comment, end="")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1718
cccl_upstream/ci/compile_time/summarize_events.py
Executable file
1718
cccl_upstream/ci/compile_time/summarize_events.py
Executable file
File diff suppressed because it is too large
Load Diff
147
cccl_upstream/ci/compile_time/summarize_tus.py
Executable file
147
cccl_upstream/ci/compile_time/summarize_tus.py
Executable file
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
GENERATED_TU_MARKER = "/headers/"
|
||||
GENERATED_TU_SOURCE_SUFFIXES = (".cu", ".cpp", ".cxx", ".cc", ".c")
|
||||
PREPROCESSED_TU_SUFFIX = ".cpp4.ii"
|
||||
PREPROCESSED_TU_SUFFIXES = (".cpp4.ii", ".ii")
|
||||
|
||||
|
||||
def strip_generated_tu_suffix(path_text: str) -> str:
|
||||
for suffix in GENERATED_TU_SOURCE_SUFFIXES:
|
||||
if path_text.endswith(suffix):
|
||||
return path_text[: -len(suffix)]
|
||||
return path_text
|
||||
|
||||
|
||||
def generated_tu_input(tu: Path) -> str:
|
||||
parts = tu.as_posix().split(GENERATED_TU_MARKER, 1)
|
||||
if len(parts) != 2:
|
||||
return tu.as_posix()
|
||||
|
||||
rel = parts[1].split("/", 1)
|
||||
if len(rel) != 2:
|
||||
return tu.as_posix()
|
||||
|
||||
return strip_generated_tu_suffix(rel[1])
|
||||
|
||||
|
||||
def find_preprocessed_tus(build_dir: Path) -> list[Path]:
|
||||
return sorted(
|
||||
{
|
||||
path
|
||||
for suffix in PREPROCESSED_TU_SUFFIXES
|
||||
for path in build_dir.glob(f"**/headers/**/*{suffix}")
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def tu_source_for_preprocessed_tu(pp_path: Path) -> Path:
|
||||
pp_text = pp_path.as_posix()
|
||||
for suffix in PREPROCESSED_TU_SUFFIXES:
|
||||
if pp_text.endswith(suffix):
|
||||
return Path(pp_text[: -len(suffix)])
|
||||
return pp_path.with_suffix("")
|
||||
|
||||
|
||||
def run_cloc(preprocessed_tus: list[Path], processes: int) -> dict[str, int]:
|
||||
if not preprocessed_tus:
|
||||
return {}
|
||||
|
||||
command = [
|
||||
"cloc",
|
||||
"--csv",
|
||||
"--by-file",
|
||||
"--skip-uniqueness",
|
||||
"--processes",
|
||||
str(processes),
|
||||
"--force-lang=C++,ii",
|
||||
*[path.as_posix() for path in preprocessed_tus],
|
||||
]
|
||||
result = subprocess.run(command, check=True, capture_output=True, text=True)
|
||||
|
||||
loc_by_file: dict[str, int] = {}
|
||||
reader = csv.reader(result.stdout.splitlines())
|
||||
for row in reader:
|
||||
if len(row) < 5 or row[1] == "filename":
|
||||
continue
|
||||
try:
|
||||
loc_by_file[row[1]] = int(row[4])
|
||||
except ValueError:
|
||||
continue
|
||||
return loc_by_file
|
||||
|
||||
|
||||
def write_summary(
|
||||
output_csv: Path,
|
||||
preprocessed_tus: list[Path],
|
||||
loc_by_file: dict[str, int],
|
||||
) -> None:
|
||||
output_csv.parent.mkdir(parents=True, exist_ok=True)
|
||||
with output_csv.open("w", newline="", encoding="utf-8") as f:
|
||||
writer = csv.DictWriter(
|
||||
f,
|
||||
fieldnames=[
|
||||
"tu_input",
|
||||
"transitive_loc",
|
||||
"tu_source",
|
||||
"preprocessed_tu",
|
||||
],
|
||||
)
|
||||
writer.writeheader()
|
||||
for pp_path in preprocessed_tus:
|
||||
tu_path = tu_source_for_preprocessed_tu(pp_path)
|
||||
writer.writerow(
|
||||
{
|
||||
"tu_input": generated_tu_input(tu_path),
|
||||
"transitive_loc": loc_by_file.get(pp_path.as_posix(), 0),
|
||||
"tu_source": tu_path.as_posix(),
|
||||
"preprocessed_tu": pp_path.as_posix(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Summarize generated TU inputs and preprocessed LOC."
|
||||
)
|
||||
parser.add_argument("--build-dir", required=True, type=Path)
|
||||
parser.add_argument("--output-csv", required=True, type=Path)
|
||||
parser.add_argument(
|
||||
"--cloc-processes",
|
||||
type=int,
|
||||
default=0,
|
||||
help="cloc process count; 0 uses nproc --all --ignore=2 when available",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
build_dir = args.build_dir.resolve(strict=False)
|
||||
preprocessed_tus = find_preprocessed_tus(build_dir)
|
||||
if not preprocessed_tus:
|
||||
raise SystemExit(f"no preprocessed generated TUs found under {build_dir}")
|
||||
|
||||
processes = args.cloc_processes
|
||||
if processes <= 0:
|
||||
try:
|
||||
processes = int(
|
||||
subprocess.check_output(
|
||||
["nproc", "--all", "--ignore=2"], text=True
|
||||
).strip()
|
||||
)
|
||||
except (subprocess.SubprocessError, ValueError):
|
||||
processes = 1
|
||||
|
||||
write_summary(
|
||||
output_csv=args.output_csv,
|
||||
preprocessed_tus=preprocessed_tus,
|
||||
loc_by_file=run_cloc(preprocessed_tus, processes),
|
||||
)
|
||||
print(f"wrote {len(preprocessed_tus)} generated TU row(s) to {args.output_csv}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1731
cccl_upstream/ci/compile_time/test_summarize_events.py
Normal file
1731
cccl_upstream/ci/compile_time/test_summarize_events.py
Normal file
File diff suppressed because it is too large
Load Diff
453
cccl_upstream/ci/compute-sanitizer-suppressions.xml
Normal file
453
cccl_upstream/ci/compute-sanitizer-suppressions.xml
Normal file
@@ -0,0 +1,453 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ComputeSanitizerOutput>
|
||||
<!--
|
||||
thrust::equal reduces using an accumulator type tuple<bool, OffsetT>.
|
||||
The padding bytes inside the tuple are not initialized.
|
||||
This causes issues during `cub::ThreadLoad`, which loads the memory as aliased
|
||||
machine words that are cast to the tuple type.
|
||||
-->
|
||||
<record>
|
||||
<kind>Initcheck</kind>
|
||||
<what>
|
||||
<text>Uninitialized __global__ memory read of size 2 bytes</text>
|
||||
<size>2</size>
|
||||
</what>
|
||||
<where>
|
||||
<func>ThreadLoad</func>
|
||||
</where>
|
||||
<deviceStack>
|
||||
<frame>
|
||||
<func>UnrolledThreadLoadImpl</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>UnrolledThreadLoad</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>ThreadLoad</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>ThreadLoad</func>
|
||||
</frame>
|
||||
</deviceStack>
|
||||
<hostStack>
|
||||
<frame>
|
||||
<module>.*libcuda.so.*</module>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>cudaLaunchKernel</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>.*cub::.*::DeviceReduce.*.*thrust::.*find_if.*</func>
|
||||
</frame>
|
||||
</hostStack>
|
||||
</record>
|
||||
<!--
|
||||
Similar to the above, thrust::equal copies a tuple<bool, OffsetT> from host -> device
|
||||
with the result of the comparison. The padding bytes trigger host API initialization
|
||||
errors during the cudaMemcpy.
|
||||
Sadly, this is a very generic suppression that may hide real issues, but it's the best
|
||||
we can do given the current tooling.
|
||||
-->
|
||||
<record>
|
||||
<kind>InitcheckApiError</kind>
|
||||
<level>Error</level>
|
||||
<what>
|
||||
<text>Host API uninitialized memory access</text>
|
||||
<accessSize>16</accessSize>
|
||||
</what>
|
||||
<hostStack>
|
||||
<saveLocation>error</saveLocation>
|
||||
<frame>
|
||||
<module>.*/libcuda.so.*</module>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static_.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static_.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>cudaMemcpyAsync</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>void C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_.*</func>
|
||||
</frame>
|
||||
</hostStack>
|
||||
</record>
|
||||
<!-- Another variant of the above with a different catch2 dispatcher -->
|
||||
<record>
|
||||
<kind>InitcheckApiError</kind>
|
||||
<level>Error</level>
|
||||
<what>
|
||||
<text>Host API uninitialized memory access</text>
|
||||
<accessSize>16</accessSize>
|
||||
</what>
|
||||
<hostStack>
|
||||
<saveLocation>error</saveLocation>
|
||||
<frame>
|
||||
<module>.*/libcuda.so.*</module>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static_.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static_.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>cudaMemcpyAsync</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>void CATCH2_INTERNAL_TEMPLATE_TEST.*</func>
|
||||
</frame>
|
||||
</hostStack>
|
||||
</record>
|
||||
<!-- Yet another instance of that tuple<Offset, bool> padding -->
|
||||
<record>
|
||||
<kind>InitcheckApiError</kind>
|
||||
<level>Error</level>
|
||||
<what>
|
||||
<text>Host API uninitialized memory access</text>
|
||||
<accessSize>16</accessSize>
|
||||
</what>
|
||||
<hostStack>
|
||||
<saveLocation>error</saveLocation>
|
||||
<frame>
|
||||
<module>.*libcuda.so.*</module>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>cudaMemcpyAsync</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>.*thrust::.*(equal|operator==|mismatch|find_if).*</func>
|
||||
</frame>
|
||||
</hostStack>
|
||||
</record>
|
||||
<!-- Yet another instance of that tuple<Offset, bool> padding -->
|
||||
<record>
|
||||
<kind>InitcheckApiError</kind>
|
||||
<level>Error</level>
|
||||
<what>
|
||||
<text>Host API uninitialized memory access</text>
|
||||
<accessSize>16</accessSize>
|
||||
</what>
|
||||
<hostStack>
|
||||
<saveLocation>error</saveLocation>
|
||||
<frame>
|
||||
<module>.*libcuda.so.*</module>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>cudaMemcpyAsync</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>bool binary_equal.*</func> <!-- Implementation detail of CUB's block radix sort tests, calls thrust::equal -->
|
||||
</frame>
|
||||
</hostStack>
|
||||
</record>
|
||||
<!-- Yet another instance of that tuple<Offset, bool> padding -->
|
||||
<record>
|
||||
<kind>InitcheckApiError</kind>
|
||||
<level>Error</level>
|
||||
<what>
|
||||
<text>Host API uninitialized memory access</text>
|
||||
<accessSize>16</accessSize>
|
||||
</what>
|
||||
<hostStack>
|
||||
<saveLocation>error</saveLocation>
|
||||
<frame>
|
||||
<module>.*libcuda.so.*</module>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>cudaMemcpyAsync</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<!-- CUB's namespace_wrapped test has this issue inlined into main: -->
|
||||
<func>main</func>
|
||||
<module>.*cub.*test.namespace_wrapped</module>
|
||||
</frame>
|
||||
</hostStack>
|
||||
</record>
|
||||
<!-- Yet another instance of that tuple<Offset, bool> padding -->
|
||||
<record>
|
||||
<kind>InitcheckApiError</kind>
|
||||
<what>
|
||||
<text>Host API uninitialized memory access</text>
|
||||
<accessSize>16</accessSize>
|
||||
</what>
|
||||
<hostStack>
|
||||
<saveLocation>error</saveLocation>
|
||||
<frame>
|
||||
<module>.*libcuda.so.*</module>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>cudaMemcpyAsync</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<!-- The segmented sort test have several stacks that have padding bit issues. -->
|
||||
<module>.*cub.*device_segmented_sort.*</module>
|
||||
</frame>
|
||||
</hostStack>
|
||||
</record>
|
||||
<!-- Yet another instance of that tuple<Offset, bool> padding -->
|
||||
<record>
|
||||
<kind>InitcheckApiError</kind>
|
||||
<what>
|
||||
<text>Host API uninitialized memory access</text>
|
||||
<accessSize>16</accessSize>
|
||||
</what>
|
||||
<hostStack>
|
||||
<saveLocation>error</saveLocation>
|
||||
<frame>
|
||||
<module>.*libcuda.so.*</module>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>cudaMemcpyAsync</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<!-- The RLE tests have several stacks that have padding bit issues. -->
|
||||
<module>.*cub.*device_run_length_encode.*</module>
|
||||
</frame>
|
||||
</hostStack>
|
||||
</record>
|
||||
<!--
|
||||
DeviceRunLengthEncode performs a WarpExchange::ScatterToStriped that 'discards'
|
||||
elements by scattering them to the same (ignored) destination address. This
|
||||
triggers a WAW race that we can safely ignore.
|
||||
-->
|
||||
<record>
|
||||
<kind>Analysis</kind>
|
||||
<level>Error</level>
|
||||
<what>
|
||||
<text>Race condition</text>
|
||||
<source>
|
||||
<direction>Write</direction>
|
||||
<where>
|
||||
<func>ScatterToStriped</func>
|
||||
</where>
|
||||
</source>
|
||||
<destination>
|
||||
<direction>Write</direction>
|
||||
<where>
|
||||
<func>ScatterToStriped</func>
|
||||
</where>
|
||||
</destination>
|
||||
<destination>
|
||||
<direction>Write</direction>
|
||||
<where>
|
||||
<func>ScatterToStriped</func>
|
||||
</where>
|
||||
</destination>
|
||||
<destination>
|
||||
<direction>Write</direction>
|
||||
<where>
|
||||
<func>ScatterToStriped</func>
|
||||
</where>
|
||||
</destination>
|
||||
</what>
|
||||
</record>
|
||||
<!-- Another variation of the above -->
|
||||
<record>
|
||||
<kind>Analysis</kind>
|
||||
<level>Error</level>
|
||||
<what>
|
||||
<text>Race condition</text>
|
||||
<source>
|
||||
<direction>Write</direction>
|
||||
<where>
|
||||
<func>ScatterToStriped</func>
|
||||
</where>
|
||||
</source>
|
||||
<destination>
|
||||
<direction>Write</direction>
|
||||
<where>
|
||||
<func>ScatterToStriped</func>
|
||||
</where>
|
||||
</destination>
|
||||
</what>
|
||||
</record>
|
||||
<!-- Another variation of the above -->
|
||||
<record>
|
||||
<kind>Analysis</kind>
|
||||
<level>Error</level>
|
||||
<what>
|
||||
<text>Race condition</text>
|
||||
<source>
|
||||
<direction>Write</direction>
|
||||
<where>
|
||||
<func>ScatterToStriped</func>
|
||||
</where>
|
||||
</source>
|
||||
<destination>
|
||||
<direction>Write</direction>
|
||||
<where>
|
||||
<func>ScatterToStriped</func>
|
||||
</where>
|
||||
</destination>
|
||||
<destination>
|
||||
<direction>Write</direction>
|
||||
<where>
|
||||
<func>ScatterToStriped</func>
|
||||
</where>
|
||||
</destination>
|
||||
</what>
|
||||
</record>
|
||||
<!--
|
||||
There are uninitialized padding bits inside cub::ConstantInputIterator,
|
||||
which is basically a struct{ T value; ptrdiff_t offset; }.
|
||||
-->
|
||||
<record>
|
||||
<kind>InitcheckApiError</kind>
|
||||
<level>Error</level>
|
||||
<what>
|
||||
<text>Host API uninitialized memory access</text>
|
||||
<accessSize>32</accessSize>
|
||||
</what>
|
||||
<hostStack>
|
||||
<saveLocation>error</saveLocation>
|
||||
<frame>
|
||||
<module>.*libcuda.so.*</module>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static_.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static_.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>cudaMemcpyAsync</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>thrust.*vector_base.*ConstantInputIterator.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>void test_iterator.*ConstantInputIterator.*</func>
|
||||
</frame>
|
||||
</hostStack>
|
||||
</record>
|
||||
<!--
|
||||
Similar to the above; cub::TransformInputIterator is a struct{TransformOp op; InputIterT iter;}.
|
||||
In this case TransformOp is 1 byte, and InputIterT is an 8-byte pointer.
|
||||
Padding bits strike again.
|
||||
-->
|
||||
<record>
|
||||
<kind>InitcheckApiError</kind>
|
||||
<level>Error</level>
|
||||
<what>
|
||||
<text>Host API uninitialized memory access</text>
|
||||
<accessSize>32</accessSize>
|
||||
</what>
|
||||
<hostStack>
|
||||
<saveLocation>error</saveLocation>
|
||||
<frame>
|
||||
<module>.*/libcuda.so.*</module>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static_.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static_.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>cudaMemcpyAsync</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>thrust.*vector_base.*TransformInputIterator.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>void test_iterator.*TransformInputIterator.*</func>
|
||||
</frame>
|
||||
</hostStack>
|
||||
</record>
|
||||
<!--
|
||||
Same as above; this time InputIterT is a cub::TexObjInputIterator.
|
||||
-->
|
||||
<record>
|
||||
<kind>InitcheckApiError</kind>
|
||||
<level>Error</level>
|
||||
<what>
|
||||
<text>Host API uninitialized memory access</text>
|
||||
<accessSize>64</accessSize>
|
||||
</what>
|
||||
<hostStack>
|
||||
<saveLocation>error</saveLocation>
|
||||
<frame>
|
||||
<module>.*/libcuda.so.*</module>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static_.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static_.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>cudaMemcpyAsync</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>thrust.*vector_base.*TransformInputIterator.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>void test_iterator.*TransformInputIterator.*</func>
|
||||
</frame>
|
||||
</hostStack>
|
||||
</record>
|
||||
<!--
|
||||
cub.test.device_reduce transfers cub::KeyValuePair<int, unwrap_value_t<...>> from
|
||||
device -> host. This suppresses warnings about transferring padding bits inside
|
||||
of the KeyValuePair.
|
||||
-->
|
||||
<record>
|
||||
<kind>InitcheckApiError</kind>
|
||||
<level>Error</level>
|
||||
<what>
|
||||
<text>Host API uninitialized memory access</text>
|
||||
</what>
|
||||
<hostStack>
|
||||
<saveLocation>error</saveLocation>
|
||||
<frame>
|
||||
<module>.*libcuda.so.*</module>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>libcudart_static.*</func>
|
||||
</frame>
|
||||
<frame>
|
||||
<func>cudaMemcpyAsync</func>
|
||||
<module>.*cub.*device_(segmented_|)reduce.*</module>
|
||||
</frame>
|
||||
</hostStack>
|
||||
</record>
|
||||
</ComputeSanitizerOutput>
|
||||
32
cccl_upstream/ci/generate_version.sh
Executable file
32
cccl_upstream/ci/generate_version.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Generate a version number string using metadata from git or JSON.
|
||||
# Use the PyPi package versioning convention for pre-release or
|
||||
# post release patches.
|
||||
|
||||
# Set some defaults for variables.
|
||||
CCCL_BRANCH="${CCCL_BRANCH:-dev}"
|
||||
PACKAGE_VERSION_PREFIX="${PACKAGE_VERSION_PREFIX:-}"
|
||||
|
||||
GIT_DESCRIBE_TAG=$(git describe --tags --match "v[0-9]*" --abbrev=0)
|
||||
GIT_DESCRIBE_NUMBER=$(git rev-list "${GIT_DESCRIBE_TAG}"..HEAD --count)
|
||||
|
||||
JSON_VERSION=$(jq -r .full /workspace/cccl-version.json)
|
||||
|
||||
# Generate a suffix depending on release or dev branch.
|
||||
PACKAGE_VERSION_SUFFIX=""
|
||||
if [[ "${GIT_DESCRIBE_NUMBER}" != "0" ]]; then
|
||||
if [[ ${CCCL_BRANCH} == "dev" ]]; then
|
||||
PACKAGE_VERSION_SUFFIX=".dev${GIT_DESCRIBE_NUMBER}"
|
||||
else
|
||||
PACKAGE_VERSION_SUFFIX=".post${GIT_DESCRIBE_NUMBER}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# If using Git metadata this could generate it from the last tag.
|
||||
# VERSION="${GIT_DESCRIBE_TAG#v}.dev${GIT_DESCRIBE_NUMBER}"
|
||||
|
||||
# Generate the version using a combination of JSON and git commit number.
|
||||
VERSION="${PACKAGE_VERSION_PREFIX}${JSON_VERSION}${PACKAGE_VERSION_SUFFIX}"
|
||||
|
||||
echo -n "${VERSION}"
|
||||
691
cccl_upstream/ci/inspect_changes.py
Executable file
691
cccl_upstream/ci/inspect_changes.py
Executable file
@@ -0,0 +1,691 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Identify dirty CCCL subprojects between two commits or explicit path lists."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import deque
|
||||
from dataclasses import dataclass, replace
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Sequence, Tuple
|
||||
|
||||
import yaml
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
CONFIG_PATH = REPO_ROOT / "ci" / "project_files_and_dependencies.yaml"
|
||||
CORE_PROJECT_KEY = "core"
|
||||
|
||||
|
||||
class SummaryWriter:
|
||||
"""Utility for duplicating output to stdout and an optional summary file."""
|
||||
|
||||
def __init__(self, path: Optional[Path]):
|
||||
self._handle = path.open("a", encoding="utf-8") if path else None
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc, tb):
|
||||
if self._handle:
|
||||
self._handle.close()
|
||||
|
||||
def log(self, line: str = "") -> None:
|
||||
print(line)
|
||||
if self._handle:
|
||||
self._handle.write(line + "\n")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ProjectConfig:
|
||||
"""Per-project configuration derived from the YAML file."""
|
||||
|
||||
key: str
|
||||
name: str
|
||||
matrix_project: Optional[str]
|
||||
include_regexes: Tuple[str, ...]
|
||||
exclude_regexes: Tuple[str, ...]
|
||||
exclude_project_files: Tuple[str, ...]
|
||||
lite_dependencies: Tuple[str, ...]
|
||||
full_dependencies: Tuple[str, ...]
|
||||
transitive_lite_dependencies: Tuple[str, ...] = ()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Config:
|
||||
"""Aggregated configuration for change detection."""
|
||||
|
||||
projects: Dict[str, ProjectConfig]
|
||||
project_keys: Tuple[str, ...]
|
||||
ignore_regexes: Tuple[str, ...]
|
||||
|
||||
def project(self, key: str) -> ProjectConfig:
|
||||
return self.projects[key]
|
||||
|
||||
|
||||
def parse_args(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
|
||||
"""Parse CLI arguments for determining dirty files."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Identify which CCCL projects require rebuilds between two commits."
|
||||
)
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument(
|
||||
"--refs",
|
||||
nargs=2,
|
||||
metavar=("BASE", "HEAD"),
|
||||
help="Compare two refs using 'git diff --name-only' to determine dirty files",
|
||||
)
|
||||
group.add_argument(
|
||||
"--file",
|
||||
metavar="PATH",
|
||||
help="Read dirty file paths (one per line) from PATH",
|
||||
)
|
||||
group.add_argument(
|
||||
"--stdin",
|
||||
action="store_true",
|
||||
help="Read dirty file paths (one per line) from stdin",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--summary",
|
||||
metavar="PATH",
|
||||
default=None,
|
||||
help="Optional path to write a markdown summary table",
|
||||
)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def load_config(path: Path) -> Config:
|
||||
raw = yaml.safe_load(path.read_text(encoding="utf-8"))
|
||||
projects_raw = raw.get("projects")
|
||||
if not isinstance(projects_raw, dict) or not projects_raw:
|
||||
raise SystemExit(f"No projects defined in {path}")
|
||||
|
||||
def to_tuple(value: Optional[Sequence[str] | str]) -> Tuple[str, ...]:
|
||||
if value is None:
|
||||
return tuple()
|
||||
if isinstance(value, (list, tuple)):
|
||||
return tuple(value)
|
||||
return (value,)
|
||||
|
||||
project_keys: List[str] = list(projects_raw.keys())
|
||||
projects: Dict[str, ProjectConfig] = {}
|
||||
for key in project_keys:
|
||||
entry = projects_raw.get(key) or {}
|
||||
name = entry.get("name", key)
|
||||
matrix_project = entry.get("matrix_project")
|
||||
include_regexes = to_tuple(entry.get("include_regexes", []))
|
||||
exclude_regexes = to_tuple(entry.get("exclude_regexes", []))
|
||||
exclude_project_files = to_tuple(entry.get("exclude_project_files"))
|
||||
lite_dependencies = to_tuple(entry.get("lite_dependencies"))
|
||||
full_dependencies = to_tuple(entry.get("full_dependencies"))
|
||||
|
||||
if key != CORE_PROJECT_KEY and not include_regexes:
|
||||
raise SystemExit(
|
||||
f"Project '{key}' must define at least one include_regex in {path}"
|
||||
)
|
||||
|
||||
projects[key] = ProjectConfig(
|
||||
key=key,
|
||||
name=name,
|
||||
matrix_project=matrix_project,
|
||||
include_regexes=include_regexes,
|
||||
exclude_regexes=exclude_regexes,
|
||||
exclude_project_files=exclude_project_files,
|
||||
lite_dependencies=lite_dependencies,
|
||||
full_dependencies=full_dependencies,
|
||||
transitive_lite_dependencies=tuple(),
|
||||
)
|
||||
|
||||
if CORE_PROJECT_KEY not in projects:
|
||||
raise SystemExit(f"Project configuration must define '{CORE_PROJECT_KEY}'")
|
||||
|
||||
dependency_graph = build_dependency_graph_raw(projects)
|
||||
transitive_map = compute_transitive_dependencies(
|
||||
project_keys, projects, dependency_graph
|
||||
)
|
||||
|
||||
for key, transitive in transitive_map.items():
|
||||
projects[key] = replace(projects[key], transitive_lite_dependencies=transitive)
|
||||
|
||||
ignore_regexes = tuple(raw.get("ignore_regexes", ()))
|
||||
|
||||
return Config(
|
||||
projects=projects,
|
||||
project_keys=tuple(project_keys),
|
||||
ignore_regexes=ignore_regexes,
|
||||
)
|
||||
|
||||
|
||||
def run_git(args: Sequence[str], *, capture_output: bool = True) -> str:
|
||||
"""Run a git command in the repo and optionally capture stdout."""
|
||||
result = subprocess.run(
|
||||
["git", *args],
|
||||
cwd=REPO_ROOT,
|
||||
check=True,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE if capture_output else None,
|
||||
stderr=subprocess.PIPE if capture_output else None,
|
||||
)
|
||||
if capture_output:
|
||||
return result.stdout.strip()
|
||||
return ""
|
||||
|
||||
|
||||
def try_rev_parse(ref: str) -> str:
|
||||
try:
|
||||
return run_git(["rev-parse", ref])
|
||||
except subprocess.CalledProcessError:
|
||||
return ref
|
||||
|
||||
|
||||
def ensure_fetched(ref: str) -> None:
|
||||
"""Fetch a ref from origin ensuring availability for merge-base."""
|
||||
run_git(["fetch", "origin", ref, "-q"], capture_output=False)
|
||||
|
||||
|
||||
def repo_is_shallow() -> bool:
|
||||
"""Return True when the repository has a shallow history."""
|
||||
output = run_git(["rev-parse", "--is-shallow-repository"])
|
||||
return output.strip().lower() == "true"
|
||||
|
||||
|
||||
def indent(text: str, prefix: str) -> str:
|
||||
"""Indent multi-line text for readable logging blocks."""
|
||||
return "\n".join(f"{prefix}{line}" for line in text.splitlines())
|
||||
|
||||
|
||||
def anchor_regex(pattern: str) -> re.Pattern[str]:
|
||||
"""Anchor a path regex to the repository root."""
|
||||
anchored = pattern if pattern.startswith("^") else f"^{pattern}"
|
||||
return re.compile(anchored)
|
||||
|
||||
|
||||
def compile_patterns(patterns: Sequence[str]) -> Tuple[re.Pattern[str], ...]:
|
||||
"""Compile a list of regex strings into anchored patterns."""
|
||||
return tuple(anchor_regex(pattern) for pattern in patterns)
|
||||
|
||||
|
||||
def matches_any(patterns: Sequence[re.Pattern[str]], path: str) -> bool:
|
||||
"""Return True when any compiled regex matches the given path."""
|
||||
return any(pattern.search(path) for pattern in patterns)
|
||||
|
||||
|
||||
def build_dependency_graph_raw(
|
||||
projects: Dict[str, ProjectConfig],
|
||||
) -> Dict[str, List[Tuple[str, str]]]:
|
||||
"""Return mapping of project -> [(dependency, type)]."""
|
||||
graph: Dict[str, List[Tuple[str, str]]] = {}
|
||||
for key, project in projects.items():
|
||||
edges: List[Tuple[str, str]] = []
|
||||
edges.extend((dep, "full") for dep in project.full_dependencies)
|
||||
edges.extend((dep, "lite") for dep in project.lite_dependencies)
|
||||
graph[key] = edges
|
||||
return graph
|
||||
|
||||
|
||||
def compute_transitive_dependencies(
|
||||
project_keys: Sequence[str],
|
||||
projects: Dict[str, ProjectConfig],
|
||||
graph: Dict[str, List[Tuple[str, str]]],
|
||||
) -> Dict[str, Tuple[str, ...]]:
|
||||
"""Return a dictionary of transitive dependencies for each project."""
|
||||
result: Dict[str, Tuple[str, ...]] = {}
|
||||
|
||||
for key in project_keys:
|
||||
visited: set[str] = set()
|
||||
queue: deque[str] = deque(dep for dep, _ in graph.get(key, []))
|
||||
|
||||
while queue:
|
||||
dep = queue.popleft()
|
||||
if dep == key or dep in visited:
|
||||
continue
|
||||
visited.add(dep)
|
||||
queue.extend(child for child, _ in graph.get(dep, []))
|
||||
|
||||
project_cfg = projects[key]
|
||||
direct_full = set(project_cfg.full_dependencies)
|
||||
direct_lite = set(project_cfg.lite_dependencies)
|
||||
transitive = [
|
||||
dep
|
||||
for dep in project_keys
|
||||
if dep in visited and dep not in direct_full and dep not in direct_lite
|
||||
]
|
||||
|
||||
result[key] = tuple(transitive)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def project_dirty_files(
|
||||
project: ProjectConfig, dirty_files: Sequence[str]
|
||||
) -> List[str]:
|
||||
"""Collect dirty files that belong to the given project. Does not apply project exclusions yet."""
|
||||
include_patterns = compile_patterns(project.include_regexes)
|
||||
exclude_patterns = compile_patterns(project.exclude_regexes)
|
||||
|
||||
if not include_patterns:
|
||||
return []
|
||||
|
||||
included = [path for path in dirty_files if matches_any(include_patterns, path)]
|
||||
|
||||
if exclude_patterns:
|
||||
included = [
|
||||
path for path in included if not matches_any(exclude_patterns, path)
|
||||
]
|
||||
|
||||
return included
|
||||
|
||||
|
||||
def build_project_dirty_map(
|
||||
config: Config, dirty_files: Sequence[str]
|
||||
) -> Dict[str, List[str]]:
|
||||
"""Compute per-project dirty file lists, including a residual `core` list."""
|
||||
project_files: Dict[str, List[str]] = {}
|
||||
|
||||
# First gather matches for every non-core project. Files may belong to multiple projects.
|
||||
for key in config.project_keys:
|
||||
if key == CORE_PROJECT_KEY:
|
||||
continue
|
||||
files = project_dirty_files(config.project(key), dirty_files)
|
||||
project_files[key] = files
|
||||
|
||||
# Remove files that are owned by other projects when requested.
|
||||
for key in config.project_keys:
|
||||
if key == CORE_PROJECT_KEY:
|
||||
continue
|
||||
project = config.project(key)
|
||||
if not project.exclude_project_files:
|
||||
continue
|
||||
excluded_sets = [
|
||||
set(project_files.get(other, [])) for other in project.exclude_project_files
|
||||
]
|
||||
if not excluded_sets:
|
||||
continue
|
||||
exclusions = set().union(*excluded_sets)
|
||||
project_files[key] = [
|
||||
path for path in project_files[key] if path not in exclusions
|
||||
]
|
||||
|
||||
matched_paths: set[str] = set()
|
||||
for key in config.project_keys:
|
||||
if key == CORE_PROJECT_KEY:
|
||||
continue
|
||||
matched_paths.update(project_files.get(key, []))
|
||||
|
||||
core_files = [path for path in dirty_files if path not in matched_paths]
|
||||
project_files[CORE_PROJECT_KEY] = core_files
|
||||
return project_files
|
||||
|
||||
|
||||
def write_output(key: str, value: str) -> None:
|
||||
"""Emit a GitHub Actions output key/value pair."""
|
||||
line = f"{key}={value}"
|
||||
print(line)
|
||||
output_path = os.environ.get("GITHUB_OUTPUT")
|
||||
if output_path:
|
||||
with open(output_path, "a", encoding="utf-8") as handle:
|
||||
handle.write(f"{line}\n")
|
||||
|
||||
|
||||
def collect_dirty_files(
|
||||
args: argparse.Namespace, config: Config
|
||||
) -> Tuple[List[str], List[str]]:
|
||||
"""Return normalized dirty file paths and the subset filtered as ignored."""
|
||||
if args.refs:
|
||||
if repo_is_shallow():
|
||||
run_git(["fetch", "origin", "--unshallow", "-q"], capture_output=False)
|
||||
|
||||
base_ref = try_rev_parse(args.refs[0])
|
||||
head_ref = try_rev_parse(args.refs[1])
|
||||
|
||||
ensure_fetched(base_ref)
|
||||
ensure_fetched(head_ref)
|
||||
|
||||
base_sha = run_git(["merge-base", base_ref, head_ref])
|
||||
head_sha = run_git(["rev-parse", head_ref])
|
||||
|
||||
print(f"Base SHA: {base_sha}")
|
||||
base_log = run_git(["log", "--oneline", "-1", base_sha])
|
||||
if base_log:
|
||||
print(indent(base_log, " "))
|
||||
print(f"HEAD SHA: {head_sha}")
|
||||
head_log = run_git(["log", "--oneline", "-1", head_sha])
|
||||
if head_log:
|
||||
print(indent(head_log, " "))
|
||||
|
||||
dirty_files = run_git(["diff", "--name-only", base_sha, head_sha]).splitlines()
|
||||
elif args.file:
|
||||
print(f"Dirty files provided via file: {args.file}")
|
||||
with open(args.file, "r", encoding="utf-8") as handle:
|
||||
dirty_files = [line.strip() for line in handle if line.strip()]
|
||||
else: # args.stdin
|
||||
print("Dirty files provided via stdin")
|
||||
dirty_files = [line.strip() for line in sys.stdin if line.strip()]
|
||||
|
||||
print()
|
||||
|
||||
dirty_files = [path for path in dirty_files if path]
|
||||
|
||||
ignore_patterns = compile_patterns(config.ignore_regexes)
|
||||
if ignore_patterns:
|
||||
kept: List[str] = []
|
||||
ignored: List[str] = []
|
||||
for path in dirty_files:
|
||||
if matches_any(ignore_patterns, path):
|
||||
ignored.append(path)
|
||||
else:
|
||||
kept.append(path)
|
||||
return kept, ignored
|
||||
|
||||
return dirty_files, []
|
||||
|
||||
|
||||
def format_bullet_list(lines: Sequence[str], indent: str = " ") -> List[str]:
|
||||
"""Convert each string into a markdown bullet line with the given indent."""
|
||||
return [f"{indent}- {line}" for line in lines]
|
||||
|
||||
|
||||
def build_reverse_dependency_graph(config: Config) -> Dict[str, List[Tuple[str, bool]]]:
|
||||
"""Return mapping of dependency -> (dependent, requires_full_rebuild)."""
|
||||
reverse: Dict[str, List[Tuple[str, bool]]] = {}
|
||||
|
||||
for project in config.projects.values():
|
||||
for dep in project.full_dependencies:
|
||||
reverse.setdefault(dep, []).append((project.key, True))
|
||||
for dep in project.lite_dependencies:
|
||||
reverse.setdefault(dep, []).append((project.key, False))
|
||||
for dep in project.transitive_lite_dependencies:
|
||||
reverse.setdefault(dep, []).append((project.key, False))
|
||||
|
||||
return reverse
|
||||
|
||||
|
||||
def propagate_dirty_projects(
|
||||
config: Config,
|
||||
initial_full: Sequence[str], # Projects with dirty files
|
||||
reverse_graph: Dict[
|
||||
str, List[Tuple[str, bool]]
|
||||
], # dependency -> [(dependent, requires_full_rebuild)]
|
||||
) -> Tuple[set[str], set[str]]: # (full_set, lite_set)
|
||||
"""Propagate rebuild requirements through the reverse dependency graph."""
|
||||
full_set: set[str] = set(initial_full)
|
||||
lite_set: set[str] = set()
|
||||
# BFS queue of (project_key, depth)
|
||||
queue: deque[Tuple[str, int]] = deque((key, 0) for key in initial_full)
|
||||
seen: set[Tuple[str, int]] = set(queue)
|
||||
|
||||
while queue:
|
||||
current, depth = queue.popleft()
|
||||
for dependent, edge_full in reverse_graph.get(current, []):
|
||||
propagate_full = edge_full and depth == 0
|
||||
|
||||
if propagate_full:
|
||||
if dependent in full_set:
|
||||
continue
|
||||
if dependent in lite_set:
|
||||
lite_set.remove(dependent)
|
||||
full_set.add(dependent)
|
||||
print(
|
||||
"- Upstream dependency change detected (full rebuild): "
|
||||
f"'{dependent}' ({config.project(dependent).name}) depends on dirty project '{current}'"
|
||||
)
|
||||
else:
|
||||
if dependent in full_set or dependent in lite_set:
|
||||
continue
|
||||
lite_set.add(dependent)
|
||||
print(
|
||||
"- Upstream dependency change detected (lite rebuild): "
|
||||
f"'{dependent}' ({config.project(dependent).name}) depends on dirty project '{current}'"
|
||||
)
|
||||
|
||||
key = (dependent, depth + 1)
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
queue.append(key)
|
||||
|
||||
return full_set, lite_set
|
||||
|
||||
|
||||
def log_dependency_overview(config: Config) -> None:
|
||||
"""Pretty-print the dependency data for each project."""
|
||||
print("Project Dependency Overview:")
|
||||
for key in config.project_keys:
|
||||
project = config.project(key)
|
||||
header = f"- {project.name} (key={key}"
|
||||
if project.matrix_project:
|
||||
header += f" matrix_project={project.matrix_project}"
|
||||
header += ")"
|
||||
print(header)
|
||||
|
||||
if project.full_dependencies:
|
||||
print(" - direct full deps:")
|
||||
for line in format_bullet_list(project.full_dependencies, indent=" "):
|
||||
print(line)
|
||||
if project.lite_dependencies:
|
||||
print(" - direct lite deps:")
|
||||
for line in format_bullet_list(project.lite_dependencies, indent=" "):
|
||||
print(line)
|
||||
if project.transitive_lite_dependencies:
|
||||
print(" - transitive deps:")
|
||||
for line in format_bullet_list(
|
||||
project.transitive_lite_dependencies, indent=" "
|
||||
):
|
||||
print(line)
|
||||
print()
|
||||
|
||||
|
||||
def build_dirty_sections(
|
||||
config: Config,
|
||||
project_dirty_map: Dict[str, List[str]],
|
||||
ignored_files: Sequence[str],
|
||||
) -> List[Tuple[str, List[str]]]:
|
||||
"""Create (heading, files) tuples for non-empty dirty buckets."""
|
||||
sections: List[Tuple[str, List[str]]] = []
|
||||
for key in config.project_keys:
|
||||
if key == CORE_PROJECT_KEY:
|
||||
continue
|
||||
files = project_dirty_map.get(key, [])
|
||||
if files:
|
||||
sections.append((f"{config.project(key).name} ({key})", list(files)))
|
||||
|
||||
core_files = project_dirty_map.get(CORE_PROJECT_KEY, [])
|
||||
if core_files:
|
||||
sections.append(
|
||||
(
|
||||
f"{config.project(CORE_PROJECT_KEY).name} ({CORE_PROJECT_KEY})",
|
||||
list(core_files),
|
||||
)
|
||||
)
|
||||
|
||||
if ignored_files:
|
||||
sections.append(("Ignored Files", list(ignored_files)))
|
||||
|
||||
return sections
|
||||
|
||||
|
||||
def log_dirty_files(
|
||||
combined_dirty: Sequence[str],
|
||||
sections: Sequence[Tuple[str, Sequence[str]]],
|
||||
) -> None:
|
||||
"""Emit dirty-file information in markdown list form."""
|
||||
if sections:
|
||||
print("Dirty files by project:")
|
||||
for idx, (heading, files) in enumerate(sections):
|
||||
print(f"{heading}:")
|
||||
for line in format_bullet_list(files):
|
||||
print(line)
|
||||
if idx != len(sections) - 1:
|
||||
print()
|
||||
print()
|
||||
|
||||
print("All dirty files:")
|
||||
if combined_dirty:
|
||||
for line in format_bullet_list(combined_dirty):
|
||||
print(line)
|
||||
else:
|
||||
print(" - (none)")
|
||||
print()
|
||||
|
||||
|
||||
def determine_rebuild_sets(
|
||||
config: Config,
|
||||
project_dirty_map: Dict[str, List[str]], # key -> dirty files
|
||||
reverse_graph: Dict[
|
||||
str, List[Tuple[str, bool]]
|
||||
], # dependency -> [(dependent, requires_full_rebuild)]
|
||||
) -> Tuple[
|
||||
Dict[str, str], set[str], set[str]
|
||||
]: # (project_statuses, full_set, lite_set)
|
||||
"""Return project statuses plus the full/lite rebuild sets."""
|
||||
core_dirty_files = project_dirty_map[CORE_PROJECT_KEY]
|
||||
|
||||
if core_dirty_files:
|
||||
project_statuses: Dict[str, str] = {key: "Dirty" for key in config.project_keys}
|
||||
return project_statuses, set(config.project_keys), set()
|
||||
|
||||
initially_dirty = [
|
||||
key
|
||||
for key in config.project_keys
|
||||
if key != CORE_PROJECT_KEY and project_dirty_map.get(key)
|
||||
]
|
||||
for key in initially_dirty:
|
||||
print(f"- Changes detected in subproject '{key}' ({config.project(key).name})")
|
||||
|
||||
full_set, lite_set = propagate_dirty_projects(
|
||||
config,
|
||||
initially_dirty,
|
||||
reverse_graph,
|
||||
)
|
||||
|
||||
project_statuses: Dict[str, str] = {}
|
||||
for key in config.project_keys:
|
||||
if key in full_set:
|
||||
project_statuses[key] = "Dirty"
|
||||
elif key in lite_set:
|
||||
project_statuses[key] = "Dirty Deps"
|
||||
else:
|
||||
project_statuses[key] = "Clean"
|
||||
|
||||
return project_statuses, full_set, lite_set
|
||||
|
||||
|
||||
def compute_outputs(
|
||||
config: Config,
|
||||
full_set: set[str],
|
||||
lite_set: set[str],
|
||||
) -> Tuple[List[str], List[str]]: # (FULL_BUILD, LITE_BUILD) matrix_project lists
|
||||
"""Convert rebuild sets into ordered matrix project lists."""
|
||||
full_output = [
|
||||
config.project(key).matrix_project
|
||||
for key in config.project_keys
|
||||
if key in full_set and config.project(key).matrix_project
|
||||
]
|
||||
lite_output = [
|
||||
config.project(key).matrix_project
|
||||
for key in config.project_keys
|
||||
if key in lite_set and config.project(key).matrix_project
|
||||
]
|
||||
return full_output, lite_output
|
||||
|
||||
|
||||
def emit_outputs(full_output: Sequence[str], lite_output: Sequence[str]) -> None:
|
||||
"""Write FULL_BUILD/LITE_BUILD strings to stdout and GitHub outputs."""
|
||||
print("Github Action Outputs:")
|
||||
write_output("FULL_BUILD", " ".join(full_output))
|
||||
write_output("LITE_BUILD", " ".join(lite_output))
|
||||
print()
|
||||
|
||||
|
||||
def write_project_summary(
|
||||
config: Config,
|
||||
project_statuses: Dict[str, str],
|
||||
writer: SummaryWriter,
|
||||
) -> None:
|
||||
"""Render the status table inside the summary section."""
|
||||
name_width = max(len(config.project(key).name) for key in config.project_keys)
|
||||
writer.log(f"| {'Project':<{name_width}} | Status |")
|
||||
writer.log(f"|{'-' * (name_width + 2)}|------------|")
|
||||
for key in config.project_keys:
|
||||
project = config.project(key)
|
||||
status = project_statuses.get(key, "Unknown?")
|
||||
writer.log(f"| {project.name:<{name_width}} | {status:<10} |")
|
||||
writer.log()
|
||||
|
||||
|
||||
def write_summary_dirty_sections(
|
||||
combined_dirty: Sequence[str],
|
||||
sections: Sequence[Tuple[str, Sequence[str]]],
|
||||
writer: SummaryWriter,
|
||||
) -> None:
|
||||
"""Render dirty-file details inside the summary section."""
|
||||
writer.log("<details><summary><h4>👉 Dirty Files</h4></summary>")
|
||||
if sections:
|
||||
for idx, (heading, files) in enumerate(sections):
|
||||
writer.log()
|
||||
writer.log(f"{heading}:")
|
||||
for line in format_bullet_list(files):
|
||||
writer.log(line)
|
||||
|
||||
writer.log()
|
||||
if combined_dirty:
|
||||
writer.log("All dirty files:")
|
||||
for line in format_bullet_list(combined_dirty):
|
||||
writer.log(line)
|
||||
|
||||
writer.log()
|
||||
writer.log("</details>")
|
||||
|
||||
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
"""Entrypoint used by the GitHub Action wrapper."""
|
||||
args = parse_args(argv)
|
||||
config = load_config(CONFIG_PATH)
|
||||
|
||||
summary_path = Path(args.summary) if args.summary else None
|
||||
|
||||
dirty_files, ignored_files = collect_dirty_files(args, config)
|
||||
|
||||
project_dirty_map = build_project_dirty_map(config, dirty_files)
|
||||
reverse_graph = build_reverse_dependency_graph(config)
|
||||
|
||||
print(f"Subprojects: {' '.join(config.project_keys)}")
|
||||
print()
|
||||
|
||||
log_dependency_overview(config)
|
||||
|
||||
combined_dirty = dirty_files + ignored_files
|
||||
sections = build_dirty_sections(config, project_dirty_map, ignored_files)
|
||||
log_dirty_files(combined_dirty, sections)
|
||||
|
||||
print("Checking for changes...")
|
||||
|
||||
project_statuses, full_set, lite_set = determine_rebuild_sets(
|
||||
config,
|
||||
project_dirty_map,
|
||||
reverse_graph,
|
||||
)
|
||||
|
||||
full_output, lite_output = compute_outputs(config, full_set, lite_set)
|
||||
|
||||
print()
|
||||
emit_outputs(full_output, lite_output)
|
||||
|
||||
with SummaryWriter(summary_path) as summary_writer:
|
||||
print("::group::Project Change Summary")
|
||||
summary_writer.log(
|
||||
"<details><summary><h3>👃 Inspect Project Changes</h3></summary>"
|
||||
)
|
||||
summary_writer.log()
|
||||
write_project_summary(config, project_statuses, summary_writer)
|
||||
write_summary_dirty_sections(combined_dirty, sections, summary_writer)
|
||||
summary_writer.log("</details>")
|
||||
print("::endgroup::")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
sys.exit(main())
|
||||
49
cccl_upstream/ci/install_cccl.sh
Executable file
49
cccl_upstream/ci/install_cccl.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
target_dir=$(realpath "$1")
|
||||
mkdir -p "$target_dir"
|
||||
|
||||
# Move script to the root directory of the project
|
||||
cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )";
|
||||
|
||||
source ./pretty_printing.sh
|
||||
|
||||
# Check if the correct number of arguments has been provided
|
||||
function usage {
|
||||
echo "Usage: $0 [OPTIONS] dir"
|
||||
echo
|
||||
echo "Installs CCCL to the provided directory"
|
||||
|
||||
echo "Options:"
|
||||
echo " -v/-verbose: Enable shell echo for debugging"
|
||||
echo
|
||||
echo "Examples:"
|
||||
echo " $ $0 ~/my/prefix"
|
||||
exit 1
|
||||
}
|
||||
|
||||
while [[ "$#" -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--verbose) VERBOSE=true; ;;
|
||||
-v) VERBOSE=true; ;;
|
||||
*) break ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ -n "$VERBOSE" ]]; then
|
||||
set -x
|
||||
fi
|
||||
|
||||
# Move to cccl/ dir
|
||||
pushd ".." > /dev/null
|
||||
GROUP_NAME="🛠️ CMake Configure CCCL - Install"
|
||||
run_command "$GROUP_NAME" cmake -G "Unix Makefiles" --preset install -DCMAKE_INSTALL_PREFIX="${target_dir}"
|
||||
status=$?
|
||||
|
||||
GROUP_NAME="🏗️ Install CCCL"
|
||||
run_command "$GROUP_NAME" cmake --build --preset install --target install
|
||||
status=$?
|
||||
popd > /dev/null
|
||||
117
cccl_upstream/ci/install_packaging.sh
Executable file
117
cccl_upstream/ci/install_packaging.sh
Executable file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Time limit for installation steps (seconds)
|
||||
readonly install_time_limit=20
|
||||
|
||||
ci_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
source "$ci_dir/pretty_printing.sh"
|
||||
|
||||
cd "$ci_dir/.."
|
||||
|
||||
# Prefixes for installations
|
||||
prefix_default=$(mktemp -d /tmp/cccl-default-XXXX)
|
||||
prefix_preset=$(mktemp -d /tmp/cccl-preset-XXXX)
|
||||
prefix_script=$(mktemp -d /tmp/cccl-script-XXXX)
|
||||
prefix_preset_unstable=$(mktemp -d /tmp/cccl-preset-unstable-XXXX)
|
||||
prefix_preset_unstable_only=$(mktemp -d /tmp/cccl-preset-unstable-only-XXXX)
|
||||
|
||||
# Default configure + install
|
||||
default_start_time=$SECONDS
|
||||
run_command "Configure default" \
|
||||
cmake -S . -B build/default -DCMAKE_INSTALL_PREFIX="$prefix_default"
|
||||
run_command "Install default" \
|
||||
cmake --build build/default --target install
|
||||
default_time=$((SECONDS - default_start_time))
|
||||
|
||||
# Preset configure + install
|
||||
preset_start_time=$SECONDS
|
||||
CCCL_BUILD_INFIX=preset \
|
||||
run_command "Configure preset" \
|
||||
cmake --preset install -DCMAKE_INSTALL_PREFIX="$prefix_preset"
|
||||
CCCL_BUILD_INFIX=preset \
|
||||
run_command "Install preset" \
|
||||
cmake --build --preset install --target install
|
||||
preset_time=$((SECONDS - preset_start_time))
|
||||
|
||||
# Preset configure + install-unstable
|
||||
preset_unstable_start_time=$SECONDS
|
||||
CCCL_BUILD_INFIX=preset_unstable \
|
||||
run_command "Configure preset-unstable" \
|
||||
cmake --preset install-unstable -DCMAKE_INSTALL_PREFIX="$prefix_preset_unstable"
|
||||
CCCL_BUILD_INFIX=preset_unstable \
|
||||
run_command "Install preset-unstable" \
|
||||
cmake --build build/preset_unstable/install-unstable --target install
|
||||
preset_unstable_time=$((SECONDS - preset_unstable_start_time))
|
||||
|
||||
# Preset configure + install-unstable-only
|
||||
preset_unstable_only_start_time=$SECONDS
|
||||
CCCL_BUILD_INFIX=preset_unstable_only \
|
||||
run_command "Configure preset-unstable-only" \
|
||||
cmake --preset install-unstable-only -DCMAKE_INSTALL_PREFIX="$prefix_preset_unstable_only"
|
||||
CCCL_BUILD_INFIX=preset_unstable_only \
|
||||
run_command "Install preset-unstable-only" \
|
||||
cmake --build build/preset_unstable_only/install-unstable-only \
|
||||
--target install
|
||||
preset_unstable_only_time=$((SECONDS - preset_unstable_only_start_time))
|
||||
|
||||
# Script install
|
||||
script_start_time=$SECONDS
|
||||
CCCL_BUILD_INFIX=script \
|
||||
run_command "Install script" \
|
||||
ci/install_cccl.sh "$prefix_script"
|
||||
script_time=$((SECONDS - script_start_time))
|
||||
|
||||
# Compare directories
|
||||
if ! diff -ruN "$prefix_default" "$prefix_preset" > /tmp/diff_default_preset.log; then
|
||||
cat /tmp/diff_default_preset.log
|
||||
echo -e "\e[0;31mDefault and preset installations differ\e[0m"
|
||||
exit 1
|
||||
fi
|
||||
if ! diff -ruN "$prefix_default" "$prefix_script" > /tmp/diff_default_script.log; then
|
||||
cat /tmp/diff_default_script.log
|
||||
echo -e "\e[0;31mDefault and script installations differ\e[0m"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify unstable-only contains only cudax headers and cmake config
|
||||
if find "$prefix_preset_unstable_only" -type f | \
|
||||
sed "s|$prefix_preset_unstable_only/||" | \
|
||||
grep -vE '^include/cuda/experimental/|^lib/cmake/+cudax/|^lib/cmake/+cccl/' | \
|
||||
grep -q .; then
|
||||
echo -e "\e[0;31minstall-unstable-only contained unexpected files\e[0m" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify install-unstable is union of install and install-unstable-only
|
||||
prefix_union=$(mktemp -d /tmp/cccl-union-XXXX)
|
||||
cp -a "$prefix_default/." "$prefix_union/"
|
||||
cp -a "$prefix_preset_unstable_only/." "$prefix_union/"
|
||||
if ! diff -ruN "$prefix_union" "$prefix_preset_unstable" > /tmp/diff_union_unstable.log; then
|
||||
echo -e "\e[0;31mUnion of install and install-unstable-only did not match install-unstable\e[0m" >&2
|
||||
cat /tmp/diff_union_unstable.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_time_summary
|
||||
|
||||
# Verify times
|
||||
violations=()
|
||||
(( default_time > install_time_limit )) && violations+=("default_time=${default_time}s")
|
||||
(( preset_time > install_time_limit )) && violations+=("preset_time=${preset_time}s")
|
||||
(( preset_unstable_time > install_time_limit )) && violations+=("preset_unstable_time=${preset_unstable_time}s")
|
||||
(( preset_unstable_only_time > install_time_limit )) && violations+=("preset_unstable_only_time=${preset_unstable_only_time}s")
|
||||
(( script_time > install_time_limit )) && violations+=("script_time=${script_time}s")
|
||||
|
||||
if (( ${#violations[@]} > 0 )); then
|
||||
echo -e "\e[0;31mInstallation time limit (${install_time_limit}s) exceeded by: ${violations[*]}\e[0m" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean up
|
||||
rm -rf "$prefix_default" "$prefix_preset" "$prefix_script" \
|
||||
"$prefix_preset_unstable" "$prefix_preset_unstable_only" \
|
||||
"$prefix_union"
|
||||
|
||||
echo -e "\e[0;32mAll installation tests passed successfully.\e[0m"
|
||||
816
cccl_upstream/ci/matrix.yaml
Normal file
816
cccl_upstream/ci/matrix.yaml
Normal file
@@ -0,0 +1,816 @@
|
||||
compile_time:
|
||||
pull_request:
|
||||
- id: public-headers-gcc13
|
||||
name: Public headers compile-time bench
|
||||
gpu: rtx2080
|
||||
launch_args: "--cuda 13.3 --host gcc13"
|
||||
baseline_ref: origin/main
|
||||
preset: all-dev
|
||||
targets:
|
||||
- cub.headers.base
|
||||
- thrust.cpp.cuda.headers.base
|
||||
- libcudacxx.test.public_headers
|
||||
args: "-arch native"
|
||||
slices:
|
||||
- id: total-compilation
|
||||
title: TU total compilation
|
||||
filter: total-compilation
|
||||
timing: inclusive
|
||||
sort: total
|
||||
top: 15
|
||||
threshold: 3.0
|
||||
- id: file-processing
|
||||
title: Direct file processing
|
||||
filter: file-processing
|
||||
timing: exclusive
|
||||
sort: total
|
||||
top: 15
|
||||
threshold: 0.2
|
||||
- id: function-bodies
|
||||
title: Function-body parsing
|
||||
filter: scanning-function-body
|
||||
timing: inclusive
|
||||
sort: total
|
||||
top: 15
|
||||
threshold: 0.25
|
||||
- id: template-instantiation
|
||||
title: Template instantiation
|
||||
filter: template-instantiation
|
||||
timing: inclusive
|
||||
sort: total
|
||||
top: 15
|
||||
threshold: 0.2
|
||||
|
||||
workflows:
|
||||
# If any jobs appear here, they will be executed instead of `pull_request' for PRs.
|
||||
# This is useful for limiting resource usage when a full matrix is not needed.
|
||||
# The branch protection checks will fail when using this override workflow.
|
||||
#
|
||||
# Example:
|
||||
# override:
|
||||
# # Full project build: slow, expensive
|
||||
# - { jobs: ['test'], project: 'thrust', std: 17, ctk: '12.X', cxx: ['gcc12', 'clang16'] }
|
||||
#
|
||||
# # Build / run targeted tests: faster turnaround, less runner usage.
|
||||
# # Use project 'target'.
|
||||
# # args are passed to ci/util/build_and_test_targets.sh. See that script for available options.
|
||||
# - { jobs: ['run_cpu'], project: 'target', ctk: ['12.X', '13.X'], cxx: ['gcc', 'clang', 'msvc'],
|
||||
# args: '--preset cub-cpp20 --build-targets "cub.cpp20.test.iterator"' }
|
||||
# - { jobs: ['run_gpu'], project: 'target', ctk: ['12.X', '13.X'], cxx: ['gcc', 'clang'], gpu: 'rtxa6000',
|
||||
# args: '--preset cub-cpp20 --build-targets "cub.cpp20.test.iterator" --ctest-targets "cub.cpp20.test.iterator"' }
|
||||
# - { jobs: ['run_cpu'], project: 'target', ctk: ['12.X', '13.X'], cxx: ['gcc', 'clang', 'msvc'],
|
||||
# args: '--preset libcudacxx --lit-precompile-tests "cuda/utility/basic_any.pass.cpp"' }
|
||||
# - { jobs: ['run_gpu'], project: 'target', ctk: ['12.X', '13.X'], cxx: ['gcc', 'clang'], gpu: 'rtx2080',
|
||||
# args: '--preset libcudacxx --lit-tests "cuda/utility/basic_any.pass.cpp"' }
|
||||
#
|
||||
# IMPORTANT: Do NOT delete or remove the `override:` key below, even when it is empty.
|
||||
override:
|
||||
|
||||
pull_request:
|
||||
# Old CTK: Oldest/newest supported host compilers:
|
||||
- {jobs: ['build'], std: 'minmax', ctk: '12.0', cxx: ['gcc12', 'clang14', 'msvc2019', 'msvc14.39']}
|
||||
- {jobs: ['build'], project: ['libcudacxx', 'thrust'], std: 'minmax', ctk: '12.0', cxx: 'gcc7'}
|
||||
- {jobs: ['build_nolid', 'build_lid1', 'build_lid2'], project: 'cub', std: 'minmax', ctk: '12.0', cxx: 'gcc7'}
|
||||
# CTK12.0/GCC7 CUB host-launch builds are memory-heavy with benchmarks enabled; keep this shard below
|
||||
# the 61 GiB linux-amd64-cpu16 runner limit.
|
||||
- {jobs: ['build_lid0'], project: 'cub', std: 'minmax', ctk: '12.0', cxx: 'gcc7', environment: ['PARALLEL_LEVEL=8']}
|
||||
- {jobs: ['build'], std: 'minmax', ctk: '12.X', cxx: ['gcc7', 'gcc14', 'clang14', 'clang19', 'msvc2019', 'msvc2022' ]}
|
||||
- {jobs: ['build'], std: 'minmax', ctk: '13.0', cxx: ['gcc11', 'gcc15', 'clang15', 'clang20', 'msvc2019', 'msvc2022' ]}
|
||||
# Old CTK: cudax has a different support matrix:
|
||||
- {jobs: ['build'], project: 'cudax', ctk: '12.0', std: 'minmax', cxx: ['gcc9', 'gcc12', 'clang14', 'msvc14.39']}
|
||||
- {jobs: ['build'], project: 'cudax', ctk: '12.X', std: 'minmax', cxx: ['gcc9', 'gcc14', 'clang14', 'clang19', 'msvc2022']}
|
||||
- {jobs: ['build'], project: 'cudax', ctk: '13.0', std: 'minmax', cxx: ['gcc11', 'gcc15', 'clang15', 'clang20', 'msvc2022']}
|
||||
# Current CTK build-only:
|
||||
- {jobs: ['build'], std: 'minmax', cxx: ['gcc11', 'clang15', 'msvc2019'] } # Oldest
|
||||
- {jobs: ['build'], std: 'max', cxx: ['gcc12', 'gcc13', 'gcc14'] }
|
||||
- {jobs: ['build'], std: 'max', cxx: ['clang16', 'clang17', 'clang18', 'clang19', 'clang20'] }
|
||||
- {jobs: ['build'], std: 'max', cxx: ['msvc2022'] }
|
||||
- {jobs: ['build'], std: 'all', cxx: ['gcc', 'clang', 'msvc']} # Latest
|
||||
# Current CTK build-only: cudax has a different support matrix:
|
||||
- {jobs: ['build'], project: 'cudax', std: 'minmax', cxx: ['gcc11', 'clang15', 'msvc2022']} # Oldest
|
||||
- {jobs: ['build'], project: 'cudax', std: 'max', cxx: ['gcc12']}
|
||||
- {jobs: ['build'], project: 'cudax', std: 'max', cxx: ['clang16', 'clang17', 'clang18', 'clang19', 'clang20']}
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', cxx: ['gcc', 'clang', 'msvc']} # Newest
|
||||
# Current CTK testing:
|
||||
- {jobs: ['test'], project: 'thrust', std: 'max', cxx: ['gcc', 'clang', 'msvc'], gpu: 'rtx4090'}
|
||||
- {jobs: ['test'], project: ['libcudacxx', 'cudax'], std: 'max', cxx: ['gcc', 'clang', 'msvc'], gpu: 't4'}
|
||||
|
||||
- {jobs: ['test_nolid', 'test_lid0'], project: 'cub', std: 'max', cxx: ['gcc', 'clang', 'msvc'], gpu: 'rtxa6000'}
|
||||
- {jobs: ['test_lid1', 'test_lid2'], project: 'cub', std: 'max', cxx: ['gcc'], gpu: 'rtxa6000'}
|
||||
# H100 coverage:
|
||||
- {jobs: ['test_nolid', 'test_lid0'], project: 'cub', std: 'max', gpu: 'h100' }
|
||||
- {jobs: ['test_gpu'], project: 'thrust', std: 'max', gpu: 'h100' }
|
||||
- {jobs: ['test'], project: ['libcudacxx', 'cudax'], std: 'max', gpu: 'h100' }
|
||||
# Multi-GPU coverage:
|
||||
- {jobs: ['test'], project: ['libcudacxx', 'cudax'], std: 'max', gpu: 'h100_2gpu', sm: 'gpu'}
|
||||
# RTX PRO 6000 coverage (limited due to small number of runners):
|
||||
- {jobs: ['test_lid0'], project: 'cub', std: 'max', cxx: 'gcc', gpu: 'rtxpro6000'}
|
||||
# Misc:
|
||||
- {jobs: ['build'], cpu: 'arm64', project: ['libcudacxx', 'cub', 'thrust', 'cudax'], std: 'max', cxx: ['gcc', 'clang']}
|
||||
- {jobs: ['test_gpu'], project: 'thrust', cmake_options: '-DTHRUST_DISPATCH_TYPE=Force32bit', gpu: 'rtx4090'}
|
||||
- {jobs: ['nvrtc'], project: 'libcudacxx', std: 'all', gpu: 'rtx2080', sm: 'gpu'}
|
||||
- {jobs: ['verify_codegen'], project: 'libcudacxx'}
|
||||
# c.parallel -- pinned to gcc13 / msvc2022 to match python
|
||||
- {jobs: ['test'], project: 'cccl_c_parallel', ctk: '12.X', cxx: ['gcc13', 'msvc2022'], gpu: ['t4']}
|
||||
- {jobs: ['test'], project: 'cccl_c_parallel', ctk: '13.X', cxx: ['gcc13', 'msvc2022'], gpu: ['rtx2080', 'l4', 'h100']}
|
||||
# RTX PRO 6000 coverage (limited due to small number of runners):
|
||||
- {jobs: ['test'], project: 'cccl_c_parallel', ctk: '13.X', cxx: ['gcc13'], gpu: ['rtxpro6000']}
|
||||
# c.parallel v2 (HostJIT-based)
|
||||
#
|
||||
# For now, this is a separate job run for Linux/CUDA13.
|
||||
# Eventually v2 will replace v1 as the default and run across the
|
||||
# entire matrix. Currently blocked on libnvfatbin availability on
|
||||
# Windows containers, and for CUDA <12.4.
|
||||
- {jobs: ['test'], project: 'cccl_c_parallel_v2', ctk: '13.X', cxx: ['gcc13', 'msvc'], gpu: 'rtx2080'}
|
||||
# Python against c.parallel v2 (HostJIT-based). Single point of coverage
|
||||
# for the v2 Python path; the main `python` matrix continues to test
|
||||
# against v1 until v2 replaces it.
|
||||
- {jobs: ['test'], project: 'python_v2', ctk: '13.X', py_version: '3.14', gpu: 'l4', cxx: 'gcc13'}
|
||||
- {jobs: ['test_py_compute_minimal'], project: 'python_v2', ctk: '13.X', py_version: '3.14t', gpu: 'l4', cxx: 'gcc13'}
|
||||
# c.experimental.stf-- pinned to gcc13 to match python
|
||||
- {jobs: ['test'], project: 'cccl_c_stf', ctk: ['12.X', '13.X'], cxx: 'gcc13', gpu: 't4'}
|
||||
- {jobs: ['test'], project: 'cccl_c_stf', ctk: '13.X', cxx: 'gcc13', gpu: ['l4', 'h100']}
|
||||
# Python -- pinned to gcc13 / msvc2022 for consistency across CTK images
|
||||
- {jobs: ['test'], project: 'python', ctk: ['12.X', '13.X'], py_version: ['3.10'], gpu: 'l4', cxx: ['gcc13', 'msvc2022']}
|
||||
- {jobs: ['test'], project: 'python', ctk: ['12.0', '12.X','13.0', '13.X'], py_version: '3.14', gpu: 'l4', cxx: ['gcc13', 'msvc2022']}
|
||||
- {jobs: ['test'], project: 'python', py_version: '3.14', gpu: 'h100', cxx: 'gcc13'}
|
||||
- {jobs: ['test'], project: 'python', ctk: '13.X', py_version: '3.14', gpu: ['t4', 'rtxa6000', 'rtxpro6000'], cxx: 'gcc13'}
|
||||
- {jobs: ['test_py_compute_minimal'], project: 'python', ctk: '13.X', py_version: '3.14', gpu: 'l4', cxx: 'gcc13'}
|
||||
- {jobs: ['test_py_compute_minimal'], project: 'python', ctk: ['12.0', '12.X', '13.0', '13.X'], py_version: '3.14t', gpu: 'l4', cxx: ['gcc13', 'msvc2022']}
|
||||
- {jobs: ['test_py_compute_minimal'], project: 'python_tsan', ctk: '13.X', py_version: '3.14t', gpu: 'l4', cxx: 'gcc13'}
|
||||
# Deliberately unpinned: py_ctk_mode 'latest' skips the CTK pin so pip
|
||||
# resolves the latest minor -- catching breakage a plain `pip install
|
||||
# cuda-cccl[cu12]/[cu13]` would hit before the container CTK bumps.
|
||||
- {jobs: ['test'], project: 'python', ctk: ['12.X', '13.X'], py_version: '3.14', gpu: 'l4', cxx: ['gcc13', 'msvc2022'], py_ctk_mode: 'latest'}
|
||||
# sysctk: install the sysctk extras (system-provided CTK, no pip cuda-toolkit)
|
||||
# rather than cu*.
|
||||
# Full test on 3.14, minimal (numba-free) on 3.14t; all CTKs, Linux+Windows.
|
||||
- {jobs: ['test'], project: 'python', ctk: ['12.0', '12.X', '13.0', '13.X'], py_version: '3.14', gpu: 'l4', cxx: ['gcc13', 'msvc2022'], py_ctk_mode: 'sysctk'}
|
||||
- {jobs: ['test_py_compute_minimal'], project: 'python', ctk: ['12.0', '12.X', '13.0', '13.X'], py_version: '3.14t', gpu: 'l4', cxx: ['gcc13', 'msvc2022'], py_ctk_mode: 'sysctk'}
|
||||
# cuda.cccl.headers: CPU-only (gpu:false), arch-insensitive
|
||||
- {jobs: ['test_headers'], project: 'python', ctk: ['12.X', '13.X'], py_version: '3.14', cxx: ['gcc13', 'msvc2022'], py_ctk_mode: ['pinned', 'latest', 'sysctk']}
|
||||
# CCCL packaging:
|
||||
- {jobs: ['test'], project: 'packaging', ctk: '12.0', cxx: ['gcc10', 'clang14'], gpu: 't4', args: '-min-cmake'}
|
||||
- {jobs: ['test'], project: 'packaging', ctk: '12.X', cxx: ['gcc10', 'clang14'], gpu: 't4'}
|
||||
- {jobs: ['test'], project: 'packaging', ctk: '13.0', cxx: ['gcc15', 'clang20'], gpu: 't4', args: '-min-cmake'}
|
||||
- {jobs: ['test'], project: 'packaging', ctk: '13.X', cxx: ['gcc', 'clang'], gpu: 't4'}
|
||||
- {jobs: ['install'], project: 'packaging'}
|
||||
# NVBench Helper testing:
|
||||
- {jobs: ['test'], project: 'nvbench_helper', ctk: ['12.0', '12.X'], cxx: ['gcc10', 'clang14'], gpu: 't4'}
|
||||
- {jobs: ['test'], project: 'nvbench_helper', ctk: '13.0', cxx: ['gcc15', 'clang20'], gpu: 't4'}
|
||||
- {jobs: ['test'], project: 'nvbench_helper', ctk: '13.X', cxx: ['gcc', 'clang'], gpu: 't4'}
|
||||
# NVHPC build
|
||||
- {jobs: ['build'], cxx: 'nvhpc', ctk: 'nvhpc', std: 'max', project: ['libcudacxx', 'thrust', 'stdpar'], cpu: 'amd64'}
|
||||
- {jobs: ['build_nolid'], cxx: 'nvhpc', ctk: 'nvhpc', std: 'max', project: 'cub', cpu: 'amd64'}
|
||||
# clang-cuda
|
||||
- {jobs: ['build'], cudacxx: 'clang', ctk: 'clang-cuda', cxx: 'clang-cuda', std: 'all', sm: '75;80;90;100;120'}
|
||||
- {jobs: ['build'], project: 'libcudacxx', cudacxx: 'clang', ctk: 'clang-cuda', cxx: 'clang-cuda', std: 23, sm: '75;80;90;100;120'}
|
||||
# libc++
|
||||
# - arm64 for now as it's closest to android.
|
||||
# - {jobs: ['build'], cpu: 'arm64', project: 'libcudacxx', std: 'all', cudacxx: 'clang', ctk: 'clang-cuda', cxx: 'clang-cuda', cmake_options: '-DCCCL_USE_LIBCXX=ON', sm: '75;80;90;100;120'}
|
||||
# clang-tidy
|
||||
#
|
||||
# The precise value of sm is not important, but it is required for cmake to identify
|
||||
# clang as a CUDA compiler (see
|
||||
# https://discourse.cmake.org/t/cmake-cuda-clang-fails/8657/5).
|
||||
#
|
||||
# Standard being exactly "min" is required. clang-tidy may emit additional diagnostics
|
||||
# for later C++ versions (for example, warning that you should use designated
|
||||
# initializers in C++20 or higher).
|
||||
- { jobs: ['build'], project: 'tidy', std: 'min', cxx: ['clang'], cudacxx: ['clang'], ctk: 'clang-cuda', sm: '75' }
|
||||
|
||||
# Used when an upstream project changes to reduce time spent smoke testing dependencies.
|
||||
pull_request_lite:
|
||||
# libcudacxx - Specialized, testing default SM
|
||||
- {project: 'libcudacxx', jobs: ['test'], std: 'max', cxx: ['gcc', 'msvc'], gpu: 'rtx2080', sm: 'gpu'}
|
||||
- {project: 'libcudacxx', jobs: ['build'], std: 'max', cxx: 'clang'}
|
||||
- {project: 'libcudacxx', jobs: ['build'], std: 'max', ctk: 'nvhpc', cxx: 'nvhpc'}
|
||||
- {project: 'libcudacxx', jobs: ['build'], std: 'max', cudacxx: 'clang', ctk: 'clang-cuda', cxx: 'clang-cuda', sm: '70;80;90;100;120'}
|
||||
- {project: 'libcudacxx', jobs: ['nvrtc'], std: 'max', gpu: 't4', sm: 'gpu'}
|
||||
- {project: 'libcudacxx', jobs: ['verify_codegen']}
|
||||
# CUB - Specialized, testing default SM
|
||||
- {project: 'cub', jobs: ['test_nolid', 'test_lid0'], std: 'max', cxx: ['gcc', 'msvc'], gpu: 'rtxa6000', sm: 'gpu'}
|
||||
- {project: 'cub', jobs: ['build_nolid', 'build_lid0'], std: 'max', cxx: 'clang'}
|
||||
- {project: 'cub', jobs: ['build_nolid', 'build_lid0'], std: 'max', ctk: 'nvhpc', cxx: 'nvhpc'}
|
||||
- {project: 'cub', jobs: ['build_nolid', 'build_lid0'], std: 'max', cudacxx: 'clang', ctk: 'clang-cuda', cxx: 'clang-cuda', sm: '75;80;90;100;120'}
|
||||
# Thrust - Keep number of sm small. Kernel coverage is in CUB. This just tests dispatch / glue in lite mode:
|
||||
- {project: 'thrust', jobs: ['test'], std: 'max', cxx: ['gcc', 'msvc'], gpu: 'rtx4090', sm: 'gpu'}
|
||||
- {project: 'thrust', jobs: ['build'], std: 'max', cxx: 'clang', sm: '75;120'}
|
||||
- {project: 'thrust', jobs: ['build'], std: 'max', ctk: 'nvhpc', cxx: 'nvhpc', sm: '75;120'}
|
||||
- {project: 'thrust', jobs: ['build'], std: 'max', cudacxx: 'clang', ctk: 'clang-cuda', cxx: 'clang-cuda', sm: '75;120'}
|
||||
# cudax
|
||||
- {project: 'cudax', jobs: ['test'], std: 'max', cxx: ['gcc', 'msvc'], gpu: 'rtx2080', sm: 'gpu'}
|
||||
- {project: 'cudax', jobs: ['build'], std: 'max', cxx: 'clang', sm: '75;120'}
|
||||
- {project: 'cudax', jobs: ['build'], std: 'max', ctk: 'nvhpc', cxx: 'nvhpc', sm: '75;120'}
|
||||
# stdpar
|
||||
- {project: 'stdpar', jobs: ['build'], std: 'max', ctk: 'nvhpc', cxx: 'nvhpc'}
|
||||
# Python + support
|
||||
- {project: 'cccl_c_parallel', jobs: ['test'], ctk: '13.X', cxx: ['gcc13', 'msvc2022'], gpu: 'rtx2080', sm: 'gpu'}
|
||||
- {project: 'cccl_c_parallel', jobs: ['test'], ctk: '13.X', cxx: 'gcc13', gpu: 'rtxpro6000', sm: 'gpu'}
|
||||
- {project: 'cccl_c_stf', jobs: ['test'], ctk: '13.X', cxx: 'gcc13', gpu: 't4', sm: 'gpu'}
|
||||
- {project: 'python', jobs: ['test'], ctk: '13.X', py_version: '3.14', gpu: 'l4', cxx: ['gcc13', 'msvc2022']}
|
||||
- {project: 'python', jobs: ['test_headers'], ctk: '13.X', py_version: '3.14', cxx: ['gcc13', 'msvc2022']}
|
||||
# Packaging / install
|
||||
- {project: 'packaging', jobs: ['test'], ctk: '13.X', cxx: ['gcc', 'clang'], gpu: 'rtx2080', sm: 'gpu'}
|
||||
- {project: 'packaging', jobs: ['test'], args: '-min-cmake', gpu: 't4', sm: 'gpu'}
|
||||
- {project: 'packaging', jobs: ['install']}
|
||||
# NVBench Helper testing:
|
||||
- {project: 'nvbench_helper', jobs: ['test'], ctk: '13.X', cxx: ['gcc', 'clang'], gpu: 'rtx2080'}
|
||||
# c.parallel v2 (HostJIT-based)
|
||||
- {jobs: ['test'], project: 'cccl_c_parallel_v2', ctk: '13.X', cxx: ['gcc13'], gpu: 'rtx2080'}
|
||||
# Python against c.parallel v2 (HostJIT-based)
|
||||
- {jobs: ['test'], project: 'python_v2', ctk: '13.X', py_version: '3.14', gpu: 'l4', cxx: 'gcc13'}
|
||||
|
||||
nightly:
|
||||
# CTK 12.0 full matrix build: default projects
|
||||
- {jobs: ['build'], std: 'all', ctk: '12.0', cxx: ['gcc7', 'gcc8', 'gcc9', 'gcc10', 'gcc11', 'gcc12']}
|
||||
- {jobs: ['build'], std: 'all', ctk: '12.0', cxx: ['clang14']}
|
||||
- {jobs: ['build'], std: 'all', ctk: '12.0', cxx: ['msvc2019', 'msvc14.39']}
|
||||
# CTK 12.X full matrix build: default projects
|
||||
- {jobs: ['build'], std: 'all', ctk: '12.X', cxx: ['gcc7', 'gcc8', 'gcc9', 'gcc10', 'gcc11', 'gcc12', 'gcc13', 'gcc14']}
|
||||
- {jobs: ['build'], std: 'all', ctk: '12.X', cxx: ['clang14', 'clang15', 'clang16', 'clang17', 'clang18', 'clang19']}
|
||||
- {jobs: ['build'], std: 'all', ctk: '12.X', cxx: ['msvc2019', 'msvc2022']}
|
||||
# CTK 13.0 full matrix build: default projects
|
||||
- {jobs: ['build'], std: 'all', ctk: '13.0', cxx: ['gcc11', 'gcc12', 'gcc13', 'gcc14', 'gcc15']}
|
||||
- {jobs: ['build'], std: 'all', ctk: '13.0', cxx: ['clang15', 'clang16', 'clang17', 'clang18', 'clang19', 'clang20']}
|
||||
- {jobs: ['build'], std: 'all', ctk: '13.0', cxx: ['msvc2019', 'msvc2022']}
|
||||
# CTK '13.X' full matrix build: default projects
|
||||
- {jobs: ['build'], std: 'all', ctk: '13.X', cxx: ['gcc11', 'gcc12', 'gcc13', 'gcc14', 'gcc15']}
|
||||
- {jobs: ['build'], std: 'all', ctk: '13.X', cxx: ['clang15', 'clang16', 'clang17', 'clang18', 'clang19', 'clang20', 'clang21']}
|
||||
- {jobs: ['build'], std: 'all', ctk: '13.X', cxx: ['msvc2019', 'msvc2022', 'msvc2026']}
|
||||
# CTK 12.0 full matrix build: cudax
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '12.0', cxx: ['gcc9', 'gcc10', 'gcc11', 'gcc12']}
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '12.0', cxx: ['clang14']}
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '12.0', cxx: ['msvc14.39']}
|
||||
# CTK 12.X full matrix build: cudax
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '12.X', cxx: ['gcc9', 'gcc10', 'gcc11', 'gcc12', 'gcc13', 'gcc14']}
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '12.X', cxx: ['clang14', 'clang15', 'clang16', 'clang17', 'clang18', 'clang19']}
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '12.X', cxx: ['msvc2022']}
|
||||
# CTK 13.0 full matrix build: cudax
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '13.0', cxx: ['gcc11', 'gcc12', 'gcc13', 'gcc14', 'gcc15']}
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '13.0', cxx: ['clang15', 'clang16', 'clang17', 'clang18', 'clang19', 'clang20']}
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '13.0', cxx: ['msvc2022']}
|
||||
# CTK '13.X' full matrix build: cudax
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '13.X', cxx: ['gcc11', 'gcc12', 'gcc13', 'gcc14', 'gcc15']}
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '13.X', cxx: ['clang15', 'clang16', 'clang17', 'clang18', 'clang19', 'clang20', 'clang21']}
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '13.X', cxx: ['msvc2022', 'msvc2026']}
|
||||
# CTK 12.X testing:
|
||||
- {jobs: ['test'], project: 'libcudacxx', ctk: '12.X', std: 'max', cxx: ['gcc14', 'clang19', 'msvc2022'], gpu: 't4'}
|
||||
- {jobs: ['test'], project: 'cub', ctk: '12.X', std: 'max', cxx: ['gcc14', 'clang19', 'msvc2022'], gpu: 'rtxa6000'}
|
||||
- {jobs: ['test'], project: 'thrust', ctk: '12.X', std: 'max', cxx: ['gcc14', 'clang19', 'msvc2022'], gpu: 'rtx4090'}
|
||||
- {jobs: ['test'], project: 'cudax', ctk: '12.X', std: 'max', cxx: ['gcc14', 'clang19', 'msvc2022'], gpu: 'rtx2080'}
|
||||
- {jobs: ['test'], project: ['libcudacxx', 'cub', 'thrust', 'cudax'], ctk: '12.X', std: 'max', cxx: 'gcc14', gpu: 'h100' }
|
||||
# CTK '13.X' testing:
|
||||
- {jobs: ['test'], project: 'libcudacxx', ctk: '13.X', std: 'max', cxx: ['gcc', 'clang', 'msvc'], gpu: 'rtx2080'}
|
||||
- {jobs: ['test'], project: 'cub', ctk: '13.X', std: 'max', cxx: ['gcc', 'clang', 'msvc'], gpu: 'rtxa6000'}
|
||||
- {jobs: ['test'], project: 'thrust', ctk: '13.X', std: 'max', cxx: ['gcc', 'clang', 'msvc'], gpu: 'rtx4090'}
|
||||
- {jobs: ['test'], project: 'cudax', ctk: '13.X', std: 'max', cxx: ['gcc', 'clang', 'msvc'], gpu: 't4'}
|
||||
- {jobs: ['test'], project: ['libcudacxx', 'cub', 'thrust', 'cudax'], ctk: '13.X', std: 'max', gpu: 'h100' }
|
||||
# RTX PRO 6000 coverage (limited due to small number of runners):
|
||||
- {jobs: ['test_nolid', 'test_lid0'], project: ['cub', 'thrust'], std: 'max', cxx: 'gcc', gpu: 'rtxpro6000'}
|
||||
# Misc:
|
||||
- {jobs: ['build'], cpu: 'arm64', project: ['libcudacxx', 'cub', 'thrust', 'cudax'], ctk: '12.X', std: 'all', cxx: ['gcc14', 'clang19']}
|
||||
- {jobs: ['build'], cpu: 'arm64', project: ['libcudacxx', 'cub', 'thrust', 'cudax'], ctk: '13.X', std: 'all', cxx: ['gcc', 'clang']}
|
||||
- {jobs: ['test_gpu'], project: 'thrust', cmake_options: '-DTHRUST_DISPATCH_TYPE=Force32bit', gpu: 'rtx4090'}
|
||||
- {jobs: ['test_gpu'], project: 'thrust', cmake_options: '-DTHRUST_DISPATCH_TYPE=Force64bit', gpu: 'rtx4090'}
|
||||
- {jobs: ['limited'], project: 'cub', std: 17, gpu: 'rtx2080'}
|
||||
# NVRTC tests don't currently support 12.0:
|
||||
- {jobs: ['nvrtc'], project: 'libcudacxx', ctk: [ '12.X', '13.0', '13.X'], cxx: 'gcc12', std: 'all', gpu: 'rtx2080', sm: 'gpu'}
|
||||
- {jobs: ['verify_codegen'], project: 'libcudacxx'}
|
||||
# c.parallel -- pinned to gcc13 / msvc2022 to match python
|
||||
- {jobs: ['test'], project: ['cccl_c_parallel'], ctk: '12.X', cxx: ['gcc13', 'msvc2022'], gpu: ['t4']}
|
||||
- {jobs: ['test'], project: ['cccl_c_parallel'], ctk: '13.X', cxx: ['gcc13', 'msvc2022'], gpu: ['rtx2080', 'l4', 'h100']}
|
||||
# RTX PRO 6000 coverage (limited due to small number of runners):
|
||||
- {jobs: ['test'], project: 'cccl_c_parallel', ctk: '13.X', cxx: ['gcc13'], gpu: ['rtxpro6000']}
|
||||
# c.experimental.stf -- pinned to gcc13 to match python
|
||||
- {jobs: ['test'], project: ['cccl_c_stf'], ctk: '12.X', cxx: 'gcc13', gpu: ['rtx2080']}
|
||||
- {jobs: ['test'], project: ['cccl_c_stf'], ctk: '13.X', cxx: 'gcc13', gpu: ['t4', 'l4', 'h100']}
|
||||
# Python -- pinned to gcc13 / msvc2022 on Linux for consistency across CTK images
|
||||
- {jobs: ['test'], project: 'python', ctk: ['12.0', '12.X', '13.0', '13.X'], py_version: ['3.10', '3.11', '3.12', '3.13', '3.14'], gpu: 'l4', cxx: ['gcc13', 'msvc2022']}
|
||||
- {jobs: ['test'], project: 'python', ctk: ['12.X', '13.X'], py_version: '3.14', gpu: 'h100', cxx: 'gcc13'}
|
||||
- {jobs: ['test'], project: 'python', ctk: ['12.X', '13.X'], py_version: '3.14', gpu: ['t4', 'rtxa6000'], cxx: 'gcc13'}
|
||||
- {jobs: ['test'], project: 'python', ctk: '13.X', py_version: '3.14', gpu: 'rtxpro6000', cxx: 'gcc13'}
|
||||
# Python free-threaded (3.14t) minimal lanes -- mirrors the pull_request rows
|
||||
# so FT regressions (e.g. from dependency bumps) surface between PRs.
|
||||
- {jobs: ['test_py_compute_minimal'], project: 'python', ctk: ['12.0', '12.X', '13.0', '13.X'], py_version: '3.14t', gpu: 'l4', cxx: ['gcc13', 'msvc2022']}
|
||||
- {jobs: ['test_py_compute_minimal'], project: 'python_v2', ctk: '13.X', py_version: '3.14t', gpu: 'l4', cxx: 'gcc13'}
|
||||
- {jobs: ['test_py_compute_minimal'], project: 'python_tsan', ctk: '13.X', py_version: '3.14t', gpu: 'l4', cxx: 'gcc13'}
|
||||
- {jobs: ['test'], project: 'python', ctk: ['12.0', '12.X', '13.0', '13.X'], py_version: '3.14', gpu: 'l4', cxx: ['gcc13', 'msvc2022'], py_ctk_mode: 'sysctk'}
|
||||
- {jobs: ['test_py_compute_minimal'], project: 'python', ctk: ['12.0', '12.X', '13.0', '13.X'], py_version: '3.14t', gpu: 'l4', cxx: ['gcc13', 'msvc2022'], py_ctk_mode: 'sysctk'}
|
||||
# cuda.cccl.headers (CPU-only): all CTK x source x OS, py endpoints 3.10 + 3.14
|
||||
- {jobs: ['test_headers'], project: 'python', ctk: ['12.0', '12.X', '13.0', '13.X'], py_version: ['3.10', '3.14'], cxx: ['gcc13', 'msvc2022'], py_ctk_mode: ['pinned', 'latest', 'sysctk']}
|
||||
# CCCL packaging:
|
||||
- {jobs: ['test'], project: 'packaging', ctk: '12.0', cxx: ['gcc10', 'clang14'], gpu: 't4', args: '-min-cmake'}
|
||||
- {jobs: ['test'], project: 'packaging', ctk: '12.X', cxx: ['gcc10', 'clang14'], gpu: 'rtx2080'}
|
||||
- {jobs: ['test'], project: 'packaging', ctk: '13.0', cxx: ['gcc15', 'clang20'], gpu: 't4', args: '-min-cmake'}
|
||||
- {jobs: ['test'], project: 'packaging', ctk: '13.X', cxx: ['gcc', 'clang'], gpu: 'rtx2080'}
|
||||
- {jobs: ['install'], project: 'packaging'}
|
||||
# NVBench Helper testing:
|
||||
- {jobs: ['test'], project: 'nvbench_helper', ctk: '12.0', cxx: ['gcc10', 'clang14'], gpu: 'rtx2080'}
|
||||
- {jobs: ['test'], project: 'nvbench_helper', ctk: '12.X', cxx: ['gcc10', 'clang14'], gpu: 't4'}
|
||||
- {jobs: ['test'], project: 'nvbench_helper', ctk: '13.0', cxx: ['gcc15', 'clang20'], gpu: 'rtx2080'}
|
||||
- {jobs: ['test'], project: 'nvbench_helper', ctk: '13.X', cxx: ['gcc', 'clang'], gpu: 't4'}
|
||||
# NVHPC build
|
||||
- {jobs: ['build'], cxx: 'nvhpc-prev', ctk: 'nvhpc-prev', std: 'all', project: ['libcudacxx', 'cub', 'thrust', 'cudax', 'stdpar'], cpu: ['amd64', 'arm64']}
|
||||
- {jobs: ['build'], cxx: 'nvhpc', ctk: 'nvhpc', std: 'all', project: ['libcudacxx', 'cub', 'thrust', 'cudax', 'stdpar'], cpu: ['amd64', 'arm64']}
|
||||
# clang-cuda
|
||||
- {jobs: ['build'], cudacxx: 'clang', ctk: 'clang-cuda', cxx: 'clang-cuda', std: 'all', sm: '75;80;90;100;120'}
|
||||
- {jobs: ['build'], cudacxx: 'clang', ctk: 'clang_preview-cuda', cxx: 'clang_preview-cuda', std: 'all', sm: '75;80;90;100;120'}
|
||||
- {jobs: ['build'], project: 'libcudacxx', cudacxx: 'clang', ctk: 'clang-cuda', cxx: 'clang-cuda', std: 23, sm: '75;80;90;100;120'}
|
||||
- {jobs: ['build'], project: 'libcudacxx', cudacxx: 'clang', ctk: 'clang_preview-cuda', cxx: 'clang_preview-cuda', std: 23, sm: '75;80;90;100;120'}
|
||||
# clang-tidy
|
||||
- { jobs: ['build'], project: 'tidy', std: 'min', cxx: ['clang'], cudacxx: ['clang'], ctk: 'clang-cuda', sm: '75' }
|
||||
# arch-specific and family-specific arch builds
|
||||
- {jobs: ['build'], project: ['libcudacxx', 'cub', 'thrust', 'cudax'], std: 'all', sm: '90a;100a;103a;110a;120a;121a'}
|
||||
- {jobs: ['build'], project: ['libcudacxx', 'cub', 'thrust', 'cudax'], std: 'all', sm: '100f;103f;110f;120f;121f'}
|
||||
|
||||
weekly:
|
||||
# CTK 12.0 full matrix build: default projects
|
||||
- {jobs: ['build'], std: 'all', ctk: '12.0', cxx: ['gcc7', 'gcc8', 'gcc9', 'gcc10', 'gcc11', 'gcc12']}
|
||||
- {jobs: ['build'], std: 'all', ctk: '12.0', cxx: ['clang14']}
|
||||
- {jobs: ['build'], std: 'all', ctk: '12.0', cxx: ['msvc2019', 'msvc14.39']}
|
||||
# CTK 12.X full matrix build: default projects
|
||||
- {jobs: ['build'], std: 'all', ctk: '12.X', cxx: ['gcc7', 'gcc8', 'gcc9', 'gcc10', 'gcc11', 'gcc12', 'gcc13', 'gcc14']}
|
||||
- {jobs: ['build'], std: 'all', ctk: '12.X', cxx: ['clang14', 'clang15', 'clang16', 'clang17', 'clang18', 'clang19']}
|
||||
- {jobs: ['build'], std: 'all', ctk: '12.X', cxx: ['msvc2019', 'msvc2022']}
|
||||
# CTK 13.0 full matrix build: default projects
|
||||
- {jobs: ['build'], std: 'all', ctk: '13.0', cxx: ['gcc11', 'gcc12', 'gcc13', 'gcc14', 'gcc15']}
|
||||
- {jobs: ['build'], std: 'all', ctk: '13.0', cxx: ['clang15', 'clang16', 'clang17', 'clang18', 'clang19', 'clang20']}
|
||||
- {jobs: ['build'], std: 'all', ctk: '13.0', cxx: ['msvc2019', 'msvc2022']}
|
||||
# CTK '13.X' full matrix build: default projects
|
||||
- {jobs: ['build'], std: 'all', ctk: '13.X', cxx: ['gcc11', 'gcc12', 'gcc13', 'gcc14', 'gcc15']}
|
||||
- {jobs: ['build'], std: 'all', ctk: '13.X', cxx: ['clang15', 'clang16', 'clang17', 'clang18', 'clang19', 'clang20', 'clang21']}
|
||||
- {jobs: ['build'], std: 'all', ctk: '13.X', cxx: ['msvc2019', 'msvc2022', 'msvc2026']}
|
||||
# CTK 12.0 full matrix build: cudax
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '12.0', cxx: ['gcc9', 'gcc10', 'gcc11', 'gcc12']}
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '12.0', cxx: ['clang14']}
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '12.0', cxx: ['msvc14.39']}
|
||||
# CTK 12.X full matrix build: cudax
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '12.X', cxx: ['gcc9', 'gcc10', 'gcc11', 'gcc12', 'gcc13', 'gcc14']}
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '12.X', cxx: ['clang14', 'clang15', 'clang16', 'clang17', 'clang18', 'clang19']}
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '12.X', cxx: ['msvc2022']}
|
||||
# CTK 13.0 full matrix build: cudax
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '13.0', cxx: ['gcc11', 'gcc12', 'gcc13', 'gcc14', 'gcc15']}
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '13.0', cxx: ['clang15', 'clang16', 'clang17', 'clang18', 'clang19', 'clang20']}
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '13.0', cxx: ['msvc2022']}
|
||||
# CTK '13.X' full matrix build: cudax
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '13.X', cxx: ['gcc11', 'gcc12', 'gcc13', 'gcc14', 'gcc15']}
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '13.X', cxx: ['clang15', 'clang16', 'clang17', 'clang18', 'clang19', 'clang20', 'clang21']}
|
||||
- {jobs: ['build'], project: 'cudax', std: 'all', ctk: '13.X', cxx: ['msvc2022', 'msvc2026']}
|
||||
# CTK 12.X testing:
|
||||
- {jobs: ['test'], project: 'libcudacxx', ctk: '12.X', std: 'minmax', cxx: ['gcc14', 'clang19', 'msvc2022'], gpu: 'rtx2080'}
|
||||
- {jobs: ['test'], project: 'cub', ctk: '12.X', std: 'minmax', cxx: ['gcc14', 'clang19', 'msvc2022'], gpu: 'rtxa6000'}
|
||||
- {jobs: ['test'], project: 'thrust', ctk: '12.X', std: 'minmax', cxx: ['gcc14', 'clang19', 'msvc2022'], gpu: 'rtx4090'}
|
||||
- {jobs: ['test'], project: 'cudax', ctk: '12.X', std: 'minmax', cxx: ['gcc14', 'clang19', 'msvc2022'], gpu: 't4'}
|
||||
- {jobs: ['test'], project: ['libcudacxx', 'cub', 'thrust', 'cudax'], ctk: '12.X', std: 'minmax', cxx: 'gcc14', gpu: 'h100' }
|
||||
# CTK '13.X' testing:
|
||||
- {jobs: ['test'], project: 'libcudacxx', ctk: '13.X', std: 'minmax', cxx: ['gcc', 'clang', 'msvc'], gpu: 't4'}
|
||||
- {jobs: ['test'], project: 'cub', ctk: '13.X', std: 'minmax', cxx: ['gcc', 'clang', 'msvc'], gpu: 'rtxa6000'}
|
||||
- {jobs: ['test'], project: 'thrust', ctk: '13.X', std: 'minmax', cxx: ['gcc', 'clang', 'msvc'], gpu: 'rtx4090'}
|
||||
- {jobs: ['test'], project: 'cudax', ctk: '13.X', std: 'minmax', cxx: ['gcc', 'clang', 'msvc'], gpu: 'rtx2080'}
|
||||
- {jobs: ['test'], project: ['libcudacxx', 'cub', 'thrust', 'cudax'], ctk: '13.X', std: 'minmax', gpu: 'h100' }
|
||||
# RTX PRO 6000 coverage (limited due to small number of runners):
|
||||
- {jobs: ['test'], project: ['cub', 'thrust'], std: 'max', cxx: 'gcc', gpu: 'rtxpro6000'}
|
||||
# Misc:
|
||||
- {jobs: ['build'], cpu: 'arm64', project: ['libcudacxx', 'cub', 'thrust', 'cudax'], ctk: '12.X', std: 'all', cxx: ['gcc14', 'clang19']}
|
||||
- {jobs: ['build'], cpu: 'arm64', project: ['libcudacxx', 'cub', 'thrust', 'cudax'], ctk: '13.X', std: 'all', cxx: ['gcc', 'clang']}
|
||||
- {jobs: ['test_gpu'], project: 'thrust', cmake_options: '-DTHRUST_DISPATCH_TYPE=Force32bit', gpu: 'rtx4090'}
|
||||
- {jobs: ['test_gpu'], project: 'thrust', cmake_options: '-DTHRUST_DISPATCH_TYPE=Force64bit', gpu: 'rtx4090'}
|
||||
- {jobs: ['limited'], project: 'cub', std: 17, gpu: 'rtx2080'}
|
||||
# sm: all-cccl:
|
||||
- {jobs: ['build'], project: ['thrust', 'libcudacxx', 'cudax'], std: 'max', sm: 'all-cccl' }
|
||||
- {jobs: ['build_nolid', 'build_lid0'], project: ['cub'], std: 'max', sm: 'all-cccl'}
|
||||
# NVRTC tests don't currently support 12.0:
|
||||
- {jobs: ['nvrtc'], project: 'libcudacxx', ctk: [ '12.X', '13.0', '13.X'], cxx: 'gcc12', std: 'all', gpu: 'rtx2080', sm: 'gpu'}
|
||||
- {jobs: ['verify_codegen'], project: 'libcudacxx'}
|
||||
# c.parallel -- pinned to gcc13 / msvc2022 to match python
|
||||
- {jobs: ['test'], project: ['cccl_c_parallel'], ctk: '12.X', cxx: ['gcc13', 'msvc2022'], gpu: ['t4']}
|
||||
- {jobs: ['test'], project: ['cccl_c_parallel'], ctk: '13.X', cxx: ['gcc13', 'msvc2022'], gpu: ['rtx2080', 'l4', 'h100']}
|
||||
# RTX PRO 6000 coverage (limited due to small number of runners):
|
||||
- {jobs: ['test'], project: 'cccl_c_parallel', ctk: '13.X', cxx: ['gcc13'], gpu: ['rtxpro6000']}
|
||||
# c.experimental.stf -- pinned to gcc13 to match python
|
||||
- {jobs: ['test'], project: ['cccl_c_stf'], ctk: '12.X', cxx: 'gcc13', gpu: ['rtx2080']}
|
||||
- {jobs: ['test'], project: ['cccl_c_stf'], ctk: '13.X', cxx: 'gcc13', gpu: ['t4', 'l4', 'h100']}
|
||||
# Python -- pinned to gcc13 / msvc2022 for consistency across CTK images
|
||||
- {jobs: ['test'], project: 'python', ctk: ['12.0', '12.X', '13.0', '13.X'], py_version: ['3.10', '3.11', '3.12', '3.13', '3.14'], gpu: 'l4', cxx: ['gcc13', 'msvc2022']}
|
||||
- {jobs: ['test'], project: 'python', ctk: ['12.X', '13.X'], py_version: '3.14', gpu: 'h100', cxx: ['gcc13', 'msvc2022']}
|
||||
- {jobs: ['test'], project: 'python', ctk: ['12.0', '12.X', '13.0', '13.X'], py_version: ['3.10', '3.14'], gpu: 't4', cxx: 'gcc13'}
|
||||
- {jobs: ['test'], project: 'python', ctk: ['12.0', '12.X', '13.0', '13.X'], py_version: '3.14', gpu: 'rtxa6000', cxx: 'gcc13'}
|
||||
- {jobs: ['test'], project: 'python', ctk: '13.X', py_version: '3.14', gpu: 'rtxpro6000', cxx: 'gcc13'}
|
||||
- {jobs: ['test'], project: 'python', ctk: ['12.0', '12.X', '13.0', '13.X'], py_version: '3.14', gpu: 'l4', cxx: ['gcc13', 'msvc2022'], py_ctk_mode: 'sysctk'}
|
||||
- {jobs: ['test_py_compute_minimal'], project: 'python', ctk: ['12.0', '12.X', '13.0', '13.X'], py_version: '3.14t', gpu: 'l4', cxx: ['gcc13', 'msvc2022'], py_ctk_mode: 'sysctk'}
|
||||
# cuda.cccl.headers (CPU-only): all CTK x source x OS, py endpoints 3.10 + 3.14
|
||||
- {jobs: ['test_headers'], project: 'python', ctk: ['12.0', '12.X', '13.0', '13.X'], py_version: ['3.10', '3.14'], cxx: ['gcc13', 'msvc2022'], py_ctk_mode: ['pinned', 'latest', 'sysctk']}
|
||||
# CCCL packaging:
|
||||
- {jobs: ['test'], project: 'packaging', ctk: '12.0', cxx: ['gcc10', 'clang14'], gpu: 't4', args: '-min-cmake'}
|
||||
- {jobs: ['test'], project: 'packaging', ctk: '12.X', cxx: ['gcc10', 'clang14'], gpu: 'rtx2080'}
|
||||
- {jobs: ['test'], project: 'packaging', ctk: '13.0', cxx: ['gcc15', 'clang20'], gpu: 't4', args: '-min-cmake'}
|
||||
- {jobs: ['test'], project: 'packaging', ctk: '13.X', cxx: ['gcc', 'clang'], gpu: 'rtx2080'}
|
||||
- {jobs: ['install'], project: 'packaging'}
|
||||
# NVBench Helper:
|
||||
- {jobs: ['test'], project: 'nvbench_helper', ctk: '12.0', cxx: ['gcc10', 'clang14'], gpu: 'rtx2080'}
|
||||
- {jobs: ['test'], project: 'nvbench_helper', ctk: '12.X', cxx: ['gcc10', 'clang14'], gpu: 't4'}
|
||||
- {jobs: ['test'], project: 'nvbench_helper', ctk: '13.0', cxx: ['gcc15', 'clang20'], gpu: 'rtx2080'}
|
||||
- {jobs: ['test'], project: 'nvbench_helper', ctk: '13.X', cxx: ['gcc', 'clang'], gpu: 't4'}
|
||||
# NVHPC build
|
||||
- {jobs: ['build'], cxx: 'nvhpc-prev', ctk: 'nvhpc-prev', std: 'all', project: ['libcudacxx', 'cub', 'thrust', 'cudax', 'stdpar'], cpu: ['amd64', 'arm64']}
|
||||
- {jobs: ['build'], cxx: 'nvhpc', ctk: 'nvhpc', std: 'all', project: ['libcudacxx', 'cub', 'thrust', 'cudax', 'stdpar'], cpu: ['amd64', 'arm64']}
|
||||
# clang-cuda
|
||||
- {jobs: ['build'], cudacxx: 'clang', ctk: 'clang-cuda', cxx: 'clang-cuda', std: 'all', sm: '75;80;90;100;120'}
|
||||
- {jobs: ['build'], cudacxx: 'clang', ctk: 'clang_preview-cuda', cxx: 'clang_preview-cuda', std: 'all', sm: '75;80;90;100;120'}
|
||||
- {jobs: ['build'], project: 'libcudacxx', cudacxx: 'clang', ctk: 'clang-cuda', cxx: 'clang-cuda', std: 23, sm: '75;80;90;100;120'}
|
||||
- {jobs: ['build'], project: 'libcudacxx', cudacxx: 'clang', ctk: 'clang_preview-cuda', cxx: 'clang_preview-cuda', std: 23, sm: '75;80;90;100;120'}
|
||||
# compute-sanitizer
|
||||
- {jobs: ['compute_sanitizer'], project: 'cub', std: 'max', gpu: 'rtxa6000', sm: 'gpu', cmake_options: '-DCMAKE_CUDA_FLAGS=-lineinfo'}
|
||||
# clang-tidy
|
||||
- { jobs: ['build'], project: 'tidy', std: 'min', cxx: ['clang'], cudacxx: ['clang'], ctk: 'clang-cuda', sm: '75' }
|
||||
# arch-specific and family-specific arch builds
|
||||
- {jobs: ['build'], project: ['libcudacxx', 'cub', 'thrust', 'cudax'], std: 'all', sm: '90a;100a;103a;110a;120a;121a'}
|
||||
- {jobs: ['build'], project: ['libcudacxx', 'cub', 'thrust', 'cudax'], std: 'all', sm: '100f;103f;110f;120f;121f'}
|
||||
|
||||
python-wheels:
|
||||
- {jobs: ['test'], project: 'python', ctk: ['12.0', '12.X', '13.0', '13.X'], py_version: ['3.10', '3.11', '3.12', '3.13', '3.14'], gpu: 'l4', cxx: ['gcc13', 'msvc2022']}
|
||||
- {jobs: ['test'], project: 'python', ctk: ['12.X', '13.X'], py_version: '3.14', gpu: 'h100', cxx: ['gcc13', 'msvc2022']}
|
||||
- {jobs: ['test'], project: 'python', cpu: 'arm64', ctk: ['12.X', '13.X'], py_version: ['3.10', '3.11', '3.12', '3.13', '3.14'], gpu: 'l4', cxx: 'gcc13'}
|
||||
|
||||
|
||||
# This is just used to ensure that we generate devcontainers for all images we build.
|
||||
# These do not map to any actual jobs.
|
||||
devcontainers:
|
||||
- {jobs: ['dc'], ctk: ['12.0', '12.X' ], cxx: ['clang14']}
|
||||
- {jobs: ['dc'], ctk: ['12.0', '12.X' ], cxx: ['gcc7', 'gcc8', 'gcc9', 'gcc10']}
|
||||
- {jobs: ['dc'], ctk: ['12.0', '12.X', '13.0', '13.X'], cxx: ['gcc11', 'gcc12']}
|
||||
- {jobs: ['dc'], ctk: [ '12.X', '13.0', '13.X'], cxx: ['gcc13', 'gcc14']}
|
||||
- {jobs: ['dc'], ctk: [ '13.0', '13.X'], cxx: ['gcc15']}
|
||||
- {jobs: ['dc'], ctk: [ '12.X', '13.0', '13.X'], cxx: ['clang15', 'clang16', 'clang17', 'clang18', 'clang19']}
|
||||
- {jobs: ['dc_ext'], ctk: [ '12.X', '13.0', '13.X'], cxx: ['gcc14', 'clang20']}
|
||||
# Clang21+CTK12.9 is currently only used for cuda-clang testing. nvcc 12.9 doesn't support clang21.
|
||||
- {jobs: ['dc_ext'], ctk: [ '12.X', '13.X'], cxx: ['clang21']}
|
||||
- {jobs: ['dc_ext'], ctk: [ '13.X'], cxx: ['gcc15']}
|
||||
# Clang22+CTK12.9 is currently only used for clang-cuda testing. nvcc 12.9 doesn't support clang22.
|
||||
- {jobs: ['dc_ext'], ctk: [ '12.X' ], cxx: ['clang_preview22']}
|
||||
# 12.0 python image, pinned at gcc13 for simplicity. CTK 12.0 doesn't really play nice with gcc13, but
|
||||
# that doesn't matter for running python tests.
|
||||
- {jobs: ['dc'], ctk: ['12.0'], cxx: 'gcc13'}
|
||||
# NVHPC
|
||||
- {jobs: ['dc'], cxx: 'nvhpc-prev', ctk: 'nvhpc-prev'}
|
||||
- {jobs: ['dc'], cxx: 'nvhpc', ctk: 'nvhpc'}
|
||||
|
||||
# Any generated jobs that match the entries in `exclude` will be removed from the final matrix for all workflows.
|
||||
exclude:
|
||||
# GPU runners are not available on Windows.
|
||||
- {jobs: ['test', 'test_gpu', 'test_nolid', 'test_lid0', 'test_lid1', 'test_lid2'], cxx: ['msvc2019', 'msvc14.39', 'msvc2022', 'msvc2026']}
|
||||
# cudax doesn't support C++17 on msvc:
|
||||
- {project: 'cudax', std: 17, cxx: ['msvc2019', 'msvc14.39', 'msvc2022', 'msvc2026']}
|
||||
|
||||
|
||||
#############################################################################################
|
||||
|
||||
|
||||
# The version of the devcontainer images to use from https://hub.docker.com/r/rapidsai/devcontainers
|
||||
devcontainer_version: '26.10'
|
||||
|
||||
# Compiler versions used for the cuda99.X internal builds:
|
||||
cuda99_gcc_version: 15
|
||||
cuda99_clang_version: 21
|
||||
|
||||
# All supported C++ standards:
|
||||
all_stds: [17, 20]
|
||||
|
||||
# Aliases:
|
||||
# - 12.X: Newest CTK 12.X version.
|
||||
# - 13.X: Newest CTK 13.X version.
|
||||
# - nvhpc: CTK shipped in newest NVHPC
|
||||
# - nvhpc-prev: CTK shipped in previous NVHPC
|
||||
# - pybuild: Selects image to use for python wheel builds' outer docker instance
|
||||
ctk_versions:
|
||||
12.0: { stds: [17, 20] }
|
||||
12.9: { stds: [17, 20], alias: ['12.X', 'pybuild', 'clang-cuda', 'clang_preview-cuda'] }
|
||||
13.0: { stds: [17, 20] }
|
||||
13.1: { stds: [17, 20], alias: ['nvhpc-prev']}
|
||||
13.2: { stds: [17, 20], alias: ['nvhpc']}
|
||||
13.3: { stds: [17, 20], alias: ['13.X'] }
|
||||
|
||||
device_compilers:
|
||||
nvcc: # Version / stds are taken from CTK
|
||||
name: 'nvcc'
|
||||
exe: 'nvcc'
|
||||
clang: # Requires cxx=clang. Version / stds are taken from cxx compiler.
|
||||
name: "ClangCUDA"
|
||||
exe: 'clang++'
|
||||
|
||||
host_compilers:
|
||||
gcc:
|
||||
name: 'GCC'
|
||||
container_tag: 'gcc'
|
||||
exe: 'g++'
|
||||
versions:
|
||||
7: { stds: [17, ] }
|
||||
8: { stds: [17, ] }
|
||||
9: { stds: [17, ] }
|
||||
10: { stds: [17, 20] }
|
||||
11: { stds: [17, 20] }
|
||||
12: { stds: [17, 20] }
|
||||
13: { stds: [17, 20] }
|
||||
14: { stds: [17, 20] }
|
||||
15: { stds: [17, 20] }
|
||||
clang:
|
||||
name: 'Clang'
|
||||
container_tag: 'llvm'
|
||||
exe: 'clang++'
|
||||
versions:
|
||||
14: { stds: [17, 20] }
|
||||
15: { stds: [17, 20] }
|
||||
16: { stds: [17, 20] }
|
||||
17: { stds: [17, 20] }
|
||||
18: { stds: [17, 20] }
|
||||
19: { stds: [17, 20] }
|
||||
20: { stds: [17, 20] }
|
||||
21: { stds: [17, 20], alias: 'cuda' }
|
||||
clang_preview:
|
||||
name: 'Clang'
|
||||
container_tag: 'llvm'
|
||||
exe: 'clang++'
|
||||
versions:
|
||||
22: { stds: [17, 20], alias: 'cuda' }
|
||||
msvc:
|
||||
name: 'MSVC'
|
||||
container_tag: 'cl'
|
||||
exe: cl
|
||||
versions:
|
||||
'14.29': { stds: [17, ], alias: '2019' }
|
||||
'14.39': { stds: [17, 20] } # CTK 12.0 doesn't recognize >14.39 as MSVC 2022.
|
||||
'14.44': { stds: [17, 20], alias: '2022' }
|
||||
'14.50': { stds: [17, 20], alias: '2026' }
|
||||
|
||||
nvhpc:
|
||||
name: 'NVHPC'
|
||||
container_tag: 'nvhpc'
|
||||
exe: nvc++
|
||||
versions:
|
||||
# !! Update the ctk_versions 'nvhpc*' aliases when updating NVHPC versions:
|
||||
26.3: { stds: [17, 20], alias: 'prev' }
|
||||
26.5: { stds: [17, 20] }
|
||||
|
||||
# Jobs support the following properties:
|
||||
#
|
||||
# - name: The human-readable name of the job. Default is the capitalized job key.
|
||||
# - needs:
|
||||
# - A list of jobs that must be completed before this job can run. Default is an empty list.
|
||||
# - These jobs are automatically added if needed:
|
||||
# - Eg. "jobs: ['test']" in the workflow def will also create the required 'build' jobs.
|
||||
# - gpu: Whether the job requires a GPU runner. Default is false.
|
||||
# - cuda_ext: Whether the job requires a devcontainer with extra CUDA libraries. Default is false.
|
||||
# - invoke:
|
||||
# - Map the job type to the script invocation spec:
|
||||
# - prefix: The script invocation prefix. Default is the job name.
|
||||
# - args: Additional arguments to pass to the script. Default is no args.
|
||||
# - The script is invoked either:
|
||||
# linux: `ci/windows/<spec[prefix]>_<project>.ps1 <spec[args]>`
|
||||
# windows: `ci/<spec[prefix]>_<project>.sh <spec[args]>`
|
||||
# - force_producer_ctk:
|
||||
# - If set, force the auto-generated producers for this job to use a specific CTK version.
|
||||
# - By default, the autogenerated job's CTK version is determined by the consumer's `ctk` tag.
|
||||
# - This is useful for testing the cross-testing major version compat.
|
||||
# - E.g. "force_producer_ctk: '12.0'" on a test step will force the generated build step to use CTK 12.0.
|
||||
|
||||
jobs:
|
||||
# General:
|
||||
build: { gpu: false }
|
||||
test: { gpu: true, needs: 'build' }
|
||||
install: { gpu: false }
|
||||
|
||||
test_nobuild: { gpu: true, name: 'Test', invoke: { prefix: 'test' } }
|
||||
|
||||
compute_sanitizer: { gpu: true, name: 'ComputeSanitizer', needs: 'build', invoke: { prefix: 'test', args: '-compute-sanitizer' } }
|
||||
|
||||
# libcudacxx:
|
||||
nvrtc: { gpu: true, name: 'NVRTC' }
|
||||
verify_codegen: { gpu: false, name: 'VerifyCodegen' }
|
||||
|
||||
# CUB:
|
||||
build_nolid: { name: 'BuildNoLaunch', gpu: false, invoke: { prefix: 'build', args: '-no-lid'} }
|
||||
build_lid0: { name: 'BuildHostLaunch', gpu: false, invoke: { prefix: 'build', args: '-lid0'} }
|
||||
build_lid1: { name: 'BuildDeviceLaunch', gpu: false, invoke: { prefix: 'build', args: '-lid1'} }
|
||||
build_lid2: { name: 'BuildGraphCapture', gpu: false, invoke: { prefix: 'build', args: '-lid2'} }
|
||||
# NoLid -> The string `lid_X` doesn't appear in the test name. Mostly warp/block tests, old device tests, and examples.
|
||||
test_nolid: { name: 'TestNoLaunch', gpu: true, needs: 'build_nolid', invoke: { prefix: 'test', args: '-no-lid --test-par 8'} }
|
||||
# CUB uses `lid` to indicate launch strategies: whether CUB algorithms are:
|
||||
# - launched from the host (lid0):
|
||||
test_lid0: { name: 'HostLaunch', gpu: true, needs: 'build_lid0', invoke: { prefix: 'test', args: '-lid0 --test-par 8'} }
|
||||
# - launched from the device (lid1):
|
||||
test_lid1: { name: 'DeviceLaunch', gpu: true, needs: 'build_lid1', invoke: { prefix: 'test', args: '-lid1 --test-par 8'} }
|
||||
# - captured in a CUDA graph for deferred launch (lid2):
|
||||
test_lid2: { name: 'GraphCapture', gpu: true, needs: 'build_lid2', invoke: { prefix: 'test', args: '-lid2 --test-par 8'} }
|
||||
# Limited build reduces the number of runtime test cases, available device memory, etc, and may be used
|
||||
# to reduce test runtime in limited environments.
|
||||
limited: { name: "SmallGMem", gpu: true, needs: 'build', invoke: { prefix: 'test', args: '-limited'} }
|
||||
# Compute sanitizer jobs:
|
||||
compute_mem_nolid: { name: 'CSMem-TestGPU', gpu: true, needs: 'build_nolid', invoke: { prefix: 'test', args: '-compute-sanitizer-memcheck -no-lid'} }
|
||||
compute_mem_lid0: { name: 'CSMem-HostLaunch', gpu: true, needs: 'build_lid0', invoke: { prefix: 'test', args: '-compute-sanitizer-memcheck -lid0'} }
|
||||
compute_race_nolid: { name: 'CSRace-TestGPU', gpu: true, needs: 'build_nolid', invoke: { prefix: 'test', args: '-compute-sanitizer-racecheck -no-lid'} }
|
||||
compute_race_lid0: { name: 'CSRace-HostLaunch', gpu: true, needs: 'build_lid0', invoke: { prefix: 'test', args: '-compute-sanitizer-racecheck -lid0'} }
|
||||
compute_init_nolid: { name: 'CSInit-TestGPU', gpu: true, needs: 'build_nolid', invoke: { prefix: 'test', args: '-compute-sanitizer-initcheck -no-lid'} }
|
||||
compute_init_lid0: { name: 'CSInit-HostLaunch', gpu: true, needs: 'build_lid0', invoke: { prefix: 'test', args: '-compute-sanitizer-initcheck -lid0'} }
|
||||
compute_sync_nolid: { name: 'CSSync-TestGPU', gpu: true, needs: 'build_nolid', invoke: { prefix: 'test', args: '-compute-sanitizer-synccheck -no-lid'} }
|
||||
compute_sync_lid0: { name: 'CSSync-HostLaunch', gpu: true, needs: 'build_lid0', invoke: { prefix: 'test', args: '-compute-sanitizer-synccheck -lid0'} }
|
||||
|
||||
# Thrust:
|
||||
test_cpu: { name: 'TestCPU', gpu: false, needs: 'build', invoke: { prefix: 'test', args: '-cpu-only'} }
|
||||
test_gpu: { name: 'TestGPU', gpu: true, needs: 'build', invoke: { prefix: 'test', args: '-gpu-only'} }
|
||||
|
||||
# Python:
|
||||
build_py_wheel: { name: "Build cuda.cccl", gpu: false, invoke: { prefix: 'build_cuda_cccl'} }
|
||||
test_headers: { name: "Test cuda.cccl.headers", gpu: false, needs: 'build_py_wheel', force_producer_ctk: "pybuild", invoke: { prefix: 'test_cuda_cccl_headers'} }
|
||||
test_py_par: { name: "Test cuda.compute", gpu: true, needs: 'build_py_wheel', force_producer_ctk: "pybuild", invoke: { prefix: 'test_cuda_compute'} }
|
||||
test_py_compute_minimal: { name: "Test cuda.compute minimal", gpu: true, needs: 'build_py_wheel', force_producer_ctk: "pybuild", invoke: { prefix: 'test_cuda_compute_minimal'} }
|
||||
test_py_examples: { name: "Test cuda.cccl.examples", gpu: true, needs: 'build_py_wheel', force_producer_ctk: "pybuild", invoke: { prefix: 'test_cuda_cccl_examples'} }
|
||||
|
||||
# Run jobs for 'target' project (ci/util/build_and_test_targets.sh):
|
||||
run_cpu: { gpu: false }
|
||||
run_gpu: { gpu: true }
|
||||
|
||||
# Only used for generating devcontainers. No scripts actually exist for these:
|
||||
dc: { gpu: false }
|
||||
dc_ext: { gpu: false, cuda_ext: true }
|
||||
|
||||
# Projects have the following properties:
|
||||
#
|
||||
# Keys are project subdirectories names. These will also be used in script names.
|
||||
#
|
||||
# - stds: A list of C++ standards to test. Required.
|
||||
# - name: The human-readable name of the project. Default is the project key.
|
||||
# - job_map: Map general jobs to arrays of project-specific jobs.
|
||||
# Useful for things like splitting cpu/gpu testing for a project.
|
||||
# E.g. "job_map: { test: ['test_cpu', 'test_gpu'] }" replaces
|
||||
# the "test" job with distinct "test_cpu" and "test_gpu" jobs.
|
||||
projects:
|
||||
packaging:
|
||||
name: 'CCCL Packaging'
|
||||
stds: [17, 20]
|
||||
job_map:
|
||||
build: []
|
||||
test: ['test_nobuild']
|
||||
libcudacxx:
|
||||
name: 'libcu++'
|
||||
stds: [17, 20]
|
||||
cub:
|
||||
name: 'CUB'
|
||||
stds: [17, 20]
|
||||
job_map:
|
||||
build: ['build_nolid', 'build_lid0', 'build_lid1', 'build_lid2']
|
||||
test: ['test_nolid', 'test_lid0', 'test_lid1', 'test_lid2']
|
||||
compute_sanitizer:
|
||||
- compute_mem_nolid
|
||||
- compute_mem_lid0
|
||||
- compute_race_nolid
|
||||
- compute_race_lid0
|
||||
- compute_init_nolid
|
||||
- compute_init_lid0
|
||||
- compute_sync_nolid
|
||||
- compute_sync_lid0
|
||||
thrust:
|
||||
name: 'Thrust'
|
||||
stds: [17, 20]
|
||||
job_map: { test: ['test_cpu', 'test_gpu'] }
|
||||
cudax:
|
||||
stds: [17, 20]
|
||||
stdpar:
|
||||
name: 'NVHPC stdpar'
|
||||
stds: [17, 20]
|
||||
python:
|
||||
name: "Python"
|
||||
job_map:
|
||||
build: ['build_py_wheel']
|
||||
test: ['test_py_par', 'test_py_examples']
|
||||
python_v2:
|
||||
name: "Python (cuda.compute on v2/HostJIT)"
|
||||
# Only cuda.compute differs between v1 and v2; cuda.cccl.headers does not
|
||||
# need separate coverage. Run the examples because they exercise
|
||||
# cuda.compute against the v2 backend.
|
||||
job_map:
|
||||
build: ['build_py_wheel']
|
||||
test: ['test_py_par', 'test_py_examples']
|
||||
python_tsan:
|
||||
name: "Python (cuda.compute free-threaded ThreadSanitizer)"
|
||||
# Runs per-PR (and nightly). The producer build_py_wheel runs
|
||||
# build_cuda_cccl_python_tsan.sh (c.parallel v1 host code built with
|
||||
# -fsanitize=thread); test_py_compute_minimal runs
|
||||
# test_cuda_compute_minimal_python_tsan.sh (the FT stress + sweep under the
|
||||
# TSan runtime). Deliberately absent from the `python-wheels` publish
|
||||
# workflow, so these instrumented wheels never reach PyPI.
|
||||
job_map:
|
||||
build: ['build_py_wheel']
|
||||
test: ['test_py_compute_minimal']
|
||||
cccl_c_parallel:
|
||||
name: 'CCCL C Parallel'
|
||||
stds: [20]
|
||||
cccl_c_parallel_v2:
|
||||
name: 'CCCL C Parallel v2 (HostJIT)'
|
||||
stds: [20]
|
||||
# test_cccl_c_parallel_v2.sh builds inline (no separate build script),
|
||||
# so suppress the default test→build dependency. test_nobuild invokes
|
||||
# test_<project>.sh directly without a producer build job.
|
||||
job_map:
|
||||
build: []
|
||||
test: ['test_nobuild']
|
||||
cccl_c_stf:
|
||||
name: 'CCCL C CUDASTF'
|
||||
stds: [20]
|
||||
nvbench_helper:
|
||||
name: 'NVBench Helper'
|
||||
stds: [17] # Only builds on oldest arch for max compat.
|
||||
job_map:
|
||||
build: []
|
||||
test: ['test_nobuild']
|
||||
|
||||
# Run specific build_and_test_targets.sh invocations across the CI matrix.
|
||||
# Use the override workflow and supply arguments via the `args` tag.
|
||||
# Example:
|
||||
# override:
|
||||
# - { jobs: ['run'], project: 'target', ctk: ['12.X', '13.X'], cxx: 'gcc', gpu: 'rtx2080',
|
||||
# args: '--preset cub-cpp20 --build-targets "cub.cpp20.test.iterator" --ctest-targets "cub.cpp20.test.iterator"' }
|
||||
target:
|
||||
name: 'Target'
|
||||
stds: [17, 20]
|
||||
bisect:
|
||||
name: 'Bisect'
|
||||
stds: [17, 20]
|
||||
tidy:
|
||||
name: 'clang-tidy'
|
||||
stds: [17]
|
||||
|
||||
# name -> Display name for generated job labels.
|
||||
# runner -> GPU/driver/count segment of the GHA runner label.
|
||||
# testing -> Runner with GPU is in a nv-gh-runners testing pool.
|
||||
gpus:
|
||||
t4: { name: 'T4', sm: 75, runner: 't4-latest-1' } # 16 GB, 10 runners
|
||||
rtx2080: { name: 'RTX2080', sm: 75, runner: 'rtx2080-latest-1' } # 8 GB, 12 runners
|
||||
rtxa6000: { name: 'RTXA6000', sm: 86, runner: 'rtxa6000-latest-1' } # 48 GB, 12 runners
|
||||
l4: { name: 'L4', sm: 89, runner: 'l4-latest-1' } # 24 GB, 48 runners
|
||||
rtx4090: { name: 'RTX4090', sm: 89, runner: 'rtx4090-latest-1' } # 24 GB, 10 runners
|
||||
h100: { name: 'H100', sm: 90, runner: 'h100-latest-1' } # 80 GB, 16 runners
|
||||
h100_2gpu: { name: 'H100 2-GPU', sm: 90, runner: 'h100-latest-2' } # 2 x 80 GB
|
||||
# Very small number of runners on loan from cuda-python while we wait for our order to arrive.
|
||||
# Limit jobs on these:
|
||||
rtxpro6000: { name: 'RTXPRO6000', sm: 120, runner: 'rtxpro6000-latest-1' }
|
||||
|
||||
# Tags are used to define a `matrix job` in the workflow section.
|
||||
#
|
||||
# Tags have the following options:
|
||||
# - required: Whether the tag is required. Default is false.
|
||||
# - default: The default value for the tag. Default is null.
|
||||
tags:
|
||||
# An array of jobs (e.g. 'build', 'test', 'nvrtc', 'infra', 'verify_codegen', ...)
|
||||
# See the `jobs` map.
|
||||
jobs: { required: true }
|
||||
# CUDA ToolKit version
|
||||
# See the `ctks` map.
|
||||
ctk: { default: '13.X' }
|
||||
# CPU architecture
|
||||
cpu: { default: 'amd64' }
|
||||
# GPU model
|
||||
gpu: { default: 'rtx2080' }
|
||||
# Host compiler {name, version, exe}
|
||||
# See the `host_compilers` map.
|
||||
cxx: { default: 'gcc' }
|
||||
# Device compiler.
|
||||
# See the `device_compilers` map.
|
||||
cudacxx: { default: 'nvcc' }
|
||||
# Project name (e.g. libcudacxx, cub, thrust, cccl)
|
||||
# See the `projects` map.
|
||||
project: { default: ['libcudacxx', 'cub', 'thrust'] }
|
||||
# Python version for Python builds/tests
|
||||
py_version: { required: false }
|
||||
# Python CTK-source mode for test lanes (Python-only):
|
||||
# 'pinned' (default) -- pin cuda-toolkit to the container CTK minor
|
||||
# 'latest' -- unpinned; pip resolves the newest minor
|
||||
# 'sysctk' -- use the system toolkit; install the sysctk extras
|
||||
# If set, passed to the script with `-ctk-mode <mode>`. Exploded if an array,
|
||||
# e.g. `['pinned','sysctk']`.
|
||||
py_ctk_mode: { required: false }
|
||||
# C++ standard
|
||||
# If set to 'all', all stds supported by the ctk/compilers/project are used.
|
||||
# If set to 'min', 'max', or 'minmax', the minimum, maximum, or both stds are used.
|
||||
# If set, will be passed to script with `-std <std>`.
|
||||
std: { required: false }
|
||||
# GPU architecture
|
||||
# - If set, passed to script with `-arch <sm>`.
|
||||
# - Format is the same as `CMAKE_CUDA_ARCHITECTURES`:
|
||||
# - PTX only: 70-virtual
|
||||
# - SASS only: 70-real
|
||||
# - Both: 70
|
||||
# - Can pass multiple architectures via "60;70-real;80-virtual"
|
||||
# - Defaults to use the settings in the CMakePresets.json file.
|
||||
# - Will be exploded if an array, e.g. `sm: ['60;70;80;90', '90a']` creates two jobs.
|
||||
# - Set to 'gpu' to only target the GPU in the `gpu` tag.
|
||||
sm: { required: false }
|
||||
# Additional CMake options to pass to the build.
|
||||
# If set, passed to script with `-cmake_options "<cmake_options>"`.
|
||||
cmake_options: { required: false }
|
||||
# Environment variables to add
|
||||
environment: { required: false, default: [] }
|
||||
# Additional arguments appended to the generated command.
|
||||
# Typically used with the `target` project to forward options to
|
||||
# ci/util/build_and_test_targets.sh, but works with all CI jobs.
|
||||
args: { required: false, default: "" }
|
||||
113
cccl_upstream/ci/matx/build_matx.sh
Executable file
113
cccl_upstream/ci/matx/build_matx.sh
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
readonly matx_repo=https://github.com/NVIDIA/MatX.git
|
||||
readonly matx_branch=main
|
||||
|
||||
# Ensure the script is being executed in the root cccl directory:
|
||||
cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/../..";
|
||||
|
||||
log_vars() {
|
||||
for var in "$@"; do
|
||||
echo "${var}=${!var}"
|
||||
done
|
||||
}
|
||||
|
||||
# Take two version strings and return the greater of the two:
|
||||
version_max() {
|
||||
local v1="${1}"
|
||||
local v2="${2}"
|
||||
if ci/util/version_compare.sh "$v1" ge "$v2"; then
|
||||
echo "$v1"
|
||||
else
|
||||
echo "$v2"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get the current CCCL info:
|
||||
readonly cccl_repo="${PWD}"
|
||||
|
||||
# Define CCCL_TAG to override the default CCCL SHA. Otherwise the current HEAD of the local checkout is used.
|
||||
echo "CCCL_TAG (override): ${CCCL_TAG-}";
|
||||
if test -n "${CCCL_TAG-}"; then
|
||||
# If CCCL_TAG is defined, fetch it to the local checkout
|
||||
git -C "${cccl_repo}" fetch origin "${CCCL_TAG}";
|
||||
cccl_sha="$(git -C "${cccl_repo}" rev-parse FETCH_HEAD)";
|
||||
else
|
||||
cccl_sha="$(git -C "${cccl_repo}" rev-parse HEAD)";
|
||||
fi
|
||||
|
||||
cccl_repo_version="$(git -C "${cccl_repo}" describe "${cccl_sha}"| grep -Eo '[0-9]+\.[0-9]+\.[0-9]+')"
|
||||
readonly cccl_repo_version
|
||||
|
||||
# Define CCCL_VERSION to override the version used by rapids-cmake to patch CCCL.
|
||||
echo "CCCL_VERSION (override): ${CCCL_VERSION-}";
|
||||
if test -n "${CCCL_VERSION-}"; then
|
||||
readonly cccl_rapids_cmake_version="${CCCL_VERSION}"
|
||||
else
|
||||
cccl_rapids_cmake_version="${cccl_repo_version}"
|
||||
# shellcheck disable=SC2034
|
||||
readonly cccl_rapids_cmake_version
|
||||
fi
|
||||
|
||||
# If the current version is less than 2.8.0, use 2.8.0 for the rapids-cmake version.
|
||||
# This is to allow rapids-cmake to correctly patch the CCCL install rules on current `main`.
|
||||
cccl_version=$(version_max "${cccl_repo_version}" "2.8.0")
|
||||
readonly cccl_version
|
||||
|
||||
readonly workdir="${cccl_repo}/build/${CCCL_BUILD_INFIX:-}/matx"
|
||||
readonly version_file="${workdir}/MatX/cmake/versions.json"
|
||||
readonly version_override_file="${workdir}/versions-override.json"
|
||||
|
||||
log_vars \
|
||||
matx_repo matx_branch \
|
||||
cccl_repo cccl_sha cccl_repo_version cccl_rapids_cmake_version \
|
||||
workdir \
|
||||
version_file version_override_file
|
||||
|
||||
mkdir -p "${workdir}"
|
||||
cd "${workdir}"
|
||||
|
||||
# Python deps:
|
||||
pip install numpy
|
||||
|
||||
# Clone MatX
|
||||
rm -rf MatX
|
||||
git clone "${matx_repo}" -b "${matx_branch}"
|
||||
|
||||
cd MatX
|
||||
echo "MatX HEAD:"
|
||||
git log -1 --format=short
|
||||
cd ..
|
||||
|
||||
# Write out version override file
|
||||
jq -r ".packages.CCCL *=
|
||||
{
|
||||
\"git_url\": \"${cccl_repo}\",
|
||||
\"git_tag\": \"${cccl_sha}\",
|
||||
\"version\": \"${cccl_version}\",
|
||||
\"always_download\": true
|
||||
}" \
|
||||
"${version_file}" > "${version_override_file}"
|
||||
|
||||
echo "Overriding MatX versions.json file:"
|
||||
cat "$version_override_file"
|
||||
|
||||
# Configure and build
|
||||
rm -rf build
|
||||
|
||||
SCCACHE_NO_DIST_COMPILE=1 cmake \
|
||||
-B build -S MatX -G Ninja \
|
||||
"-DCMAKE_CUDA_ARCHITECTURES=75;120" \
|
||||
"-DRAPIDS_CMAKE_CPM_OVERRIDE_VERSION_FILE=${version_override_file}" \
|
||||
-DMATX_BUILD_TESTS=ON \
|
||||
-DMATX_BUILD_EXAMPLES=ON \
|
||||
-DMATX_BUILD_BENCHMARKS=ON \
|
||||
-DMATX_EN_CUTENSOR=ON
|
||||
|
||||
# Disabled because `cmake --build -j ""` is invalid, but so is
|
||||
# `cmake --build -j8`. CMake expects a space between `-j` and
|
||||
# the numeric argument, or no argument at all.
|
||||
# shellcheck disable=SC2086
|
||||
time cmake --build build -j ${PARALLEL_LEVEL:-}
|
||||
390
cccl_upstream/ci/ninja_summary.py
Executable file
390
cccl_upstream/ci/ninja_summary.py
Executable file
@@ -0,0 +1,390 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2018 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
r"""Summarize the last ninja build, invoked with ninja's -C syntax.
|
||||
|
||||
This script is designed to be automatically run after each ninja build in
|
||||
order to summarize the build's performance. Making build performance information
|
||||
more visible should make it easier to notice anomalies and opportunities. To use
|
||||
this script on Windows just set NINJA_SUMMARIZE_BUILD=1 and run autoninja.bat.
|
||||
|
||||
On Linux you can get autoninja to invoke this script using this syntax:
|
||||
|
||||
$ NINJA_SUMMARIZE_BUILD=1 autoninja -C out/Default/ chrome
|
||||
|
||||
You can also call this script directly using ninja's syntax to specify the
|
||||
output directory of interest:
|
||||
|
||||
> python3 post_build_ninja_summary.py -C out/Default
|
||||
|
||||
Typical output looks like this:
|
||||
|
||||
>ninja -C out\debug_component base
|
||||
ninja.exe -C out\debug_component base -j 960 -l 48 -d keeprsp
|
||||
ninja: Entering directory `out\debug_component'
|
||||
[1 processes, 1/1 @ 0.3/s : 3.092s ] Regenerating ninja files
|
||||
Longest build steps:
|
||||
0.1 weighted s to build obj/base/base/trace_log.obj (6.7 s elapsed time)
|
||||
0.2 weighted s to build nasm.exe, nasm.exe.pdb (0.2 s elapsed time)
|
||||
0.3 weighted s to build obj/base/base/win_util.obj (12.4 s elapsed time)
|
||||
1.2 weighted s to build base.dll, base.dll.lib (1.2 s elapsed time)
|
||||
Time by build-step type:
|
||||
0.0 s weighted time to generate 6 .lib files (0.3 s elapsed time sum)
|
||||
0.1 s weighted time to generate 25 .stamp files (1.2 s elapsed time sum)
|
||||
0.2 s weighted time to generate 20 .o files (2.8 s elapsed time sum)
|
||||
1.7 s weighted time to generate 4 PEFile (linking) files (2.0 s elapsed
|
||||
time sum)
|
||||
23.9 s weighted time to generate 770 .obj files (974.8 s elapsed time sum)
|
||||
26.1 s weighted time (982.9 s elapsed time sum, 37.7x parallelism)
|
||||
839 build steps completed, average of 32.17/s
|
||||
|
||||
If no gn clean has been done then results will be for the last non-NULL
|
||||
invocation of ninja. Ideas for future statistics, and implementations are
|
||||
appreciated.
|
||||
|
||||
The "weighted" time is the elapsed time of each build step divided by the number
|
||||
of tasks that were running in parallel. This makes it an excellent approximation
|
||||
of how "important" a slow step was. A link that is entirely or mostly serialized
|
||||
will have a weighted time that is the same or similar to its elapsed time. A
|
||||
compile that runs in parallel with 999 other compiles will have a weighted time
|
||||
that is tiny."""
|
||||
|
||||
import argparse
|
||||
import errno
|
||||
import fnmatch
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# The number of long build times to report:
|
||||
long_count = 10
|
||||
# The number of long times by extension to report
|
||||
long_ext_count = 10
|
||||
|
||||
|
||||
class Target:
|
||||
"""Represents a single line read for a .ninja_log file."""
|
||||
|
||||
def __init__(self, start, end):
|
||||
"""Creates a target object by passing in the start/end times in seconds
|
||||
as a float."""
|
||||
self.start = start
|
||||
self.end = end
|
||||
# A list of targets, appended to by the owner of this object.
|
||||
self.targets = []
|
||||
self.weighted_duration = 0.0
|
||||
|
||||
def Duration(self):
|
||||
"""Returns the task duration in seconds as a float."""
|
||||
return self.end - self.start
|
||||
|
||||
def SetWeightedDuration(self, weighted_duration):
|
||||
"""Sets the duration, in seconds, passed in as a float."""
|
||||
self.weighted_duration = weighted_duration
|
||||
|
||||
def WeightedDuration(self):
|
||||
"""Returns the task's weighted duration in seconds as a float.
|
||||
|
||||
Weighted_duration takes the elapsed time of the task and divides it
|
||||
by how many other tasks were running at the same time. Thus, it
|
||||
represents the approximate impact of this task on the total build time,
|
||||
with serialized or serializing steps typically ending up with much
|
||||
longer weighted durations.
|
||||
weighted_duration should always be the same or shorter than duration.
|
||||
"""
|
||||
# Allow for modest floating-point errors
|
||||
epsilon = 0.000002
|
||||
if self.weighted_duration > self.Duration() + epsilon:
|
||||
print("%s > %s?" % (self.weighted_duration, self.Duration()))
|
||||
assert self.weighted_duration <= self.Duration() + epsilon
|
||||
return self.weighted_duration
|
||||
|
||||
def DescribeTargets(self):
|
||||
"""Returns a printable string that summarizes the targets."""
|
||||
# Some build steps generate dozens of outputs - handle them sanely.
|
||||
# The max_length was chosen so that it can fit most of the long
|
||||
# single-target names, while minimizing word wrapping.
|
||||
result = ", ".join(self.targets)
|
||||
max_length = 65
|
||||
if len(result) > max_length:
|
||||
result = result[:max_length] + "..."
|
||||
return result
|
||||
|
||||
|
||||
# Copied with some modifications from ninjatracing
|
||||
def ReadTargets(log, show_all):
|
||||
"""Reads all targets from .ninja_log file |log_file|, sorted by duration.
|
||||
|
||||
The result is a list of Target objects."""
|
||||
header = log.readline()
|
||||
# Handle empty ninja_log gracefully by silently returning an empty list of
|
||||
# targets.
|
||||
if not header:
|
||||
return []
|
||||
assert header == "# ninja log v6\n", "unrecognized ninja log version %r" % header
|
||||
targets_dict = {}
|
||||
last_end_seen = 0.0
|
||||
for line in log:
|
||||
parts = line.strip().split("\t")
|
||||
if len(parts) != 5:
|
||||
# If ninja.exe is rudely halted then the .ninja_log file may be
|
||||
# corrupt. Silently continue.
|
||||
continue
|
||||
start, end, _, name, cmdhash = parts # Ignore restat.
|
||||
# Convert from integral milliseconds to float seconds.
|
||||
start = int(start) / 1000.0
|
||||
end = int(end) / 1000.0
|
||||
if not show_all and end < last_end_seen:
|
||||
# An earlier time stamp means that this step is the first in a new
|
||||
# build, possibly an incremental build. Throw away the previous
|
||||
# data so that this new build will be displayed independently.
|
||||
# This has to be done by comparing end times because records are
|
||||
# written to the .ninja_log file when commands complete, so end
|
||||
# times are guaranteed to be in order, but start times are not.
|
||||
targets_dict = {}
|
||||
target = None
|
||||
if cmdhash in targets_dict:
|
||||
target = targets_dict[cmdhash]
|
||||
if not show_all and (target.start != start or target.end != end):
|
||||
# If several builds in a row just run one or two build steps
|
||||
# then the end times may not go backwards so the last build may
|
||||
# not be detected as such. However in many cases there will be a
|
||||
# build step repeated in the two builds and the changed
|
||||
# start/stop points for that command, identified by the hash,
|
||||
# can be used to detect and reset the target dictionary.
|
||||
targets_dict = {}
|
||||
target = None
|
||||
if not target:
|
||||
targets_dict[cmdhash] = target = Target(start, end)
|
||||
last_end_seen = end
|
||||
target.targets.append(name)
|
||||
return list(targets_dict.values())
|
||||
|
||||
|
||||
def GetExtension(target, extra_patterns):
|
||||
"""Return the file extension that best represents a target.
|
||||
|
||||
For targets that generate multiple outputs it is important to return a
|
||||
consistent 'canonical' extension. Ultimately the goal is to group build steps
|
||||
by type."""
|
||||
for output in target.targets:
|
||||
if extra_patterns:
|
||||
for fn_pattern in extra_patterns.split(";"):
|
||||
if fnmatch.fnmatch(output, "*" + fn_pattern + "*"):
|
||||
return fn_pattern
|
||||
# Not a true extension, but a good grouping.
|
||||
if output.endswith("type_mappings"):
|
||||
extension = "type_mappings"
|
||||
break
|
||||
|
||||
# Capture two extensions if present. For example: file.javac.jar should
|
||||
# be distinguished from file.interface.jar.
|
||||
root, ext1 = os.path.splitext(output)
|
||||
_, ext2 = os.path.splitext(root)
|
||||
extension = ext2 + ext1 # Preserve the order in the file name.
|
||||
|
||||
if len(extension) == 0:
|
||||
extension = "(no extension found)"
|
||||
|
||||
if ext1 in [".pdb", ".dll", ".exe"]:
|
||||
extension = "PEFile (linking)"
|
||||
# Make sure that .dll and .exe are grouped together and that the
|
||||
# .dll.lib files don't cause these to be listed as libraries
|
||||
break
|
||||
if ext1 in [".so", ".TOC"]:
|
||||
extension = ".so (linking)"
|
||||
# Attempt to identify linking, avoid identifying as '.TOC'
|
||||
break
|
||||
# Make sure .obj files don't get categorized as mojo files
|
||||
if ext1 in [".obj", ".o"]:
|
||||
break
|
||||
# Jars are the canonical output of java targets.
|
||||
if ext1 == ".jar":
|
||||
break
|
||||
# Normalize all mojo related outputs to 'mojo'.
|
||||
if output.count(".mojom") > 0:
|
||||
extension = "mojo"
|
||||
break
|
||||
return extension
|
||||
|
||||
|
||||
def SummarizeEntries(entries, extra_step_types, elapsed_time_sorting):
|
||||
"""Print a summary of the passed in list of Target objects."""
|
||||
|
||||
# Create a list that is in order by time stamp and has entries for the
|
||||
# beginning and ending of each build step (one time stamp may have multiple
|
||||
# entries due to multiple steps starting/stopping at exactly the same time).
|
||||
# Iterate through this list, keeping track of which tasks are running at all
|
||||
# times. At each time step calculate a running total for weighted time so
|
||||
# that when each task ends its own weighted time can easily be calculated.
|
||||
task_start_stop_times = []
|
||||
|
||||
earliest = -1
|
||||
latest = 0
|
||||
total_cpu_time = 0
|
||||
for target in entries:
|
||||
if earliest < 0 or target.start < earliest:
|
||||
earliest = target.start
|
||||
if target.end > latest:
|
||||
latest = target.end
|
||||
total_cpu_time += target.Duration()
|
||||
task_start_stop_times.append((target.start, "start", target))
|
||||
task_start_stop_times.append((target.end, "stop", target))
|
||||
length = latest - earliest
|
||||
weighted_total = 0.0
|
||||
|
||||
# Sort by the time/type records and ignore |target|
|
||||
task_start_stop_times.sort(key=lambda times: times[:2])
|
||||
# Now we have all task start/stop times sorted by when they happen. If a
|
||||
# task starts and stops on the same time stamp then the start will come
|
||||
# first because of the alphabet, which is important for making this work
|
||||
# correctly.
|
||||
# Track the tasks which are currently running.
|
||||
running_tasks = {}
|
||||
# Record the time we have processed up to so we know how to calculate time
|
||||
# deltas.
|
||||
last_time = task_start_stop_times[0][0]
|
||||
# Track the accumulated weighted time so that it can efficiently be added
|
||||
# to individual tasks.
|
||||
last_weighted_time = 0.0
|
||||
# Scan all start/stop events.
|
||||
for event in task_start_stop_times:
|
||||
time, action_name, target = event
|
||||
# Accumulate weighted time up to now.
|
||||
num_running = len(running_tasks)
|
||||
if num_running > 0:
|
||||
# Update the total weighted time up to this moment.
|
||||
last_weighted_time += (time - last_time) / float(num_running)
|
||||
if action_name == "start":
|
||||
# Record the total weighted task time when this task starts.
|
||||
running_tasks[target] = last_weighted_time
|
||||
if action_name == "stop":
|
||||
# Record the change in the total weighted task time while this task
|
||||
# ran.
|
||||
weighted_duration = last_weighted_time - running_tasks[target]
|
||||
target.SetWeightedDuration(weighted_duration)
|
||||
weighted_total += weighted_duration
|
||||
del running_tasks[target]
|
||||
last_time = time
|
||||
assert len(running_tasks) == 0
|
||||
|
||||
# Warn if the sum of weighted times is off by more than half a second.
|
||||
if abs(length - weighted_total) > 500:
|
||||
print(
|
||||
"Warning: Possible corrupt ninja log, results may be "
|
||||
"untrustworthy. Length = %.3f, weighted total = %.3f"
|
||||
% (length, weighted_total)
|
||||
)
|
||||
|
||||
# Print the slowest build steps:
|
||||
print(" Longest build steps:")
|
||||
if elapsed_time_sorting:
|
||||
entries.sort(key=lambda x: x.Duration())
|
||||
else:
|
||||
entries.sort(key=lambda x: x.WeightedDuration())
|
||||
for target in entries[-long_count:]:
|
||||
print(
|
||||
" %8.1f weighted s to build %s (%.1f s elapsed time)"
|
||||
% (target.WeightedDuration(), target.DescribeTargets(), target.Duration())
|
||||
)
|
||||
|
||||
# Sum up the time by file extension/type of the output file
|
||||
count_by_ext = {}
|
||||
time_by_ext = {}
|
||||
weighted_time_by_ext = {}
|
||||
# Scan through all of the targets to build up per-extension statistics.
|
||||
for target in entries:
|
||||
extension = GetExtension(target, extra_step_types)
|
||||
time_by_ext[extension] = time_by_ext.get(extension, 0) + target.Duration()
|
||||
weighted_time_by_ext[extension] = (
|
||||
weighted_time_by_ext.get(extension, 0) + target.WeightedDuration()
|
||||
)
|
||||
count_by_ext[extension] = count_by_ext.get(extension, 0) + 1
|
||||
|
||||
print(" Time by build-step type:")
|
||||
# Copy to a list with extension name and total time swapped, to (time, ext)
|
||||
if elapsed_time_sorting:
|
||||
weighted_time_by_ext_sorted = sorted((y, x) for (x, y) in time_by_ext.items())
|
||||
else:
|
||||
weighted_time_by_ext_sorted = sorted(
|
||||
(y, x) for (x, y) in weighted_time_by_ext.items()
|
||||
)
|
||||
# Print the slowest build target types:
|
||||
for time, extension in weighted_time_by_ext_sorted[-long_ext_count:]:
|
||||
print(
|
||||
" %8.1f s weighted time to generate %d %s files "
|
||||
"(%1.1f s elapsed time sum)"
|
||||
% (time, count_by_ext[extension], extension, time_by_ext[extension])
|
||||
)
|
||||
|
||||
print(
|
||||
" %.1f s weighted time (%.1f s elapsed time sum, %1.1fx "
|
||||
"parallelism)" % (length, total_cpu_time, total_cpu_time * 1.0 / length)
|
||||
)
|
||||
print(
|
||||
" %d build steps completed, average of %1.2f/s"
|
||||
% (len(entries), len(entries) / (length))
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
log_file = ".ninja_log"
|
||||
metrics_file = "siso_metrics.json"
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-C", dest="build_directory", help="Build directory.")
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--step-types",
|
||||
help="semicolon separated fnmatch patterns for build-step grouping",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--elapsed_time_sorting",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Sort output by elapsed time instead of weighted time",
|
||||
)
|
||||
parser.add_argument("--log-file", help="specific ninja log file to analyze.")
|
||||
args, _extra_args = parser.parse_known_args()
|
||||
if args.build_directory:
|
||||
log_file = os.path.join(args.build_directory, log_file)
|
||||
metrics_file = os.path.join(args.build_directory, metrics_file)
|
||||
if args.log_file:
|
||||
log_file = args.log_file
|
||||
if not args.step_types:
|
||||
# Offer a convenient way to add extra step types automatically,
|
||||
# including when this script is run by autoninja. get() returns None if
|
||||
# the variable isn't set.
|
||||
args.step_types = os.environ.get("chromium_step_types")
|
||||
if args.step_types:
|
||||
# Make room for the extra build types.
|
||||
global long_ext_count
|
||||
long_ext_count += len(args.step_types.split(";"))
|
||||
|
||||
if os.path.exists(metrics_file):
|
||||
# Automatically handle summarizing siso builds.
|
||||
cmd = ["siso.bat" if "win32" in sys.platform else "siso"]
|
||||
cmd.extend(["metrics", "summary"])
|
||||
if args.build_directory:
|
||||
cmd.extend(["-C", args.build_directory])
|
||||
if args.step_types:
|
||||
cmd.extend(["--step_types", args.step_types])
|
||||
if args.elapsed_time_sorting:
|
||||
cmd.append("--elapsed_time_sorting")
|
||||
subprocess.run(cmd)
|
||||
else:
|
||||
try:
|
||||
with open(log_file, "r") as log:
|
||||
entries = ReadTargets(log, False)
|
||||
if entries:
|
||||
SummarizeEntries(
|
||||
entries, args.step_types, args.elapsed_time_sorting
|
||||
)
|
||||
except IOError:
|
||||
print("Log file %r not found, no build summary created." % log_file)
|
||||
return errno.ENOENT
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
16
cccl_upstream/ci/nvrtc_libcudacxx.sh
Executable file
16
cccl_upstream/ci/nvrtc_libcudacxx.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# shellcheck source=ci/build_common.sh
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/build_common.sh"
|
||||
|
||||
print_environment_details
|
||||
|
||||
|
||||
PRESET="libcudacxx-nvrtc"
|
||||
CMAKE_OPTIONS=("-DCMAKE_CXX_STANDARD=${CXX_STANDARD}" "-DCMAKE_CUDA_STANDARD=${CXX_STANDARD}")
|
||||
|
||||
configure_and_build_preset "libcudacxx NVRTC" "$PRESET" "${CMAKE_OPTIONS[@]}"
|
||||
|
||||
sccache -z > /dev/null || :
|
||||
test_preset "libcudacxx NVRTC" "${PRESET}"
|
||||
sccache --show-adv-stats || :
|
||||
122
cccl_upstream/ci/pretty_printing.sh
Normal file
122
cccl_upstream/ci/pretty_printing.sh
Normal file
@@ -0,0 +1,122 @@
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Print "ARG=${ARG}" for all args.
|
||||
function print_var_values() {
|
||||
# Iterate through the arguments
|
||||
for var_name in "$@"; do
|
||||
if [[ -z "$var_name" ]]; then
|
||||
echo "Usage: print_var_values <variable_name1> <variable_name2> ..."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Dereference the variable and print the result
|
||||
echo "$var_name=${!var_name:-(undefined)}"
|
||||
done
|
||||
}
|
||||
|
||||
# begin_group: Start a named section of log output, possibly with color.
|
||||
# Usage: begin_group "Group Name" [Color]
|
||||
# Group Name: A string specifying the name of the group.
|
||||
# Color (optional): ANSI color code to set text color. Default is blue (1;34).
|
||||
function begin_group() {
|
||||
# See options for colors here: https://gist.github.com/JBlond/2fea43a3049b38287e5e9cefc87b2124
|
||||
local blue="34"
|
||||
local name="${1:-}"
|
||||
local color="${2:-$blue}"
|
||||
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
echo -e "::group::\e[${color}m${name}\e[0m"
|
||||
else
|
||||
echo -e "\e[${color}m================== ${name} ======================\e[0m"
|
||||
fi
|
||||
}
|
||||
|
||||
# end_group: End a named section of log output and print status based on exit status.
|
||||
# Usage: end_group "Group Name" [Exit Status]
|
||||
# Group Name: A string specifying the name of the group.
|
||||
# Exit Status (optional): The exit status of the command run within the group. Default is 0.
|
||||
function end_group() {
|
||||
local name="${1:-}"
|
||||
local build_status="${2:-0}"
|
||||
local duration="${3:-}"
|
||||
local red="31"
|
||||
local blue="34"
|
||||
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
echo "::endgroup::"
|
||||
|
||||
if [[ "$build_status" -ne 0 ]]; then
|
||||
echo -e "::error::\e[${red}m ${name} - Failed (⬆️ click above for full log ⬆️)\e[0m"
|
||||
fi
|
||||
else
|
||||
if [[ "$build_status" -ne 0 ]]; then
|
||||
echo -e "\e[${red}m================== End ${name} - Failed${duration:+ - Duration: ${duration}s} ==================\e[0m"
|
||||
else
|
||||
echo -e "\e[${blue}m================== End ${name} - Success${duration:+ - Duration: ${duration}s} ==================\n\e[0m"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
declare -A command_durations
|
||||
|
||||
# Runs a command within a named group, handles the exit status, and prints appropriate messages based on the result.
|
||||
# Usage: run_command "Group Name" command [arguments...]
|
||||
function run_command() {
|
||||
local group_name="${1:-}"
|
||||
shift
|
||||
local command=("$@")
|
||||
local status
|
||||
|
||||
begin_group "$group_name"
|
||||
echo "Working directory: $(pwd)"
|
||||
echo "Running command: ${command[*]}"
|
||||
set +e
|
||||
local start_time
|
||||
start_time=$(date +%s)
|
||||
# If RUN_COMMAND_RETRY_PARAMS is set to "<retries> <sleep_time>", use retry.sh to run the command:
|
||||
if [[ -v RUN_COMMAND_RETRY_PARAMS && "${#RUN_COMMAND_RETRY_PARAMS[@]}" -eq 2 ]]; then
|
||||
status=0
|
||||
"$ci_dir/util/retry.sh" "${RUN_COMMAND_RETRY_PARAMS[@]}" "${command[@]}" || status=$?
|
||||
else
|
||||
status=0
|
||||
"${command[@]}" || status=$?
|
||||
fi
|
||||
local end_time
|
||||
end_time=$(date +%s)
|
||||
set -e
|
||||
local duration=$((end_time - start_time))
|
||||
end_group "$group_name" "$status" "$duration"
|
||||
command_durations["$group_name"]=$duration
|
||||
return "$status"
|
||||
}
|
||||
|
||||
function string_width() {
|
||||
local str="$1"
|
||||
echo "$str" | awk '{print length}'
|
||||
}
|
||||
|
||||
function print_time_summary() {
|
||||
local max_length=0
|
||||
local group
|
||||
|
||||
# Find the longest group name for formatting
|
||||
for group in "${!command_durations[@]}"; do
|
||||
local group_length
|
||||
group_length=$(echo "$group" | awk '{print length}')
|
||||
if [[ "$group_length" -gt "$max_length" ]]; then
|
||||
max_length=$group_length
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$max_length" -eq 0 ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo "Time Summary:"
|
||||
for group in "${!command_durations[@]}"; do
|
||||
printf "%-${max_length}s : %s seconds\n" "$group" "${command_durations[$group]}"
|
||||
done
|
||||
|
||||
# Clear the array of timing info
|
||||
declare -gA command_durations=()
|
||||
}
|
||||
287
cccl_upstream/ci/project_files_and_dependencies.yaml
Normal file
287
cccl_upstream/ci/project_files_and_dependencies.yaml
Normal file
@@ -0,0 +1,287 @@
|
||||
# Configuration for CCCL project change detection.
|
||||
# Projects are declared in this file along with their human-friendly names, path
|
||||
# matching patterns, and dependency relationships. Every path pattern is
|
||||
# evaluated as a regular expression anchored to the repository root.
|
||||
#
|
||||
# Changed files that satisfy a project's include_regexes, exclude_regexes, and
|
||||
# exclude_project_files checks will mark that project as "dirty". The current project
|
||||
# will be added to the FULL_BUILD if it has dirty files or if any of its
|
||||
# full_dependencies are dirty.
|
||||
#
|
||||
# Additionally, if any of a project's lite_dependencies are dirty, the current project
|
||||
# will be added to the LITE_BUILD.
|
||||
#
|
||||
# If any transitive dependencies are dirty (eg. a full or lite dependency has
|
||||
# dirty dependencies of its own), the current project will also be added to the LITE_BUILD.
|
||||
#
|
||||
# Projects with large dependency trees are split into "public" and "internal" sub-projects.
|
||||
# The "public" sub-project contains only the public API files, while the "internal"
|
||||
# sub-project contains tests, examples, and other non-public files. This allows changes to
|
||||
# internal files to avoid triggering rebuilds of dependent projects when the public API
|
||||
# is not changed.
|
||||
#
|
||||
# Layout:
|
||||
# project_key: mapping from project key to configuration
|
||||
# name: display name used in logs/reports
|
||||
# matrix_project: project name to use for matrix generation. If not provided,
|
||||
# the project is considered internal-only and will not be listed
|
||||
# in the FULL_BUILD or LITE_BUILD outputs.
|
||||
# lite_dependencies: Add the current project to the `lite` matrix if any of these
|
||||
# dependencies are dirty.
|
||||
# full_dependencies: Add the current project to the `full` matrix if any of these
|
||||
# dependencies are dirty.
|
||||
# include_regexes: regexes that match files for this project
|
||||
# exclude_regexes: regexes that exclude files from the included set
|
||||
# exclude_project_files: list of other project keys whose files should be
|
||||
# excluded from this project
|
||||
|
||||
projects:
|
||||
# `core` collects any dirty files that are not matched by other projects. It
|
||||
# never appears in dependency lists because the script treats it specially,
|
||||
# triggering a full rebuild of all projects when any core file is dirty.
|
||||
core:
|
||||
name: "CCCL Infrastructure"
|
||||
|
||||
libcudacxx_public:
|
||||
name: "libcu++ Public API"
|
||||
lite_dependencies: ['thrust_public', 'cub_public']
|
||||
full_dependencies: []
|
||||
include_regexes: ["libcudacxx/include/"]
|
||||
|
||||
libcudacxx_internal:
|
||||
name: "libcu++ Tests/Infra"
|
||||
matrix_project: "libcudacxx"
|
||||
lite_dependencies: [c2h]
|
||||
full_dependencies: [libcudacxx_public]
|
||||
include_regexes: ["libcudacxx/"]
|
||||
exclude_project_files: [libcudacxx_public]
|
||||
|
||||
cub_public:
|
||||
name: "CUB Public API"
|
||||
lite_dependencies: [libcudacxx_public, thrust_public]
|
||||
full_dependencies: []
|
||||
include_regexes: ["cub/cub/"]
|
||||
|
||||
cub_internal:
|
||||
name: "CUB Tests/Infra"
|
||||
matrix_project: "cub"
|
||||
lite_dependencies: [c2h, nvbench_helper]
|
||||
full_dependencies: [cub_public]
|
||||
include_regexes: ["cub/"]
|
||||
exclude_project_files: [cub_public]
|
||||
|
||||
thrust_public:
|
||||
name: "Thrust Public API"
|
||||
lite_dependencies: [libcudacxx_public, cub_public]
|
||||
full_dependencies: []
|
||||
include_regexes: ["thrust/thrust/"]
|
||||
|
||||
thrust_internal:
|
||||
name: "Thrust Tests/Infra"
|
||||
matrix_project: "thrust"
|
||||
lite_dependencies: [nvbench_helper]
|
||||
full_dependencies: [thrust_public]
|
||||
include_regexes: ["thrust/"]
|
||||
exclude_project_files: [thrust_public]
|
||||
|
||||
cudax_public:
|
||||
name: "CUDA Experimental Public API"
|
||||
lite_dependencies: [libcudacxx_public, thrust_public, cub_public]
|
||||
full_dependencies: []
|
||||
include_regexes: ["cudax/include/"]
|
||||
|
||||
cudax_internal:
|
||||
name: "CUDA Experimental Tests/Infra"
|
||||
matrix_project: "cudax"
|
||||
lite_dependencies: [c2h, nvbench_helper]
|
||||
full_dependencies: [cudax_public]
|
||||
include_regexes: ["cudax/"]
|
||||
exclude_project_files: [cudax_public]
|
||||
|
||||
cccl_c_parallel_public:
|
||||
name: "CCCL C Parallel Library Public API"
|
||||
lite_dependencies: [libcudacxx_public, cub_public, thrust_public]
|
||||
full_dependencies: []
|
||||
include_regexes:
|
||||
- "c/parallel/include/"
|
||||
- "c/parallel/src/"
|
||||
|
||||
cccl_c_parallel_internal:
|
||||
name: "CCCL C Parallel Library Tests/Infra"
|
||||
matrix_project: "cccl_c_parallel"
|
||||
lite_dependencies: [c2h]
|
||||
full_dependencies: [cccl_c_parallel_public]
|
||||
include_regexes: ["c/parallel/"]
|
||||
exclude_project_files: [cccl_c_parallel_public]
|
||||
|
||||
cccl_c_parallel_v2:
|
||||
name: "CCCL C Parallel Library v2 (HostJIT)"
|
||||
matrix_project: "cccl_c_parallel_v2"
|
||||
# v2 depends on libcudacxx, cub, and thrust headers (it JIT-compiles
|
||||
# CUB's host+device code via HostJIT). Any change to those should trigger
|
||||
# v2 to run.
|
||||
lite_dependencies: [libcudacxx_public, cub_public, thrust_public, c2h]
|
||||
full_dependencies: []
|
||||
include_regexes:
|
||||
- "c/parallel\\.v2/"
|
||||
|
||||
python_v2:
|
||||
name: "Python (cuda.compute on v2/HostJIT)"
|
||||
matrix_project: "python_v2"
|
||||
# cccl_c_parallel_v2 already pulls in libcudacxx/cub/thrust, so listing
|
||||
# it here transitively triggers python_v2 on any of those upstream
|
||||
# changes too. Direct includes catch Python-only edits.
|
||||
lite_dependencies: [cccl_c_parallel_v2]
|
||||
full_dependencies: []
|
||||
include_regexes:
|
||||
- "python/cuda_cccl/"
|
||||
- "pyproject\\.toml"
|
||||
|
||||
python_tsan:
|
||||
name: "Python (cuda.compute free-threaded ThreadSanitizer)"
|
||||
matrix_project: "python_tsan"
|
||||
# Same v1 sources as `python`, rebuilt with ThreadSanitizer. Must be listed
|
||||
# here or the per-PR python_tsan job is pruned as never-dirty and never runs.
|
||||
# cccl_c_parallel_public covers c/parallel/{src,include}; c/parallel/CMakeLists.txt
|
||||
# is listed directly because the TSan build config (the -fsanitize=thread option)
|
||||
# lives there and is owned by cccl_c_parallel_internal, which this lane otherwise
|
||||
# does not depend on -- without it a change to the TSan flags would skip this lane.
|
||||
lite_dependencies: [cccl_c_parallel_public]
|
||||
full_dependencies: []
|
||||
include_regexes:
|
||||
- "python/cuda_cccl/"
|
||||
- "pyproject\\.toml"
|
||||
- "c/parallel/CMakeLists\\.txt"
|
||||
|
||||
cccl_c_stf:
|
||||
name: "CCCL C CUDASTF Library"
|
||||
matrix_project: "cccl_c_stf"
|
||||
lite_dependencies: [libcudacxx_public, cudax_public, c2h]
|
||||
full_dependencies: []
|
||||
include_regexes: ["c/experimental/stf/"]
|
||||
exclude_regexes: []
|
||||
|
||||
python:
|
||||
name: "Python"
|
||||
matrix_project: "python"
|
||||
lite_dependencies: [cccl_c_parallel_public]
|
||||
full_dependencies: []
|
||||
include_regexes:
|
||||
- "python/"
|
||||
- "pyproject.toml"
|
||||
|
||||
packaging:
|
||||
name: "CCCL Packaging"
|
||||
matrix_project: "packaging"
|
||||
# Anything that affects CMake packages / install rules:
|
||||
lite_dependencies:
|
||||
- libcudacxx_internal
|
||||
- libcudacxx_public
|
||||
- cub_internal
|
||||
- cub_public
|
||||
- thrust_internal
|
||||
- thrust_public
|
||||
- cudax_internal
|
||||
- cudax_public
|
||||
full_dependencies: []
|
||||
include_regexes:
|
||||
- "examples/"
|
||||
- "test/cmake/"
|
||||
- "ci/test/"
|
||||
|
||||
stdpar:
|
||||
name: "stdpar"
|
||||
matrix_project: "stdpar"
|
||||
lite_dependencies: [thrust_public]
|
||||
full_dependencies: []
|
||||
include_regexes: ["test/stdpar/"]
|
||||
|
||||
c2h:
|
||||
name: "Catch2Helper"
|
||||
lite_dependencies: [libcudacxx_public, cub_public, thrust_public]
|
||||
full_dependencies: []
|
||||
include_regexes: ["c2h/"]
|
||||
|
||||
nvbench_helper:
|
||||
name: "NVBench Helper"
|
||||
matrix_project: "nvbench_helper"
|
||||
lite_dependencies: [libcudacxx_public, cub_public, thrust_public]
|
||||
full_dependencies: []
|
||||
include_regexes:
|
||||
- "nvbench_helper/"
|
||||
# CCCLBenchmarkRegistry.cmake is effectively part of nvbench_helper:
|
||||
- "benchmarks/cmake/"
|
||||
|
||||
nvrtcc:
|
||||
name: "nvrtcc"
|
||||
matrix_project: "nvrtcc"
|
||||
lite_dependencies: []
|
||||
full_dependencies: []
|
||||
include_regexes: ["nvrtcc/"]
|
||||
|
||||
# This is a dummy project, and only really serves to encode dependencies. Any time any
|
||||
# of the C/C++ projects are modified, we want to launch one (and only one) clang-tidy
|
||||
# job for the whole of CCCL.
|
||||
tidy:
|
||||
name: "clang-tidy"
|
||||
matrix_project: "tidy"
|
||||
full_dependencies:
|
||||
- libcudacxx_public
|
||||
- libcudacxx_internal
|
||||
- cub_public
|
||||
- cub_internal
|
||||
- thrust_public
|
||||
- thrust_internal
|
||||
- cudax_public
|
||||
- cudax_internal
|
||||
- cccl_c_parallel_public
|
||||
- cccl_c_parallel_internal
|
||||
- cccl_c_parallel_v2
|
||||
- cccl_c_stf
|
||||
- stdpar
|
||||
- c2h
|
||||
- nvrtcc
|
||||
include_regexes: [".clang-tidy"]
|
||||
|
||||
# Files matching any of these regexes will be ignored globally.
|
||||
ignore_regexes:
|
||||
- '.+\.md$'
|
||||
- '\.branch_notes/'
|
||||
- '\.coderabbit\.yaml'
|
||||
- '\.clang-format'
|
||||
- '\.clangd'
|
||||
# Do not add .clang-tidy to this list. If we modify .clang-tidy to either add or remove
|
||||
# a check we still want CI to run the clang-tidy job even if it results in no C/C++ code
|
||||
# changes.
|
||||
#
|
||||
# - '\.clang-tidy'
|
||||
- '\.devcontainer/img'
|
||||
- '\.git-blame-ignore-revs'
|
||||
- '\.github/actions/docs-build'
|
||||
- '\.github/CODEOWNERS'
|
||||
- '\.github/copy-pr-bot\.yaml'
|
||||
- '\.github/ISSUE_TEMPLATE/'
|
||||
- '\.github/problem-matchers/problem-matcher\.json'
|
||||
- '\.github/workflows/backport-prs\.yml'
|
||||
- '\.github/workflows/bench.*\.yml'
|
||||
- '\.github/workflows/compile-time-bench\.yml'
|
||||
- '\.github/workflows/build-docs\.yml'
|
||||
- '\.github/workflows/build-matx\.yml'
|
||||
- '\.github/workflows/build-pytorch\.yml'
|
||||
- '\.github/workflows/build-rapids\.yml'
|
||||
- '\.github/workflows/git-bisect\.yml'
|
||||
- '\.github/workflows/project_automation.*\.yml'
|
||||
- '\.github/workflows/release.*\.yml'
|
||||
- '\.github/workflows/triage_rotation\.yml'
|
||||
- '\.github/workflows/update_branch_version\.yml'
|
||||
- '\.github/workflows/verify-devcontainers\.yml'
|
||||
- '\.gitignore'
|
||||
- "benchmarks/scripts/" # Python+bash scripts for running benchmarks, not used in tests.
|
||||
- 'cccl-version.json'
|
||||
- 'ci/bench/'
|
||||
- 'ci/bench.+yaml'
|
||||
- 'ci/matx/'
|
||||
- 'ci/pytorch/'
|
||||
- 'ci/rapids/'
|
||||
- 'docs/'
|
||||
- 'LICENSE'
|
||||
77
cccl_upstream/ci/pyenv_helper.sh
Normal file
77
cccl_upstream/ci/pyenv_helper.sh
Normal file
@@ -0,0 +1,77 @@
|
||||
setup_python_env() {
|
||||
local py_version=$1
|
||||
|
||||
# Source pretty_printing.sh for begin_group/end_group helpers
|
||||
local script_dir
|
||||
script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
# shellcheck source=ci/pretty_printing.sh
|
||||
source "${script_dir}/pretty_printing.sh"
|
||||
|
||||
begin_group "🐍 Setting up Python ${py_version} (uv)"
|
||||
|
||||
# Install uv if not present
|
||||
if ! command -v uv &> /dev/null; then
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
fi
|
||||
|
||||
# Create a venv with the requested Python version.
|
||||
# uv downloads a pre-built CPython binary automatically — no compilation needed.
|
||||
uv venv --seed --python "${py_version}" "${HOME}/.cccl-venv"
|
||||
|
||||
# Windows venvs use Scripts/, Linux/macOS use bin/
|
||||
if [[ -f "${HOME}/.cccl-venv/Scripts/activate" ]]; then
|
||||
#shellcheck disable=SC1091
|
||||
source "${HOME}/.cccl-venv/Scripts/activate"
|
||||
else
|
||||
#shellcheck disable=SC1091
|
||||
source "${HOME}/.cccl-venv/bin/activate"
|
||||
fi
|
||||
|
||||
end_group "🐍 Setting up Python ${py_version} (uv)"
|
||||
}
|
||||
|
||||
# Pin the cuda-toolkit wheels to the container's CTK major.minor (read from nvcc)
|
||||
# via PIP_CONSTRAINT when the mode ($1) is "pinned" (the default; empty also means
|
||||
# pinned). "latest" and "sysctk" leave it unpinned; any other value is a hard
|
||||
# error. This is the lane's mode gate -- it runs before ctk_extra_flavor in every
|
||||
# script, so ctk_extra_flavor can assume the mode is already valid. Also sets and
|
||||
# exports cuda_version / cuda_major_version; the caller uses cuda_major_version in
|
||||
# the pip-extra name (e.g. minimal-cu${cuda_major_version}).
|
||||
pin_cuda_toolkit() {
|
||||
cuda_version=$(nvcc --version | grep release | awk '{print $6}' | tr -d ',' | cut -d '.' -f 1-2 | cut -d 'V' -f 2)
|
||||
cuda_major_version=$(echo "$cuda_version" | cut -d '.' -f 1)
|
||||
export cuda_version cuda_major_version
|
||||
|
||||
local mode="${1:-pinned}"
|
||||
case "${mode,,}" in
|
||||
pinned)
|
||||
export PIP_CONSTRAINT="${TMPDIR:-/tmp}/ctk-constraint.txt"
|
||||
echo "cuda-toolkit==${cuda_version}.*" > "${PIP_CONSTRAINT}"
|
||||
;;
|
||||
latest | sysctk)
|
||||
# No pin. Clear any inherited constraint so it cannot affect the
|
||||
# resolve (latest tests the newest minor; sysctk installs no
|
||||
# cuda-toolkit wheel at all).
|
||||
unset PIP_CONSTRAINT
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: invalid ctk mode '${mode}' (expected pinned|latest|sysctk)" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Echoes the pip-extra toolkit "flavor" for the mode ($1): "sysctk" when the mode
|
||||
# is sysctk (rely on the system-provided CUDA toolkit) or "cu" otherwise
|
||||
# (pip-installed toolkit). The mode is validated by pin_cuda_toolkit, which every
|
||||
# lane calls first. Combine with the CUDA major, e.g.
|
||||
# "minimal-$(ctk_extra_flavor "${ctk_mode}")${cuda_major_version}" -> minimal-sysctk12.
|
||||
ctk_extra_flavor() {
|
||||
local mode="${1:-}"
|
||||
if [[ "${mode,,}" == "sysctk" ]]; then
|
||||
echo "sysctk"
|
||||
else
|
||||
echo "cu"
|
||||
fi
|
||||
}
|
||||
130
cccl_upstream/ci/pytorch/build_pytorch.sh
Executable file
130
cccl_upstream/ci/pytorch/build_pytorch.sh
Executable file
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
readonly pytorch_repo=https://github.com/pytorch/pytorch.git
|
||||
readonly pytorch_branch=main
|
||||
|
||||
# Ensure the script is being executed in the root cccl directory:
|
||||
cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/../..";
|
||||
readonly cccl_repo="${PWD}"
|
||||
|
||||
log_vars() {
|
||||
for var in "$@"; do
|
||||
echo "${var}=${!var}"
|
||||
done
|
||||
}
|
||||
|
||||
# Define CCCL_TAG to override the default CCCL SHA. Otherwise the current HEAD of the local checkout is used.
|
||||
echo "CCCL_TAG (override): ${CCCL_TAG-}";
|
||||
if test -n "${CCCL_TAG-}"; then
|
||||
# If CCCL_TAG is defined, fetch it to the local checkout
|
||||
git -C "${cccl_repo}" fetch origin "${CCCL_TAG}";
|
||||
cccl_sha="$(git -C "${cccl_repo}" rev-parse FETCH_HEAD)";
|
||||
else
|
||||
cccl_sha="$(git -C "${cccl_repo}" rev-parse HEAD)";
|
||||
fi
|
||||
|
||||
readonly workdir="${cccl_repo}/build/${CCCL_BUILD_INFIX:-}/pytorch"
|
||||
|
||||
log_vars \
|
||||
pytorch_repo pytorch_branch \
|
||||
cccl_repo cccl_sha \
|
||||
workdir
|
||||
|
||||
mkdir -p "${workdir}"
|
||||
cd "${workdir}"
|
||||
echo "Working in ${workdir}"
|
||||
|
||||
echo "::group::Cloning CCCL..."
|
||||
rm -rf cccl
|
||||
git clone "${cccl_repo}"
|
||||
git -C cccl checkout "${cccl_sha}"
|
||||
echo "CCCL HEAD:"
|
||||
git -C cccl log -1 --format=short
|
||||
echo "::endgroup::"
|
||||
|
||||
# Setup a CUDA environment with the requested CCCL.
|
||||
# Use a local directory to avoid modifying the actual CUDA install:
|
||||
echo "::group::Setting up clone of CUDA environment with custom CCCL..."
|
||||
(
|
||||
set -x
|
||||
rm -rf ./cuda
|
||||
cp -Hr /usr/local/cuda ./cuda
|
||||
rm -rf ./cuda/include/cccl/*
|
||||
cccl/ci/install_cccl.sh ./cccl-install > /dev/null
|
||||
cp -r ./cccl-install/include/* ./cuda/include/cccl
|
||||
)
|
||||
export PATH="$PWD/cuda/bin:$PATH"
|
||||
export CUDA_HOME="$PWD/cuda"
|
||||
export CUDA_PATH="$PWD/cuda"
|
||||
command -v nvcc
|
||||
nvcc --version
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::Cloning PyTorch..."
|
||||
rm -rf pytorch
|
||||
git clone "${pytorch_repo}" -b "${pytorch_branch}" --recursive --depth 1
|
||||
echo "PyTorch HEAD:"
|
||||
git -C pytorch log -1 --format=short
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::Installing PyTorch build dependencies..."
|
||||
pytorch_root="$PWD/pytorch"
|
||||
export PYTHONPATH="${pytorch_root}:${pytorch_root}/tools:${PYTHONPATH:-}"
|
||||
pip install -r "${pytorch_root}/requirements-build.txt"
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::Configuring PyTorch..."
|
||||
rm -rf build
|
||||
mkdir build
|
||||
declare -a cmake_args=(
|
||||
"-DUSE_NCCL=OFF"
|
||||
# Need to define this explicitly, torch's FindCUDA logic adds ancient arches if left undefined:
|
||||
"-DTORCH_CUDA_ARCH_LIST=7.5;8.0;9.0;10.0;12.0"
|
||||
)
|
||||
SCCACHE_NO_DIST_COMPILE=1 cmake -S ./pytorch -B ./build -G Ninja "${cmake_args[@]}"
|
||||
echo "::endgroup::"
|
||||
|
||||
# Verify that the configured build is using the custom CUDA dir for CTK and nvcc:
|
||||
if ! grep -q "CUDA_TOOLKIT_ROOT_DIR:PATH=$PWD/cuda" ./build/CMakeCache.txt; then
|
||||
echo "Error: CUDA_TOOLKIT_ROOT_DIR does not point to the custom CUDA";
|
||||
exit 1;
|
||||
fi
|
||||
if ! grep -q "CUDA_NVCC_EXECUTABLE:FILEPATH=$PWD/cuda/bin/nvcc" ./build/CMakeCache.txt; then
|
||||
echo "Error: CUDA_NVCC_EXECUTABLE does not point to the custom CUDA";
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
# This builds a bunch of unnecessary targets. Leaving here to use as a fallback if the
|
||||
# ninja target extraction below starts failing:
|
||||
# echo "::group::Building torch_cuda target..."
|
||||
# cmake --build ./build/ --target torch_cuda
|
||||
# echo "::endgroup::"
|
||||
|
||||
# This cuts the number of built targets roughly in half:
|
||||
echo "::group::Extracting cuda targets from build.ninja..."
|
||||
# Query ninja for all object files built from CUDA source files
|
||||
# that are part of the torch_cuda library:
|
||||
ninja -C ./build -t query lib/libtorch_cuda.so |
|
||||
grep -E "torch_cuda\\.dir/.*\\.cu\\.o$" |
|
||||
sort | uniq | tee build/cuda_targets.txt
|
||||
# At the time this script was written, there were 311 cuda targets.
|
||||
# Check that there are at least 100 detected targets, otherwise fail.
|
||||
num_targets=$(wc -l < build/cuda_targets.txt)
|
||||
if test "$num_targets" -lt 100; then
|
||||
echo "Error: extracted cuda targets count is less than 100! ($num_targets)";
|
||||
echo "This likely indicates a failure to extract the targets from ninja.";
|
||||
exit 1;
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::Building $num_targets pytorch CUDA targets with custom CCCL..."
|
||||
torch_cuda_targets="$(xargs -a build/cuda_targets.txt)"
|
||||
declare -a torch_cuda_targets="($torch_cuda_targets)"
|
||||
|
||||
time ninja -C ./build "${torch_cuda_targets[@]}" "-j${PARALLEL_LEVEL:-}"
|
||||
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "PyTorch CUDA targets built successfully with custom CCCL."
|
||||
124
cccl_upstream/ci/rapids/cuda13.3-conda/devcontainer.json
Normal file
124
cccl_upstream/ci/rapids/cuda13.3-conda/devcontainer.json
Normal file
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"image": "rapidsai/devcontainers:26.10-cpp-mambaforge",
|
||||
"runArgs": [
|
||||
"--init",
|
||||
"--rm",
|
||||
"--name",
|
||||
"${localEnv:USER:anon}-${localWorkspaceFolderBasename}-rapids-26.10-cuda13.3-conda",
|
||||
"--ulimit",
|
||||
"nofile=500000"
|
||||
],
|
||||
"hostRequirements": {
|
||||
"gpu": "optional"
|
||||
},
|
||||
"containerEnv": {
|
||||
"AWS_ROLE_ARN": "arn:aws:iam::279114543810:role/nv-gha-token-sccache-devs",
|
||||
"CI": "${localEnv:CI}",
|
||||
"CUDA_VERSION": "13.3",
|
||||
"CUDAARCHS": "75-real",
|
||||
"DEFAULT_CONDA_ENV": "rapids",
|
||||
"DEVCONTAINER_UTILS_ENABLE_SCCACHE_DIST": "true",
|
||||
"HISTFILE": "/home/coder/.cache/._bash_history",
|
||||
"INFER_NUM_DEVICE_ARCHITECTURES": "1",
|
||||
"LIBCUDF_KERNEL_CACHE_PATH": "/home/coder/cudf/cpp/build/latest/jitify_cache",
|
||||
"MAX_DEVICE_OBJ_TO_COMPILE_IN_PARALLEL": "20",
|
||||
"PYTHON_PACKAGE_MANAGER": "conda",
|
||||
"PYTHONDONTWRITEBYTECODE": "1",
|
||||
"PYTHONSAFEPATH": "1",
|
||||
"PYTHONUNBUFFERED": "1",
|
||||
"RAPIDS_cmake_GIT_REPO": "${localEnv:RAPIDS_cmake_GIT_REPO}",
|
||||
"RAPIDS_cudf_GIT_REPO": "${localEnv:RAPIDS_cudf_GIT_REPO}",
|
||||
"RAPIDS_cugraph_GIT_REPO": "${localEnv:RAPIDS_cugraph_GIT_REPO}",
|
||||
"RAPIDS_cugraph_gnn_GIT_REPO": "${localEnv:RAPIDS_cugraph_gnn_GIT_REPO}",
|
||||
"RAPIDS_cuml_GIT_REPO": "${localEnv:RAPIDS_cuml_GIT_REPO}",
|
||||
"RAPIDS_cuopt_GIT_REPO": "${localEnv:RAPIDS_cuopt_GIT_REPO}",
|
||||
"RAPIDS_cuvs_GIT_REPO": "${localEnv:RAPIDS_cuvs_GIT_REPO}",
|
||||
"RAPIDS_kvikio_GIT_REPO": "${localEnv:RAPIDS_kvikio_GIT_REPO}",
|
||||
"RAPIDS_LIBS": "${localEnv:RAPIDS_LIBS}",
|
||||
"RAPIDS_nvforest_GIT_REPO": "${localEnv:RAPIDS_nvforest_GIT_REPO}",
|
||||
"RAPIDS_raft_GIT_REPO": "${localEnv:RAPIDS_raft_GIT_REPO}",
|
||||
"RAPIDS_rmm_GIT_REPO": "${localEnv:RAPIDS_rmm_GIT_REPO}",
|
||||
"RAPIDS_ucxx_GIT_REPO": "${localEnv:RAPIDS_ucxx_GIT_REPO}",
|
||||
"SCCACHE_BUCKET": "rapids-sccache-devs",
|
||||
"SCCACHE_DIST_FALLBACK_TO_LOCAL_COMPILE": "${localEnv:SCCACHE_DIST_FALLBACK_TO_LOCAL_COMPILE:true}",
|
||||
"SCCACHE_DIST_MAX_RETRIES": "${localEnv:SCCACHE_DIST_MAX_RETRIES:4}",
|
||||
"SCCACHE_DIST_REQUEST_TIMEOUT": "${localEnv:SCCACHE_DIST_REQUEST_TIMEOUT:7140}",
|
||||
"SCCACHE_IDLE_TIMEOUT": "${localEnv:SCCACHE_IDLE_TIMEOUT:0}",
|
||||
"SCCACHE_REGION": "us-east-2",
|
||||
"SCCACHE_S3_USE_PREPROCESSOR_CACHE_MODE": "true",
|
||||
"SCCACHE_SERVER_LOG": "${localEnv:SCCACHE_SERVER_LOG:sccache=debug}"
|
||||
},
|
||||
"initializeCommand": [
|
||||
"/bin/bash",
|
||||
"-c",
|
||||
"mkdir -m 0755 -p ${localWorkspaceFolder}/.{aws,cache,config,local/state} ${localWorkspaceFolder}/ci/rapids/.{conda,log/devcontainer-utils} ${localWorkspaceFolder}/ci/rapids/.repos/{rmm,kvikio,ucxx,cudf,rapidsmpf,raft,cuvs,nvforest,cuml,cugraph,cugraph-gnn,cuopt}"
|
||||
],
|
||||
"postCreateCommand": [
|
||||
"/bin/bash",
|
||||
"-c",
|
||||
"if [ ${CI:-false} = 'false' ]; then . /home/coder/cccl/ci/rapids/post-create-command.sh; fi; if test -z \"${DISABLE_SCCACHE:+x}\"; then echo \"export SCCACHE_DIST_URL='https://$(uname -m | sed -e 's/x86_/amd/' -e 's/aarch/arm/').linux.sccache.rapids.nvidia.com'\" >> /home/coder/.bashrc; fi"
|
||||
],
|
||||
"postAttachCommand": [
|
||||
"/bin/bash",
|
||||
"-c",
|
||||
"if [ ${CODESPACES:-false} = 'true' ]; then . devcontainer-utils-post-attach-command; fi"
|
||||
],
|
||||
"workspaceFolder": "/home/coder/cccl",
|
||||
"workspaceMount": "source=${localWorkspaceFolder},target=/home/coder/cccl,type=bind",
|
||||
"mounts": [
|
||||
"source=/etc/timezone,target=/etc/timezone,type=bind",
|
||||
"source=/etc/localtime,target=/etc/localtime,type=bind",
|
||||
"source=${localWorkspaceFolder}/.aws,target=/home/coder/.aws,type=bind",
|
||||
"source=${localWorkspaceFolder}/.cache,target=/home/coder/.cache,type=bind",
|
||||
"source=${localWorkspaceFolder}/.config,target=/home/coder/.config,type=bind",
|
||||
"source=${localWorkspaceFolder}/ci/rapids/.repos/rmm,target=/home/coder/rmm,type=bind",
|
||||
"source=${localWorkspaceFolder}/ci/rapids/.repos/kvikio,target=/home/coder/kvikio,type=bind",
|
||||
"source=${localWorkspaceFolder}/ci/rapids/.repos/ucxx,target=/home/coder/ucxx,type=bind",
|
||||
"source=${localWorkspaceFolder}/ci/rapids/.repos/cudf,target=/home/coder/cudf,type=bind",
|
||||
"source=${localWorkspaceFolder}/ci/rapids/.repos/rapidsmpf,target=/home/coder/rapidsmpf,type=bind",
|
||||
"source=${localWorkspaceFolder}/ci/rapids/.repos/raft,target=/home/coder/raft,type=bind",
|
||||
"source=${localWorkspaceFolder}/ci/rapids/.repos/cuvs,target=/home/coder/cuvs,type=bind",
|
||||
"source=${localWorkspaceFolder}/ci/rapids/.repos/nvforest,target=/home/coder/nvforest,type=bind",
|
||||
"source=${localWorkspaceFolder}/ci/rapids/.repos/cuml,target=/home/coder/cuml,type=bind",
|
||||
"source=${localWorkspaceFolder}/ci/rapids/.repos/cugraph,target=/home/coder/cugraph,type=bind",
|
||||
"source=${localWorkspaceFolder}/ci/rapids/.repos/cugraph-gnn,target=/home/coder/cugraph-gnn,type=bind",
|
||||
"source=${localWorkspaceFolder}/ci/rapids/.repos/cuopt,target=/home/coder/cuopt,type=bind",
|
||||
"source=${localWorkspaceFolder}/ci/rapids/.conda,target=/home/coder/.conda,type=bind",
|
||||
"source=${localWorkspaceFolder}/ci/rapids/.log/devcontainer-utils,target=/var/log/devcontainer-utils,type=bind"
|
||||
],
|
||||
"features": {
|
||||
"ghcr.io/rapidsai/devcontainers/features/rapids-build-utils:26.10": {}
|
||||
},
|
||||
"overrideFeatureInstallOrder": [
|
||||
"ghcr.io/rapidsai/devcontainers/features/rapids-build-utils"
|
||||
],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"augustocdias.tasks-shell-input",
|
||||
"ms-python.flake8",
|
||||
"nvidia.nsight-vscode-edition"
|
||||
],
|
||||
"files.watcherExclude": {
|
||||
"**/build/**": true,
|
||||
"**/_skbuild/**": true,
|
||||
"**/target/**": true,
|
||||
"/home/coder/.aws/**/*": true,
|
||||
"/home/coder/.cache/**/*": true,
|
||||
"/home/coder/.conda/**/*": true,
|
||||
"/home/coder/.local/share/**/*": true,
|
||||
"/home/coder/.vscode-server/**/*": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/build/**": true,
|
||||
"**/_skbuild/**": true,
|
||||
"**/*.code-search": true,
|
||||
"/home/coder/.aws/**/*": true,
|
||||
"/home/coder/.cache/**/*": true,
|
||||
"/home/coder/.conda/**/*": true,
|
||||
"/home/coder/.local/share/**/*": true,
|
||||
"/home/coder/.vscode-server/**/*": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
146
cccl_upstream/ci/rapids/post-create-command.sh
Executable file
146
cccl_upstream/ci/rapids/post-create-command.sh
Executable file
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
PROJECT_MANIFEST_YML="${PROJECT_MANIFEST_YML:-"/opt/rapids-build-utils/manifest.yaml"}"
|
||||
|
||||
_restore_original_manifest() {
|
||||
if ! test -f "/tmp/manifest.yaml.orig"; then
|
||||
cp "${PROJECT_MANIFEST_YML}" /tmp/manifest.yaml.orig;
|
||||
fi
|
||||
sudo cp /tmp/manifest.yaml.orig "${PROJECT_MANIFEST_YML}";
|
||||
}
|
||||
|
||||
_apply_manifest_modifications() {
|
||||
# Restore the original manifest.yaml
|
||||
_restore_original_manifest;
|
||||
# Remove unnecessary libs from manifest.yaml
|
||||
_prune_libs_from_manifest;
|
||||
# Update manifest.yaml repo git info
|
||||
_update_repo_git_info;
|
||||
# Create rapids-cmake override JSON file and update default CMake arguments in manifest.yaml
|
||||
_create_rapids_cmake_override_json;
|
||||
# Print the entire manifest.yaml after modifications
|
||||
cat "${PROJECT_MANIFEST_YML}";
|
||||
# Regenerate the RAPIDS build scripts from the new manifest.yaml
|
||||
rapids-generate-scripts;
|
||||
}
|
||||
|
||||
_prune_libs_from_manifest() {
|
||||
local -;
|
||||
set -euo pipefail;
|
||||
if test -n "${RAPIDS_LIBS-}"; then
|
||||
local -a filters="(${RAPIDS_LIBS})";
|
||||
# prefix each element
|
||||
filters=("${filters[@]/#/'"'}");
|
||||
# suffix each element
|
||||
filters=("${filters[@]/%/'",'}");
|
||||
# Remove trailing comma
|
||||
local -r filters_str="$(cut -d',' -f1-${#filters[@]} <<< "${filters[*]}")";
|
||||
sudo yq -i ".repos |= filter(.cpp[].name | contains(${filters_str}))" "${PROJECT_MANIFEST_YML}";
|
||||
fi
|
||||
}
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
_update_repo_git_info() {
|
||||
local -;
|
||||
set -euo pipefail;
|
||||
yq '.repos[].name' "${PROJECT_MANIFEST_YML}" \
|
||||
| xargs -r -n1 bash -c '
|
||||
var="RAPIDS_${1//-/_}_GIT_REPO";
|
||||
if test -v "${var}" && test -n "${!var}"; then
|
||||
sudo yq -i "(.repos[] | select(.name == \"$1\") | .git) *= ${!var}" "${0}";
|
||||
fi' "${PROJECT_MANIFEST_YML}";
|
||||
}
|
||||
|
||||
_create_rapids_cmake_override_json() {
|
||||
local -;
|
||||
set -euo pipefail;
|
||||
local rapids_cmake_tag=;
|
||||
local rapids_cmake_upstream=;
|
||||
if test -n "${RAPIDS_cmake_GIT_REPO-}"; then
|
||||
rapids_cmake_tag="$(jq -r '.tag' <<< "${RAPIDS_cmake_GIT_REPO}")";
|
||||
rapids_cmake_upstream="$(jq -r '.upstream' <<< "${RAPIDS_cmake_GIT_REPO}")";
|
||||
else
|
||||
rapids_cmake_tag="$(yq '.x-git-defaults.tag' /opt/rapids-build-utils/manifest.yaml)";
|
||||
rapids_cmake_upstream="$(yq '.x-git-defaults.upstream' /opt/rapids-build-utils/manifest.yaml)";
|
||||
fi
|
||||
|
||||
# Define CCCL_TAG to override the default CCCL SHA. Otherwise the current HEAD of the local checkout is used.
|
||||
if test -n "${CCCL_TAG-}"; then
|
||||
# If CCCL_TAG is defined, fetch it to the local checkout
|
||||
git -C "${HOME}/cccl" fetch origin "${CCCL_TAG}";
|
||||
cccl_sha="$(git -C "${HOME}/cccl" rev-parse FETCH_HEAD)";
|
||||
else
|
||||
cccl_sha="$(git -C "${HOME}/cccl" rev-parse HEAD)";
|
||||
fi
|
||||
echo "CCCL_VERSION: ${CCCL_VERSION-}";
|
||||
echo "CCCL_TAG: ${CCCL_TAG-}";
|
||||
echo "cccl_sha: ${cccl_sha}";
|
||||
|
||||
echo
|
||||
echo "Replacing CCCL repo information in rapids-cmake versions.json:";
|
||||
curl -fsSL -o- "https://raw.githubusercontent.com/${rapids_cmake_upstream}/rapids-cmake/${rapids_cmake_tag}/rapids-cmake/cpm/versions.json" \
|
||||
| jq -r ".packages.CCCL *= {\"git_url\": \"${HOME}/cccl\", \"git_tag\": \"${cccl_sha}\", \"always_download\": true}" \
|
||||
| jq -r "del(.packages.CCCL.url) | del(.packages.CCCL.url_hash)" \
|
||||
> ~/rapids-cmake-override-versions-cccl-repo.json;
|
||||
|
||||
if test -n "${CCCL_VERSION-}"; then
|
||||
echo "Patching CCCL_VERSION in rapids-cmake versions.json:";
|
||||
jq -r ".packages.CCCL.version = \"${CCCL_VERSION}\"" ~/rapids-cmake-override-versions-cccl-repo.json \
|
||||
> ~/rapids-cmake-override-versions.json;
|
||||
else
|
||||
echo "Using the default CCCL version in rapids-cmake versions.json:";
|
||||
mv ~/rapids-cmake-override-versions-cccl-repo.json ~/rapids-cmake-override-versions.json;
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Final rapids-cmake-override-versions.json:";
|
||||
cat ~/rapids-cmake-override-versions.json;
|
||||
|
||||
# Define default CMake args for each repo
|
||||
local -a cmake_args=(BUILD_TESTS BUILD_BENCHMARKS BUILD_PRIMS_BENCH BUILD_CUGRAPH_MG_TESTS);
|
||||
# Enable tests
|
||||
cmake_args=("${cmake_args[@]/#/"-D"}");
|
||||
cmake_args=("${cmake_args[@]/%/"=${RAPIDS_ENABLE_TESTS:-ON}"}");
|
||||
|
||||
# Always build RAFT shared lib
|
||||
cmake_args+=("-DBUILD_SHARED_LIBS=ON");
|
||||
cmake_args+=("-DRAFT_COMPILE_LIBRARY=ON");
|
||||
|
||||
# Tell rapids-cmake to use custom CCCL and cuCollections forks
|
||||
cmake_args+=("-Drapids-cmake-branch=${rapids_cmake_tag}");
|
||||
cmake_args+=("-Drapids-cmake-repo=${rapids_cmake_upstream}/rapids-cmake");
|
||||
cmake_args+=("-DRAPIDS_CMAKE_CPM_DEFAULT_VERSION_FILE=${HOME}/rapids-cmake-override-versions.json");
|
||||
|
||||
# Inject the CMake args into manifest.yaml
|
||||
sudo yq -i "(.repos[] | .cpp[] | .args.cmake) += \" ${cmake_args[*]}\"" "${PROJECT_MANIFEST_YML}";
|
||||
sudo yq -i "(.repos[] | .python[] | .args.cmake) += \" ${cmake_args[*]}\"" "${PROJECT_MANIFEST_YML}";
|
||||
}
|
||||
|
||||
_run_post_create_command() {
|
||||
local -;
|
||||
set -e;
|
||||
|
||||
# Install `rapids-build-utils` feature if it's not installed (i.e. if running locally via `.devcontainer/launch.sh -d`)
|
||||
if ! test -f "${PROJECT_MANIFEST_YML}"; then
|
||||
git clone --depth 1 --filter=blob:none --sparse https://github.com/rapidsai/devcontainers.git /tmp/rapidsai-devcontainers;
|
||||
git -C /tmp/rapidsai-devcontainers sparse-checkout set features/src/rapids-build-utils;
|
||||
(
|
||||
cd /tmp/rapidsai-devcontainers/features/src/rapids-build-utils;
|
||||
sudo bash ./install.sh;
|
||||
)
|
||||
rm -rf /tmp/rapidsai-devcontainers;
|
||||
fi
|
||||
|
||||
# Modify manifest.yaml based on envvars
|
||||
_apply_manifest_modifications;
|
||||
|
||||
# Clone all the repos
|
||||
gh config set git_protocol https;
|
||||
gh config set git_protocol https --host github.com;
|
||||
|
||||
clone-all -j "$(nproc --all)" -v -q --clone-upstream --single-branch --shallow-submodules --no-update-env;
|
||||
}
|
||||
|
||||
if [[ "$(basename "${BASH_SOURCE[${#BASH_SOURCE[@]}-1]}")" = post-create-command.sh ]]; then
|
||||
_run_post_create_command;
|
||||
fi
|
||||
22
cccl_upstream/ci/rapids/rapids-entrypoint.sh
Executable file
22
cccl_upstream/ci/rapids/rapids-entrypoint.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
|
||||
set -e;
|
||||
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
echo "::group::Cloning RAPIDS..."
|
||||
fi
|
||||
|
||||
ci/rapids/post-create-command.sh;
|
||||
rapids-post-start-command -f;
|
||||
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
echo "::endgroup::"
|
||||
fi
|
||||
|
||||
if test $# -gt 0; then
|
||||
exec "$@";
|
||||
else
|
||||
exec /bin/bash -li;
|
||||
fi
|
||||
20
cccl_upstream/ci/run_cpu_bisect.sh
Executable file
20
cccl_upstream/ci/run_cpu_bisect.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# CI wrapper for the `bisect` project build job.
|
||||
# Forwards all arguments to ci/util/git_bisect.sh to run a bisection
|
||||
# using the provided configuration/build/test options.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
repo_dir=$(cd "${ci_dir}/.." && pwd)
|
||||
|
||||
user_args=("$@")
|
||||
set --
|
||||
source "${ci_dir}/build_common.sh"
|
||||
set -- "${user_args[@]}"
|
||||
|
||||
cd "${repo_dir}"
|
||||
cmd=("${ci_dir}/util/git_bisect.sh" "$@")
|
||||
printf '\033[34m%s\033[0m\n' "${cmd[*]}"
|
||||
"${cmd[@]}"
|
||||
20
cccl_upstream/ci/run_cpu_target.sh
Executable file
20
cccl_upstream/ci/run_cpu_target.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# CI wrapper for the `target` project build job.
|
||||
# Forwards all arguments to ci/util/build_and_test_targets.sh to configure
|
||||
# and build selected targets on a CPU runner.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
repo_dir=$(cd "${ci_dir}/.." && pwd)
|
||||
|
||||
user_args=("$@")
|
||||
set --
|
||||
source "${ci_dir}/build_common.sh"
|
||||
set -- "${user_args[@]}"
|
||||
|
||||
cd "${repo_dir}"
|
||||
cmd=("${ci_dir}/util/build_and_test_targets.sh" "$@")
|
||||
printf '\033[34m%s\033[0m\n' "${cmd[*]}"
|
||||
"${cmd[@]}"
|
||||
20
cccl_upstream/ci/run_gpu_bisect.sh
Executable file
20
cccl_upstream/ci/run_gpu_bisect.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# CI wrapper for the `bisect` project test job.
|
||||
# Invokes ci/util/git_bisect.sh with the provided arguments to
|
||||
# run a bisection on a GPU runner.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
repo_dir=$(cd "${ci_dir}/.." && pwd)
|
||||
|
||||
user_args=("$@")
|
||||
set --
|
||||
source "${ci_dir}/build_common.sh"
|
||||
set -- "${user_args[@]}"
|
||||
|
||||
cd "${repo_dir}"
|
||||
cmd=("${ci_dir}/util/git_bisect.sh" "$@")
|
||||
printf '\033[34m%s\033[0m\n' "${cmd[*]}"
|
||||
"${cmd[@]}"
|
||||
20
cccl_upstream/ci/run_gpu_target.sh
Executable file
20
cccl_upstream/ci/run_gpu_target.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# CI wrapper for the `target` project test job.
|
||||
# Invokes ci/util/build_and_test_targets.sh with the provided arguments to
|
||||
# build and test selected targets on a GPU runner.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
repo_dir=$(cd "${ci_dir}/.." && pwd)
|
||||
|
||||
user_args=("$@")
|
||||
set --
|
||||
source "${ci_dir}/build_common.sh"
|
||||
set -- "${user_args[@]}"
|
||||
|
||||
cd "${repo_dir}"
|
||||
cmd=("${ci_dir}/util/build_and_test_targets.sh" "$@")
|
||||
printf '\033[34m%s\033[0m\n' "${cmd[*]}"
|
||||
"${cmd[@]}"
|
||||
1
cccl_upstream/ci/test/CMakeLists.txt
Normal file
1
cccl_upstream/ci/test/CMakeLists.txt
Normal file
@@ -0,0 +1 @@
|
||||
add_subdirectory("inspect_changes")
|
||||
22
cccl_upstream/ci/test/inspect_changes/CMakeLists.txt
Normal file
22
cccl_upstream/ci/test/inspect_changes/CMakeLists.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
find_package(Python3 REQUIRED COMPONENTS Interpreter)
|
||||
|
||||
file(GLOB output_files "${CMAKE_CURRENT_SOURCE_DIR}/*.output")
|
||||
foreach (output_file IN LISTS output_files)
|
||||
get_filename_component(test_name "${output_file}" NAME_WE)
|
||||
set(dirty_file "${CMAKE_CURRENT_SOURCE_DIR}/${test_name}.dirty_files")
|
||||
if (NOT EXISTS "${dirty_file}")
|
||||
message(FATAL_ERROR "Missing dirty file for ${test_name}")
|
||||
endif()
|
||||
|
||||
add_test(
|
||||
NAME cccl.ci.test.inspect_changes.${test_name}
|
||||
# gersemi: off
|
||||
COMMAND
|
||||
"${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/run_inspect_changes_test.py"
|
||||
"--python" "${Python3_EXECUTABLE}"
|
||||
"--script" "${PROJECT_SOURCE_DIR}/ci/inspect_changes.py"
|
||||
"--dirty" "${dirty_file}"
|
||||
"--expected" "${output_file}"
|
||||
# gersemi: on
|
||||
)
|
||||
endforeach()
|
||||
@@ -0,0 +1 @@
|
||||
c2h/catch2_runner.cu
|
||||
@@ -0,0 +1,2 @@
|
||||
FULL_BUILD=tidy
|
||||
LITE_BUILD=libcudacxx cub cudax cccl_c_parallel cccl_c_parallel_v2 python_v2 cccl_c_stf packaging
|
||||
@@ -0,0 +1 @@
|
||||
CMakePresets.json
|
||||
2
cccl_upstream/ci/test/inspect_changes/core_dirty.output
Normal file
2
cccl_upstream/ci/test/inspect_changes/core_dirty.output
Normal file
@@ -0,0 +1,2 @@
|
||||
FULL_BUILD=libcudacxx cub thrust cudax cccl_c_parallel cccl_c_parallel_v2 python_v2 python_tsan cccl_c_stf python packaging stdpar nvbench_helper nvrtcc tidy
|
||||
LITE_BUILD=
|
||||
@@ -0,0 +1 @@
|
||||
docs/index.rst
|
||||
@@ -0,0 +1,2 @@
|
||||
FULL_BUILD=
|
||||
LITE_BUILD=
|
||||
@@ -0,0 +1,2 @@
|
||||
libcudacxx/CMakeLists.txt
|
||||
libcudacxx/include/cuda/__device/device_ref.h
|
||||
@@ -0,0 +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 packaging stdpar nvbench_helper
|
||||
@@ -0,0 +1 @@
|
||||
libcudacxx/CMakeLists.txt
|
||||
@@ -0,0 +1,2 @@
|
||||
FULL_BUILD=libcudacxx tidy
|
||||
LITE_BUILD=packaging
|
||||
@@ -0,0 +1 @@
|
||||
libcudacxx/include/cuda/__device/device_ref.h
|
||||
@@ -0,0 +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 packaging stdpar nvbench_helper
|
||||
@@ -0,0 +1,3 @@
|
||||
libcudacxx/include/cuda/__device/device_ref.h
|
||||
thrust/thrust/version.h
|
||||
README.md
|
||||
@@ -0,0 +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 packaging stdpar nvbench_helper
|
||||
@@ -0,0 +1,2 @@
|
||||
python/cuda_cccl/pyproject.toml
|
||||
examples/basic/CMakeLists.txt
|
||||
@@ -0,0 +1,2 @@
|
||||
FULL_BUILD=python_v2 python_tsan python packaging
|
||||
LITE_BUILD=
|
||||
2
cccl_upstream/ci/test/inspect_changes/no_changes.output
Normal file
2
cccl_upstream/ci/test/inspect_changes/no_changes.output
Normal file
@@ -0,0 +1,2 @@
|
||||
FULL_BUILD=
|
||||
LITE_BUILD=
|
||||
@@ -0,0 +1 @@
|
||||
examples/CMakeLists.txt
|
||||
@@ -0,0 +1,2 @@
|
||||
FULL_BUILD=packaging
|
||||
LITE_BUILD=
|
||||
21
cccl_upstream/ci/test/inspect_changes/regenerate_outputs.sh
Executable file
21
cccl_upstream/ci/test/inspect_changes/regenerate_outputs.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
project_root=$(cd "${script_dir}/../../.." && pwd)
|
||||
inspect_changes="${project_root}/ci/inspect_changes.py"
|
||||
|
||||
if [[ ! -x "${inspect_changes}" ]]; then
|
||||
echo "Error: ${inspect_changes} not found or not executable." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "${script_dir}"
|
||||
|
||||
for dirty_file in *.dirty_files; do
|
||||
test_name=${dirty_file%.dirty_files}
|
||||
output_file="${test_name}.output"
|
||||
echo "Regenerating ${output_file}"
|
||||
python "${inspect_changes}" --file "${dirty_file}" \
|
||||
| awk '/^FULL_BUILD=/{print}/^LITE_BUILD=/{print}' > "${output_file}"
|
||||
done
|
||||
81
cccl_upstream/ci/test/inspect_changes/run_inspect_changes_test.py
Executable file
81
cccl_upstream/ci/test/inspect_changes/run_inspect_changes_test.py
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test harness for ci/inspect_changes.py."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import difflib
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run inspect_changes.py and compare output"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--script", required=True, type=Path, help="Path to inspect_changes.py"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dirty", required=True, type=Path, help="File listing dirty files"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--expected", required=True, type=Path, help="Expected stdout contents"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--python", default=sys.executable, help="Python interpreter to use"
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def build_command(python: str, script: Path, dirty_file: Path) -> list[str]:
|
||||
cmd = [python, str(script), "--file", str(dirty_file)]
|
||||
return cmd
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
cmd = build_command(args.python, args.script, args.dirty)
|
||||
|
||||
sys.stdout.write(f"COMMAND: {' '.join(cmd)}\n")
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
sys.stdout.write("OUTPUT:\n")
|
||||
sys.stdout.write(result.stdout)
|
||||
if result.stdout and not result.stdout.endswith("\n"):
|
||||
sys.stdout.write("\n")
|
||||
if result.returncode != 0:
|
||||
sys.stderr.write(result.stderr)
|
||||
sys.stderr.write("\nCommand failed: {}\n".format(" ".join(cmd)))
|
||||
return result.returncode
|
||||
|
||||
actual_lines = result.stdout.splitlines()
|
||||
expected_lines = [
|
||||
line.strip()
|
||||
for line in args.expected.read_text(encoding="utf-8").splitlines()
|
||||
if line.strip()
|
||||
]
|
||||
|
||||
missing = [line for line in expected_lines if line not in actual_lines]
|
||||
if missing:
|
||||
diff = "\n".join(
|
||||
difflib.unified_diff(
|
||||
expected_lines,
|
||||
actual_lines,
|
||||
fromfile="expected(subset)",
|
||||
tofile="actual",
|
||||
lineterm="",
|
||||
)
|
||||
)
|
||||
if diff:
|
||||
sys.stderr.write(diff + "\n\n")
|
||||
sys.stderr.write("\nExpected lines missing from output:\n")
|
||||
for line in missing:
|
||||
sys.stderr.write(f" {line}\n")
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
13
cccl_upstream/ci/test_cccl_c_parallel.sh
Executable file
13
cccl_upstream/ci/test_cccl_c_parallel.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/build_common.sh"
|
||||
|
||||
print_environment_details
|
||||
|
||||
./build_cccl_c_parallel.sh "$@"
|
||||
|
||||
PRESET="cccl-c-parallel"
|
||||
|
||||
test_preset "CCCL C Parallel Library" "${PRESET}"
|
||||
|
||||
print_time_summary
|
||||
34
cccl_upstream/ci/test_cccl_c_parallel_v2.sh
Executable file
34
cccl_upstream/ci/test_cccl_c_parallel_v2.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/build_common.sh"
|
||||
|
||||
print_environment_details
|
||||
|
||||
# libnvfatbin is required by hostjit but is not included in the base rapidsai devcontainer
|
||||
# image. Detect the installed CTK version and install the matching package if missing.
|
||||
if [[ "$(uname -s)" == "Linux" ]] && ! ldconfig -p 2>/dev/null | grep -q libnvfatbin; then
|
||||
CTK_DEB_VER=$(nvcc --version 2>/dev/null \
|
||||
| grep -oP 'release \K[0-9]+\.[0-9]+' | tr '.' '-')
|
||||
if [[ -n "$CTK_DEB_VER" ]]; then
|
||||
echo "Installing libnvfatbin-dev-${CTK_DEB_VER}..."
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y --no-install-recommends "libnvfatbin-dev-${CTK_DEB_VER}"
|
||||
else
|
||||
echo "WARNING: could not determine CTK version; skipping libnvfatbin install"
|
||||
fi
|
||||
fi
|
||||
|
||||
PRESET="cccl-c-parallel-v2"
|
||||
|
||||
CMAKE_OPTIONS=()
|
||||
if test -n "${CXX_STANDARD:+x}"; then
|
||||
CMAKE_OPTIONS+=("-DCMAKE_CXX_STANDARD=${CXX_STANDARD}" "-DCMAKE_CUDA_STANDARD=${CXX_STANDARD}")
|
||||
fi
|
||||
|
||||
configure_and_build_preset "CCCL C Parallel Library v2 (HostJIT)" "$PRESET" "${CMAKE_OPTIONS[@]}"
|
||||
|
||||
test_preset "CCCL C Parallel Library v2 (HostJIT)" "$PRESET"
|
||||
|
||||
print_time_summary
|
||||
13
cccl_upstream/ci/test_cccl_c_stf.sh
Executable file
13
cccl_upstream/ci/test_cccl_c_stf.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/build_common.sh"
|
||||
|
||||
print_environment_details
|
||||
|
||||
./build_cccl_c_stf.sh "$@"
|
||||
|
||||
PRESET="cccl-c-stf"
|
||||
|
||||
test_preset "CCCL C Parallel Library" "${PRESET}"
|
||||
|
||||
print_time_summary
|
||||
142
cccl_upstream/ci/test_cub.sh
Executable file
142
cccl_upstream/ci/test_cub.sh
Executable file
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
NO_LID=false
|
||||
LID0=false
|
||||
LID1=false
|
||||
LID2=false
|
||||
LIMITED=false
|
||||
COMPUTE_SANITIZER=false
|
||||
ARTIFACT_TAGS=()
|
||||
|
||||
ci_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
|
||||
new_args="$("${ci_dir}/util/extract_switches.sh" \
|
||||
-no-lid \
|
||||
-lid0 \
|
||||
-lid1 \
|
||||
-lid2 \
|
||||
-limited \
|
||||
-compute-sanitizer-memcheck \
|
||||
-compute-sanitizer-racecheck \
|
||||
-compute-sanitizer-initcheck \
|
||||
-compute-sanitizer-synccheck \
|
||||
-- "$@")"
|
||||
|
||||
declare -a new_args="(${new_args})"
|
||||
set -- "${new_args[@]}"
|
||||
while true; do
|
||||
case "$1" in
|
||||
-no-lid)
|
||||
ARTIFACT_TAGS+=("no_lid")
|
||||
NO_LID=true
|
||||
shift
|
||||
;;
|
||||
-lid0)
|
||||
ARTIFACT_TAGS+=("lid_0")
|
||||
LID0=true
|
||||
shift
|
||||
;;
|
||||
-lid1)
|
||||
ARTIFACT_TAGS+=("lid_1")
|
||||
LID1=true
|
||||
shift
|
||||
;;
|
||||
-lid2)
|
||||
ARTIFACT_TAGS+=("lid_2")
|
||||
LID2=true
|
||||
shift
|
||||
;;
|
||||
-limited)
|
||||
# Pull all artifacts:
|
||||
ARTIFACT_TAGS+=("no_lid" "lid_0" "lid_1" "lid_2")
|
||||
LIMITED=true
|
||||
shift
|
||||
;;
|
||||
-compute-sanitizer-memcheck)
|
||||
COMPUTE_SANITIZER=true
|
||||
TOOL=memcheck
|
||||
shift
|
||||
;;
|
||||
-compute-sanitizer-racecheck)
|
||||
COMPUTE_SANITIZER=true
|
||||
TOOL=racecheck
|
||||
shift
|
||||
;;
|
||||
-compute-sanitizer-initcheck)
|
||||
COMPUTE_SANITIZER=true
|
||||
TOOL=initcheck
|
||||
shift
|
||||
;;
|
||||
-compute-sanitizer-synccheck)
|
||||
COMPUTE_SANITIZER=true
|
||||
TOOL=synccheck
|
||||
shift
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if $LIMITED; then
|
||||
|
||||
export C2H_SEED_COUNT_OVERRIDE=1
|
||||
readonly device_mem_GiB=8
|
||||
export C2H_DEVICE_MEMORY_LIMIT=$((device_mem_GiB * 1024 * 1024 * 1024))
|
||||
export C2H_DEBUG_CHECKED_ALLOC_FAILURES=1
|
||||
|
||||
echo "Configuring limited environment:"
|
||||
echo " C2H_SEED_COUNT_OVERRIDE=${C2H_SEED_COUNT_OVERRIDE}"
|
||||
echo " C2H_DEVICE_MEMORY_LIMIT=${C2H_DEVICE_MEMORY_LIMIT} (${device_mem_GiB} GiB)"
|
||||
echo " C2H_DEBUG_CHECKED_ALLOC_FAILURES=${C2H_DEBUG_CHECKED_ALLOC_FAILURES}"
|
||||
echo
|
||||
fi
|
||||
|
||||
# shellcheck source=ci/build_common.sh
|
||||
source "${ci_dir}/build_common.sh"
|
||||
|
||||
print_environment_details
|
||||
|
||||
|
||||
if [[ -z "${GITHUB_ACTIONS:-}" ]]; then
|
||||
./build_cub.sh "$@"
|
||||
else
|
||||
producer_id=$(util/workflow/get_producer_id.sh)
|
||||
for tag in "${ARTIFACT_TAGS[@]}"; do
|
||||
artifact="z_cub-test-artifacts-${DEVCONTAINER_NAME:?}-$producer_id-$tag"
|
||||
run_command "📦 Unpacking artifact '$artifact'" \
|
||||
"${ci_dir}/util/artifacts/download_packed.sh" "$artifact" /home/coder/cccl
|
||||
done
|
||||
fi
|
||||
|
||||
if $NO_LID; then
|
||||
PRESETS=("cub-nolid")
|
||||
elif $LID0; then
|
||||
PRESETS=("cub-lid0")
|
||||
elif $LID1; then
|
||||
PRESETS=("cub-lid1")
|
||||
elif $LID2; then
|
||||
PRESETS=("cub-lid2")
|
||||
else
|
||||
PRESETS=("cub")
|
||||
fi
|
||||
|
||||
if $COMPUTE_SANITIZER; then
|
||||
echo "Setting CCCL_TEST_MODE=compute-sanitizer-${TOOL}"
|
||||
export CCCL_TEST_MODE=compute-sanitizer-${TOOL}
|
||||
echo "Setting C2H_SEED_COUNT_OVERRIDE=1"
|
||||
export C2H_SEED_COUNT_OVERRIDE=1
|
||||
fi
|
||||
|
||||
for PRESET in "${PRESETS[@]}"; do
|
||||
test_preset "CUB (${PRESET})" "${PRESET}"
|
||||
done
|
||||
|
||||
print_time_summary
|
||||
67
cccl_upstream/ci/test_cuda_cccl_examples_python.sh
Executable file
67
cccl_upstream/ci/test_cuda_cccl_examples_python.sh
Executable file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$ci_dir/pyenv_helper.sh"
|
||||
|
||||
# Parse common arguments
|
||||
source "$ci_dir/util/python/common_arg_parser.sh"
|
||||
parse_python_args "$@"
|
||||
# Pin cuda-toolkit to the container's CTK minor and set cuda_version /
|
||||
# cuda_major_version (-ctk-mode latest opts out). See pyenv_helper.sh.
|
||||
pin_cuda_toolkit "${ctk_mode}"
|
||||
|
||||
# Setup Python environment
|
||||
setup_python_env "${py_version}"
|
||||
|
||||
# Fetch or build the cuda_cccl wheel:
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
wheel_artifact_name=$("$ci_dir/util/workflow/get_wheel_artifact_name.sh")
|
||||
"$ci_dir/util/artifacts/download.sh" "${wheel_artifact_name}" /home/coder/cccl/
|
||||
else
|
||||
"$ci_dir/build_cuda_cccl_python.sh" -py-version "${py_version}"
|
||||
fi
|
||||
|
||||
# Install cuda_cccl, plus CuPy which the cuda.compute examples require, plus
|
||||
# pytest-benchmark for the host-overhead benchmark smoke test below. (cuda-bench,
|
||||
# for the throughput smoke, is installed best-effort further down since it does
|
||||
# not always ship a wheel for the newest Python.)
|
||||
CUDA_CCCL_WHEEL_PATH="$(ls /home/coder/cccl/wheelhouse/cuda_cccl-*.whl)"
|
||||
ctk_flavor="$(ctk_extra_flavor "${ctk_mode}")"
|
||||
python -m pip install "${CUDA_CCCL_WHEEL_PATH}[test-${ctk_flavor}${cuda_major_version}]" "cupy-cuda${cuda_major_version}x" pytest-benchmark
|
||||
|
||||
# Run tests for parallel module
|
||||
cd "/home/coder/cccl/python/cuda_cccl/tests/"
|
||||
python -m pytest -n 6 test_examples.py
|
||||
|
||||
# Smoke-test the host-overhead benchmark harness: run every benchmark case
|
||||
# exactly once (pass/fail only, no timing) so harness rot fails CI here instead
|
||||
# of silently surviving until someone runs the perf suite.
|
||||
cd "/home/coder/cccl/python/cuda_cccl/benchmarks/compute/host/"
|
||||
python -m pytest -v --benchmark-disable .
|
||||
|
||||
# Smoke-test the throughput (nvbench) benchmarks the same way. --profile runs
|
||||
# each configuration once (no sampling); --quick uses the reduced quick_configs
|
||||
# axes (one dtype, smallest size) so every benchmark harness still imports,
|
||||
# registers, launches, and completes. cuda-bench does not always ship a wheel for
|
||||
# the newest Python, so skip the throughput smoke ONLY for that known no-wheel
|
||||
# case. Note pip prints "No matching distribution"/"Could not find a version"
|
||||
# even when the index is unreachable, so check for fetch/network failures first
|
||||
# and fail on those; any other install error fails the lane too rather than
|
||||
# silently passing. tee streams pip's output live while capturing it for the
|
||||
# grep checks below (pipefail keeps pip's exit status, not tee's).
|
||||
install_log="$(mktemp)"
|
||||
if python -m pip install "cuda-bench[cu${cuda_major_version}]" pyyaml 2>&1 | tee "${install_log}"; then
|
||||
cd "/home/coder/cccl/python/cuda_cccl/benchmarks/compute/"
|
||||
python run_benchmarks.py --py --profile --quick
|
||||
elif grep -qiE "Could not fetch URL|Retrying \(Retry|connection broken|Failed to establish a new connection|Name or service not known|timed out|SSLError|certificate verify failed|ProxyError" "${install_log}"; then
|
||||
echo "::error::cuda-bench install failed because pip could not reach the package index (network/DNS/TLS/auth); not skipping." >&2
|
||||
exit 1
|
||||
elif grep -qiE "No matching distribution found for cuda-bench|Could not find a version that satisfies the requirement cuda-bench" "${install_log}"; then
|
||||
echo "::warning::cuda-bench has no wheel for Python ${py_version}; skipping the throughput benchmark smoke test."
|
||||
else
|
||||
echo "::error::cuda-bench install failed for an unrecognized reason." >&2
|
||||
exit 1
|
||||
fi
|
||||
rm -f "${install_log}"
|
||||
10
cccl_upstream/ci/test_cuda_cccl_examples_python_v2.sh
Executable file
10
cccl_upstream/ci/test_cuda_cccl_examples_python_v2.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run the cuda.compute examples against a wheel built with the v2 (HostJIT)
|
||||
# backend. Mirrors test_cuda_cccl_examples_python.sh; the only difference is
|
||||
# exporting CCCL_PYTHON_USE_V2 so the wheel build uses cccl.c.parallel.v2.
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
export CCCL_PYTHON_USE_V2=1
|
||||
exec "$ci_dir/test_cuda_cccl_examples_python.sh" "$@"
|
||||
33
cccl_upstream/ci/test_cuda_cccl_headers_python.sh
Executable file
33
cccl_upstream/ci/test_cuda_cccl_headers_python.sh
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
source "$ci_dir/pyenv_helper.sh"
|
||||
|
||||
# Parse common arguments
|
||||
source "$ci_dir/util/python/common_arg_parser.sh"
|
||||
parse_python_args "$@"
|
||||
# Pin cuda-toolkit to the container's CTK minor and set cuda_version /
|
||||
# cuda_major_version (-ctk-mode latest opts out). See pyenv_helper.sh.
|
||||
pin_cuda_toolkit "${ctk_mode}"
|
||||
|
||||
# Setup Python environment
|
||||
setup_python_env "${py_version}"
|
||||
|
||||
# Fetch or build the cuda_cccl wheel:
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
wheel_artifact_name=$("$ci_dir/util/workflow/get_wheel_artifact_name.sh")
|
||||
"$ci_dir/util/artifacts/download.sh" "${wheel_artifact_name}" /home/coder/cccl/
|
||||
else
|
||||
"$ci_dir/build_cuda_cccl_python.sh" -py-version "${py_version}"
|
||||
fi
|
||||
|
||||
# Install cuda_cccl
|
||||
CUDA_CCCL_WHEEL_PATH="$(ls /home/coder/cccl/wheelhouse/cuda_cccl-*.whl)"
|
||||
ctk_flavor="$(ctk_extra_flavor "${ctk_mode}")"
|
||||
python -m pip install "${CUDA_CCCL_WHEEL_PATH}[test-${ctk_flavor}${cuda_major_version}]"
|
||||
|
||||
# Run tests for core package
|
||||
cd "/home/coder/cccl/python/cuda_cccl/tests/"
|
||||
python -m pytest -n auto -v headers/
|
||||
75
cccl_upstream/ci/test_cuda_compute_minimal_python.sh
Executable file
75
cccl_upstream/ci/test_cuda_compute_minimal_python.sh
Executable file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
repo_root="$(cd "$ci_dir/.." && pwd)"
|
||||
source "$ci_dir/pyenv_helper.sh"
|
||||
|
||||
# Parse common arguments
|
||||
source "$ci_dir/util/python/common_arg_parser.sh"
|
||||
parse_python_args "$@"
|
||||
require_py_version "Usage: $0 -py-version <python_version>"
|
||||
|
||||
# Pin cuda-toolkit to the container's CTK minor and set cuda_version /
|
||||
# cuda_major_version (-ctk-mode latest opts out). See pyenv_helper.sh.
|
||||
pin_cuda_toolkit "${ctk_mode}"
|
||||
|
||||
# Setup Python environment
|
||||
setup_python_env "${py_version}"
|
||||
|
||||
# Fetch or build the cuda_cccl wheel:
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
wheel_artifact_name=$("$ci_dir/util/workflow/get_wheel_artifact_name.sh")
|
||||
"$ci_dir/util/artifacts/download.sh" "${wheel_artifact_name}" "${repo_root}/"
|
||||
wheelhouse_dir="${repo_root}/wheelhouse"
|
||||
else
|
||||
"$ci_dir/build_cuda_cccl_python.sh" -py-version "${py_version}"
|
||||
wheelhouse_dir="${repo_root}/wheelhouse"
|
||||
fi
|
||||
|
||||
# Install cuda_cccl with the minimal CUDA extra. This intentionally avoids the
|
||||
# full cu*/sysctk* extras because those pull in numba/numba-cuda. The flavor is
|
||||
# "cu" (pip toolkit) or "sysctk" (system toolkit) per the -ctk-mode arg.
|
||||
CUDA_CCCL_WHEEL_PATH="$(ls "${wheelhouse_dir}"/cuda_cccl-*.whl)"
|
||||
ctk_flavor="$(ctk_extra_flavor "${ctk_mode}")"
|
||||
python -m pip install "${CUDA_CCCL_WHEEL_PATH}[minimal-${ctk_flavor}${cuda_major_version}]"
|
||||
python -m pip install pytest pytest-xdist
|
||||
|
||||
cd "${repo_root}/python/cuda_cccl/tests/"
|
||||
python -m pytest -n 6 -v compute/test_no_numba.py
|
||||
if [[ "${py_version}" == "3.14t" ]]; then
|
||||
# Select only tests that support the minimal extra so pytest does not collect
|
||||
# tests that import numba-cuda and re-enable the GIL. These tests provide their
|
||||
# own worker threads, so keep pytest itself in a single process.
|
||||
# The serialization node-ids are module-skipped on the v2 backend today and
|
||||
# will start running there automatically once v2 gains serialization support.
|
||||
python -m pytest -n 0 -v \
|
||||
compute/test_free_threading_stress.py \
|
||||
compute/test_multi_cc_serialization.py::test_aot_build_result_load_failure_is_shared_and_retryable \
|
||||
compute/test_multi_cc_serialization.py::test_aot_serialization_waits_for_canonical_first_load
|
||||
|
||||
# Broad thread-safety sweep (pytest-run-parallel): re-run the numba-free
|
||||
# functional suite with each test executed concurrently across threads
|
||||
# (barrier-synchronized start), stressing the process-wide build cache,
|
||||
# single-flight coordination, and the Cython bindings from many threads at
|
||||
# once. Complements test_free_threading_stress.py above, which targets specific
|
||||
# shared-object scenarios by hand. -n 0 so the threads share one interpreter.
|
||||
#
|
||||
# --parallel-threads=2 matches CuPy's free-threading CI (the closest GPU
|
||||
# precedent); a small fixed count bounds GPU-memory pressure from concurrent
|
||||
# kernels and stays reproducible across runners, unlike =auto (the runner's
|
||||
# logical-core count).
|
||||
#
|
||||
# pytest-run-parallel is only used by this sweep, so install it on the 3.14t
|
||||
# path rather than for every minimal (e.g. non-free-threaded 3.14) run.
|
||||
python -m pip install pytest-run-parallel
|
||||
|
||||
# Fail fast if the interpreter is not actually GIL-free (wrong build /
|
||||
# PYTHON_GIL=1): pytest-run-parallel does NOT catch a GIL that is enabled from
|
||||
# the start -- it would run threads GIL-serialized and pass vacuously. (A GIL
|
||||
# *re-enabled mid-run* by a non-free-threaded import IS caught by the plugin,
|
||||
# which is why we do not pass --ignore-gil-enabled.)
|
||||
python -c "import sys; assert not sys._is_gil_enabled(), 'GIL is enabled; parallel sweep has no signal'"
|
||||
python -m pytest -n 0 -v --parallel-threads=2 compute/test_no_numba.py
|
||||
fi
|
||||
107
cccl_upstream/ci/test_cuda_compute_minimal_python_tsan.sh
Executable file
107
cccl_upstream/ci/test_cuda_compute_minimal_python_tsan.sh
Executable file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ThreadSanitizer variant of the minimal free-threaded cuda.compute lane.
|
||||
#
|
||||
# Installs the TSan-instrumented cuda_cccl wheel (produced by the `python_tsan`
|
||||
# build, which compiles c.parallel v1 host code with -fsanitize=thread) and runs
|
||||
# the free-threaded stress tests + the pytest-run-parallel sweep under the TSan
|
||||
# runtime. A real data race in c.parallel (e.g. build-owned state mutated at
|
||||
# launch and shared across threads) fails the nightly here instead of silently
|
||||
# corrupting results under some interleaving.
|
||||
#
|
||||
# Only meaningful on a free-threaded (3.14t) interpreter; the GIL would serialize
|
||||
# the threads and hide the very races we are looking for.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
repo_root="$(cd "$ci_dir/.." && pwd)"
|
||||
source "$ci_dir/pyenv_helper.sh"
|
||||
|
||||
# Parse common arguments
|
||||
source "$ci_dir/util/python/common_arg_parser.sh"
|
||||
parse_python_args "$@"
|
||||
require_py_version "Usage: $0 -py-version <python_version>"
|
||||
|
||||
if [[ "${py_version}" != *t ]]; then
|
||||
echo "ERROR: the TSan lane requires a free-threaded (…t) interpreter; got '${py_version}'." >&2
|
||||
echo "On a GIL interpreter the sweep serializes and TSan has no signal." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Instrument c.parallel when this script builds the wheel itself (local runs). In
|
||||
# CI the wheel is the pre-built `python_tsan` artifact, already instrumented; the
|
||||
# export is harmless there.
|
||||
export CCCL_C_PARALLEL_SANITIZE_THREAD=1
|
||||
|
||||
cuda_major_version=$(nvcc --version | grep release | awk '{print $6}' | tr -d ',' | cut -d '.' -f 1 | cut -d 'V' -f 2)
|
||||
|
||||
# Setup Python environment
|
||||
setup_python_env "${py_version}"
|
||||
|
||||
# Fetch or build the TSan-instrumented cuda_cccl wheel. Under project
|
||||
# `python_tsan`, get_wheel_artifact_name.sh resolves to the distinct `-tsan`
|
||||
# artifact, so this never grabs an uninstrumented wheel.
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
wheel_artifact_name=$("$ci_dir/util/workflow/get_wheel_artifact_name.sh")
|
||||
"$ci_dir/util/artifacts/download.sh" "${wheel_artifact_name}" "${repo_root}/"
|
||||
wheelhouse_dir="${repo_root}/wheelhouse"
|
||||
else
|
||||
"$ci_dir/build_cuda_cccl_python.sh" -py-version "${py_version}"
|
||||
wheelhouse_dir="${repo_root}/wheelhouse"
|
||||
fi
|
||||
|
||||
# minimal-cu* extra intentionally avoids numba/numba-cuda (which re-enable the
|
||||
# GIL). pytest-run-parallel drives the concurrent sweep.
|
||||
CUDA_CCCL_WHEEL_PATH="$(ls "${wheelhouse_dir}"/cuda_cccl-*.whl)"
|
||||
python -m pip install "${CUDA_CCCL_WHEEL_PATH}[minimal-cu${cuda_major_version}]"
|
||||
python -m pip install pytest pytest-xdist pytest-run-parallel
|
||||
|
||||
# The instrumented .so links libtsan but keeps it external (auditwheel --exclude),
|
||||
# so the TSan runtime must be present from process start -- LD_PRELOAD the
|
||||
# runner's libtsan (same soname/major as the gcc-13 build). Without preload the
|
||||
# .so fails to load ("cannot allocate memory in static TLS block").
|
||||
tsan_runtime="$(gcc -print-file-name=libtsan.so.2)"
|
||||
if [[ ! -e "${tsan_runtime}" ]]; then
|
||||
echo "ERROR: libtsan.so.2 not found (gcc -print-file-name returned '${tsan_runtime}')." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# setarch -R disables ASLR for the process tree. Required: TSan reserves fixed
|
||||
# shadow-memory regions and aborts ("unexpected memory mapping") when ASLR drops
|
||||
# something into them (google/sanitizers#1686). Uses personality(ADDR_NO_RANDOMIZE)
|
||||
# -- if a runner's seccomp profile blocks it, this call fails and must be allowed
|
||||
# (e.g. --security-opt seccomp=unconfined on the job's container).
|
||||
#
|
||||
# ignore_noninstrumented_modules=1: only c.parallel is instrumented, so ignore
|
||||
# races inside uninstrumented CPython / CUDA libs (avoids boundary false
|
||||
# positives). halt_on_error=1: stop at the first race -- it is usually the root
|
||||
# cause, and later reports are typically downstream noise.
|
||||
# exitcode=66: exit non-zero when any (unsuppressed) race is found, failing the
|
||||
# job even though pytest itself passes.
|
||||
run_under_tsan() {
|
||||
setarch -R env \
|
||||
LD_PRELOAD="${tsan_runtime}" \
|
||||
TSAN_OPTIONS="ignore_noninstrumented_modules=1 halt_on_error=1 history_size=7 exitcode=66" \
|
||||
"$@"
|
||||
}
|
||||
|
||||
# Fail fast if the interpreter is not actually GIL-free (wrong build /
|
||||
# PYTHON_GIL=1): the sweep would run GIL-serialized and pass vacuously.
|
||||
run_under_tsan python -c "import sys; assert not sys._is_gil_enabled(), 'GIL is enabled; TSan sweep has no signal'"
|
||||
|
||||
cd "${repo_root}/python/cuda_cccl/tests/"
|
||||
|
||||
# Hand-written free-threaded stress scenarios (they spawn their own worker
|
||||
# threads) + the serialization node-ids, all sharing one interpreter (-n 0).
|
||||
run_under_tsan python -m pytest -n 0 -v \
|
||||
compute/test_free_threading_stress.py \
|
||||
compute/test_multi_cc_serialization.py::test_aot_build_result_load_failure_is_shared_and_retryable \
|
||||
compute/test_multi_cc_serialization.py::test_aot_serialization_waits_for_canonical_first_load
|
||||
|
||||
# Broad sweep: run the numba-free functional suite with each test executed
|
||||
# concurrently across threads (barrier-synchronized), exercising many more
|
||||
# c.parallel algorithms under contention than the hand-written stress tests.
|
||||
# -n 0 so the threads share one interpreter; --parallel-threads=2 bounds
|
||||
# GPU-memory pressure and stays reproducible across runners.
|
||||
run_under_tsan python -m pytest -n 0 -v --parallel-threads=2 compute/test_no_numba.py
|
||||
10
cccl_upstream/ci/test_cuda_compute_minimal_python_v2.sh
Executable file
10
cccl_upstream/ci/test_cuda_compute_minimal_python_v2.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run the minimal cuda.compute suite against a wheel built with the v2
|
||||
# (HostJIT) backend. The shared minimal script owns dependency installation and
|
||||
# selects the free-threading stress and serialization tests for Python 3.14t.
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
export CCCL_PYTHON_USE_V2=1
|
||||
exec "$ci_dir/test_cuda_compute_minimal_python.sh" "$@"
|
||||
50
cccl_upstream/ci/test_cuda_compute_python.sh
Executable file
50
cccl_upstream/ci/test_cuda_compute_python.sh
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$ci_dir/pyenv_helper.sh"
|
||||
|
||||
# Parse common arguments
|
||||
source "$ci_dir/util/python/common_arg_parser.sh"
|
||||
parse_python_args "$@"
|
||||
# Pin cuda-toolkit to the container's CTK minor and set cuda_version /
|
||||
# cuda_major_version (-ctk-mode latest opts out). See pyenv_helper.sh.
|
||||
pin_cuda_toolkit "${ctk_mode}"
|
||||
|
||||
# Setup Python environment
|
||||
setup_python_env "${py_version}"
|
||||
|
||||
# Fetch or build the cuda_cccl wheel:
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
wheel_artifact_name=$("$ci_dir/util/workflow/get_wheel_artifact_name.sh")
|
||||
"$ci_dir/util/artifacts/download.sh" "${wheel_artifact_name}" /home/coder/cccl/
|
||||
else
|
||||
"$ci_dir/build_cuda_cccl_python.sh" -py-version "${py_version}"
|
||||
fi
|
||||
|
||||
# Install cuda_cccl. The extra flavor is "cu" (pip-installed toolkit) or "sysctk"
|
||||
# (system-provided toolkit) depending on the -ctk-mode arg.
|
||||
CUDA_CCCL_WHEEL_PATH="$(ls /home/coder/cccl/wheelhouse/cuda_cccl-*.whl)"
|
||||
ctk_flavor="$(ctk_extra_flavor "${ctk_mode}")"
|
||||
python -m pip install "${CUDA_CCCL_WHEEL_PATH}[test-${ctk_flavor}${cuda_major_version}]"
|
||||
|
||||
# Run tests for compute module.
|
||||
# On the v2 (HostJIT) backend, abort on first failure — the suite is still
|
||||
# stabilizing and a single early failure is enough signal to investigate
|
||||
# without scrolling through hundreds of subsequent passes.
|
||||
pytest_extra=()
|
||||
if [[ "${CCCL_PYTHON_USE_V2:-}" =~ ^(1|true|TRUE|on|ON)$ ]]; then
|
||||
pytest_extra+=(-x)
|
||||
fi
|
||||
|
||||
cd "/home/coder/cccl/python/cuda_cccl/tests/"
|
||||
if [[ "${CCCL_PYTHON_USE_V2:-}" =~ ^(1|true|TRUE|on|ON)$ ]]; then
|
||||
# The test isolates itself in a fresh subprocess (LLVM initialization is
|
||||
# process-wide and only cold once), but it carries the free_threading marker,
|
||||
# so it must be selected by node-id here or the sweeps below never run it.
|
||||
python -m pytest "${pytest_extra[@]}" -n 0 -v \
|
||||
compute/test_free_threading_stress.py::test_v2_concurrent_cold_llvm_initialization
|
||||
fi
|
||||
python -m pytest "${pytest_extra[@]}" -n 6 -v compute/ -m "not large and not free_threading"
|
||||
python -m pytest "${pytest_extra[@]}" -n 0 -v compute/ -m "large and not free_threading"
|
||||
11
cccl_upstream/ci/test_cuda_compute_python_v2.sh
Executable file
11
cccl_upstream/ci/test_cuda_compute_python_v2.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run the cuda.compute pytest suite against a wheel built with the v2
|
||||
# (HostJIT) backend. Mirrors test_cuda_compute_python.sh; the only difference
|
||||
# is exporting CCCL_PYTHON_USE_V2 so the wheel build (and downstream pytest)
|
||||
# uses cccl.c.parallel.v2.
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
export CCCL_PYTHON_USE_V2=1
|
||||
exec "$ci_dir/test_cuda_compute_python.sh" "$@"
|
||||
13
cccl_upstream/ci/test_cudax.sh
Executable file
13
cccl_upstream/ci/test_cudax.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/build_common.sh"
|
||||
|
||||
print_environment_details
|
||||
|
||||
./build_cudax.sh "$@"
|
||||
|
||||
PRESET="cudax"
|
||||
|
||||
test_preset "CUDA Experimental" "${PRESET}"
|
||||
|
||||
print_time_summary
|
||||
38
cccl_upstream/ci/test_libcudacxx.sh
Executable file
38
cccl_upstream/ci/test_libcudacxx.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
|
||||
# shellcheck source=ci/build_common.sh
|
||||
source "${ci_dir}/build_common.sh"
|
||||
|
||||
print_environment_details
|
||||
|
||||
PRESET="libcudacxx"
|
||||
CMAKE_OPTIONS=("-DCMAKE_CXX_STANDARD=${CXX_STANDARD}" "-DCMAKE_CUDA_STANDARD=${CXX_STANDARD}")
|
||||
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
producer_id="$(util/workflow/get_producer_id.sh)"
|
||||
artifact="z_libcudacxx-test-artifacts-${DEVCONTAINER_NAME:?}-$producer_id"
|
||||
run_command "📦 Unpacking artifact '$artifact'" \
|
||||
"${ci_dir}/util/artifacts/download_packed.sh" "$artifact" /home/coder/cccl/
|
||||
else
|
||||
"${ci_dir}/build_libcudacxx.sh" "$@"
|
||||
configure_preset libcudacxx "$PRESET" "${CMAKE_OPTIONS[@]}"
|
||||
fi
|
||||
|
||||
test_preset "libcudacxx (CTest)" "libcudacxx-ctest"
|
||||
|
||||
sccache -z > /dev/null || :
|
||||
|
||||
lit_test_name="libcudacxx (lit)"
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
export LIT_OPTS="${LIT_OPTS:+${LIT_OPTS} }-Dtest_executable_mode=replay"
|
||||
lit_test_name="libcudacxx (lit replay)"
|
||||
fi
|
||||
test_preset "${lit_test_name}" "libcudacxx-lit"
|
||||
|
||||
sccache --show-adv-stats || :
|
||||
|
||||
print_time_summary
|
||||
17
cccl_upstream/ci/test_nvbench_helper.sh
Executable file
17
cccl_upstream/ci/test_nvbench_helper.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# shellcheck source=ci/build_common.sh
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/build_common.sh"
|
||||
|
||||
print_environment_details
|
||||
|
||||
PRESET="nvbench-helper"
|
||||
|
||||
CMAKE_OPTIONS=()
|
||||
|
||||
GPU_REQUIRED="true"
|
||||
|
||||
configure_and_build_preset "NVBench Helper" "$PRESET" "${CMAKE_OPTIONS[@]}"
|
||||
test_preset "NVBench Helper" "$PRESET" "$GPU_REQUIRED"
|
||||
|
||||
print_time_summary
|
||||
71
cccl_upstream/ci/test_packaging.sh
Executable file
71
cccl_upstream/ci/test_packaging.sh
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir="$(dirname "${BASH_SOURCE[0]}")"
|
||||
cccl_dir="$(realpath "${ci_dir}/..")"
|
||||
|
||||
MIN_CMAKE=false
|
||||
minimum_cmake_version=3.18.0
|
||||
|
||||
new_args="$("${ci_dir}/util/extract_switches.sh" -min-cmake -- "$@")"
|
||||
declare -a new_args="(${new_args})"
|
||||
set -- "${new_args[@]}"
|
||||
while true; do
|
||||
case "$1" in
|
||||
-min-cmake)
|
||||
MIN_CMAKE=true
|
||||
shift
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if $MIN_CMAKE; then
|
||||
echo "Installing minimum CMake version v${minimum_cmake_version}..."
|
||||
wget -q \
|
||||
https://github.com/Kitware/CMake/releases/download/v"${minimum_cmake_version}"/cmake-"${minimum_cmake_version}"-Linux-x86_64.sh \
|
||||
-O /tmp/cmake-install.sh
|
||||
prefix=/tmp/cmake-${minimum_cmake_version}
|
||||
mkdir -p "${prefix}"
|
||||
bash /tmp/cmake-install.sh --skip-license --prefix="${prefix}"
|
||||
export MIN_CTEST_COMMAND="${prefix}/bin/ctest"
|
||||
fi
|
||||
|
||||
# Needs to happen after cmake is installed:
|
||||
# shellcheck source=ci/build_common.sh
|
||||
source "${ci_dir}/build_common.sh"
|
||||
cd "${ci_dir}"
|
||||
|
||||
print_environment_details
|
||||
|
||||
PRESET="packaging"
|
||||
|
||||
CMAKE_OPTIONS=()
|
||||
|
||||
GPU_REQUIRED="true"
|
||||
|
||||
CMAKE_OPTIONS=("-DCCCL_EXAMPLE_CPM_REPOSITORY=${cccl_dir}")
|
||||
|
||||
# Local -- build against the current repo's HEAD commit:
|
||||
if [[ -z "${GITHUB_ACTIONS:-}" ]]; then
|
||||
CMAKE_OPTIONS+=("-DCCCL_EXAMPLE_CPM_TAG=HEAD")
|
||||
else
|
||||
CMAKE_OPTIONS+=("-DCCCL_EXAMPLE_CPM_TAG=${GITHUB_SHA:?}")
|
||||
fi
|
||||
|
||||
if [[ -n "${MIN_CTEST_COMMAND:-}" ]]; then
|
||||
CMAKE_OPTIONS+=("-DCCCL_EXAMPLE_CTEST_COMMAND=${MIN_CTEST_COMMAND}")
|
||||
fi
|
||||
|
||||
configure_and_build_preset "Packaging" "$PRESET" "${CMAKE_OPTIONS[@]}"
|
||||
test_preset "Packaging" "$PRESET" "$GPU_REQUIRED"
|
||||
|
||||
print_time_summary
|
||||
30
cccl_upstream/ci/test_python_common.sh
Normal file
30
cccl_upstream/ci/test_python_common.sh
Normal file
@@ -0,0 +1,30 @@
|
||||
set -euo pipefail
|
||||
|
||||
function list_environment {
|
||||
begin_group "⚙️ Existing site-packages"
|
||||
pip freeze
|
||||
end_group "⚙️ Existing site-packages"
|
||||
}
|
||||
|
||||
function run_tests {
|
||||
module=$1
|
||||
|
||||
pushd "../python/${module}" >/dev/null
|
||||
|
||||
TEMP_VENV_DIR="/tmp/${module}_venv"
|
||||
rm -rf "${TEMP_VENV_DIR}"
|
||||
python -m venv "${TEMP_VENV_DIR}"
|
||||
# shellcheck disable=SC1091
|
||||
. "${TEMP_VENV_DIR}/bin/activate"
|
||||
echo 'cuda-cccl @ file:///home/coder/cccl/python/cuda_cccl' > /tmp/cuda-cccl_constraints.txt
|
||||
run_command "⚙️ Pip install ${module}" pip install -c /tmp/cuda-cccl_constraints.txt ".[test]"
|
||||
begin_group "⚙️ ${module} site-packages"
|
||||
pip freeze
|
||||
end_group "⚙️ ${module} site-packages"
|
||||
run_command "🚀 Pytest ${module}" pytest -n "${PARALLEL_LEVEL:-$(nproc --all --ignore=1)}" -v ./tests
|
||||
deactivate
|
||||
|
||||
popd >/dev/null
|
||||
|
||||
print_time_summary
|
||||
}
|
||||
71
cccl_upstream/ci/test_thrust.sh
Executable file
71
cccl_upstream/ci/test_thrust.sh
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CPU_ONLY=false
|
||||
GPU_ONLY=false
|
||||
|
||||
ci_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
|
||||
new_args="$("${ci_dir}/util/extract_switches.sh" -cpu-only -gpu-only -- "$@")"
|
||||
declare -a new_args="(${new_args})"
|
||||
set -- "${new_args[@]}"
|
||||
while true; do
|
||||
case "$1" in
|
||||
-cpu-only)
|
||||
ARTIFACT_TAG="test_cpu"
|
||||
CPU_ONLY=true
|
||||
shift
|
||||
;;
|
||||
-gpu-only)
|
||||
ARTIFACT_TAG="test_gpu"
|
||||
GPU_ONLY=true
|
||||
shift
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# shellcheck source=ci/build_common.sh
|
||||
source "${ci_dir}/build_common.sh"
|
||||
|
||||
print_environment_details
|
||||
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
if ! $CPU_ONLY && ! $GPU_ONLY; then
|
||||
echo "Error: test_thrust.sh requires -cpu-only or -gpu-only in CI" >&2
|
||||
exit 1
|
||||
fi
|
||||
producer_id="$(util/workflow/get_producer_id.sh)"
|
||||
run_command "📦 Unpacking test artifacts" \
|
||||
"${ci_dir}/util/artifacts/download_packed.sh" \
|
||||
"z_thrust-test-artifacts-${DEVCONTAINER_NAME:?}-$producer_id-$ARTIFACT_TAG" \
|
||||
/home/coder/cccl/
|
||||
else
|
||||
./build_thrust.sh "$@"
|
||||
fi
|
||||
|
||||
declare -a PRESET_GPU_PAIRS=()
|
||||
|
||||
if $CPU_ONLY; then
|
||||
PRESET_GPU_PAIRS+=("thrust-cpu:false")
|
||||
elif $GPU_ONLY; then
|
||||
PRESET_GPU_PAIRS+=("thrust-gpu:true")
|
||||
else
|
||||
PRESET_GPU_PAIRS+=("thrust-cpu:false" "thrust-gpu:true")
|
||||
fi
|
||||
|
||||
for pair in "${PRESET_GPU_PAIRS[@]}"; do
|
||||
PRESET="${pair%%:*}"
|
||||
GPU_REQUIRED="${pair##*:}"
|
||||
test_preset "Thrust (${PRESET})" "${PRESET}" "${GPU_REQUIRED}"
|
||||
done
|
||||
|
||||
print_time_summary
|
||||
43
cccl_upstream/ci/update_rapids_version.sh
Executable file
43
cccl_upstream/ci/update_rapids_version.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) 2024-2025, NVIDIA CORPORATION.
|
||||
##########################
|
||||
# RAPIDS Version Updater #
|
||||
##########################
|
||||
|
||||
## Usage
|
||||
# bash update_rapids_version.sh <new_version>
|
||||
|
||||
# Format is YY.MM.PP - no leading 'v' or trailing 'a'
|
||||
NEXT_FULL_TAG=$1
|
||||
|
||||
#Get <major>.<minor> for next version
|
||||
NEXT_MAJOR=$(echo "$NEXT_FULL_TAG" | awk '{split($0, a, "."); print a[1]}')
|
||||
NEXT_MINOR=$(echo "$NEXT_FULL_TAG" | awk '{split($0, a, "."); print a[2]}')
|
||||
# shellcheck disable=SC2034
|
||||
NEXT_PATCH=$(echo "$NEXT_FULL_TAG" | awk '{split($0, a, "."); print a[3]}')
|
||||
NEXT_SHORT_TAG=${NEXT_MAJOR}.${NEXT_MINOR}
|
||||
|
||||
# Need to distutils-normalize the versions for some use cases
|
||||
NEXT_SHORT_TAG_PEP440=$(python -c "from packaging.version import Version; print(Version('${NEXT_SHORT_TAG}'))")
|
||||
|
||||
echo "Updating RAPIDS and devcontainers to $NEXT_FULL_TAG"
|
||||
|
||||
# Inplace sed replace; workaround for Linux and Mac
|
||||
function sed_runner() {
|
||||
sed -i.bak ''"$1"'' "$2" && rm -f "${2}".bak
|
||||
}
|
||||
|
||||
# Update CI files
|
||||
sed_runner "/devcontainer_version/ s/'[0-9.]*'/'${NEXT_SHORT_TAG}'/g" ci/matrix.yaml
|
||||
sed_runner "/devcontainer_version=/ s/=[0-9.]*/=${NEXT_SHORT_TAG}/g" ci/build_cuda_cccl_python.sh
|
||||
|
||||
function update_devcontainer() {
|
||||
sed_runner "s@rapidsai/devcontainers:[0-9.]*@rapidsai/devcontainers:${NEXT_SHORT_TAG}@g" "${1}"
|
||||
sed_runner "s@rapidsai/devcontainers/features/rapids-build-utils:[0-9.]*@rapidsai/devcontainers/features/rapids-build-utils:${NEXT_SHORT_TAG_PEP440}@" "${1}"
|
||||
sed_runner "s@\${localWorkspaceFolderBasename}-rapids-[0-9.]*@\${localWorkspaceFolderBasename}-rapids-${NEXT_SHORT_TAG}@g" "${1}"
|
||||
}
|
||||
|
||||
# Update .devcontainer files
|
||||
find .devcontainer/ ci/rapids/ -type f -name devcontainer.json -print0 | while IFS= read -r -d '' filename; do
|
||||
update_devcontainer "${filename}"
|
||||
done
|
||||
121
cccl_upstream/ci/update_version.sh
Executable file
121
cccl_upstream/ci/update_version.sh
Executable file
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Usage: ./update_version.sh [--dry-run] <major> <minor> <patch>
|
||||
# Example: ./update_version.sh --dry-run 2 2 1
|
||||
|
||||
# Run in root cccl/
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/.." || exit
|
||||
|
||||
DRY_RUN=false
|
||||
|
||||
while [[ "$#" -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dry-run) DRY_RUN=true; ;;
|
||||
*) break ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
major="$1"
|
||||
minor="$2"
|
||||
patch="$3"
|
||||
|
||||
if [[ -z "$major" ]] || [[ -z "$minor" ]] || [[ -z "$patch" ]]; then
|
||||
echo "Usage: $0 [--dry-run] <major> <minor> <patch>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Version file paths
|
||||
REPO_VERSION_FILE="cccl-version.json"
|
||||
DOCS_VERSION_MD_FILE="docs/VERSION.md"
|
||||
CCCL_VERSION_FILE="libcudacxx/include/cuda/std/__cccl/version.h"
|
||||
THRUST_VERSION_FILE="thrust/thrust/version.h"
|
||||
CUB_VERSION_FILE="cub/cub/version.cuh"
|
||||
CCCL_CMAKE_VERSION_FILE="lib/cmake/cccl/cccl-config-version.cmake"
|
||||
CUB_CMAKE_VERSION_FILE="lib/cmake/cub/cub-config-version.cmake"
|
||||
LIBCUDACXX_CMAKE_VERSION_FILE="lib/cmake/libcudacxx/libcudacxx-config-version.cmake"
|
||||
THRUST_CMAKE_VERSION_FILE="lib/cmake/thrust/thrust-config-version.cmake"
|
||||
CUDAX_CMAKE_VERSION_FILE="lib/cmake/cudax/cudax-config-version.cmake"
|
||||
CUDA_CCCL_VERSION_FILE="python/cuda_cccl/cuda/cccl/_version.py"
|
||||
|
||||
# Calculated version codes
|
||||
new_cccl_version=$((major * 1000000 + minor * 1000 + patch)) # MMMmmmppp
|
||||
new_thrust_cub_version=$((major * 100000 + minor * 100 + patch)) # MMMmmmpp
|
||||
|
||||
# Fetch current version from file
|
||||
current_cccl_version=$(grep -oP "define CCCL_VERSION \K[0-9]+" "$CCCL_VERSION_FILE")
|
||||
|
||||
# Fetch the latest tag from git and strip the 'v' prefix if present
|
||||
latest_tag=$(git tag --sort=-v:refname | head -n 1 | sed 's/^v//')
|
||||
|
||||
# Since the tags and versions are numerically comparable, we cast them to integers
|
||||
latest_tag_version=$(echo "$latest_tag" | awk -F. '{ printf("%d%03d%03d", $1,$2,$3) }')
|
||||
|
||||
echo "Running in $(pwd)"
|
||||
echo "New MMMmmmppp version: $new_cccl_version"
|
||||
echo "New MMMmmmpp version: $new_thrust_cub_version"
|
||||
echo "Current CCCL version: $current_cccl_version"
|
||||
echo "Latest git tag: $latest_tag"
|
||||
|
||||
# Check if new version is less than or equal to current or the latest tag
|
||||
if (( new_cccl_version < current_cccl_version )) || (( new_cccl_version < latest_tag_version )); then
|
||||
echo "Error: New version $new_cccl_version is less than current version $current_cccl_version or latest git tag version $latest_tag_version."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
update_file () {
|
||||
local file=$1
|
||||
local pattern=$2
|
||||
local new_value=$3
|
||||
if [[ "$DRY_RUN" = true ]]; then
|
||||
local temp_file
|
||||
temp_file=$(mktemp)
|
||||
sed "s/$pattern/$new_value/g" "$file" > "$temp_file"
|
||||
diff --color=auto -U 0 "$file" "$temp_file" || true
|
||||
rm "$temp_file"
|
||||
else
|
||||
sed -i "s/$pattern/$new_value/" "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Update version information in files:
|
||||
|
||||
update_file "$REPO_VERSION_FILE" " \"full\":.*," " \"full\": \"$major.$minor.$patch\","
|
||||
update_file "$REPO_VERSION_FILE" " \"major\":.*" " \"major\": $major,"
|
||||
update_file "$REPO_VERSION_FILE" " \"minor\":.*" " \"minor\": $minor,"
|
||||
update_file "$REPO_VERSION_FILE" " \"patch\":.*" " \"patch\": $patch"
|
||||
|
||||
update_file "$DOCS_VERSION_MD_FILE" ".*" "$major.$minor"
|
||||
|
||||
update_file "$CCCL_VERSION_FILE" "^#define CCCL_VERSION \([0-9]\+\)" "#define CCCL_VERSION $new_cccl_version"
|
||||
update_file "$THRUST_VERSION_FILE" "^#define THRUST_VERSION \([0-9]\+\)" "#define THRUST_VERSION $new_thrust_cub_version"
|
||||
update_file "$CUB_VERSION_FILE" "^#define CUB_VERSION \([0-9]\+\)" "#define CUB_VERSION $new_thrust_cub_version"
|
||||
|
||||
update_file "$CUB_CMAKE_VERSION_FILE" "set(CUB_VERSION_MAJOR \([0-9]\+\))" "set(CUB_VERSION_MAJOR $major)"
|
||||
update_file "$CUB_CMAKE_VERSION_FILE" "set(CUB_VERSION_MINOR \([0-9]\+\))" "set(CUB_VERSION_MINOR $minor)"
|
||||
update_file "$CUB_CMAKE_VERSION_FILE" "set(CUB_VERSION_PATCH \([0-9]\+\))" "set(CUB_VERSION_PATCH $patch)"
|
||||
|
||||
update_file "$LIBCUDACXX_CMAKE_VERSION_FILE" "set(libcudacxx_VERSION_MAJOR \([0-9]\+\))" "set(libcudacxx_VERSION_MAJOR $major)"
|
||||
update_file "$LIBCUDACXX_CMAKE_VERSION_FILE" "set(libcudacxx_VERSION_MINOR \([0-9]\+\))" "set(libcudacxx_VERSION_MINOR $minor)"
|
||||
update_file "$LIBCUDACXX_CMAKE_VERSION_FILE" "set(libcudacxx_VERSION_PATCH \([0-9]\+\))" "set(libcudacxx_VERSION_PATCH $patch)"
|
||||
|
||||
update_file "$THRUST_CMAKE_VERSION_FILE" "set(THRUST_VERSION_MAJOR \([0-9]\+\))" "set(THRUST_VERSION_MAJOR $major)"
|
||||
update_file "$THRUST_CMAKE_VERSION_FILE" "set(THRUST_VERSION_MINOR \([0-9]\+\))" "set(THRUST_VERSION_MINOR $minor)"
|
||||
update_file "$THRUST_CMAKE_VERSION_FILE" "set(THRUST_VERSION_PATCH \([0-9]\+\))" "set(THRUST_VERSION_PATCH $patch)"
|
||||
|
||||
update_file "$CCCL_CMAKE_VERSION_FILE" "set(CCCL_VERSION_MAJOR \([0-9]\+\))" "set(CCCL_VERSION_MAJOR $major)"
|
||||
update_file "$CCCL_CMAKE_VERSION_FILE" "set(CCCL_VERSION_MINOR \([0-9]\+\))" "set(CCCL_VERSION_MINOR $minor)"
|
||||
update_file "$CCCL_CMAKE_VERSION_FILE" "set(CCCL_VERSION_PATCH \([0-9]\+\))" "set(CCCL_VERSION_PATCH $patch)"
|
||||
|
||||
update_file "$CUDAX_CMAKE_VERSION_FILE" "set(cudax_VERSION_MAJOR \([0-9]\+\))" "set(cudax_VERSION_MAJOR $major)"
|
||||
update_file "$CUDAX_CMAKE_VERSION_FILE" "set(cudax_VERSION_MINOR \([0-9]\+\))" "set(cudax_VERSION_MINOR $minor)"
|
||||
update_file "$CUDAX_CMAKE_VERSION_FILE" "set(cudax_VERSION_PATCH \([0-9]\+\))" "set(cudax_VERSION_PATCH $patch)"
|
||||
|
||||
update_file "$CUDA_CCCL_VERSION_FILE" "^__version__ = \"\([0-9.]\+\)\"" "__version__ = \"$major.$minor.$patch\""
|
||||
|
||||
|
||||
if [[ "$DRY_RUN" = true ]]; then
|
||||
echo "Dry run completed. No changes made."
|
||||
else
|
||||
echo "Version updated to $major.$minor.$patch"
|
||||
fi
|
||||
107
cccl_upstream/ci/upload_cub_test_artifacts.sh
Executable file
107
cccl_upstream/ci/upload_cub_test_artifacts.sh
Executable file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "${GITHUB_ACTIONS:-}" ]]; then
|
||||
echo "This script must be run in a GitHub Actions environment." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly ci_dir
|
||||
repo_root="$(cd "${ci_dir}/.." && pwd)"
|
||||
readonly repo_root
|
||||
|
||||
cd "$repo_root"
|
||||
|
||||
if ! ci/util/workflow/has_consumers.sh; then
|
||||
echo "No consumers found for this job. Exiting." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$#" -gt 0 ]]; then
|
||||
preset_variants=("$@")
|
||||
else
|
||||
# Figure out which artifacts need to be built:
|
||||
consumers=$(ci/util/workflow/get_consumers.sh)
|
||||
preset_variants=()
|
||||
if grep -q "TestGPU" <<< "$consumers"; then
|
||||
preset_variants+=("no_lid")
|
||||
fi
|
||||
if grep -q "HostLaunch" <<< "$consumers"; then
|
||||
preset_variants+=("lid_0")
|
||||
fi
|
||||
if grep -q "DeviceLaunch" <<< "$consumers"; then
|
||||
preset_variants+=("lid_1")
|
||||
fi
|
||||
if grep -q "GraphCapture" <<< "$consumers"; then
|
||||
preset_variants+=("lid_2")
|
||||
fi
|
||||
# Limited jobs run the entire test suite:
|
||||
if grep -q "SmallGMem" <<< "$consumers"; then
|
||||
preset_variants+=("no_lid" "lid_0" "lid_1" "lid_2")
|
||||
fi
|
||||
fi
|
||||
|
||||
# Remove duplicates:
|
||||
mapfile -t preset_variants < <(echo "${preset_variants[*]}" | tr ' ' '\n' | sort -u)
|
||||
|
||||
artifact_prefix="z_cub-test-artifacts-${DEVCONTAINER_NAME:?}-${JOB_ID:?}"
|
||||
|
||||
# BUILD_INFIX is undefined on windows CI
|
||||
build_dir_regex="build${CCCL_BUILD_INFIX:+/$CCCL_BUILD_INFIX}/cub[^/]*"
|
||||
|
||||
# Just collect the minimum set of files needed for running each ctest preset:
|
||||
for preset_variant in "${preset_variants[@]}"; do
|
||||
|
||||
# Shared across all presets:
|
||||
ci/util/artifacts/stage.sh "$artifact_prefix-$preset_variant" \
|
||||
"$build_dir_regex/build\.ninja$" \
|
||||
"$build_dir_regex/.*rules\.ninja$" \
|
||||
"$build_dir_regex/CMakeCache\.txt$" \
|
||||
"$build_dir_regex/.*VerifyGlobs\.cmake$" \
|
||||
"$build_dir_regex/.*CTestTestfile\.cmake$" \
|
||||
"$build_dir_regex/cub/rapids-cmake/.*" > /dev/null
|
||||
|
||||
# Add per-preset executables:
|
||||
if [[ "$preset_variant" == lid_* ]]; then
|
||||
ci/util/artifacts/stage.sh \
|
||||
"$artifact_prefix-$preset_variant" \
|
||||
"$build_dir_regex/bin/.*\.$preset_variant.*" > /dev/null
|
||||
# The CUDA runtime smoke binary is invoked explicitly from build_common.sh
|
||||
ci/util/artifacts/stage.sh \
|
||||
"$artifact_prefix-$preset_variant" \
|
||||
"$build_dir_regex/bin/cccl\.test\.cuda_runtime_smoke$" > /dev/null
|
||||
# Windows builds generate binaries for the header tests, remove these:
|
||||
ci/util/artifacts/unstage.sh \
|
||||
"$artifact_prefix-$preset_variant" \
|
||||
"$build_dir_regex/.*\.headers\..*" > /dev/null || :
|
||||
|
||||
ci/util/artifacts/upload_stage_packed.sh "$artifact_prefix-$preset_variant"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ " ${preset_variants[*]} " == *" no_lid "* ]]; then
|
||||
# Initially add all binaries to no_lid, then remove the lid variants in later passes:
|
||||
ci/util/artifacts/stage.sh \
|
||||
"$artifact_prefix-no_lid" \
|
||||
"$build_dir_regex/bin/.*" > /dev/null
|
||||
# Remove the benchmarks if present, we don't run those in CI, just build them:
|
||||
ci/util/artifacts/unstage.sh \
|
||||
"$artifact_prefix-no_lid" \
|
||||
"$build_dir_regex/.*\.bench\..*" > /dev/null || :
|
||||
# Remove all lid variants:
|
||||
ci/util/artifacts/unstage.sh \
|
||||
"$artifact_prefix-no_lid" \
|
||||
"$build_dir_regex/.*\.lid_[0-2].*" > /dev/null
|
||||
# Windows builds generate binaries for the header tests, remove these:
|
||||
ci/util/artifacts/unstage.sh \
|
||||
"$artifact_prefix-no_lid" \
|
||||
"$build_dir_regex/.*\.headers\..*" > /dev/null || :
|
||||
# These cubin outputs are needed for FileCheck tests in test/cubin-check
|
||||
ci/util/artifacts/stage.sh \
|
||||
"$artifact_prefix-no_lid" \
|
||||
"$build_dir_regex/cub/test/cubin-check/.*\.cubin$" > /dev/null || :
|
||||
|
||||
ci/util/artifacts/upload_stage_packed.sh "$artifact_prefix-no_lid"
|
||||
fi
|
||||
66
cccl_upstream/ci/upload_job_result_artifacts.sh
Executable file
66
cccl_upstream/ci/upload_job_result_artifacts.sh
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "${GITHUB_ACTIONS:-}" ]]; then
|
||||
echo "This script must be run in a GitHub Actions environment." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly ci_dir
|
||||
repo_root="$(cd "${ci_dir}/.." && pwd)"
|
||||
readonly repo_root
|
||||
|
||||
cd "$repo_root"
|
||||
|
||||
usage=$(cat <<EOF
|
||||
Usage: $0 <job_id> <exit code>
|
||||
EOF
|
||||
)
|
||||
|
||||
if [[ "$#" -ne 2 ]]; then
|
||||
echo "Error: Invalid number of arguments." >&2
|
||||
echo "$usage" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
job_id="$1"
|
||||
exit_code="$2"
|
||||
|
||||
# Collect workflow-related artifacts -- success state, sccache info, etc.
|
||||
# These are unpacked and parsed in the workflow-results action.
|
||||
|
||||
source ci/util/artifacts/common.sh
|
||||
|
||||
# The root of the shared artifact structure:
|
||||
jobs_artifact_dir="$ARTIFACT_UPLOAD_STAGE/jobs"
|
||||
|
||||
# This job's artifact directory:
|
||||
job_artifacts="$jobs_artifact_dir/$job_id"
|
||||
mkdir -p "$job_artifacts"
|
||||
|
||||
if [[ "$exit_code" -eq 0 ]]; then
|
||||
touch "$job_artifacts/success"
|
||||
fi
|
||||
|
||||
# Finds a matching file in the root and copies it to the artifact directory.
|
||||
find_and_copy_job_artifact_from() {
|
||||
root="$1"
|
||||
name="$2"
|
||||
if find "$root"/ -maxdepth 4 -name "$name" -type f -printf '' -quit 2>/dev/null; then
|
||||
find "$root"/ -maxdepth 4 -name "$name" -type f -print0 | xargs -0 -P4 -I% cp -v % "$job_artifacts"/
|
||||
else
|
||||
echo "No file matching '$name' found in '$root'."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
find_and_copy_job_artifact_from /tmp "sccache*.log" || : # Nonfatal if not found
|
||||
find_and_copy_job_artifact_from build "sccache_stats.json" || : # Nonfatal if not found
|
||||
find_and_copy_job_artifact_from build ".ninja_log" || : # Nonfatal if not found
|
||||
find_and_copy_job_artifact_from build "build.ninja" || : # Nonfatal if not found
|
||||
find_and_copy_job_artifact_from build "rules.ninja" || : # Nonfatal if not found
|
||||
find_and_copy_job_artifact_from build "ctest.log" || : # Nonfatal if not found
|
||||
|
||||
ci/util/artifacts/upload/register.sh "zz_jobs-$job_id" "$jobs_artifact_dir"
|
||||
55
cccl_upstream/ci/upload_libcudacxx_test_artifacts.sh
Executable file
55
cccl_upstream/ci/upload_libcudacxx_test_artifacts.sh
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "${GITHUB_ACTIONS:-}" ]]; then
|
||||
echo "This script must be run in a GitHub Actions environment." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly ci_dir
|
||||
repo_root="$(cd "${ci_dir}/.." && pwd)"
|
||||
readonly repo_root
|
||||
|
||||
cd "$repo_root"
|
||||
|
||||
if ! ci/util/workflow/has_consumers.sh; then
|
||||
echo "No consumers found for this job. Exiting." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
artifact="z_libcudacxx-test-artifacts-${DEVCONTAINER_NAME:?}-${JOB_ID:?}"
|
||||
|
||||
# BUILD_INFIX is undefined on windows CI.
|
||||
build_dir_regex="build${CCCL_BUILD_INFIX:+/$CCCL_BUILD_INFIX}/libcudacxx[^/]*"
|
||||
lit_executable_regex="$build_dir_regex/libcudacxx/test/libcudacxx/test/.*Output/.*\.exe$"
|
||||
|
||||
# Minimum CTest/lit metadata needed to run from the unpacked build tree.
|
||||
ci/util/artifacts/stage.sh "$artifact" \
|
||||
"$build_dir_regex/build\.ninja$" \
|
||||
"$build_dir_regex/.*rules\.ninja$" \
|
||||
"$build_dir_regex/CMakeCache\.txt$" \
|
||||
"$build_dir_regex/.*VerifyGlobs\.cmake$" \
|
||||
"$build_dir_regex/.*CTestTestfile\.cmake$" \
|
||||
"$build_dir_regex/libcudacxx/test/libcudacxx/lit\.site\.cfg$" \
|
||||
> /dev/null
|
||||
|
||||
# Test executables plus shared libraries/smoke binaries used by CTest/lit.
|
||||
ci/util/artifacts/stage.sh "$artifact" \
|
||||
"$build_dir_regex/bin/.*" \
|
||||
"$build_dir_regex/lib/.*" \
|
||||
"$lit_executable_regex" \
|
||||
> /dev/null
|
||||
|
||||
if ! find . -type f -regex "\./$lit_executable_regex" -print -quit | grep -q .; then
|
||||
echo "No lit test executables found for artifact '$artifact'." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Windows builds generate binaries for header tests that are never executed.
|
||||
ci/util/artifacts/unstage.sh \
|
||||
"$artifact" \
|
||||
"$build_dir_regex/.*\.headers\..*" > /dev/null || :
|
||||
|
||||
ci/util/artifacts/upload_stage_packed.sh "$artifact"
|
||||
93
cccl_upstream/ci/upload_thrust_test_artifacts.sh
Executable file
93
cccl_upstream/ci/upload_thrust_test_artifacts.sh
Executable file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "${GITHUB_ACTIONS:-}" ]]; then
|
||||
echo "This script must be run in a GitHub Actions environment." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly ci_dir
|
||||
repo_root="$(cd "${ci_dir}/.." && pwd)"
|
||||
readonly repo_root
|
||||
|
||||
cd "$repo_root"
|
||||
|
||||
if ! ci/util/workflow/has_consumers.sh; then
|
||||
echo "No consumers found for this job. Exiting." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Figure out which artifact needs to be built:
|
||||
consumers=$(ci/util/workflow/get_consumers.sh)
|
||||
preset_variants=()
|
||||
if grep -q "TestGPU" <<< "$consumers"; then
|
||||
preset_variants+=("test_gpu")
|
||||
fi
|
||||
if grep -q "TestCPU" <<< "$consumers"; then
|
||||
preset_variants+=("test_cpu")
|
||||
fi
|
||||
|
||||
artifact_prefix="z_thrust-test-artifacts-${DEVCONTAINER_NAME:?}-${JOB_ID:?}"
|
||||
|
||||
# BUILD_INFIX is undefined on windows CI
|
||||
build_dir_regex="build${CCCL_BUILD_INFIX:+/$CCCL_BUILD_INFIX}/thrust[^/]*"
|
||||
|
||||
# Just collect the minimum set of files needed for running each ctest preset:
|
||||
for preset_variant in "${preset_variants[@]}"; do
|
||||
# Shared across all presets:
|
||||
ci/util/artifacts/stage.sh "$artifact_prefix-$preset_variant" \
|
||||
"$build_dir_regex/build\.ninja$" \
|
||||
"$build_dir_regex/.*rules\.ninja$" \
|
||||
"$build_dir_regex/CMakeCache\.txt$" \
|
||||
"$build_dir_regex/.*VerifyGlobs\.cmake$" \
|
||||
"$build_dir_regex/.*CTestTestfile\.cmake$" \
|
||||
> /dev/null
|
||||
done
|
||||
|
||||
if [[ " ${preset_variants[*]} " == *" test_cpu "* ]]; then
|
||||
# Initially add all binaries, then remove all containing 'cuda' in the name:
|
||||
ci/util/artifacts/stage.sh \
|
||||
"$artifact_prefix-test_cpu" \
|
||||
"$build_dir_regex/bin/.*" > /dev/null
|
||||
ci/util/artifacts/unstage.sh \
|
||||
"$artifact_prefix-test_cpu" \
|
||||
"$build_dir_regex/bin/thrust\..*\.cuda\..*" > /dev/null
|
||||
|
||||
ci/util/artifacts/stage.sh \
|
||||
"$artifact_prefix-test_cpu" \
|
||||
"$build_dir_regex/lib/.*\.test\.framework\..*" > /dev/null
|
||||
ci/util/artifacts/unstage.sh \
|
||||
"$artifact_prefix-test_cpu" \
|
||||
"$build_dir_regex/lib/.*\.cuda\.test\.framework\..*" > /dev/null
|
||||
|
||||
# Windows builds generate binaries for the header tests, remove these:
|
||||
ci/util/artifacts/unstage.sh \
|
||||
"$artifact_prefix-test_cpu" \
|
||||
"$build_dir_regex/.*\.headers\..*" > /dev/null || :
|
||||
|
||||
ci/util/artifacts/upload_stage_packed.sh "$artifact_prefix-test_cpu"
|
||||
fi
|
||||
|
||||
if [[ " ${preset_variants[*]} " == *" test_gpu "* ]]; then
|
||||
# Only binaries containing 'cuda':
|
||||
ci/util/artifacts/stage.sh \
|
||||
"$artifact_prefix-test_gpu" \
|
||||
"$build_dir_regex/bin/thrust\..*\.cuda\..*" > /dev/null
|
||||
ci/util/artifacts/stage.sh \
|
||||
"$artifact_prefix-test_gpu" \
|
||||
"$build_dir_regex/lib/.*\.cuda\.test\.framework\..*" > /dev/null
|
||||
# The CUDA runtime smoke binary is invoked explicitly from build_common.sh
|
||||
ci/util/artifacts/stage.sh \
|
||||
"$artifact_prefix-test_gpu" \
|
||||
"$build_dir_regex/bin/cccl\.test\.cuda_runtime_smoke$" > /dev/null
|
||||
|
||||
# Windows builds generate binaries for the header tests, remove these:
|
||||
ci/util/artifacts/unstage.sh \
|
||||
"$artifact_prefix-test_gpu" \
|
||||
"$build_dir_regex/.*\.headers\..*" > /dev/null || :
|
||||
|
||||
|
||||
ci/util/artifacts/upload_stage_packed.sh "$artifact_prefix-test_gpu"
|
||||
fi
|
||||
39
cccl_upstream/ci/util/artifacts/common.sh
Executable file
39
cccl_upstream/ci/util/artifacts/common.sh
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
echo "This script must be sourced, not executed directly." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${GITHUB_ACTIONS:-}" ]]; then
|
||||
echo "This script must be run in a GitHub Actions environment." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
to_posix_path() {
|
||||
local path="$1"
|
||||
|
||||
if [[ "$path" =~ ^([A-Za-z]):([\\/]?.*)$ ]]; then
|
||||
local drive="${BASH_REMATCH[1]}"
|
||||
local rest="${BASH_REMATCH[2]}"
|
||||
rest="${rest//\\/\/}"
|
||||
printf '/%s%s\n' "${drive,,}" "$rest"
|
||||
return
|
||||
fi
|
||||
|
||||
printf '%s\n' "$path"
|
||||
}
|
||||
|
||||
runner_temp_posix="$(to_posix_path "${RUNNER_TEMP:-/tmp}")"
|
||||
|
||||
export ARTIFACT_UPLOAD_STAGE="${runner_temp_posix}/artifact_upload_stage"
|
||||
export ARTIFACT_ARCHIVES="${runner_temp_posix}/artifact_archives"
|
||||
export ARTIFACT_UPLOAD_REGISTERY="${ARTIFACT_UPLOAD_STAGE}/artifact_upload_registry.json"
|
||||
|
||||
mkdir -p "$ARTIFACT_UPLOAD_STAGE" "$ARTIFACT_ARCHIVES"
|
||||
|
||||
if [[ ! -f "$ARTIFACT_UPLOAD_REGISTERY" ]]; then
|
||||
echo "[]" > "$ARTIFACT_UPLOAD_REGISTERY"
|
||||
fi
|
||||
40
cccl_upstream/ci/util/artifacts/download.sh
Executable file
40
cccl_upstream/ci/util/artifacts/download.sh
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ci_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../" && pwd)"
|
||||
readonly ci_dir
|
||||
# shellcheck source=ci/util/artifacts/common.sh
|
||||
source "$ci_dir/util/artifacts/common.sh"
|
||||
|
||||
usage=$(cat <<EOF
|
||||
Usage: $0 <name> [<path>]
|
||||
|
||||
Download artifacts uploaded by other jobs in this CI run.
|
||||
|
||||
Example Usage:
|
||||
Download an artifact to the current directory:
|
||||
$0 source_artifact.tar.gz
|
||||
|
||||
Download a packed artifact and extract it to the provided path:
|
||||
$0 job-\$ID-products some/path/
|
||||
EOF
|
||||
)
|
||||
readonly usage
|
||||
|
||||
if [[ "$#" -lt 1 ]]; then
|
||||
echo "Error: Missing artifact name." >&2
|
||||
echo "$usage" >&2
|
||||
exit 1
|
||||
fi
|
||||
readonly artifact_name="$1"
|
||||
|
||||
if [[ "$#" -eq 1 ]]; then
|
||||
artifact_path="./"
|
||||
else
|
||||
artifact_path="$2"
|
||||
fi
|
||||
|
||||
start=$SECONDS
|
||||
"$ci_dir/util/artifacts/download/fetch.sh" "$artifact_name" "$artifact_path"
|
||||
echo "Artifact '$artifact_name' downloaded to '$artifact_path' in $((SECONDS - start)) seconds."
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user