ADR-1177: Containerised self-hosted GitHub Actions runner for Intel Arc SYCL parity CI¶
- Status: Accepted
- Date: 2026-09-04
- Deciders: Lusoris
- Supersedes: none
- Superseded by: none
- Tags: sycl, gpu, ci, runner, arc-a380, docker
Context¶
The VMAFx fork maintains an extensive SYCL GPU backend across all registered feature extractors, subject to strict numeric parity gates against the CPU reference implementation (ADR-0214, ADR-0220, ADR-0234). Hosted GitHub Actions infrastructure (e.g. ubuntu-latest, ubuntu-24.04) provides CPU and software emulation (Mesa lavapipe) but provides no physical Intel discrete GPU silicon or Level-Zero userspace compute runtime (intel-opencl-icd, libze-intel-gpu1). Consequently, SYCL kernel execution was historically unexercised in hosted pull-request CI, relying on manual developer verification or post-merge checks.
The workstation environment running CachyOS contains an Intel Arc A380 discrete GPU (PCI 0000:03:00.0, vendor 0x8086, device 0x56a5, DG2-G10 die class) coexisting with an NVIDIA GeForce RTX 4090 and an AMD Raphael processor integrated GPU. To establish an automated, reliable CI gate for SYCL kernel execution on real Intel silicon without compromising host security, polluting the host OS, or interfering with adjacent accelerators, a containerised self-hosted GitHub Actions runner architecture is required.
Decision¶
- Containerised runner isolation: The runner executes within a dedicated, isolated Docker container (
vmaf-sycl-arc-runner:local, defined indev/Containerfile.runner) built on top of the verified oneAPI environment (vmaf-dev-mcp:local). The runner never runs as a host service or systemd unit. - Strict device node isolation: Only the Intel Arc A380 DRI render node is passed into the container:
- Host path:
/dev/dri/by-path/pci-0000:03:00.0-render(vendor0x8086, device0x56a5),renderD129at the time of writing. Because render-node numbers and PCI BDFs change after a PCI re-enumeration, the compose file takes the node fromARC_RENDER_NODE, resolved bydev/scripts/arc-render-node.sh(exactly one0x8086render node, else it refuses). - Neither the NVIDIA RTX 4090 render node (
renderD128) nor the AMD iGPU render node (renderD130) is mapped. - Verified inside the container via
sycl-ls: Level-Zero and OpenCL platforms see exclusively the Intel Arc A380 Graphics adapter. - Non-root execution and permissions:
- The runner runs as unprivileged user
runner(uid 1001, gid 1001), member of host render group (gid 988) and video group (gid 984). - Container security options specify
seccomp=unconfinedas required by Intel Level-Zero NEO runtime 26.x on Linux kernel >= 7.0 (ADR-0541) to avoidzeInit()permission denials. - The host Docker daemon socket (
/var/run/docker.sock) is NOT mounted into the container. - Ephemeral runner lifecycle:
- Configured with
--ephemeral: the runner registers, processes exactly one job, cleanly unregisters from GitHub Actions, and terminates. - Workspace data is stored in a named Docker volume (
runner-scratch) at/actions-runner/_work, with no host directory bind mounts. The image creates that directory owned byrunnerso the volume is seeded writable (a root-owned mount point madeconfig.shfail withEACCESin the first build). - Container resource limits are bounded to 8 CPUs and 16 GB memory.
- Software version pinning:
- Pinned to GitHub Actions Runner
v2.337.0(linux-x64) with SHA256 checksum verification:70920811a4f8ad4328818682bca5c6469c1c942fab52448868071d0063816613. - Workflow and security gating:
- Workflow
.github/workflows/sycl-parity.ymldefines the REQUIRED CI jobSYCL Parity (Arc A380)(name <= 30 characters and carrying# required-aggregator: SYCL Parity (Arc A380)for PR #1286 compliance). - Strict security guard: Never runs on untrusted fork pull requests (
head.repo.full_name == github.repository). - Job 1 (
runner-available) onubuntu-24.04runsscripts/ci/check-runner-available.sh. The lane is switched by the repository variableSYCL_ARC_RUNNER_ENABLED, not by auto-detecting the runner:GET /repos/{owner}/{repo}/actions/runnersneeds the Administration: read repository permission, which the workflowpermissions:key cannot grant toGITHUB_TOKEN. Lane disabled:available=false, exit 0, Job 2 skips. Lane enabled: the probe queries the runner list with theSYCL_RUNNER_PROBE_TOKENsecret (fine-grained PAT, Administration: read-only) and requires an ONLINE runner labelledsycl-arc; a missing/403 token, no registered runner, or an offline runner all fail loudly (exit 1,::error::) — an API error is never interpreted as "unregistered". - Job 2 (
sycl-parity) runs on[self-hosted, linux, x64, sycl-arc], verifies GPU visibility withsycl-ls, compiles with-Denable_sycl=true -Denable_float=true, runs all 23 SYCL tests viameson test -C core/build --suite sycl, and executesscripts/ci/cross_backend_parity_gate.pywith--gpu-id sycl:0x8086:0x56a5. - Generates and uploads
sycl_parity.jsonandsycl_parity.mdartifacts. - Aggregator integration:
.github/workflows/required-aggregator.ymlincludes'SYCL Parity (Arc A380)'inrequired.- The aggregator reads the same
SYCL_ARC_RUNNER_ENABLEDvariable (no runner API call): lane disabled → absence orskippedis accepted; lane enabled → the job must reportsuccess, and absence orskipped(the probe failed) is a hard CI failure. The operator pauses the lane by flipping the variable tofalsebefore stopping the container (daily-driver workstation). - Test suite tagging:
- All 23 SYCL parity and unit tests in
core/test/meson.buildare registered withsuite : ['fast', 'gpu', 'sycl'].
Alternatives considered¶
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
| Host systemd runner service | Direct hardware and driver access without container overhead | Mutates host system packages; risks container breakout / privilege escalation; exposes RTX 4090 and AMD iGPU; subject to host rolling-release toolchain drift on CachyOS | Violates multi-GPU isolation and host immutability requirements |
Privileged Docker with /dev/dri pass-all | Simple Docker flags (--device /dev/dri) | Exposes RTX 4090 (renderD128) and AMD iGPU (renderD130) inside runner container; breaks isolation | Violates strict requirement to isolate Intel Arc A380 |
| Auto-detect the runner instead of an operator switch | No variable to flip | GITHUB_TOKEN cannot list self-hosted runners (Administration: read is not grantable via permissions:), so a probe that treats a 403 as "unregistered" makes the required check silently green forever; an ephemeral runner is also unregistered between jobs, so "registered" is not a stable signal | Explicit SYCL_ARC_RUNNER_ENABLED variable + PAT-backed online probe; API errors fail loudly |
Hosted oneAPI CPU-device lane (opencl:cpu on ubuntu-24.04) | No self-hosted infrastructure | Runs the SYCL kernels on a CPU device: none of the Arc-specific fp32 / fp64-free behaviour that the ADR-0234 calibration exists for is exercised | Rejected by the maintainer — Arc-specific calibration is the point |
Local-only gate (developer runs --suite sycl before pushing) | Zero CI cost | Not enforced; exactly how #865 merged | Rejected |
| Persistent non-ephemeral runner container with Docker socket | Retains warm build cache across jobs | Runner contamination between untrusted runs; docker socket enables host root escalation; residual files persist | Ephemeral mode with isolated scratch volume is strictly safer |
| Cloud GPU runner (AWS/Azure/GCP) | Fully managed, no local workstation reliance | No major hyperscaler provides affordable Intel Arc discrete GPUs with Level-Zero support | Silicon is physically available on local workstation |
Consequences¶
- Positive:
- Provides real-hardware Intel Arc A380 Level-Zero kernel execution verification in CI.
- Guarantees complete isolation from NVIDIA RTX 4090 and AMD iGPU.
- Ephemeral runner lifecycle prevents workspace pollution and credential retention.
- Required checks aggregator tolerates absence while the lane is disabled and fails loudly (never silently green) when the lane is enabled and the runner is missing, offline, or the probe token is rejected.
- Targeted Meson test suite (
--suite sycl) runs all 23 SYCL tests in ~15 seconds without running CUDA tests. - Negative:
- Requires workstation operator action to obtain a registration token and launch the container; the ephemeral runner disappears after each job, so a host-side loop (runbook §3) is needed for a review session, and the lane must be disabled before the workstation is used for something else.
- Requires a fine-grained PAT (
SYCL_RUNNER_PROBE_TOKEN, Administration: read-only, single repository) as a repository secret. - Neutral / follow-ups:
- Operator runbook documented in
docs/development/ci-self-hosted-sycl.md. - Future calibration sweeps for remaining SYCL features (
adm,vif,motion,ciede,ssimulacra2Kahan-IIR).
References¶
- Maintainer decision (paraphrased, 2026-09-04): register the workstation's Arc A380 as a self-hosted runner so SYCL parity becomes a required CI check; the runner must be containerised, expose only the Arc, and never run fork code.
- ADR-0214 — GPU-parity CI gate.
- ADR-0220 — SYCL fp64-less device contract.
- ADR-0234 — Per-GPU-generation ULP calibration table.
- ADR-0313 — Required checks aggregator.
- ADR-0541 — dev-container SYCL/HIP runtime fix (NEO matched to the host kernel,
seccomp=unconfined). - Research-0985 §3 — DG2-G10 float_ssim two-cause divergence; source of the
float_ssim: 5.0e-4(places=3) tolerance, matchingPARITY_TOLincore/test/test_sycl_float_ssim_parity.c. - GitHub REST:
GET /repos/{owner}/{repo}/actions/runnersrequires Administration (read); thepermissions:key forGITHUB_TOKENhas noadministrationscope (docs, workflow-syntax § permissions, checked 2026-09-05).