Skip to content

Make gpu.md executable (@example) and decide on a GPU-backed docs build for dev/stable #885

Description

@ocots

Summary

docs/src/solve/gpu.md is the only page in docs/ that opts out of execution
(Draft = true, plain ```julia fences instead of ```@example). It reads as
reference prose, not as tested output, and it's currently annotated as such. This issue is
about (1) making the page genuinely executable everywhere, using a pattern already proven
elsewhere in these docs, and (2) a follow-up, cross-repo question about whether we also want
the deployed dev/stable docs to show real GPU numbers rather than a graceful CPU fallback.

Part 1 — make gpu.md executable (self-contained, no CI change needed)

We don't need a GPU to make this page @example-based. docs/src/getting-started/guided-tour.md:376-386
already does exactly this, today, on plain ubuntu-latest:

try
    global sol_gpu = solve(goddard, :gpu; grid_size=1000, display=false)
    println("GPU solve succeeded — a functional GPU is available.")
catch e
    println("GPU solve failed, as expected without a functional GPU.")
    println("CUDA.functional() = ", CUDA.functional())
    println("Exception: ", first(sprint(showerror, e), 400))
end

That block is a real @example, executed at every build, on every runner — it just prints a
different (still true) story depending on whether CUDA is functional. gpu.md should adopt
the same pattern instead of Draft = true:

  • Drop the @meta Draft = true block and the "This page doesn't execute" admonition.
  • Convert the julia fences under "Prerequisites", "Descriptive mode", "Explicit mode" and
    "Performance notes" to @example gpu (shared session) blocks.
  • Wrap the actual :gpu solve calls in the same CUDA.functional() ? … : … /
    try/catch style as guided-tour.md, so the page is honest about what ran where.
  • docs/Project.toml already depends on CUDA, MadNLPGPU and ExaModels (used by
    guided-tour.md and others already), so no dependency work is needed for this part.
  • The "What combinations work" section (OptimalControl.ADNLP{GPU}()TypeError, etc.) is
    actually CPU-safe to execute for real right now — those are type-system errors, not
    hardware-dependent ones — and should move from prose to executed @example/@repl blocks
    wrapped in try/catch showing the real exception, same spirit as everything else on the
    page.

This part is low-risk, self-contained in this repo, and doesn't depend on anything below.

Part 2 — real GPU numbers in the deployed dev/stable docs (cross-repo, needs a decision)

Part 1 makes every build honest, but on ubuntu-latest (what Documentation.yml uses today)
the deployed page will always show the CPU-fallback branch — never real GPU output — because
GitHub-hosted runners have no CUDA device. If we want the live dev/stable docs to show actual
GPU numbers, the Documenter build itself has to run on a CUDA-capable machine at least for the
runs that get deployed.

What's actually available, and a naming wrinkle worth resolving first

The org has two self-hosted GPU boxes, not one, and they are not interchangeable/renamed
versions of each other — see Handbook/WORKFLOWS.md:151-173:

  • kkt — the original self-hosted GPU runner.
  • occidata — added 2026-08-24, "for heavier GPU workloads", alongside kkt, not a
    replacement (per the Handbook). It runs as a SLURM job
    (CTActions/.github/workflows/occidata-runner-maintenance.yml,
    occidata-runner-watchdog.yml: sbatch /projects/ctb/gha-runner/runner.slurm on
    occidata-cluster.irit.fr), watched by a daily cron (15 3 * * *) that restarts it if
    offline — i.e. it can legitimately be queued or down for up to ~24h before anything notices.

OptimalControl's own CI is still on kkt only.github/workflows/CI.yml has
test-gpu-kkt (runs_on: '[["kkt"]]', label run ci kkt-runner), no occidata job, no
occidata-runner label defined in this repo (gh label list confirms). Meanwhile
CTParser.jl, CTDirect.jl have already moved their GPU CI to occidata only, CTFlows.jl
runs both, and Handbook/WORKFLOWS.md's own summary table (row occidata-runner) still only
lists CTFlows.jl (CTLie planned) — i.e. the Handbook table itself is already lagging the actual
per-repo CI.yml files. Worth deciding explicitly, not by drift:

  1. Does OptimalControl's GPU-backed docs build target kkt, occidata, or both?
  2. Should this be the moment OptimalControl's GPU test CI also picks up occidata
    (matching CTParser/CTDirect), independent of the docs question?

I don't have a strong opinion on kkt vs occidata from here — whichever box is expected to
carry heavier/longer jobs reliably is probably right for a full Documenter build (it's a bigger
job than a test suite run), which points at occidata, but that's a call for whoever owns
those machines.

Reliability constraint this drives the design

Given the SLURM-queue + daily-watchdog behavior above, a GPU self-hosted box should not be
the sole path to publishing docs — Documentation.yml deploys on every push to main (dev)
and every version tag (stable); if the only build path depends on a box that can be queued or
silently offline for hours, doc publishing stalls with it.

Proposed shape: a job that attempts the GPU runner first (with a bounded timeout to absorb
SLURM queueing) and falls back to the current ubuntu-latest build automatically if it
doesn't complete in time — for both the main push (dev) and tag (stable) triggers. This needs
changes in the reusable CTActions/.github/workflows/documentation.yml (currently a single
hardcoded runs-on: ubuntu-latest job) plus the calling Documentation.yml here. PR-preview
builds (run documentation label) can stay ubuntu-latest-only — no need for real GPU numbers
on every PR iteration, and it keeps preview builds fast.

This part touches a shared reusable workflow used by every package in the org
(documentation.yml), so it should be scoped and reviewed as its own piece of work, not bundled
silently into a docs content PR.

Suggested split

  • Part 1 (this repo only): rewrite gpu.md to be fully @example-based, following
    guided-tour.md's CUDA.functional()/try-catch pattern. No CI change required.
  • Decision: kkt vs occidata (or both) as the target for a GPU-backed docs build,
    and whether to also bring OptimalControl's GPU test CI onto occidata while we're at it.
  • Part 2 (cross-repo, CTActions + here): add a GPU-attempt-with-CPU-fallback job to
    the reusable documentation.yml, wired for the main-push and tag triggers here.
  • Update Handbook/WORKFLOWS.md §3.1/§3.2 once the above lands, since its occidata-runner
    adoption table is already out of sync with CTParser.jl/CTDirect.jl's actual CI.yml.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions