Skip to content

ci: Dockerized GPU self-hosted runner with cron auto-restart#1348

Merged
kunxian-xia merged 12 commits into
masterfrom
ci/gpu-runner
Jun 4, 2026
Merged

ci: Dockerized GPU self-hosted runner with cron auto-restart#1348
kunxian-xia merged 12 commits into
masterfrom
ci/gpu-runner

Conversation

@kunxian-xia

@kunxian-xia kunxian-xia commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

What

A self-hosted GPU CI runner for Ceno, plus a workflow that runs GPU integration tests on it.

ci/gpu-runner/ — the runner

  • Dockerfile: nvidia/cuda:12.8-devel-ubuntu24.04 + actions runner + pinned Rust toolchain. No secrets baked in.
  • entrypoint.sh: mints a fresh registration token from a PAT each start (survives restarts); runs an ephemeral runner (clean container per job).
  • start-runner.sh: builds + docker run --gpus all; persists cargo registry and CARGO_TARGET_DIR on volumes for warm rebuilds.
  • watchdog.sh: cron-driven; restarts the container on stop/crash or GPU-unreachable. flock-guarded against overlap.
  • README.md: host setup, secrets, cron registration.

.github/workflows/gpu-integration.yml — the test

Proves an example (default keccak_syscall) with --features gpu on the gpu-labeled runner. Triggered manually (workflow_dispatch) or by a gpu-ci PR label. Release-only by design (debug + release would compile the workspace twice). Steps: load CENO_GPU_DEPLOY_KEY → clone private ceno-gpu (--recurse-submodules) → activate its Cargo [patch] → build + prove.

Secrets

  • GITHUB_PAT (host runner.env, gitignored): registers the runner.
  • CENO_GPU_DEPLOY_KEY (repo secret): read-only deploy key for the private ceno-gpu backend, loaded per-job via ssh-agent.

No existing workflows are modified.

🤖 Generated with Claude Code

Adds ci/gpu-runner/: a GPU-capable, ephemeral GitHub Actions runner
packaged as a Docker image and kept alive by a cron watchdog.

- Dockerfile: nvidia/cuda 12.8 devel (ubuntu24.04) + actions runner +
  Rust toolchain + git-lfs/m4; carries no secrets. Rewrites only the
  private ceno-gpu-mock dep URL https->ssh (public deps stay on HTTPS).
- entrypoint.sh: mints a fresh registration token from a PAT on every
  start (survives restarts), runs an ephemeral runner.
- start-runner.sh: builds/(re)launches with --gpus all and persistent
  cargo-registry + CARGO_TARGET_DIR volumes for warm incremental builds.
- watchdog.sh: per-minute cron check; restarts on container stop or
  GPU-unreachable.
- README.md: host setup, secrets (host PAT + repo deploy key via
  ssh-agent), and cron registration.

The runner-registration PAT lives in ci/gpu-runner/runner.env, which is
gitignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds .github/workflows/gpu-integration.yml: proves an example (default
keccak_syscall) end-to-end with the GPU prover (--features gpu) on the
self-hosted `gpu` runner.

- Trigger: workflow_dispatch (with an `example` input) or a PR labeled
  `gpu-ci` (opt-in; GPU runner time is scarce).
- Loads CENO_GPU_DEPLOY_KEY into ssh-agent and clones the private
  ceno-gpu backend to ../ceno-gpu (the path Cargo.toml's active [patch]
  expects) before building.
- Runs the example single-shard and multi-shard; no actions/cache since
  the runner persists target/ on a host volume via CARGO_TARGET_DIR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kunxian-xia kunxian-xia linked an issue Jun 3, 2026 that may be closed by this pull request
kunxian-xia and others added 5 commits June 4, 2026 00:42
The committed Cargo.toml ships the ceno_gpu -> ../ceno-gpu/cuda_hal
[patch] commented out, so a default --features gpu build resolves the
public, API-incompatible ceno-gpu-mock and fails to compile. After
cloning the private ceno-gpu backend, uncomment the patch with sed and
fail fast if it's still commented (so we can never silently build the
mock again).

Also document that the job is release-only by design: running debug +
release would compile the workspace (and the CUDA cuda_hal crate) twice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace a machine-specific absolute path with /ABS/PATH placeholder.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cron runs the watchdog every minute; the first invocation triggers a
long image build during which the container doesn't yet exist, so later
ticks would each launch another start-runner.sh and race on docker
build/run. Guard with a non-blocking flock so overlapping ticks exit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
If the crontab user can't write the redirect target, cron aborts the
line before watchdog.sh runs, so it silently never executes and the
error is only mailed to the user. Default the example to $HOME and
document the /var/log + docker-group requirements.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cuda_hal depends on the public `sppark` submodule (ceno-gpu/sppark/rust);
a plain clone omits it and cargo fails with "failed to read
sppark/rust/Cargo.toml". Clone with --recurse-submodules.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kunxian-xia kunxian-xia requested a review from hero78119 June 3, 2026 17:58
kunxian-xia and others added 5 commits June 4, 2026 10:41
The e2e binary defaults to LevelFilter::DEBUG when RUST_LOG is unset
(ceno_zkvm/src/bin/e2e.rs), producing ~1400 debug lines per run. Pin
RUST_LOG=info for the GPU steps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract the proving time (run_e2e_proof span from `--profiling 1`) and
feed it to benchmark-action/github-action-benchmark (customSmallerIsBetter).
History is stored on gh-pages: master pushes append the baseline; PRs
compare and fail on a >10% regression (alert-threshold 110%,
fail-on-alert), commenting on the PR.

Adds a push:master trigger to seed/update the baseline, a dedicated
build step, and contents/pull-requests write perms for the action.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v1.21.0+ run on node24, which the runner image (actions runner v2.321.0)
doesn't support, failing at "Set up job". v1.20.7 is the last node20
release. Bump RUNNER_VERSION to a node24-capable runner to unpin later.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The profiling tree's root is ZKVM_create_proof (pure proof generation),
not run_e2e_proof, so the previous grep found nothing and the step
errored. Parse ZKVM_create_proof, and guard the grep with `|| true` so a
no-match yields a clear error instead of being swallowed by set -e.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
github-action-benchmark runs `git switch gh-pages` in the checkout, which
aborts while the "Activate patch" edits to Cargo.toml/Cargo.lock are
uncommitted. Move the multi-shard run ahead of it (still needs the
patch), then `git checkout -- Cargo.toml Cargo.lock` to clean the tree
before the benchmark action. bench.json is untracked and survives.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@hero78119 hero78119 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@kunxian-xia kunxian-xia added this pull request to the merge queue Jun 4, 2026
Merged via the queue into master with commit 7d8086c Jun 4, 2026
6 checks passed
@kunxian-xia kunxian-xia deleted the ci/gpu-runner branch June 4, 2026 06:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add gpu CI runner

2 participants