pytest-gpu-proof 0.4.0__tar.gz → 0.4.1__tar.gz
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.
- pytest_gpu_proof-0.4.1/.github/workflows/docs.yml +69 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/.gitignore +3 -1
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/CHANGELOG.md +51 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/PKG-INFO +29 -7
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/README.md +26 -4
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/docs/ci_gpu_mode.md +4 -1
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/docs/index.md +3 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/docs/policy.md +2 -2
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/docs/security_model.md +7 -3
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/docs/sharding.md +18 -4
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/mkdocs.yml +2 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/pyproject.toml +3 -3
- pytest_gpu_proof-0.4.1/src/pytest_gpu_proof/__init__.py +1 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/src/pytest_gpu_proof/cli.py +108 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/src/pytest_gpu_proof/config.py +9 -2
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/src/pytest_gpu_proof/gitutils.py +9 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/src/pytest_gpu_proof/merge.py +130 -2
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/src/pytest_gpu_proof/plugin.py +22 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/src/pytest_gpu_proof/receipt.py +40 -7
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/src/pytest_gpu_proof/verify.py +55 -12
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/tests/test_config.py +17 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/tests/test_merge.py +2 -2
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/tests/test_receipt.py +41 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/tests/test_sharding.py +229 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/tests/test_verifier.py +91 -0
- pytest_gpu_proof-0.4.1/website/README.md +85 -0
- pytest_gpu_proof-0.4.1/website/build_site.py +82 -0
- pytest_gpu_proof-0.4.1/website/check_site.py +90 -0
- pytest_gpu_proof-0.4.1/website/favicon.svg +1 -0
- pytest_gpu_proof-0.4.1/website/index.html +127 -0
- pytest_gpu_proof-0.4.1/website/main.js +17 -0
- pytest_gpu_proof-0.4.1/website/overrides/main.html +4 -0
- pytest_gpu_proof-0.4.1/website/provenance.json +10 -0
- pytest_gpu_proof-0.4.1/website/requirements.txt +2 -0
- pytest_gpu_proof-0.4.1/website/style.css +67 -0
- pytest_gpu_proof-0.4.1/website/test_site.py +89 -0
- pytest_gpu_proof-0.4.1/website/workflow-mobile.svg +32 -0
- pytest_gpu_proof-0.4.1/website/workflow.svg +35 -0
- pytest_gpu_proof-0.4.0/.github/workflows/docs.yml +0 -29
- pytest_gpu_proof-0.4.0/src/pytest_gpu_proof/__init__.py +0 -1
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/.github/workflows/ci.yml +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/.github/workflows/publish-testpypi.yml +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/.github/workflows/publish.yml +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/AGENTS.md +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/CLAUDE.md +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/CONTRIBUTING.md +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/LICENSE +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/RELEASING.md +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/ROADMAP.md +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/SECURITY.md +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/docs/architecture.md +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/docs/landscape.md +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/docs/local_mode.md +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/docs/quickstart.md +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/cuda_ctypes_matmul/Makefile +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/cuda_ctypes_matmul/README.md +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/cuda_ctypes_matmul/cuda_ctypes_matmul.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/cuda_ctypes_matmul/matmul_kernel.cu +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/cuda_ctypes_matmul/test_cuda_ctypes_matmul.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/cuda_nanobind_matmul/CMakeLists.txt +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/cuda_nanobind_matmul/README.md +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/cuda_nanobind_matmul/src/bindings.cpp +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/cuda_nanobind_matmul/src/matmul_cuda.h +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/cuda_nanobind_matmul/src/matmul_kernel.cu +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/cuda_nanobind_matmul/test_cuda_nanobind_matmul.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/cuda_pybind11_matmul/CMakeLists.txt +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/cuda_pybind11_matmul/README.md +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/cuda_pybind11_matmul/src/bindings.cpp +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/cuda_pybind11_matmul/src/matmul_cuda.h +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/cuda_pybind11_matmul/src/matmul_kernel.cu +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/cuda_pybind11_matmul/test_cuda_pybind11_matmul.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/github_gpu_runner/.github/workflows/gpu-test.yml +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/jax_ffi_cuda_matmul/CMakeLists.txt +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/jax_ffi_cuda_matmul/README.md +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/jax_ffi_cuda_matmul/jax_ffi_matmul.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/jax_ffi_cuda_matmul/src/matmul_ffi.cu +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/jax_ffi_cuda_matmul/test_jax_ffi_cuda_matmul.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/local_receipt_verify/.github/workflows/verify.yml +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/minimal_python_only/conftest.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/minimal_python_only/test_minimal.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/wrapped_cuda_ctypes/fake_cuda_lib.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/examples/wrapped_cuda_ctypes/test_cuda_wrapper.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/install.sh +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/requirements-dev.txt +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/requirements.txt +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/src/pytest_gpu_proof/__main__.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/src/pytest_gpu_proof/compare.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/src/pytest_gpu_proof/fingerprint.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/src/pytest_gpu_proof/signers/__init__.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/src/pytest_gpu_proof/signers/base.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/src/pytest_gpu_proof/signers/ed25519.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/tests/conftest.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/tests/test_cli.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/tests/test_compare.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/tests/test_fingerprint.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/tests/test_gitutils.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/tests/test_plugin_capture.py +0 -0
- {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.4.1}/tests/test_signing.py +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
paths:
|
|
8
|
+
- 'docs/**'
|
|
9
|
+
- 'website/**'
|
|
10
|
+
- 'mkdocs.yml'
|
|
11
|
+
- '.github/workflows/docs.yml'
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: docs-${{ github.ref }}
|
|
18
|
+
cancel-in-progress: false
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
build:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.12"
|
|
29
|
+
|
|
30
|
+
- name: Install website dependencies
|
|
31
|
+
run: pip install -r website/requirements.txt
|
|
32
|
+
|
|
33
|
+
- name: Build and check the complete website
|
|
34
|
+
run: python website/build_site.py
|
|
35
|
+
|
|
36
|
+
- name: Test website checks
|
|
37
|
+
run: python -m unittest discover -s website -p 'test_*.py'
|
|
38
|
+
|
|
39
|
+
- uses: actions/upload-artifact@v4
|
|
40
|
+
with:
|
|
41
|
+
name: website-preview
|
|
42
|
+
path: build/website
|
|
43
|
+
include-hidden-files: true
|
|
44
|
+
|
|
45
|
+
deploy:
|
|
46
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
47
|
+
needs: build
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
permissions:
|
|
50
|
+
contents: write
|
|
51
|
+
steps:
|
|
52
|
+
- uses: actions/checkout@v4
|
|
53
|
+
with:
|
|
54
|
+
fetch-depth: 0
|
|
55
|
+
- uses: actions/setup-python@v5
|
|
56
|
+
with:
|
|
57
|
+
python-version: "3.12"
|
|
58
|
+
- run: pip install -r website/requirements.txt
|
|
59
|
+
- uses: actions/download-artifact@v4
|
|
60
|
+
with:
|
|
61
|
+
name: website-preview
|
|
62
|
+
path: build/website
|
|
63
|
+
- name: Configure git author
|
|
64
|
+
run: |
|
|
65
|
+
git config user.name "github-actions[bot]"
|
|
66
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
67
|
+
|
|
68
|
+
- name: Deploy to gh-pages
|
|
69
|
+
run: ghp-import --no-jekyll --push --branch gh-pages build/website
|
|
@@ -4,6 +4,57 @@ All notable changes to pytest-gpu-proof are documented here. The format is
|
|
|
4
4
|
based on [Keep a Changelog](https://keepachangelog.com/); versions follow
|
|
5
5
|
[SemVer](https://semver.org/) (pre-1.0: minor bumps may break).
|
|
6
6
|
|
|
7
|
+
## [0.4.1] — 2026-10-12
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Shard entries record `dirty` (the recording tree's state); merge ORs shard
|
|
12
|
+
flags into the merged `repo.dirty`, carry-forward refuses a dirty shard, and
|
|
13
|
+
verification refuses any dirty shard unless `allow_dirty` is set.
|
|
14
|
+
- `gpu-proof merge --carry-from` refuses a source receipt recorded with a dirty
|
|
15
|
+
working tree. The merged `repo.dirty` is OR-ed over the fresh inputs only, so a
|
|
16
|
+
carried shard from a dirty recording was laundered into a clean-looking merged
|
|
17
|
+
receipt (found by GRiD's split driver, 2026-10-10). Carried shards now record
|
|
18
|
+
`carried.original_dirty`, and verification refuses a carried shard recorded
|
|
19
|
+
dirty unless `allow_dirty` is set.
|
|
20
|
+
|
|
21
|
+
- The expected-skips mismatch names the delta (unexpected skips and stale
|
|
22
|
+
baseline entries) instead of only reporting that the sets differ.
|
|
23
|
+
- The receipt's `environment.gpu_info` falls back to the same torch probe the
|
|
24
|
+
`gpu_required` marker uses when nvidia-smi is absent or fails (`source`
|
|
25
|
+
records which probe answered), so a torch-only box no longer records
|
|
26
|
+
`gpu_info: null` and then fails a `require_gpu` policy.
|
|
27
|
+
|
|
28
|
+
- Verification refuses a receipt whose session started more than five
|
|
29
|
+
minutes before its commit existed (committer time): a cheap replay and
|
|
30
|
+
forgery guard that needs no nonce.
|
|
31
|
+
|
|
32
|
+
- `mode: ci-gpu` is now bound to the signing key: a ci-gpu receipt verifies
|
|
33
|
+
only when the policy lists `allowed_key_fingerprints` and the receipt was
|
|
34
|
+
signed by one of them (previously the mode was an unverified label).
|
|
35
|
+
- Without an explicit `carried_max_age_days`, a carried shard's original run
|
|
36
|
+
may not be older than the receipt's `max_age_days` (was a fixed 30 days).
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- `gpu-proof merge --verify-inputs`: authenticate every input shard against
|
|
41
|
+
its signer's current GitHub keys before merging (unsigned or tampered inputs
|
|
42
|
+
are refused).
|
|
43
|
+
- `gpu-proof status PATH...`: one-line summaries of receipt files or
|
|
44
|
+
directories (commit, dirty, signed, outcome, counts, shards, carried).
|
|
45
|
+
- `gpu-proof durations RECEIPT... --out map.json`: fold per-test durations
|
|
46
|
+
into a JSON map for shard bin-packing.
|
|
47
|
+
- Command-line equivalents for the toml-only options:
|
|
48
|
+
`--gpu-proof-no-gpu-uuid`, `--gpu-proof-require-gpu`,
|
|
49
|
+
`--gpu-proof-max-age-days N`.
|
|
50
|
+
- `gpu-proof plan --receipt OLD [--repo .] [--json]`: a dry run of the
|
|
51
|
+
carry-forward conditions against the current tree, reporting per shard
|
|
52
|
+
whether it could be carried or must be re-run (and why). Drivers that plan
|
|
53
|
+
partial re-runs no longer reimplement the checks.
|
|
54
|
+
- `record_gpu_uuid` (`[tool.gpu_proof]`, default `true`): set `false` to omit each GPU's
|
|
55
|
+
nvidia-smi UUID from the receipt environment while keeping name, driver, memory and
|
|
56
|
+
compute capability. Verification does not read the UUID.
|
|
57
|
+
|
|
7
58
|
## [0.4.0] — 2026-08-17
|
|
8
59
|
|
|
9
60
|
### Added
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest-gpu-proof
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: pytest plugin for GPU equivalence testing with signed receipts verified via GitHub SSH keys
|
|
5
|
-
Project-URL: Homepage, https://
|
|
6
|
-
Project-URL: Documentation, https://a2r-lab.
|
|
5
|
+
Project-URL: Homepage, https://a2r-lab.org/pytest-gpu-proof/
|
|
6
|
+
Project-URL: Documentation, https://a2r-lab.org/pytest-gpu-proof/docs/
|
|
7
7
|
Project-URL: Repository, https://github.com/A2R-Lab/pytest-gpu-proof
|
|
8
8
|
Project-URL: Issues, https://github.com/A2R-Lab/pytest-gpu-proof/issues
|
|
9
9
|
Project-URL: Changelog, https://github.com/A2R-Lab/pytest-gpu-proof/blob/main/CHANGELOG.md
|
|
@@ -39,13 +39,17 @@ Description-Content-Type: text/markdown
|
|
|
39
39
|
# pytest-gpu-proof
|
|
40
40
|
|
|
41
41
|
[](https://github.com/A2R-Lab/pytest-gpu-proof/actions/workflows/ci.yml)
|
|
42
|
-
[](https://a2r-lab.
|
|
42
|
+
[](https://a2r-lab.org/pytest-gpu-proof/docs/)
|
|
43
43
|
[](https://pypi.org/project/pytest-gpu-proof/)
|
|
44
44
|
[](https://pypi.org/project/pytest-gpu-proof/)
|
|
45
45
|
[](https://github.com/A2R-Lab/pytest-gpu-proof/actions/workflows/ci.yml)
|
|
46
46
|
|
|
47
47
|
Signed pytest receipts for local GPU runs, verified in CPU-only CI.
|
|
48
48
|
|
|
49
|
+
[Project overview](https://a2r-lab.org/pytest-gpu-proof/) ·
|
|
50
|
+
[Documentation](https://a2r-lab.org/pytest-gpu-proof/docs/) ·
|
|
51
|
+
[Website preview instructions](website/README.md)
|
|
52
|
+
|
|
49
53
|
`pytest-gpu-proof` records which marked tests ran, their outcomes, the Git
|
|
50
54
|
commit and source fingerprint, the environment, and the run time. It signs
|
|
51
55
|
that payload with an existing SSH key. A normal CPU runner can then verify the
|
|
@@ -126,15 +130,23 @@ fingerprint scope, no selected tests, or write failure makes pytest fail and
|
|
|
126
130
|
leaves no stale receipt behind. `--gpu-proof-best-effort` is an explicit
|
|
127
131
|
development-only opt-out.
|
|
128
132
|
|
|
129
|
-
##
|
|
133
|
+
## Five interfaces
|
|
130
134
|
|
|
131
|
-
The project exposes
|
|
135
|
+
The project exposes five small interfaces:
|
|
132
136
|
|
|
133
137
|
1. **Markers** select receipt tests.
|
|
134
|
-
2. **`gpu_proof_check`** compares a reference callable with a candidate
|
|
138
|
+
2. **`gpu_proof_check`** compares a reference callable with a candidate
|
|
139
|
+
(optional; every current user selects tests with the marker alone).
|
|
135
140
|
3. **`gpu-proof verify`** validates a receipt and repository policy.
|
|
136
141
|
4. **`gpu-proof merge`** combines separately executed shards and optionally
|
|
137
142
|
carries unchanged shards forward.
|
|
143
|
+
5. **`gpu-proof plan`** reports, before a partial re-run, which shards of an
|
|
144
|
+
old receipt could still be carried and which must be re-run.
|
|
145
|
+
|
|
146
|
+
Two maintenance helpers round it out: `gpu-proof status PATH...` summarizes
|
|
147
|
+
receipt files (commit, dirty, signed, outcome, counts, shards), and
|
|
148
|
+
`gpu-proof durations RECEIPT... --out map.json` folds per-test durations into
|
|
149
|
+
a map that shard bin-packers can calibrate from.
|
|
138
150
|
|
|
139
151
|
### Markers
|
|
140
152
|
|
|
@@ -197,6 +209,7 @@ fingerprint_excluded_paths = ["gpu-proof.json"]
|
|
|
197
209
|
required_marker = "gpu_proof"
|
|
198
210
|
max_age_days = 30
|
|
199
211
|
require_gpu = true
|
|
212
|
+
record_gpu_uuid = false # keep device name/driver/memory, omit the card's UUID
|
|
200
213
|
```
|
|
201
214
|
|
|
202
215
|
By default, the fingerprint covers every tracked file in the repository,
|
|
@@ -304,6 +317,15 @@ runtime environments, duplicate tests, and duplicate shard names. It records
|
|
|
304
317
|
input provenance but does not verify each input signature; the merger signs
|
|
305
318
|
for the union. See [sharding and carry-forward](docs/sharding.md).
|
|
306
319
|
|
|
320
|
+
Carry-forward (`gpu-proof merge ... --carry-from last-green.json`) grafts an
|
|
321
|
+
absent old shard only when its receipt commit is an ancestor of the current
|
|
322
|
+
one, both the receipt and the shard were recorded with a clean tree, its
|
|
323
|
+
narrow fingerprint recomputes identically, and it introduces no duplicate
|
|
324
|
+
test. `gpu-proof plan --receipt last-green.json` prints the same verdicts
|
|
325
|
+
without merging. Generated inputs named through `fingerprint_extra_paths`
|
|
326
|
+
should be gitignored: an untracked generated file makes the recording tree
|
|
327
|
+
dirty, and dirty recordings are never carried.
|
|
328
|
+
|
|
307
329
|
## Receipt schema
|
|
308
330
|
|
|
309
331
|
New receipts use schema `"3"`. Signer identity is inside the signed payload,
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
# pytest-gpu-proof
|
|
2
2
|
|
|
3
3
|
[](https://github.com/A2R-Lab/pytest-gpu-proof/actions/workflows/ci.yml)
|
|
4
|
-
[](https://a2r-lab.
|
|
4
|
+
[](https://a2r-lab.org/pytest-gpu-proof/docs/)
|
|
5
5
|
[](https://pypi.org/project/pytest-gpu-proof/)
|
|
6
6
|
[](https://pypi.org/project/pytest-gpu-proof/)
|
|
7
7
|
[](https://github.com/A2R-Lab/pytest-gpu-proof/actions/workflows/ci.yml)
|
|
8
8
|
|
|
9
9
|
Signed pytest receipts for local GPU runs, verified in CPU-only CI.
|
|
10
10
|
|
|
11
|
+
[Project overview](https://a2r-lab.org/pytest-gpu-proof/) ·
|
|
12
|
+
[Documentation](https://a2r-lab.org/pytest-gpu-proof/docs/) ·
|
|
13
|
+
[Website preview instructions](website/README.md)
|
|
14
|
+
|
|
11
15
|
`pytest-gpu-proof` records which marked tests ran, their outcomes, the Git
|
|
12
16
|
commit and source fingerprint, the environment, and the run time. It signs
|
|
13
17
|
that payload with an existing SSH key. A normal CPU runner can then verify the
|
|
@@ -88,15 +92,23 @@ fingerprint scope, no selected tests, or write failure makes pytest fail and
|
|
|
88
92
|
leaves no stale receipt behind. `--gpu-proof-best-effort` is an explicit
|
|
89
93
|
development-only opt-out.
|
|
90
94
|
|
|
91
|
-
##
|
|
95
|
+
## Five interfaces
|
|
92
96
|
|
|
93
|
-
The project exposes
|
|
97
|
+
The project exposes five small interfaces:
|
|
94
98
|
|
|
95
99
|
1. **Markers** select receipt tests.
|
|
96
|
-
2. **`gpu_proof_check`** compares a reference callable with a candidate
|
|
100
|
+
2. **`gpu_proof_check`** compares a reference callable with a candidate
|
|
101
|
+
(optional; every current user selects tests with the marker alone).
|
|
97
102
|
3. **`gpu-proof verify`** validates a receipt and repository policy.
|
|
98
103
|
4. **`gpu-proof merge`** combines separately executed shards and optionally
|
|
99
104
|
carries unchanged shards forward.
|
|
105
|
+
5. **`gpu-proof plan`** reports, before a partial re-run, which shards of an
|
|
106
|
+
old receipt could still be carried and which must be re-run.
|
|
107
|
+
|
|
108
|
+
Two maintenance helpers round it out: `gpu-proof status PATH...` summarizes
|
|
109
|
+
receipt files (commit, dirty, signed, outcome, counts, shards), and
|
|
110
|
+
`gpu-proof durations RECEIPT... --out map.json` folds per-test durations into
|
|
111
|
+
a map that shard bin-packers can calibrate from.
|
|
100
112
|
|
|
101
113
|
### Markers
|
|
102
114
|
|
|
@@ -159,6 +171,7 @@ fingerprint_excluded_paths = ["gpu-proof.json"]
|
|
|
159
171
|
required_marker = "gpu_proof"
|
|
160
172
|
max_age_days = 30
|
|
161
173
|
require_gpu = true
|
|
174
|
+
record_gpu_uuid = false # keep device name/driver/memory, omit the card's UUID
|
|
162
175
|
```
|
|
163
176
|
|
|
164
177
|
By default, the fingerprint covers every tracked file in the repository,
|
|
@@ -266,6 +279,15 @@ runtime environments, duplicate tests, and duplicate shard names. It records
|
|
|
266
279
|
input provenance but does not verify each input signature; the merger signs
|
|
267
280
|
for the union. See [sharding and carry-forward](docs/sharding.md).
|
|
268
281
|
|
|
282
|
+
Carry-forward (`gpu-proof merge ... --carry-from last-green.json`) grafts an
|
|
283
|
+
absent old shard only when its receipt commit is an ancestor of the current
|
|
284
|
+
one, both the receipt and the shard were recorded with a clean tree, its
|
|
285
|
+
narrow fingerprint recomputes identically, and it introduces no duplicate
|
|
286
|
+
test. `gpu-proof plan --receipt last-green.json` prints the same verdicts
|
|
287
|
+
without merging. Generated inputs named through `fingerprint_extra_paths`
|
|
288
|
+
should be gitignored: an untracked generated file makes the recording tree
|
|
289
|
+
dirty, and dirty recordings are never carried.
|
|
290
|
+
|
|
269
291
|
## Receipt schema
|
|
270
292
|
|
|
271
293
|
New receipts use schema `"3"`. Signer identity is inside the signed payload,
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
CI-GPU mode uses the same receipt and verifier but records `mode: ci-gpu`.
|
|
4
4
|
Choose it when GPU tests must run on a controlled runner rather than a
|
|
5
|
-
developer machine.
|
|
5
|
+
developer machine. The verifier binds the mode to the runner's signing key:
|
|
6
|
+
a `ci-gpu` receipt verifies only when the repository policy lists
|
|
7
|
+
`allowed_key_fingerprints` and the receipt was signed by one of them, so the
|
|
8
|
+
label cannot be claimed by an arbitrary developer key.
|
|
6
9
|
|
|
7
10
|
## Dedicated signing identity
|
|
8
11
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# pytest-gpu-proof
|
|
2
2
|
|
|
3
|
+
This is the code documentation. Use the project-overview link above for the
|
|
4
|
+
workflow figure, paper, and introduction to the tool.
|
|
5
|
+
|
|
3
6
|
**Signed pytest receipts for local GPU runs, verified in CPU-only CI.**
|
|
4
7
|
|
|
5
8
|
The plugin turns an ordinary marked pytest run into a signed, reviewable
|
|
@@ -53,7 +53,7 @@ schema 3. Repositories that have finished migrating should pin
|
|
|
53
53
|
| `allowed_key_fingerprints` | SSH SHA-256 fingerprints accepted in restricted mode |
|
|
54
54
|
| `min_schema` | Minimum acceptable receipt schema version (1, 2, or 3) |
|
|
55
55
|
| `max_age_days` | Maximum age of the merged/session receipt |
|
|
56
|
-
| `require_mode` | Required receipt mode: `local` or `ci-gpu` |
|
|
56
|
+
| `require_mode` | Required receipt mode: `local` or `ci-gpu` (a `ci-gpu` receipt additionally requires `allowed_key_fingerprints` and must be signed by a listed key) |
|
|
57
57
|
| `allow_dirty` | Permit dirty recording or verification trees |
|
|
58
58
|
| `required_fingerprint_paths` | Exact tracked path list |
|
|
59
59
|
| `required_fingerprint_extra_paths` | Exact generated/ignored path list |
|
|
@@ -61,7 +61,7 @@ schema 3. Repositories that have finished migrating should pin
|
|
|
61
61
|
| `required_test_manifest` | Repository-relative file containing exact node IDs |
|
|
62
62
|
| `required_shard_fingerprints` | Exact paths/extras for every named shard |
|
|
63
63
|
| `allow_carried` | Permit carry-forward shards |
|
|
64
|
-
| `carried_max_age_days` | Maximum age of each carried shard's original run |
|
|
64
|
+
| `carried_max_age_days` | Maximum age of each carried shard's original run (default: `max_age_days`, capped at 30) |
|
|
65
65
|
|
|
66
66
|
Example shard scope:
|
|
67
67
|
|
|
@@ -75,7 +75,9 @@ escaping, or unmerged inputs fail closed.
|
|
|
75
75
|
Schema 3 rejects dirty recording and verification trees by default. A receipt
|
|
76
76
|
may verify at its exact commit or a descendant only when the source manifest
|
|
77
77
|
still matches. Freshness limits replay duration but no server-issued nonce is
|
|
78
|
-
currently used
|
|
78
|
+
currently used; a session that started more than five minutes before its
|
|
79
|
+
commit's committer time is refused, so a receipt cannot be back-dated onto a
|
|
80
|
+
commit that did not yet exist.
|
|
79
81
|
|
|
80
82
|
`allow_dirty: true`, long age limits, `--allow-skipped`, and
|
|
81
83
|
`--allow-unsigned` weaken guarantees. Unsigned mode authenticates no signer,
|
|
@@ -98,8 +100,10 @@ each input signature. The merger's signature attests to the union. A review
|
|
|
98
100
|
workflow that needs independent shard authentication should verify every
|
|
99
101
|
input before merging.
|
|
100
102
|
|
|
101
|
-
Carry-forward means a test passed at an ancestor commit
|
|
102
|
-
inputs are byte-identical now.
|
|
103
|
+
Carry-forward means a test passed at an ancestor commit, with a clean working
|
|
104
|
+
tree, and its declared shard inputs are byte-identical now. A receipt recorded
|
|
105
|
+
dirty is never a carry source: the merged `repo.dirty` is computed from fresh
|
|
106
|
+
inputs only, so carrying from it would launder the dirty flag. It does not establish that omitted dependencies
|
|
103
107
|
or the old environment remain equivalent. Carried shards are rejected unless
|
|
104
108
|
policy opts in and bounds their original age.
|
|
105
109
|
|
|
@@ -38,8 +38,10 @@ session failed.
|
|
|
38
38
|
|
|
39
39
|
The merged receipt records input source names, counts, times, and recorded
|
|
40
40
|
signers, then signs the result with the merger's key. Input signatures are
|
|
41
|
-
provenance, not
|
|
42
|
-
|
|
41
|
+
provenance, not verified during an offline merge, unless `--verify-inputs` is
|
|
42
|
+
passed: then every input must verify against its signer's current GitHub keys
|
|
43
|
+
(unsigned inputs are refused) before the union is formed. The merger attests
|
|
44
|
+
to the union either way.
|
|
43
45
|
|
|
44
46
|
## Carry forward unchanged shards
|
|
45
47
|
|
|
@@ -55,10 +57,22 @@ An absent old shard is carried only when:
|
|
|
55
57
|
1. its receipt commit is the current commit or an ancestor;
|
|
56
58
|
2. its stored narrow manifest—including explicit extra paths—recomputes
|
|
57
59
|
identically at the current tree;
|
|
58
|
-
3.
|
|
59
|
-
|
|
60
|
+
3. its receipt was recorded with a clean working tree (`repo.dirty` false): a
|
|
61
|
+
dirty recording may have exercised uncommitted bytes outside the shard's
|
|
62
|
+
narrow manifest, and the merged `repo.dirty` covers fresh inputs only;
|
|
63
|
+
4. it does not duplicate a fresh node ID;
|
|
64
|
+
5. every claimed node ID exists in the old tests list.
|
|
65
|
+
|
|
66
|
+
Every shard entry records `dirty` (the recording tree's state), merge ORs
|
|
67
|
+
shard flags into the merged `repo.dirty`, carry-forward refuses a dirty shard,
|
|
68
|
+
each carried shard records `carried.original_dirty`, and verification refuses
|
|
69
|
+
any dirty shard unless `allow_dirty` is set.
|
|
60
70
|
|
|
61
71
|
Freshly rerun shards always win. Changed or malformed scopes require a rerun.
|
|
72
|
+
`gpu-proof plan --receipt old.json --repo .` applies the same conditions as a
|
|
73
|
+
dry run and reports, per shard, `carryable` or `stale` with the reason (add
|
|
74
|
+
`--json` for drivers), so a partial re-run can be planned without reimplementing
|
|
75
|
+
the checks.
|
|
62
76
|
|
|
63
77
|
Verification rejects carried shards by default:
|
|
64
78
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
site_name: pytest-gpu-proof
|
|
2
|
+
site_url: https://a2r-lab.org/pytest-gpu-proof/docs/
|
|
2
3
|
site_description: >-
|
|
3
4
|
pytest plugin for GPU equivalence testing with signed receipts verified via
|
|
4
5
|
GitHub SSH keys
|
|
@@ -7,6 +8,7 @@ repo_name: A2R-Lab/pytest-gpu-proof
|
|
|
7
8
|
|
|
8
9
|
theme:
|
|
9
10
|
name: material
|
|
11
|
+
custom_dir: website/overrides
|
|
10
12
|
palette:
|
|
11
13
|
- scheme: default
|
|
12
14
|
primary: indigo
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pytest-gpu-proof"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.1"
|
|
8
8
|
description = "pytest plugin for GPU equivalence testing with signed receipts verified via GitHub SSH keys"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -29,8 +29,8 @@ dependencies = [
|
|
|
29
29
|
]
|
|
30
30
|
|
|
31
31
|
[project.urls]
|
|
32
|
-
Homepage = "https://
|
|
33
|
-
Documentation = "https://a2r-lab.
|
|
32
|
+
Homepage = "https://a2r-lab.org/pytest-gpu-proof/"
|
|
33
|
+
Documentation = "https://a2r-lab.org/pytest-gpu-proof/docs/"
|
|
34
34
|
Repository = "https://github.com/A2R-Lab/pytest-gpu-proof"
|
|
35
35
|
Issues = "https://github.com/A2R-Lab/pytest-gpu-proof/issues"
|
|
36
36
|
Changelog = "https://github.com/A2R-Lab/pytest-gpu-proof/blob/main/CHANGELOG.md"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.1"
|
|
@@ -106,9 +106,116 @@ def main():
|
|
|
106
106
|
"allow_carried (default: rejected).")
|
|
107
107
|
mp.add_argument("--repo", default=".", metavar="PATH",
|
|
108
108
|
help="Repo root for carry-from fingerprint/ancestry checks")
|
|
109
|
+
mp.add_argument("--verify-inputs", action="store_true", default=False,
|
|
110
|
+
help="Authenticate every input shard's signature against the "
|
|
111
|
+
"signer's current GitHub keys before merging (unsigned inputs "
|
|
112
|
+
"are refused)")
|
|
113
|
+
|
|
114
|
+
# status
|
|
115
|
+
sp = subparsers.add_parser(
|
|
116
|
+
"status",
|
|
117
|
+
help="Summarize receipt files (commit, dirty, outcome, tests, shards, signed)",
|
|
118
|
+
)
|
|
119
|
+
sp.add_argument("paths", nargs="+", metavar="PATH",
|
|
120
|
+
help="Receipt files or directories (every *.json inside)")
|
|
121
|
+
sp.add_argument("--json", action="store_true", default=False)
|
|
122
|
+
|
|
123
|
+
# durations
|
|
124
|
+
dp = subparsers.add_parser(
|
|
125
|
+
"durations",
|
|
126
|
+
help="Fold per-test durations from receipts into a JSON map (bin-packing calibration)",
|
|
127
|
+
)
|
|
128
|
+
dp.add_argument("receipts", nargs="+", metavar="RECEIPT")
|
|
129
|
+
dp.add_argument("--out", required=True, metavar="PATH",
|
|
130
|
+
help="JSON map node_id -> seconds; merged into an existing file")
|
|
131
|
+
|
|
132
|
+
# plan
|
|
133
|
+
pp = subparsers.add_parser(
|
|
134
|
+
"plan",
|
|
135
|
+
help="Dry run of carry-forward: which shards of an old receipt could be "
|
|
136
|
+
"carried against the current tree and which must be re-run",
|
|
137
|
+
)
|
|
138
|
+
pp.add_argument("--receipt", required=True, metavar="PATH",
|
|
139
|
+
help="Older sharded receipt (the one you would --carry-from)")
|
|
140
|
+
pp.add_argument("--repo", default=".", metavar="PATH",
|
|
141
|
+
help="Repository root for fingerprint/ancestry checks (default: .)")
|
|
142
|
+
pp.add_argument("--json", action="store_true", default=False,
|
|
143
|
+
help="Print the plan as JSON instead of one line per shard")
|
|
109
144
|
|
|
110
145
|
args = parser.parse_args()
|
|
111
146
|
|
|
147
|
+
if args.command == "plan":
|
|
148
|
+
import json
|
|
149
|
+
|
|
150
|
+
from .merge import MergeError, load_receipt, plan_carry
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
report = plan_carry(load_receipt(args.receipt), repo_root=args.repo)
|
|
154
|
+
except MergeError as e:
|
|
155
|
+
print(f"gpu-proof plan: {e}", file=sys.stderr)
|
|
156
|
+
sys.exit(1)
|
|
157
|
+
if args.json:
|
|
158
|
+
print(json.dumps(report, indent=2, sort_keys=True))
|
|
159
|
+
else:
|
|
160
|
+
r = report["receipt"]
|
|
161
|
+
print(f"receipt {str(r.get('commit_sha'))[:12]} vs HEAD {str(r.get('head'))[:12]}: "
|
|
162
|
+
f"{'carryable' if r['carryable'] else 'NOT carryable'} ({r['reason']})")
|
|
163
|
+
for s in report["shards"]:
|
|
164
|
+
print(f" {s['verdict']:9s} {s['name']} ({s['node_count']} tests): {s['reason']}")
|
|
165
|
+
n = sum(1 for s in report["shards"] if s["verdict"] == "carryable")
|
|
166
|
+
print(f"{n}/{len(report['shards'])} shard(s) carryable")
|
|
167
|
+
sys.exit(0)
|
|
168
|
+
|
|
169
|
+
if args.command == "status":
|
|
170
|
+
import json
|
|
171
|
+
import os
|
|
172
|
+
|
|
173
|
+
from .merge import receipt_status
|
|
174
|
+
|
|
175
|
+
files = []
|
|
176
|
+
for p in args.paths:
|
|
177
|
+
if os.path.isdir(p):
|
|
178
|
+
files += sorted(os.path.join(p, f) for f in os.listdir(p) if f.endswith(".json"))
|
|
179
|
+
else:
|
|
180
|
+
files.append(p)
|
|
181
|
+
rows = [receipt_status(f) for f in files]
|
|
182
|
+
if args.json:
|
|
183
|
+
print(json.dumps(rows, indent=2, sort_keys=True))
|
|
184
|
+
else:
|
|
185
|
+
for r in rows:
|
|
186
|
+
if "error" in r:
|
|
187
|
+
print(f"{r['file']}: {r['error']}")
|
|
188
|
+
continue
|
|
189
|
+
print(f"{r['file']}: schema {r['schema']} @ {str(r['commit_sha'])[:12]}"
|
|
190
|
+
f"{' DIRTY' if r['dirty'] else ''}{'' if r['signed'] else ' UNSIGNED'}"
|
|
191
|
+
f" {r['outcome'] or '-'} tests={r['tests']} failed={r['failed']} "
|
|
192
|
+
f"skipped={r['skipped']} shards={len(r['shards'])} carried={r['carried']} "
|
|
193
|
+
f"ended={r['ended_at']}")
|
|
194
|
+
sys.exit(0)
|
|
195
|
+
|
|
196
|
+
if args.command == "durations":
|
|
197
|
+
import json
|
|
198
|
+
|
|
199
|
+
from .merge import MergeError, collect_durations, load_receipt
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
receipts = [load_receipt(p) for p in args.receipts]
|
|
203
|
+
except MergeError as e:
|
|
204
|
+
print(f"gpu-proof durations: {e}", file=sys.stderr)
|
|
205
|
+
sys.exit(1)
|
|
206
|
+
existing = {}
|
|
207
|
+
try:
|
|
208
|
+
with open(args.out) as f:
|
|
209
|
+
existing = json.load(f)
|
|
210
|
+
except (OSError, ValueError):
|
|
211
|
+
existing = {}
|
|
212
|
+
merged = collect_durations(receipts, existing if isinstance(existing, dict) else {})
|
|
213
|
+
with open(args.out, "w") as f:
|
|
214
|
+
json.dump(merged, f, indent=2, sort_keys=True)
|
|
215
|
+
f.write("\n")
|
|
216
|
+
print(f"durations: {len(merged)} node(s) -> {args.out}")
|
|
217
|
+
sys.exit(0)
|
|
218
|
+
|
|
112
219
|
if args.command == "merge":
|
|
113
220
|
from .merge import MergeError, merge_receipts
|
|
114
221
|
|
|
@@ -120,6 +227,7 @@ def main():
|
|
|
120
227
|
unsigned=args.unsigned,
|
|
121
228
|
carry_from=args.carry_from,
|
|
122
229
|
repo_root=args.repo,
|
|
230
|
+
verify_inputs=args.verify_inputs,
|
|
123
231
|
)
|
|
124
232
|
except MergeError as e:
|
|
125
233
|
print(f"gpu-proof merge: {e}", file=sys.stderr)
|
|
@@ -21,6 +21,9 @@ class GpuProofConfig:
|
|
|
21
21
|
github_username: Optional[str] = None
|
|
22
22
|
max_age_days: int = 30
|
|
23
23
|
require_gpu: bool = False
|
|
24
|
+
# Record each device's nvidia-smi UUID in the receipt environment. Off keeps the
|
|
25
|
+
# device name, driver, memory and compute capability but not the card identity.
|
|
26
|
+
record_gpu_uuid: bool = True
|
|
24
27
|
# Sharded emission: a declared shard name + its narrow
|
|
25
28
|
# fingerprint paths (None -> the global fingerprint_paths).
|
|
26
29
|
shard_name: Optional[str] = None
|
|
@@ -92,7 +95,7 @@ def load_config(pytest_config) -> GpuProofConfig:
|
|
|
92
95
|
return [p.strip() for p in raw.split(",") if p.strip()]
|
|
93
96
|
return [str(p) for p in raw]
|
|
94
97
|
|
|
95
|
-
max_age =
|
|
98
|
+
max_age = resolve("--gpu-proof-max-age-days", "max_age_days", None)
|
|
96
99
|
max_age_days = int(max_age) if max_age is not None else 30
|
|
97
100
|
|
|
98
101
|
return GpuProofConfig(
|
|
@@ -117,7 +120,11 @@ def load_config(pytest_config) -> GpuProofConfig:
|
|
|
117
120
|
),
|
|
118
121
|
github_username=resolve("--gpu-proof-github-user", "github_username", None),
|
|
119
122
|
max_age_days=max_age_days,
|
|
120
|
-
|
|
123
|
+
# store_true flags can only turn a setting ON (or, for the UUID, OFF).
|
|
124
|
+
require_gpu=bool(opt("--gpu-proof-require-gpu", False)
|
|
125
|
+
or toml_cfg.get("require_gpu", False)),
|
|
126
|
+
record_gpu_uuid=bool(toml_cfg.get("record_gpu_uuid", True))
|
|
127
|
+
and not bool(opt("--gpu-proof-no-gpu-uuid", False)),
|
|
121
128
|
shard_name=resolve("--gpu-proof-shard", "shard_name", None),
|
|
122
129
|
shard_fingerprint_paths=shard_paths,
|
|
123
130
|
shard_fingerprint_extra_paths=(
|
|
@@ -49,6 +49,15 @@ def get_branch(root: str = ".") -> Optional[str]:
|
|
|
49
49
|
return _git("rev-parse", "--abbrev-ref", "HEAD", root=root)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
def get_commit_time(sha: str, root: str = ".") -> Optional[int]:
|
|
53
|
+
"""Committer time (Unix seconds) of ``sha``, or None when git cannot answer."""
|
|
54
|
+
value = _git("show", "-s", "--format=%ct", sha, root=root)
|
|
55
|
+
try:
|
|
56
|
+
return int(value) if value else None
|
|
57
|
+
except ValueError:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
52
61
|
def is_dirty(
|
|
53
62
|
root: str = ".",
|
|
54
63
|
*,
|