Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
000d03f
Add Python-HPC libraries/programming-models tutorial: SWE solver
aaronj0 Jun 19, 2026
c8cd099
pyhpc: rename tutorial directory from python-hpc-libraries-swe to pyhpc
brycelelbach Jun 19, 2026
597632f
pyhpc: port accelerated-python exercises and renumber to 00-13
brycelelbach Jun 19, 2026
09f2b86
pyhpc: adapt Docker, requirements, and tests for ported exercises
brycelelbach Jun 19, 2026
48dcae2
pyhpc: rewrite README and add 8-hour syllabus
brycelelbach Jun 19, 2026
35ae0d5
pyhpc: ignore runtime artifacts from the ported kernel/async notebooks
brycelelbach Jun 19, 2026
2a08131
pyhpc: make swe_core importable from the solutions directory
brycelelbach Jun 19, 2026
4aca2bd
ci: scope tutorial image build to pyhpc and trigger on event branches
brycelelbach Jun 20, 2026
d790bc6
ci: grant fs.read entitlement for any runner home in the image build
brycelelbach Jun 20, 2026
859d6eb
ci: rewrite generated compose image owner to the repo owner
brycelelbach Jun 20, 2026
55c0e95
pyhpc: rename notebooks to <NN>[__<EXAMPLE>][__<TECH>][__<CONCEPT>] s…
brycelelbach Jun 21, 2026
e8913ce
pyhpc: rename 8-hour syllabus, drop numbers from SWE titles
brycelelbach Jun 21, 2026
8ed1636
pyhpc: rewrite notebook titles to match the new names
brycelelbach Jun 21, 2026
c3f8240
Move thrust cpp source into file, minor readme update
aaronj0 Jun 21, 2026
6d91eb9
Add material for Python/C++ Interop + minor docker updates
aaronj0 Jun 21, 2026
0a166f1
Merge PR #2 (Python/C++ interop) onto event, conformed to naming scheme
brycelelbach Jun 21, 2026
9b6b037
pyhpc: reorder 8-hour syllabus name to cupy_mpi4py_jax
brycelelbach Jun 21, 2026
8a3012b
pyhpc: gitignore run artifacts under notebooks/solutions/
brycelelbach Jun 21, 2026
d8841ee
Port dev containers to CSCS rootless runtimes
brycelelbach Jun 25, 2026
30f02e8
Make PyHPC container builds architecture-aware
brycelelbach Jul 21, 2026
e26f0a7
Harden rootless runtime support on ARM64
brycelelbach Jul 21, 2026
65c89c8
Run PyHPC MPI notebooks under CSCS Slurm
brycelelbach Jul 21, 2026
a098b28
Stabilize PyHPC notebooks on CSCS ARM64
brycelelbach Jul 21, 2026
184eed9
Document and automate CSCS PyHPC validation
brycelelbach Jul 21, 2026
f559db5
Keep base image mirroring changes on main
brycelelbach Jul 21, 2026
433c1b5
Build PyHPC images for ARM64 in GitHub CI
brycelelbach Jul 21, 2026
fa03125
Make multi-architecture builds tutorial-driven
brycelelbach Jul 21, 2026
9d09380
Address CSCS event branch review feedback
brycelelbach Jul 21, 2026
c55763d
Address follow-up CSCS review feedback
brycelelbach Jul 21, 2026
235cec0
Address CSCS review feedback and add web deployment
brycelelbach Jul 22, 2026
20fe285
Add scripted CSCS web deployment workflow
brycelelbach Jul 23, 2026
caeeaf5
Harden CSCS web deployment on Daint
brycelelbach Jul 23, 2026
f4bbb8d
Simplify CSCS tutorial bootstrap
brycelelbach Jul 24, 2026
f463357
Clarify CSCS event branch default
brycelelbach Jul 24, 2026
5c02432
Default CSCS identity and suffix helpers
brycelelbach Jul 24, 2026
2697936
Stream CSCS helpers and discover project
brycelelbach Jul 24, 2026
f8adc0a
Use HTTP for CSCS web services
brycelelbach Jul 24, 2026
07bf3cb
Avoid CSCS compute host-key prompt
brycelelbach Jul 24, 2026
c23ec73
Reorder PyHPC profiling notebooks
brycelelbach Jul 24, 2026
a259a64
Make signature CI pull-request aware
brycelelbach Jul 25, 2026
5ac83ce
Support process-substituted CSCS runner
brycelelbach Jul 25, 2026
7c254dd
Update clean CSCS tutorial checkouts
brycelelbach Jul 25, 2026
9a361c7
Tune PyHPC power iteration for H100
brycelelbach Jul 24, 2026
5049f0a
Restart web service group after Jupyter shutdown
brycelelbach Jul 25, 2026
9d19b5f
Rename PyHPC eight-hour tutorial
brycelelbach Jul 25, 2026
4ffcb4d
Rename PyHPC tutorial duration to two days
brycelelbach Jul 25, 2026
4e17de9
PyHPC: add profiler-backed cell magics
brycelelbach Jul 25, 2026
2ff381a
Tune PyHPC async check-frequency sweep
brycelelbach Jul 25, 2026
6b3ba5f
Fix PyHPC Nsight Systems notebook flow
brycelelbach Jul 25, 2026
1970629
Default PyHPC kernel notebooks to Nsight Compute
brycelelbach Jul 25, 2026
971212e
Use CSCS-compatible Nsightful cell magics
brycelelbach Jul 25, 2026
cb9b642
Allow approved notebook kernels and update Nsightful
brycelelbach Jul 25, 2026
390f7b5
Update Nsightful compatibility fix
brycelelbach Jul 25, 2026
c1c0a9e
Update Nsightful tested tip
brycelelbach Jul 25, 2026
d700242
Update Nsightful CI-clean tip
brycelelbach Jul 25, 2026
684e4da
Update Nsightful green CI tip
brycelelbach Jul 25, 2026
0ac0e60
Use verified Nsight Systems readiness check
brycelelbach Jul 25, 2026
3820576
Use single Nsight Systems readiness path
brycelelbach Jul 25, 2026
306dd88
Use merged Nsightful readiness fix
brycelelbach Jul 25, 2026
24807e2
PyHPC: Rewrite mpi4py lesson with heat stencil.
brycelelbach Jul 21, 2026
7185c6e
Remove JupyterLab Nsight extension from event images
brycelelbach Jul 26, 2026
288cf8b
Apply PyHPC Matplotlib theme lazily
brycelelbach Jul 26, 2026
c2e90f3
PyHPC: Revamp SWE tutorial with improved GPU kernels, benchmarks and …
aaronj0 Jul 25, 2026
79d2b51
PyHPC: Make machine probes portable to aarch64.
aaronj0 Jul 25, 2026
841077b
Address PyHPC profiler notebook review
brycelelbach Jul 26, 2026
b974e14
Configure NCU kernel filters in wrapper
brycelelbach Jul 26, 2026
1df7292
Correct profiler kernel setup comment
brycelelbach Jul 26, 2026
289a869
Pass NCU kernel filters from notebook cells
brycelelbach Jul 26, 2026
95668e4
Pin merged Nsightful kernel-filter support
brycelelbach Jul 26, 2026
1977966
Fix PyHPC Daint notebook execution
brycelelbach Jul 26, 2026
9ab4e66
Ignore untracked files when updating CSCS checkout
brycelelbach Jul 26, 2026
f9a1ab3
PyHPC: Fix mpi4py setup and further tutorial improvements
aaronj0 Jul 26, 2026
c7dac42
PyHPC: Restore the interop and mpi4py wording from before the merge.
aaronj0 Jul 27, 2026
e25db3c
PyHPC: Merge separate but adjacent markdown cells.
aaronj0 Jul 27, 2026
7161916
PyHPC: Fix JAX scan re-tracing and source the breakdown from Nsight S…
aaronj0 Jul 27, 2026
fa376b5
PyHPC: Use one checkout for CSCS containers.
brycelelbach Jul 27, 2026
e070109
PyHPC: Support CSCS Slurm reservations.
brycelelbach Jul 27, 2026
6d2455f
PyHPC: Upload the CSCS compose adapter.
brycelelbach Jul 27, 2026
69cb867
PyHPC: Pass the CSCS adapter into Slurm.
brycelelbach Jul 27, 2026
904ace6
PyHPC: Avoid redundant CSCS branch switches.
brycelelbach Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
403 changes: 282 additions & 121 deletions .github/workflows/build-brev-tutorial-docker-images.yml

Large diffs are not rendered by default.

31 changes: 26 additions & 5 deletions .github/workflows/pr-comment-git-signatures.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,26 @@ jobs:
const pullRequests = context.payload.workflow_run.pull_requests;
let prNumber;
if (pullRequests && pullRequests.length > 0) {
const pullRequest = pullRequests[0];
const currentRepo = process.env.GITHUB_REPOSITORY;
const currentPullPrefix =
`${process.env.GITHUB_API_URL}/repos/${currentRepo}/pulls/`;
const baseRepo = pullRequest.base?.repo?.full_name;
if ((pullRequest.url && !pullRequest.url.startsWith(currentPullPrefix)) ||
(baseRepo && baseRepo !== currentRepo)) {
console.log('Associated pull request belongs to another repository; skipping.');
return;
}
prNumber = pullRequests[0].number;
} else {
try {
prNumber = parseInt(fs.readFileSync('pr_number', 'utf8').trim());
} catch (e) {
console.log('No PR number found, skipping comment.');
const mirror = context.payload.workflow_run.head_branch.match(
/^pull-request\/([1-9]\d*)$/
);
if (!mirror) {
console.log('Workflow run is not associated with a pull request; skipping.');
return;
}
prNumber = Number(mirror[1]);
}
if (!prNumber || isNaN(prNumber)) {
console.log('No valid PR number found, skipping comment.');
Expand All @@ -55,6 +67,14 @@ jobs:
return;
}

const { data: pullRequest } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
const baseCloneUrl = pullRequest.base.repo.clone_url;
const baseRef = pullRequest.base.ref;

const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${{ github.event.workflow_run.id }}`;

const commentBody = `
Expand Down Expand Up @@ -85,7 +105,8 @@ jobs:

2. **Re-sign your commits:**
\`\`\`bash
git rebase -i origin/main --exec "git commit --amend --no-edit -S"
git fetch ${baseCloneUrl} ${baseRef}
git rebase -i FETCH_HEAD --exec "git commit --amend --no-edit -S"
git push --force-with-lease
\`\`\`

Expand Down
135 changes: 112 additions & 23 deletions .github/workflows/test-git-signatures.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ concurrency:
# Minimal permissions - only read access needed for checks
permissions:
contents: read
pull-requests: read

jobs:
test-git-signatures:
Expand All @@ -23,40 +24,133 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Need full history to compare with main
fetch-depth: 0 # Needed for merge-group commit ranges

- name: Check commit signatures
id: signature-check
uses: actions/github-script@v7
with:
script: |
const { execSync } = require('child_process');
const fs = require('fs');
const { execFileSync } = require('child_process');

// Get current branch name
const currentBranch = process.env.GITHUB_REF_NAME;
console.log(`Current branch: ${currentBranch}`);

// Skip signature check on main branch to avoid failing on historical unsigned commits
// Main contains historical unsigned commits. Feature and event
// branches are checked when their commits are introduced.
if (currentBranch === 'main') {
console.log('On main branch - skipping signature check for historical commits.');
console.log('Signature verification only runs on PR branches.');
return;
}

// Get commits that are on this branch but not on origin/main
let commitShas;
try {
const output = execSync('git rev-list origin/main..HEAD', { encoding: 'utf-8' });
commitShas = output.trim().split('\n').filter(sha => sha.length > 0);
} catch (error) {
console.log('Could not compare with origin/main, checking all commits on this branch.');
// Fallback: just check the current commit
const unique = values => [...new Set(values.filter(Boolean))];

const commitsForOpenPullRequests = async () => {
try {
const mirror = currentBranch.match(/^pull-request\/(\d+)$/);
let pulls;
if (mirror) {
const pull = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: Number(mirror[1])
});
pulls = [pull.data];
} else {
pulls = await github.paginate(
github.rest.repos.listPullRequestsAssociatedWithCommit,
{
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: context.payload.after,
per_page: 100
}
);
pulls = pulls.filter(pull =>
pull.state === 'open' && pull.head.sha === context.payload.after
);
}

if (pulls.length === 0) {
return null;
}

const shas = [];
for (const pull of pulls) {
const targetOwner = pull.base.repo.owner.login;
const targetRepo = pull.base.repo.name;
console.log(
`Checking commits from ${targetOwner}/${targetRepo}#${pull.number} ` +
`(base=${pull.base.ref}).`
);
const commits = await github.paginate(
github.rest.repos.compareCommitsWithBasehead,
{
owner: targetOwner,
repo: targetRepo,
basehead: `${pull.base.sha}...${context.payload.after}`,
per_page: 100
},
response => response.data.commits
);
shas.push(...commits.map(commit => commit.sha));
}
return unique(shas);
} catch (error) {
throw new Error(`Could not determine pull-request commits: ${error.message}`);
}
};

const commitsFromPush = () => {
const pushed = context.payload.commits || [];
if (pushed.length >= 2048) {
throw new Error(
'Push exposes 2048 commits, the event payload limit; ' +
'refusing to skip signature checks.'
);
}
return pushed.map(commit => commit.id);
};

const commitsInRange = (base, head) => {
const shaPattern = /^[0-9a-f]{40}$/;
if (!shaPattern.test(base) || !shaPattern.test(head)) {
throw new Error(`Invalid commit range: ${base}..${head}`);
}
const output = execFileSync(
'git', ['rev-list', `${base}..${head}`], { encoding: 'utf-8' }
);
return output.trim().split('\n').filter(Boolean);
};

let commitShas = null;
if (context.eventName === 'push') {
if (context.payload.deleted) {
console.log('Branch deleted - no commits to check.');
return;
}

commitShas = await commitsForOpenPullRequests();
if (commitShas === null) {
if (context.payload.created) {
console.log('New branch without an open PR - checking pushed commits.');
} else {
console.log('No open PR found - checking commits introduced by this push.');
}
commitShas = commitsFromPush();
}
} else if (context.eventName === 'merge_group') {
const mergeGroup = context.payload.merge_group;
commitShas = commitsInRange(mergeGroup.base_sha, mergeGroup.head_sha);
} else {
commitShas = [process.env.GITHUB_SHA];
}

