pytest-gpu-proof
Signed pytest receipts for local GPU runs, verified in CPU-only CI.
pytest-gpu-proof records which marked tests ran, their outcomes, the Git
commit and source fingerprint, the environment, and the run time. It signs
that payload with an existing SSH key. A normal CPU runner can then verify the
receipt against the signer's public keys on GitHub without rerunning CUDA.
This is a practical bridge for projects with local or lab GPUs but no always-on GPU CI. It is signer attestation—not hardware attestation. See the security model before making stronger claims.
The workflow
GPU machine CPU-only CI
────────────────────────────────── ───────────────────────────────
pytest --gpu-proof-enable gpu-proof verify --receipt ...
run marked tests validate schema and signature
capture setup/call/teardown outcomes fetch current GitHub SSH keys
fingerprint the checked-out tree recompute source fingerprint
sign one schema-3 receipt enforce repository policy
The default signer policy is open: a valid receipt from any GitHub user is accepted. This supports contributor-signed pull requests. Repositories that only trust maintainers or dedicated CI keys can use restricted mode with username and/or key-fingerprint allowlists.
Install
python -m pip install pytest-gpu-proof
For development:
git clone https://github.com/A2R-Lab/pytest-gpu-proof.git
cd pytest-gpu-proof
python -m pip install -e ".[dev]"
Requirements: Python 3.11+, pytest 7+, and cryptography 41+.
Quick start
Mark a test, or use the comparison fixture:
import pytest
@pytest.mark.gpu_proof
def test_rnea(gpu_proof_check):
gpu_proof_check(
name="rnea",
reference=python_rnea,
candidate=cuda_rnea,
args=(model, q, qd, qdd),
metadata={"robot": "go2"},
)
Run from the root of the project being attested:
pytest tests/gpu --gpu-proof-enable --gpu-proof-github-user YOUR_USER
gpu-proof verify --receipt gpu-proof.json --repo .
Commit gpu-proof.json with the code, then add a CPU-only CI step:
- name: Verify local GPU test receipt
run: gpu-proof verify --receipt gpu-proof.json --repo .
Receipt creation is fail-closed. A missing key, invalid Git state, empty
fingerprint scope, no selected tests, or write failure makes pytest fail and
leaves no stale receipt behind. --gpu-proof-best-effort is an explicit
development-only opt-out.
Four interfaces
The project exposes four small interfaces:
- Markers select receipt tests.
gpu_proof_checkcompares a reference callable with a candidate.gpu-proof verifyvalidates a receipt and repository policy.gpu-proof mergecombines separately executed shards and optionally carries unchanged shards forward.
Markers
| Marker | Meaning |
|---|---|
@pytest.mark.gpu_proof |
Include the test in the receipt |
@pytest.mark.gpu_equivalence |
Alias for gpu_proof |
@pytest.mark.gpu_required |
Skip when neither nvidia-smi nor PyTorch reports a GPU |
Skipped tests are recorded. Verification rejects them by default. Prefer an
exact --expected-skips baseline over the broad --allow-skipped escape
hatch.
Comparison fixture
gpu_proof_check(
name="operation",
reference=reference_fn,
candidate=gpu_fn,
args=(arg1, arg2),
kwargs={"option": value},
compare=custom_compare,
metadata={"case": "small"},
)
The default comparator is shape-safe: float/complex NumPy-compatible arrays
use numpy.allclose(..., equal_nan=True) and other arrays use exact equality.
Provide a comparator for GPU tensors, domain-specific tolerances, or structured
outputs. A comparator should return normally on success and raise
AssertionError on mismatch.
Recording options
| Option | Default | Purpose |
|---|---|---|
--gpu-proof-enable |
off | Enable receipt generation |
--gpu-proof-mode |
local |
Record local or ci-gpu provenance |
--gpu-proof-out |
gpu-proof.json |
Output artifact |
--gpu-proof-key |
discovered | SSH private-key file |
--gpu-proof-github-user |
discovered | GitHub account that owns the public key |
--gpu-proof-signing-backend |
ed25519 |
SSH signing, or explicit none |
--gpu-proof-required-marker |
gpu_proof |
Custom receipt marker |
--gpu-proof-fail-on-skip |
off | Fail and suppress the receipt on selected skips |
--gpu-proof-fingerprint-paths |
. |
Comma-separated Git-tracked scope |
--gpu-proof-fingerprint-extra-paths |
empty | Explicit ignored/generated inputs |
--gpu-proof-fingerprint-excluded-paths |
gpu-proof.json |
Receipt artifacts omitted to avoid self-reference |
--gpu-proof-best-effort |
off | Warn instead of failing if emission fails |
Most defaults can live in the consumer's pyproject.toml:
[tool.gpu_proof]
mode = "local"
output = "gpu-proof.json"
fingerprint_paths = ["."]
fingerprint_extra_paths = ["generated/kernel_table.cuh"]
fingerprint_excluded_paths = ["gpu-proof.json"]
required_marker = "gpu_proof"
max_age_days = 30
require_gpu = true
By default, the fingerprint covers every tracked file in the repository, including symlink targets and submodule gitlinks. Ignored/generated artifacts are excluded unless explicitly named as extra paths. An empty or unreadable scope is an error. The receipt artifact itself is excluded because a signed file cannot hash its own final contents; set the exclusion explicitly if your committed receipt uses a different path.
Verification and policy
gpu-proof verify \
--receipt gpu-proof.json \
--repo . \
--policy gpu-proof-policy.yaml
Schema-3 verification checks:
- strict receipt structure, complete test collection, and session outcome;
- signature, signed username, key fingerprint, and key algorithm;
- the signer's current public SSH keys at
github.com/<user>.keys; - tracked and explicit-extra source fingerprints;
- current/ancestor Git commit and clean recording/verification trees;
- test and comparison outcomes, exact skip policy, and optional test manifest;
- optional shard membership, shard fingerprints, and carry-forward policy;
- mode, GPU-information requirement, and freshness.
Open policy, suitable for contributor-signed PRs:
signer_mode: open
max_age_days: 30
require_mode: local
required_fingerprint_paths: ["."]
required_fingerprint_excluded_paths: [gpu-proof.json]
required_test_manifest: gpu-proof-tests.txt
Restricted policy, suitable for a maintainer or CI allowlist:
signer_mode: restricted
allowed_signers: [alice, release-bot]
allowed_key_fingerprints:
- "SHA256:..."
max_age_days: 14
require_mode: ci-gpu
required_fingerprint_paths: ["."]
required_fingerprint_extra_paths: [generated/kernel_table.cuh]
required_fingerprint_excluded_paths: [gpu-proof.json]
allow_dirty: false
allow_carried: false
If both restricted allowlists are present, both must match. Unknown policy
fields fail verification so misspellings cannot silently weaken policy.
YAML support is available through pytest-gpu-proof[yaml]; JSON policy files
need no optional dependency.
--allow-unsigned accepts "signature": null with a loud warning. It removes
signer authentication and should not be used as a merge gate.
Signing identity
The GitHub username is resolved in this order:
--gpu-proof-github-userorgithub_usernameconfiguration;- the authenticated
ghCLI user; - the origin owner, with a warning because organization owners normally do not own an individual's SSH key.
The private-key file is resolved in this order:
--gpu-proof-key;git config user.signingKey;~/.ssh/id_ed25519,id_ecdsa, thenid_rsa.
Ed25519, ECDSA, and RSA-PSS keys are supported. Agent-only and hardware-backed keys are not yet supported because signing currently requires a readable private-key file.
Shards
Run shards as separate pytest processes; xdist workers are intentionally rejected for receipt generation.
pytest tests/gpu/a --gpu-proof-enable \
--gpu-proof-shard=a \
--gpu-proof-shard-fingerprint-paths=src/a,tests/gpu/a \
--gpu-proof-out=receipts/a.json
pytest tests/gpu/b --gpu-proof-enable \
--gpu-proof-shard=b \
--gpu-proof-shard-fingerprint-paths=src/b,tests/gpu/b \
--gpu-proof-out=receipts/b.json
gpu-proof merge receipts/a.json receipts/b.json --out gpu-proof.json
The merger refuses mixed commits, schemas, global fingerprints, modes, runtime environments, duplicate tests, and duplicate shard names. It records input provenance but does not verify each input signature; the merger signs for the union. See sharding and carry-forward.
Receipt schema
New receipts use schema "3". Signer identity is inside the signed payload,
test node IDs exactly match the recorded collection, setup and teardown
failures are terminal outcomes, and writes are atomic. The verifier retains
schema-1/2 compatibility, but their legacy defaults are less strict.
Development
python -m pytest tests -q
coverage run -m pytest tests -q
coverage report --fail-under=100
mkdocs build --strict
python -m build
python -m twine check --strict dist/*
CI tests Python 3.11–3.13 and enforces 100% line and branch coverage. See CONTRIBUTING.md, SECURITY.md, and RELEASING.md.
License
MIT.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytest_gpu_proof-0.4.0.tar.gz.
File metadata
- Download URL: pytest_gpu_proof-0.4.0.tar.gz
- Upload date:
- Size: 81.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4fc5b21bdaafdcba8ee47551db1174e2388e313a938954f16bb07447c7782a7f
|
|
| MD5 |
e68d1283d240303dd779941156f233b6
|
|
| BLAKE2b-256 |
73302cfe230f1437c4194b8b29eeee7b91692415a98338b78e59c7de66d8a834
|
Provenance
The following attestation bundles were made for pytest_gpu_proof-0.4.0.tar.gz:
Publisher:
publish.yml on A2R-Lab/pytest-gpu-proof
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_gpu_proof-0.4.0.tar.gz -
Subject digest:
4fc5b21bdaafdcba8ee47551db1174e2388e313a938954f16bb07447c7782a7f - Sigstore transparency entry: 2500135042
- Sigstore integration time:
-
Permalink:
A2R-Lab/pytest-gpu-proof@7ac77ce1a0c3dfcc29a84aa402d6f3364fa9cb20 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/A2R-Lab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7ac77ce1a0c3dfcc29a84aa402d6f3364fa9cb20 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pytest_gpu_proof-0.4.0-py3-none-any.whl.
File metadata
- Download URL: pytest_gpu_proof-0.4.0-py3-none-any.whl
- Upload date:
- Size: 36.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9966787811a136bc353fb636413fa84b027b622d0f542afe82b7f6b62c5af1f9
|
|
| MD5 |
9a1e9f3a78ab2dd262de629c30a486c2
|
|
| BLAKE2b-256 |
28c226bbe368e6c1ac7b855f8afd717f0495b8ad9776195c4a07dc150124c2ea
|
Provenance
The following attestation bundles were made for pytest_gpu_proof-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on A2R-Lab/pytest-gpu-proof
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_gpu_proof-0.4.0-py3-none-any.whl -
Subject digest:
9966787811a136bc353fb636413fa84b027b622d0f542afe82b7f6b62c5af1f9 - Sigstore transparency entry: 2500135043
- Sigstore integration time:
-
Permalink:
A2R-Lab/pytest-gpu-proof@7ac77ce1a0c3dfcc29a84aa402d6f3364fa9cb20 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/A2R-Lab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7ac77ce1a0c3dfcc29a84aa402d6f3364fa9cb20 -
Trigger Event:
release
-
Statement type: