pytest-gpu-proof 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,400 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-gpu-proof
3
+ Version: 0.1.0
4
+ Summary: pytest plugin for GPU equivalence testing with signed receipts verified via GitHub SSH keys
5
+ Project-URL: Homepage, https://github.com/A2R-Lab/pytest-gpu-proof
6
+ Project-URL: Documentation, https://a2r-lab.github.io/pytest-gpu-proof/
7
+ Project-URL: Repository, https://github.com/A2R-Lab/pytest-gpu-proof
8
+ Project-URL: Issues, https://github.com/A2R-Lab/pytest-gpu-proof/issues
9
+ Project-URL: Changelog, https://github.com/A2R-Lab/pytest-gpu-proof/blob/main/CHANGELOG.md
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: attestation,cuda,gpu,pytest,signing,testing
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Framework :: Pytest
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Security :: Cryptography
23
+ Classifier: Topic :: Software Development :: Testing
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: cryptography>=41.0
26
+ Requires-Dist: pytest>=7.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: numpy; extra == 'dev'
29
+ Requires-Dist: pytest-cov; extra == 'dev'
30
+ Provides-Extra: sigstore
31
+ Requires-Dist: sigstore; extra == 'sigstore'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # pytest-gpu-proof
35
+
36
+ A pytest plugin that lets you run GPU equivalence tests locally, sign the results with your existing SSH key, and have GitHub Actions verify the receipt — **without re-running the GPU tests in CI**.
37
+
38
+ The trust model is simple: if you can push to GitHub, you can sign a receipt. Verification fetches your public keys from `github.com/{username}.keys`, exactly as SSH does.
39
+
40
+ > **Requirements:** Python 3.11+ · pytest 7.0+ · cryptography 41.0+
41
+ > The package is not yet on PyPI — install from source with `pip install -e .` (see [Installation](#installation)).
42
+
43
+ ---
44
+
45
+ ## Why this exists
46
+
47
+ GPU CI is expensive. For many teams, the typical workflow is:
48
+
49
+ 1. Run GPU correctness tests locally (or on a lab machine).
50
+ 2. Push code and let CI run only CPU tests.
51
+ 3. Hope the GPU tests still pass.
52
+
53
+ This plugin closes that gap by producing a **cryptographically signed receipt** that proves a specific person ran specific tests against specific code at a specific time — verifiable in ordinary CPU-only CI with no GPU and no secrets.
54
+
55
+ ---
56
+
57
+ ## How it works
58
+
59
+ ```
60
+ Local machine (GPU) GitHub Actions (CPU only)
61
+ ───────────────────────────── ──────────────────────────────────────
62
+ pytest --gpu-proof-enable → → gpu-proof verify --receipt gpu-proof.json
63
+ runs your GPU tests fetches your public keys from
64
+ computes code fingerprint github.com/{you}.keys
65
+ signs receipt with SSH key verifies signature + fingerprint
66
+ writes gpu-proof.json exits 0 (pass) or 1 (fail)
67
+ ```
68
+
69
+ **Zero new key management.** The plugin uses the SSH key you already have in `~/.ssh/` (the same one you use to push to GitHub). Your public key is already on GitHub. The verifier reads it from there.
70
+
71
+ ---
72
+
73
+ ## Installation
74
+
75
+ From PyPI (v0.1.0+):
76
+
77
+ ```bash
78
+ pip install pytest-gpu-proof
79
+ ```
80
+
81
+ Or from a clone / git submodule (how the A2R-Lab consumers pin exact versions):
82
+
83
+ ```bash
84
+ git clone https://github.com/A2R-Lab/pytest-gpu-proof
85
+ cd pytest-gpu-proof
86
+ pip install -e . # base install (pytest + cryptography)
87
+ pip install -e ".[dev]" # also installs numpy, pytest-cov
88
+ ```
89
+
90
+ Or use the helper script (checks your Python version first):
91
+
92
+ ```bash
93
+ bash install.sh # base install
94
+ bash install.sh dev # dev install
95
+ ```
96
+
97
+ Maintainers: release process in [RELEASING.md](RELEASING.md).
98
+
99
+ ---
100
+
101
+ ## Quick start
102
+
103
+ ### Step 1 — Install from source
104
+
105
+ ```bash
106
+ git clone <this-repo>
107
+ cd pytest-gpu-proof
108
+ pip install -e .
109
+ ```
110
+
111
+ ### Step 2 — Run the bundled demo (no GPU needed)
112
+
113
+ The repo includes a no-GPU demo that works on any machine:
114
+
115
+ ```bash
116
+ cd examples/minimal_python_only
117
+ pytest test_minimal.py --gpu-proof-enable -v
118
+ ```
119
+
120
+ Expected output:
121
+ ```
122
+ PASSED test_minimal.py::test_relu
123
+ PASSED test_minimal.py::test_softmax
124
+ ...
125
+ [gpu-proof] Receipt written to gpu-proof.json
126
+ [gpu-proof] Signed with key SHA256:...
127
+ ```
128
+
129
+ ### Step 3 — Verify the receipt
130
+
131
+ ```bash
132
+ gpu-proof verify --receipt gpu-proof.json
133
+ ```
134
+
135
+ The verifier fetches your public keys from `github.com/{you}.keys` automatically — no secrets needed.
136
+
137
+ ### Step 4 — Use it in your own project
138
+
139
+ Add the marker and fixture to your tests:
140
+
141
+ ```python
142
+ import pytest
143
+
144
+ @pytest.mark.gpu_proof
145
+ def test_my_kernel(gpu_proof_check):
146
+ gpu_proof_check(
147
+ name="relu",
148
+ reference=python_relu, # your reference implementation
149
+ candidate=cuda_relu, # your GPU wrapper
150
+ args=([1.0, -2.0, 3.0],),
151
+ metadata={"kernel": "relu"},
152
+ )
153
+ ```
154
+
155
+ Run from **your project's root** (not the pytest-gpu-proof source directory):
156
+
157
+ ```bash
158
+ pytest path/to/your/tests/ --gpu-proof-enable -v
159
+ # → writes gpu-proof.json in the current directory
160
+ ```
161
+
162
+ > **Note:** The `--gpu-proof-enable` flag only writes a receipt if at least one test is marked with `@pytest.mark.gpu_proof` or uses the `gpu_proof_check` fixture. Running it against the plugin's own `tests/` directory (which tests the plugin internals) will not produce a receipt.
163
+
164
+ ### Step 5 — Commit and verify in CI
165
+
166
+ ```bash
167
+ git add gpu-proof.json
168
+ git commit -m "update GPU proof receipt"
169
+ git push
170
+ ```
171
+
172
+ ```yaml
173
+ # .github/workflows/ci.yml
174
+ - name: Verify GPU proof
175
+ run: gpu-proof verify --receipt gpu-proof.json
176
+ ```
177
+
178
+ ---
179
+
180
+ ## The `gpu_proof_check` fixture
181
+
182
+ ```python
183
+ gpu_proof_check(
184
+ name="my_op", # unique name within the test
185
+ reference=python_fn, # callable: the ground truth
186
+ candidate=cuda_fn, # callable: the GPU implementation
187
+ args=(arg1, arg2), # positional arguments (tuple)
188
+ kwargs={"key": "val"}, # keyword arguments (dict, optional)
189
+ compare=my_compare_fn, # optional: (ref_out, cand_out) -> None, raises on mismatch
190
+ metadata={"info": "..."}, # optional: included verbatim in the receipt
191
+ )
192
+ ```
193
+
194
+ **Default comparison:** `numpy.allclose` for float arrays/tensors, `==` otherwise.
195
+
196
+ **Custom comparison:** any callable that raises `AssertionError` on mismatch and returns `None` on success.
197
+
198
+ ---
199
+
200
+ ## Markers
201
+
202
+ | Marker | Effect |
203
+ |---|---|
204
+ | `@pytest.mark.gpu_proof` | Include test outcomes in the receipt |
205
+ | `@pytest.mark.gpu_equivalence` | Alias for `gpu_proof` |
206
+ | `@pytest.mark.gpu_required` | Skip test if no GPU is detected (via `nvidia-smi` or `torch.cuda`) |
207
+
208
+ ---
209
+
210
+ ## CLI options
211
+
212
+ | Option | Default | Description |
213
+ |---|---|---|
214
+ | `--gpu-proof-enable` | off | Enable receipt generation |
215
+ | `--gpu-proof-mode` | `local` | `local` or `ci-gpu` |
216
+ | `--gpu-proof-out` | `gpu-proof.json` | Receipt output path |
217
+ | `--gpu-proof-key` | auto | SSH private key path |
218
+ | `--gpu-proof-signing-backend` | `ed25519` | `ed25519` or `none` (writes an **unsigned** receipt with `"signature": null`; the verifier rejects it unless `--allow-unsigned` is passed) |
219
+ | `--gpu-proof-required-marker` | `gpu_proof` | Marker name that flags a test for the receipt |
220
+ | `--gpu-proof-fingerprint-paths` | `src,tests` | Comma-separated paths to fingerprint |
221
+ | `--gpu-proof-github-user` | auto | GitHub username (auto-detected from git remote) |
222
+ | `--gpu-proof-policy` | — | Path to policy YAML |
223
+ | `--gpu-proof-fail-on-skip` | off | Exit non-zero and write no receipt if any marked or `gpu_required` test is skipped |
224
+
225
+ Defaults for most of these can also be set in your project's `pyproject.toml`
226
+ under `[tool.gpu_proof]` (CLI flags take precedence):
227
+
228
+ ```toml
229
+ [tool.gpu_proof]
230
+ mode = "local"
231
+ output = "gpu-proof.json"
232
+ fingerprint_paths = ["src", "tests"]
233
+ required_marker = "gpu_proof"
234
+ max_age_days = 30 # used by the verifier
235
+ require_gpu = false # used by the verifier (see below)
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Verification CLI
241
+
242
+ ```bash
243
+ gpu-proof verify \
244
+ --receipt gpu-proof.json \
245
+ --repo . \
246
+ --max-age-days 30
247
+
248
+ # With an explicit GitHub username (non-GitHub remotes):
249
+ gpu-proof verify --receipt gpu-proof.json --github-user myusername
250
+ ```
251
+
252
+ Also callable as:
253
+
254
+ ```bash
255
+ python -m pytest_gpu_proof verify --receipt gpu-proof.json
256
+ ```
257
+
258
+ ### What the verifier checks
259
+
260
+ 1. **Signature** — fetches `github.com/{signer}.keys`, verifies Ed25519/ECDSA/RSA signature
261
+ 2. **Fingerprint** — recomputes SHA-256 digest of `src/` and `tests/`, compares to receipt
262
+ 3. **Commit SHA** — compares receipt commit SHA to current HEAD
263
+ 4. **Test outcomes** — all tests recorded in the receipt must have passed; skipped marked tests fail verification unless `--allow-skipped` is passed, or an `--expected-skips` baseline is given and the receipt's skip set matches it exactly
264
+ 5. **Freshness** — receipt must be younger than `max_age_days` (default: 30)
265
+ 6. **Dirty policy** — configurable via policy file
266
+ 7. **GPU info** (optional) — with `--require-gpu` (or `require_gpu = true` in `[tool.gpu_proof]`), the receipt's `environment.gpu_info` must be present
267
+
268
+ Additional flags:
269
+
270
+ - `--allow-unsigned` — accept receipts with `"signature": null` (produced by `--gpu-proof-signing-backend=none`). This disables the entire trust story; the verifier prints a loud warning.
271
+ - `--allow-skipped` — accept receipts that contain skipped marked tests (any skips, no questions asked).
272
+ - `--expected-skips PATH` — the strict alternative to `--allow-skipped` (the two are mutually exclusive): a baseline file of node IDs (one per line, `#` comments) that the receipt's skipped tests must match **exactly**. A skip not in the baseline fails verification, and a baseline entry that no longer skips fails too (stale baseline — update it). Use this when a project has a small set of permanent, documented skips. Can also be set as an inline list via `expected_skips = [...]` in `[tool.gpu_proof]`.
273
+ - `--require-gpu` — reject receipts whose `environment.gpu_info` is null/absent. This is **modest hardening, not proof**: `gpu_info` is self-reported by the recording machine, so it only guards against accidentally signing on a GPU-less box, not against a dishonest signer.
274
+
275
+ ---
276
+
277
+ ## Key management
278
+
279
+ **Local mode:** Uses your existing `~/.ssh/id_ed25519` (or whatever `git config user.signingKey` points to). No new keys to generate.
280
+
281
+ **CI-GPU mode:** Generate a dedicated CI signing key, store the private key as a GitHub Actions secret, and add the public key to your GitHub account.
282
+
283
+ ```bash
284
+ ssh-keygen -t ed25519 -f ci-signing-key -N ""
285
+ # Add ci-signing-key.pub to github.com/settings/keys
286
+ # Add contents of ci-signing-key to GitHub Actions secrets as GPU_PROOF_SIGNING_KEY
287
+ ```
288
+
289
+ ---
290
+
291
+ ## Receipt format
292
+
293
+ ```json
294
+ {
295
+ "schema_version": "1",
296
+ "mode": "local",
297
+ "repo": {
298
+ "remote_url": "git@github.com:you/myrepo.git",
299
+ "github_username": "you",
300
+ "commit_sha": "abc123...",
301
+ "branch": "main",
302
+ "dirty": false
303
+ },
304
+ "fingerprint": {
305
+ "algorithm": "sha256",
306
+ "included_paths": ["src", "tests"],
307
+ "file_count": 12,
308
+ "digest": "deadbeef..."
309
+ },
310
+ "session": {
311
+ "started_at": "2024-01-01T10:00:00Z",
312
+ "ended_at": "2024-01-01T10:01:30Z",
313
+ "node_ids": ["tests/test_relu.py::test_relu"]
314
+ },
315
+ "tests": [
316
+ {
317
+ "node_id": "tests/test_relu.py::test_relu",
318
+ "outcome": "passed",
319
+ "duration_s": 1.23,
320
+ "checks": [{"name": "relu", "outcome": "passed", "metadata": {}}]
321
+ }
322
+ ],
323
+ "environment": {
324
+ "python_version": "3.11.0",
325
+ "platform": "linux",
326
+ "pytest_version": "7.4.0",
327
+ "gpu_info": {"name": "NVIDIA RTX 3090", "driver_version": "535.104"}
328
+ },
329
+ "signature": {
330
+ "algorithm": "ed25519",
331
+ "backend": "ssh-local",
332
+ "signer": "you",
333
+ "key_fingerprint": "SHA256:...",
334
+ "value": "<base64-encoded signature>"
335
+ }
336
+ }
337
+ ```
338
+
339
+ ---
340
+
341
+ ## Security model
342
+
343
+ A signed receipt proves that **an accepted signer attested to a specific test run over a specific code state**. It does not prove:
344
+
345
+ - The local machine was fully trustworthy or uncompromised.
346
+ - The GPU execution environment was hardware-attested.
347
+ - The signing key was protected with a hardware security module.
348
+
349
+ This is appropriate for **team workflows where the signer is a trusted team member** and the goal is to avoid paying for GPU CI on every merge, not to provide adversarial security guarantees.
350
+
351
+ The optional `--require-gpu` verifier flag adds a modest extra check — the receipt must contain self-reported `environment.gpu_info` — but this is hardening against mistakes (signing on a GPU-less machine), not proof of GPU execution.
352
+
353
+ **SSH key support caveats:**
354
+
355
+ - The plugin signs the raw receipt bytes with the key loaded from disk — it does **not** produce SSHSIG-format signatures, so `ssh-keygen -Y verify` cannot validate receipts. Use `gpu-proof verify` instead.
356
+ - Keys that live only in an SSH agent, and FIDO/hardware-backed `sk-ssh-ed25519`/`sk-ecdsa` keys, are **not** supported: signing needs direct access to a private key file readable by the `cryptography` library.
357
+
358
+ See [docs/security_model.md](docs/security_model.md) for a full discussion.
359
+
360
+ ---
361
+
362
+ ## Examples
363
+
364
+ | Example | Location | What it shows |
365
+ |---|---|---|
366
+ | Minimal (no GPU needed) | `examples/minimal_python_only/` | Full plugin flow with pure-Python "fake GPU" |
367
+ | Wrapped CUDA via ctypes (fake library) | `examples/wrapped_cuda_ctypes/` | ctypes-style wrapper pattern that runs without CUDA |
368
+ | Real CUDA via ctypes | `examples/cuda_ctypes_matmul/` | C ABI CUDA shared library loaded with Python `ctypes` |
369
+ | Real CUDA via pybind11 | `examples/cuda_pybind11_matmul/` | CUDA-backed Python extension using `pybind11_add_module` |
370
+ | Real CUDA via nanobind | `examples/cuda_nanobind_matmul/` | CUDA-backed Python extension using `nanobind_add_module` |
371
+ | Real CUDA via JAX FFI | `examples/jax_ffi_cuda_matmul/` | Typed CUDA custom call registered with `jax.ffi` |
372
+ | Local sign, CI verify | `examples/local_receipt_verify/` | GitHub Actions workflow for CPU-only verification |
373
+ | CI-GPU execution | `examples/github_gpu_runner/` | GitHub Actions workflow on a GPU runner |
374
+
375
+ ---
376
+
377
+ ## Development
378
+
379
+ ```bash
380
+ python3 -m pip install -e ".[dev]"
381
+ pytest tests/ -v
382
+ ```
383
+
384
+ If you do not want to install the package, run tests directly from the source
385
+ tree with:
386
+
387
+ ```bash
388
+ PYTHONPATH=src pytest -q
389
+ ```
390
+
391
+ Tests are CPU-only. No GPU or network access required.
392
+
393
+ ---
394
+
395
+ ## Compatibility
396
+
397
+ - Python 3.11+
398
+ - pytest 7.0+
399
+ - `cryptography` 41.0+
400
+ - `pytest-xdist` is **not** supported in v1 (parallel workers would write conflicting receipts)
@@ -0,0 +1,18 @@
1
+ pytest_gpu_proof/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
2
+ pytest_gpu_proof/__main__.py,sha256=I2k_Y-0KCU8V4bbV0edDEoaSP1MjJrwqNZMNbap_VOM,46
3
+ pytest_gpu_proof/cli.py,sha256=7Vqlp_xRBDSfXcmUn_1wjlFcrHceaq4Atyf-K7rw1Os,3010
4
+ pytest_gpu_proof/compare.py,sha256=7oDgspsB5dj0EStzDELpoOw1VfGk7cDZlerd6p29UBQ,1653
5
+ pytest_gpu_proof/config.py,sha256=gWSsKQdUSEoal77uiKY20E65FBNZapqPDP5LG6oS6lo,3076
6
+ pytest_gpu_proof/fingerprint.py,sha256=yZCsPDTK-OiFq8m5xp7yjaQaY1ziPWho9pn2UfrNiV0,972
7
+ pytest_gpu_proof/gitutils.py,sha256=ZDxY5nSXNCTrSuKg-updJwzKw_oNi8Pp6SsCJn1_XOE,3558
8
+ pytest_gpu_proof/plugin.py,sha256=l2t-6i0QCLZp40-d7Nw0EvCrDFU_6aNKuXOyLJ6Gcmc,11385
9
+ pytest_gpu_proof/receipt.py,sha256=ZaIfb1z5xE5bbUVmMmkdqL6wY9hKCWon0W00jgBqriU,4395
10
+ pytest_gpu_proof/verify.py,sha256=U3kJzd_2XeaJ4TVW92u8j1OsI1Vtey_SvYi1SwNEaEI,12375
11
+ pytest_gpu_proof/signers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ pytest_gpu_proof/signers/base.py,sha256=emZApFpLHdiq_87edw2b2OfbE1TkYRtRVj__nGAJtMs,392
13
+ pytest_gpu_proof/signers/ed25519.py,sha256=J5_lx913ermZsYbpwlUmk0DLvnD_TvZEH9TyMgGtjpg,6089
14
+ pytest_gpu_proof-0.1.0.dist-info/METADATA,sha256=Dr7dAK5943kCYrJYq7WtNGRMJiqwUv_y_ZM4VnMqp8Q,14694
15
+ pytest_gpu_proof-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
16
+ pytest_gpu_proof-0.1.0.dist-info/entry_points.txt,sha256=KUVolKsUWq-aqFBXgaI25_TD8PcYrg4mq0Ndiz9pgHY,104
17
+ pytest_gpu_proof-0.1.0.dist-info/licenses/LICENSE,sha256=KXfdMIlN2d_t6pY6UbLXCh7jpl6UfFsBdcIhqmCMFFU,1064
18
+ pytest_gpu_proof-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ gpu-proof = pytest_gpu_proof.cli:main
3
+
4
+ [pytest11]
5
+ gpu-proof = pytest_gpu_proof.plugin
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 A2R Lab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.