commitShas = unique(commitShas);

if (commitShas.length === 0) {
console.log('No new commits to check (branch is up to date with main).');
console.log('No new commits to check.');
return;
}

Expand Down Expand Up @@ -90,8 +184,10 @@ jobs:
});
}
} catch (error) {
console.log(`⚠ ${sha.substring(0, 7)} - Could not verify (${error.message})`);
// Don't fail on API errors for individual commits
core.setFailed(
`Could not verify ${sha.substring(0, 7)} via GitHub API: ${error.message}`
);
return;
}
}

Expand All @@ -111,11 +207,6 @@ jobs:
console.log(`\n✅ All ${commitShas.length} commit(s) are properly signed.`);
}

- name: Get PR info
id: get-pr-info
if: always() && startsWith(github.ref_name, 'pull-request/')
uses: nv-gha-runners/get-pr-info@main

- name: Save PR comment data
if: always()
run: |
Expand All @@ -124,8 +215,6 @@ jobs:
cat << 'EOF' > ./pr-comment-data/unsigned_commits
${{ steps.signature-check.outputs.unsigned_commits }}
EOF
PR_NUMBER='${{ startsWith(github.ref_name, 'pull-request/') && fromJSON(steps.get-pr-info.outputs.pr-info).number || '' }}'
echo "${PR_NUMBER}" > ./pr-comment-data/pr_number

- name: Upload PR comment data
if: always()
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The following interactive tutorials are available and can be used on [NVIDIA Bre
| [CUDA C++ Tutorial](tutorials/cuda-cpp/README.md) | [docker-compose.yml](tutorials/cuda-cpp/brev/docker-compose.yml) | L40S, L4, or T4 | Crusoe or any other with Flexible Ports |
| [Standard Parallelism Tutorial](tutorials/stdpar/README.md) | [docker-compose.yml](tutorials/stdpar/brev/docker-compose.yml) | 4xL4, 2xL4, 2xL40S, or 1x L40S | GCP, AWS, or any other with Flexible Ports and Linux 6.1.24+, 6.2.11+, or 6.3+ (for HMM) |
| [Accelerated Python Tutorial](tutorials/accelerated-python/README.md) | [docker-compose.yml](tutorials/accelerated-python/brev/docker-compose.yml) | L40S, L4, or T4; 4xL4 or 2xL4 for distributed | Crusoe or any other with Flexible Ports |
| [PyHPC Tutorial](tutorials/pyhpc/README.md) | [docker-compose.yml](tutorials/pyhpc/brev/docker-compose.yml) | L40S, L4, or T4 | Crusoe or any other with Flexible Ports; host driver must support CUDA 13 |
| [nvmath-python Tutorial](tutorials/nvmath-python/README.md) | [docker-compose.yml](tutorials/nvmath-python/brev/docker-compose.yml) | 4xL4, 2xL4, 2xL40S, or 1x L40S | Crusoe or any other with Flexible Ports |
| [CUDA Python Tutorial - CuPy, cuDF, CCCL, & Kernels - 8 Hours](tutorials/accelerated-python/notebooks/syllabi/cuda_python__cupy_cudf_cccl_kernels__8_hours.ipynb) | [docker-compose.yml](https://github.com/NVIDIA/accelerated-computing-hub/blob/generated/main/tutorials/accelerated-python/notebooks/syllabi/cuda_python__cupy_cudf_cccl_kernels__8_hours__docker_compose.yml) | L40S, L4, or T4 | Crusoe or any other with Flexible Ports |
| [CUDA Python Tutorial - cuda.core & CCCL - 2 Hours](tutorials/accelerated-python/notebooks/syllabi/cuda_python__cuda_core_cccl__2_hours.ipynb) | [docker-compose.yml](https://github.com/NVIDIA/accelerated-computing-hub/blob/generated/main/tutorials/accelerated-python/notebooks/syllabi/cuda_python__cuda_core_cccl__2_hours__docker_compose.yml) | L40S, L4, or T4 | Crusoe or any other with Flexible Ports |
Expand Down
7 changes: 5 additions & 2 deletions brev/dev-build.bash
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#! /bin/bash

# This script builds Docker containers for tutorials.
# This script builds containers for tutorials.
#
# Usage:
# ./dev-build.bash [--no-cache] [<tutorial-name>]
Expand Down Expand Up @@ -29,6 +29,9 @@ done
SCRIPT_PATH=$(cd $(dirname ${0}); pwd -P)
REPO_ROOT=$(cd ${SCRIPT_PATH}/..; pwd -P)

source "${SCRIPT_PATH}/dev-common.bash"
setup_container_engine

# Function to build a single tutorial
build_tutorial() {
local ACH_TUTORIAL_PATH=${1}
Expand All @@ -55,7 +58,7 @@ build_tutorial() {
echo "Dockerfile generated successfully"
fi

docker compose --progress=plain -f "${ACH_TUTORIAL_PATH}/brev/docker-compose.yml" build ${NO_CACHE}
compose -f "${ACH_TUTORIAL_PATH}/brev/docker-compose.yml" build ${NO_CACHE}

echo "Successfully built image for ${ACH_TUTORIAL}"
echo ""
Expand Down
Loading
Loading