Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
392 changes: 392 additions & 0 deletions .github/workflows/vulkan-gpu-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,392 @@
name: Vulkan GPU Gate

on:
# The controller is loaded from the protected default branch. It may inspect
# fork metadata, but it never checks out or executes a fork head.
pull_request_target:
branches: [main]
types: [opened, synchronize, reopened, ready_for_review]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: vulkan-gpu-gate-${{ github.event.pull_request.merge_commit_sha || github.event.pull_request.head.sha || github.sha }}
queue: max
Comment thread
Irk2wd marked this conversation as resolved.

jobs:
trust_policy:
name: Trust Policy
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
trusted: ${{ steps.policy.outputs.trusted }}
target_sha: ${{ steps.policy.outputs.target_sha }}
reason: ${{ steps.policy.outputs.reason }}
steps:
- name: Classify source without executing it
id: policy
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }}
BASE_REPOSITORY: ${{ github.repository }}
WORKFLOW_REF: ${{ github.ref }}
PR_DRAFT: ${{ github.event.pull_request.draft }}
TARGET_SHA: ${{ github.event.pull_request.merge_commit_sha || github.event.pull_request.head.sha || github.sha }}
run: |
set -euo pipefail
trusted=true
reason=trusted
if [[ "$EVENT_NAME" == "pull_request_target" && "$HEAD_REPOSITORY" != "$BASE_REPOSITORY" ]]; then
trusted=false
reason=fork_requires_reviewed_mirror
fi
if [[ "$EVENT_NAME" == "pull_request_target" && "$PR_DRAFT" == "true" ]]; then
trusted=false
reason=draft_requires_ready_for_review
fi
if [[ "$EVENT_NAME" == "workflow_dispatch" && "$WORKFLOW_REF" != "refs/heads/main" ]]; then
trusted=false
reason=manual_dispatch_requires_main
fi
echo "trusted=$trusted" >> "$GITHUB_OUTPUT"
echo "target_sha=$TARGET_SHA" >> "$GITHUB_OUTPUT"
echo "reason=$reason" >> "$GITHUB_OUTPUT"
if [[ "$trusted" != "true" ]]; then
echo "Persistent GPU execution denied: $reason"
echo "Fork changes must be reviewed and mirrored to a branch in $BASE_REPOSITORY."
fi

status_pending:
name: Initialize Gate Status
runs-on: ubuntu-latest
if: always() && needs.trust_policy.result == 'success'
needs: trust_policy
timeout-minutes: 5
permissions:
statuses: write
steps:
- name: Mark the tested commit pending before GPU execution
shell: bash
env:
GH_TOKEN: ${{ github.token }}
TARGET_SHA: ${{ needs.trust_policy.outputs.target_sha }}
run: |
set -euo pipefail
run_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
gh api --method POST \
"repos/$GITHUB_REPOSITORY/statuses/$TARGET_SHA" \
-f state=pending \
-f context="Vulkan GPU Gate" \
-f description="Real Vulkan GPU validation is queued" \
-f target_url="$run_url" >/dev/null

gpu_validation:
name: Vulkan GPU Validation
needs: [trust_policy, status_pending]
if: >-
needs.trust_policy.outputs.trusted == 'true' &&
needs.status_pending.result == 'success'
runs-on: [self-hosted, Windows, X64, label-valerie]
environment: vulkan-gpu-persistent
timeout-minutes: 120
concurrency:
group: moerengine-gpu-valerie
queue: max
env:
BUILD_DIR: ${{ runner.temp }}\moerengine-gpu-${{ github.run_id }}-${{ github.run_attempt }}\build
OUTPUT_DIR: ${{ runner.temp }}\moerengine-gpu-${{ github.run_id }}-${{ github.run_attempt }}\output
EVIDENCE_DIR: ${{ github.workspace }}\target\validation\gpu-ci\${{ github.run_id }}-${{ github.run_attempt }}
MOER_GATE_SHA: ${{ needs.trust_policy.outputs.target_sha }}
MOER_GATE_PR_NUMBER: ${{ github.event.pull_request.number }}
MOER_GATE_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
steps:
- name: Checkout trusted source
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
repository: ${{ github.repository }}
ref: ${{ needs.trust_policy.outputs.target_sha }}
clean: true
fetch-depth: 1
persist-credentials: false
submodules: recursive

- name: Parser contract tests
timeout-minutes: 5
shell: powershell
run: |
$ErrorActionPreference = "Stop"
New-Item -ItemType Directory -Force -Path $env:EVIDENCE_DIR | Out-Null
$stdoutPath = Join-Path $env:EVIDENCE_DIR "runner-tests.stdout.log"
$stderrPath = Join-Path $env:EVIDENCE_DIR "runner-tests.stderr.log"
$process = Start-Process -FilePath python `
-ArgumentList @("-B", "tools/threading/test_run_parallel_record_vulkan_test.py") `
-NoNewWindow -Wait -PassThru `
-RedirectStandardOutput $stdoutPath `
-RedirectStandardError $stderrPath
$stdoutText = [string](Get-Content -Raw $stdoutPath)
$stderrText = [string](Get-Content -Raw $stderrPath)
Set-Content -NoNewline -Encoding utf8 `
"$env:EVIDENCE_DIR\runner-tests.log" `
($stdoutText + $stderrText)
if ($stdoutText) { Write-Host $stdoutText }
if ($stderrText) { Write-Host $stderrText }
if ($process.ExitCode -ne 0) {
throw "Vulkan gate parser tests failed with exit code $($process.ExitCode)"
}

- name: Qualify runner, build, and execute GPU matrix
timeout-minutes: 105
shell: powershell
run: |
$ErrorActionPreference = "Stop"
function Invoke-NativeWithLog {
param(
[Parameter(Mandatory = $true)][string]$FilePath,
[Parameter(Mandatory = $true)][string[]]$ArgumentList,
[Parameter(Mandatory = $true)][string]$LogPath
)
$stdoutPath = "$LogPath.stdout"
$stderrPath = "$LogPath.stderr"
$process = Start-Process -FilePath $FilePath `
-ArgumentList $ArgumentList `
-NoNewWindow -Wait -PassThru `
-RedirectStandardOutput $stdoutPath `
-RedirectStandardError $stderrPath
$stdoutText = [string](Get-Content -Raw $stdoutPath)
$stderrText = [string](Get-Content -Raw $stderrPath)
Set-Content -NoNewline -Encoding utf8 $LogPath ($stdoutText + $stderrText)
if ($stdoutText) { Write-Host $stdoutText }
if ($stderrText) { Write-Host $stderrText }
return $process.ExitCode
}

$preflight = Join-Path $env:EVIDENCE_DIR "preflight"
$cmakeEvidence = Join-Path $env:EVIDENCE_DIR "cmake"
New-Item -ItemType Directory -Force -Path $preflight, $cmakeEvidence | Out-Null

$actualSha = (& git rev-parse HEAD).Trim()
if ($LASTEXITCODE -ne 0 -or $actualSha -ne $env:MOER_GATE_SHA) {
throw "Checkout provenance mismatch: expected $env:MOER_GATE_SHA, got $actualSha"
}
@(
"requested_sha=$env:MOER_GATE_SHA"
"actual_sha=$actualSha"
"pull_request=$env:MOER_GATE_PR_NUMBER"
"run_url=$env:MOER_GATE_RUN_URL"
) | Set-Content -Encoding utf8 (Join-Path $preflight "provenance.txt")

$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
if (-not (Test-Path -LiteralPath $vswhere)) {
throw "vswhere.exe is required on the GPU runner"
}
$vsInstall = & $vswhere -latest -products * `
-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 `
-property installationPath
if (-not $vsInstall) {
throw "Visual Studio C++ tools are required on the GPU runner"
}
$vsDevCmd = Join-Path $vsInstall "Common7\Tools\VsDevCmd.bat"
$environmentLines = & cmd.exe /d /s /c `
"`"$vsDevCmd`" -no_logo -arch=x64 -host_arch=x64 >nul && set"
if ($LASTEXITCODE -ne 0) {
throw "VsDevCmd failed with exit code $LASTEXITCODE"
}
foreach ($line in $environmentLines) {
if ($line -match '^([^=]+)=(.*)$') {
Set-Item -Path "env:$($Matches[1])" -Value $Matches[2]
}
}

foreach ($command in @("cmake", "ninja", "clang", "clang++", "python", "rc", "link", "nvidia-smi", "vulkaninfo")) {
if (-not (Get-Command $command -ErrorAction SilentlyContinue)) {
throw "Required command is unavailable: $command"
}
}

$cmakeVersionText = (& cmake --version | Select-Object -First 1)
if ($cmakeVersionText -notmatch '(\d+\.\d+\.\d+)') {
throw "Unable to parse CMake version: $cmakeVersionText"
}
$cmakeVersion = [version]$Matches[1]
if ($cmakeVersion -lt [version]'3.26.0' -or $cmakeVersion -ge [version]'4.0.0') {
throw "CMake >=3.26 and <4.0 is required; found $cmakeVersion"
}
$clangVersionText = (& clang --version | Select-Object -First 1)
if ($clangVersionText -notmatch 'clang version (\d+)\.') {
throw "Unable to parse Clang version: $clangVersionText"
}
$clangMajor = [int]$Matches[1]
if ($clangMajor -ne 22) {
throw "The qualified GPU runner requires LLVM/Clang 22; found $clangVersionText"
}
$pythonVersion = & python -c "import platform; print(platform.python_version())"
if ([version]$pythonVersion -lt [version]'3.10.0') {
throw "Python 3.10 or newer is required; found $pythonVersion"
}

@(
$cmakeVersionText
(& ninja --version)
$clangVersionText
"Python $pythonVersion"
"Visual Studio: $vsInstall"
) | Set-Content -Encoding utf8 (Join-Path $preflight "toolchain.txt")

$nvidiaLog = Join-Path $preflight "nvidia-smi.csv"
$nvidiaExit = Invoke-NativeWithLog `
-FilePath "nvidia-smi" `
-ArgumentList @(
"--query-gpu=name,pci.bus_id,pci.device_id,driver_version",
"--format=csv,noheader"
) `
-LogPath $nvidiaLog
$nvidiaText = Get-Content -Raw $nvidiaLog
if ($nvidiaExit -ne 0 -or $nvidiaText -notmatch 'RTX 5080.*0x2C0210DE') {
throw "The qualified runner must expose the registered RTX 5080 (PCI device 0x2C0210DE)"
}

$vulkanLog = Join-Path $preflight "vulkaninfo-summary.txt"
$vulkanInfoExit = Invoke-NativeWithLog `
-FilePath "vulkaninfo" `
-ArgumentList @("--summary") `
-LogPath $vulkanLog
$vulkanText = Get-Content -Raw $vulkanLog
if ($vulkanInfoExit -ne 0) {
throw "vulkaninfo --summary failed with exit code $vulkanInfoExit"
}
if ($vulkanText -notmatch 'VK_LAYER_KHRONOS_validation') {
throw "VK_LAYER_KHRONOS_validation is unavailable"
}
if ($vulkanText -notmatch 'apiVersion\s*=\s*1\.[3-9]\.\d+') {
throw "No Vulkan 1.3+ device was reported"
}

Copy-Item template.MoerEngine.toml MoerEngine.toml -Force
$configureArguments = @(
"-S", ".",
"-B", "`"$env:BUILD_DIR`"",
"-G", "Ninja",
"-DCMAKE_BUILD_TYPE=Debug",
"-DCMAKE_C_COMPILER=clang",
"-DCMAKE_CXX_COMPILER=clang++",
"-DBINARY_ROOT_DIR=`"$env:OUTPUT_DIR`"",
"-Dmoer_build_test=ON",
"-DMOER_IGNORE_ENABLE_FEATURES=ON",
"-DWITH_CUDA=OFF",
"-DWITH_NRD=OFF",
"-DWITH_RENDERDOC=OFF",
"-DWITH_PROFILE=OFF"
)
$configureExit = Invoke-NativeWithLog `
-FilePath "cmake" `
-ArgumentList $configureArguments `
-LogPath (Join-Path $cmakeEvidence "configure.log")
if ($configureExit -ne 0) {
throw "CMake configure failed with exit code $configureExit"
}

$buildExit = Invoke-NativeWithLog `
-FilePath "cmake" `
-ArgumentList @(
"--build", "`"$env:BUILD_DIR`"",
"--target", "TestRHIParallelRecordVulkan",
"--parallel", "30"
) `
-LogPath (Join-Path $cmakeEvidence "build.log")
if ($buildExit -ne 0) {
throw "GPU test build failed with exit code $buildExit"
}

$testExecutable = Join-Path $env:OUTPUT_DIR "bin\Debug\TestRHIParallelRecordVulkan.exe"
$modeOutput = Join-Path $env:EVIDENCE_DIR "modes"
$matrixExit = Invoke-NativeWithLog `
-FilePath "python" `
-ArgumentList @(
"-B", "tools/threading/run_parallel_record_vulkan_test.py",
"--executable", "`"$testExecutable`"",
"--outdir", "`"$modeOutput`"",
"--timeout", "180",
"--strict-gpu-gate",
"--require-vendor-id", "0x000010de",
"--require-device-id", "0x00002c02",
"--require-device-type", "discrete_gpu",
"--minimum-device-api", "1.3.0"
) `
-LogPath (Join-Path $env:EVIDENCE_DIR "matrix-runner.log")
if ($matrixExit -ne 0) {
throw "Vulkan GPU matrix failed with exit code $matrixExit"
}

- name: Collect CMake evidence
if: always()
shell: powershell
run: |
$ErrorActionPreference = "Stop"
$cmakeEvidence = Join-Path $env:EVIDENCE_DIR "cmake"
New-Item -ItemType Directory -Force -Path $cmakeEvidence | Out-Null
$cache = Join-Path $env:BUILD_DIR "CMakeCache.txt"
if (Test-Path -LiteralPath $cache) {
Copy-Item $cache $cmakeEvidence -Force
}
$configureLog = Join-Path $env:BUILD_DIR "CMakeFiles\CMakeConfigureLog.yaml"
if (Test-Path -LiteralPath $configureLog) {
Copy-Item $configureLog $cmakeEvidence -Force
}

- name: Upload GPU evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: moer-vulkan-gpu-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ env.EVIDENCE_DIR }}
if-no-files-found: error
retention-days: 14
compression-level: 6

gate:
name: Publish Gate Status
runs-on: ubuntu-latest
if: always()
needs: [trust_policy, status_pending, gpu_validation]
timeout-minutes: 5
permissions:
statuses: write
steps:
- name: Publish status on the tested merge commit
shell: bash
env:
GH_TOKEN: ${{ github.token }}
TARGET_SHA: ${{ needs.trust_policy.outputs.target_sha }}
TRUSTED: ${{ needs.trust_policy.outputs.trusted }}
TRUST_REASON: ${{ needs.trust_policy.outputs.reason }}
TRUST_RESULT: ${{ needs.trust_policy.result }}
PENDING_RESULT: ${{ needs.status_pending.result }}
GPU_RESULT: ${{ needs.gpu_validation.result }}
run: |
set -euo pipefail
conclusion=failure
description="GPU validation failed or was denied"
if [[ "$TRUST_RESULT" == "success" && "$PENDING_RESULT" == "success" && "$TRUSTED" == "true" && "$GPU_RESULT" == "success" ]]; then
conclusion=success
description="21-mode real Vulkan GPU validation passed"
fi
run_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
latest_url="$(gh api \
"repos/$GITHUB_REPOSITORY/commits/$TARGET_SHA/statuses?per_page=100" \
--jq '[.[] | select(.context == "Vulkan GPU Gate")][0].target_url // ""')"
if [[ "$PENDING_RESULT" == "success" && -n "$latest_url" && "$latest_url" != "$run_url" ]]; then
echo "A newer run owns Vulkan GPU Gate on $TARGET_SHA; leaving its status unchanged."
exit 0
fi
gh api --method POST \
"repos/$GITHUB_REPOSITORY/statuses/$TARGET_SHA" \
-f state="$conclusion" \
-f context="Vulkan GPU Gate" \
-f description="$description" \
-f target_url="$run_url" >/dev/null
echo "Published Vulkan GPU Gate=$conclusion on $TARGET_SHA"
echo "Trust: $TRUST_RESULT/$TRUSTED ($TRUST_REASON); pending: $PENDING_RESULT; GPU: $GPU_RESULT"
[[ "$conclusion" == "success" ]]
Loading