pytest-gpu-proof 0.1.0__tar.gz → 0.2.0__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.1.0 → pytest_gpu_proof-0.2.0}/CHANGELOG.md +17 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/PKG-INFO +1 -1
- pytest_gpu_proof-0.2.0/docs/sharding.md +62 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/mkdocs.yml +1 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/pyproject.toml +1 -1
- pytest_gpu_proof-0.2.0/src/pytest_gpu_proof/__init__.py +1 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/src/pytest_gpu_proof/cli.py +45 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/src/pytest_gpu_proof/config.py +11 -5
- pytest_gpu_proof-0.2.0/src/pytest_gpu_proof/merge.py +161 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/src/pytest_gpu_proof/plugin.py +5 -5
- pytest_gpu_proof-0.2.0/tests/test_merge.py +163 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/tests/test_plugin_capture.py +31 -0
- pytest_gpu_proof-0.1.0/src/pytest_gpu_proof/__init__.py +0 -1
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/.github/workflows/ci.yml +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/.github/workflows/docs.yml +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/.github/workflows/publish-testpypi.yml +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/.github/workflows/publish.yml +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/.gitignore +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/CLAUDE.md +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/LICENSE +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/README.md +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/RELEASING.md +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/ROADMAP.md +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/docs/architecture.md +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/docs/ci_gpu_mode.md +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/docs/index.md +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/docs/landscape.md +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/docs/local_mode.md +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/docs/quickstart.md +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/docs/security_model.md +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_ctypes_matmul/Makefile +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_ctypes_matmul/README.md +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_ctypes_matmul/cuda_ctypes_matmul.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_ctypes_matmul/matmul_kernel.cu +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_ctypes_matmul/test_cuda_ctypes_matmul.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_nanobind_matmul/CMakeLists.txt +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_nanobind_matmul/README.md +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_nanobind_matmul/src/bindings.cpp +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_nanobind_matmul/src/matmul_cuda.h +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_nanobind_matmul/src/matmul_kernel.cu +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_nanobind_matmul/test_cuda_nanobind_matmul.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_pybind11_matmul/CMakeLists.txt +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_pybind11_matmul/README.md +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_pybind11_matmul/src/bindings.cpp +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_pybind11_matmul/src/matmul_cuda.h +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_pybind11_matmul/src/matmul_kernel.cu +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_pybind11_matmul/test_cuda_pybind11_matmul.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/github_gpu_runner/.github/workflows/gpu-test.yml +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/jax_ffi_cuda_matmul/CMakeLists.txt +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/jax_ffi_cuda_matmul/README.md +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/jax_ffi_cuda_matmul/jax_ffi_matmul.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/jax_ffi_cuda_matmul/src/matmul_ffi.cu +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/jax_ffi_cuda_matmul/test_jax_ffi_cuda_matmul.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/local_receipt_verify/.github/workflows/verify.yml +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/minimal_python_only/conftest.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/minimal_python_only/test_minimal.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/wrapped_cuda_ctypes/fake_cuda_lib.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/wrapped_cuda_ctypes/test_cuda_wrapper.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/install.sh +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/requirements-dev.txt +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/requirements.txt +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/src/pytest_gpu_proof/__main__.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/src/pytest_gpu_proof/compare.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/src/pytest_gpu_proof/fingerprint.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/src/pytest_gpu_proof/gitutils.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/src/pytest_gpu_proof/receipt.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/src/pytest_gpu_proof/signers/__init__.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/src/pytest_gpu_proof/signers/base.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/src/pytest_gpu_proof/signers/ed25519.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/src/pytest_gpu_proof/verify.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/tests/conftest.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/tests/test_compare.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/tests/test_fingerprint.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/tests/test_gitutils.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/tests/test_receipt.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/tests/test_signing.py +0 -0
- {pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/tests/test_verifier.py +0 -0
|
@@ -4,6 +4,23 @@ 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.2.0] — 2026-08-07
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- `gpu-proof merge`: union N shard receipts from one commit into a single
|
|
11
|
+
re-signed receipt (per-module crash isolation / machine sharding). Refuses
|
|
12
|
+
shards that disagree on schema, commit SHA, fingerprint, mode, or
|
|
13
|
+
environment; duplicate node IDs across shards are a hard error. Records
|
|
14
|
+
per-shard provenance under `session.shards` (additive — the verifier is
|
|
15
|
+
unchanged). `repo.dirty` is OR-ed; `gpu_info` survives CPU-only shards.
|
|
16
|
+
Docs: `docs/sharding.md`.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- Explicit CLI values equal to their built-in defaults are no longer silently
|
|
20
|
+
ignored in favor of `[tool.gpu_proof]` (value-taking options now register a
|
|
21
|
+
`None` sentinel; `--gpu-proof-fail-on-skip` ORs with the toml value since a
|
|
22
|
+
store_true flag can only turn it on).
|
|
23
|
+
|
|
7
24
|
## [0.1.0] — 2026-07-07
|
|
8
25
|
|
|
9
26
|
First public release.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest-gpu-proof
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: pytest plugin for GPU equivalence testing with signed receipts verified via GitHub SSH keys
|
|
5
5
|
Project-URL: Homepage, https://github.com/A2R-Lab/pytest-gpu-proof
|
|
6
6
|
Project-URL: Documentation, https://a2r-lab.github.io/pytest-gpu-proof/
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Sharded runs & merging receipts
|
|
2
|
+
|
|
3
|
+
Large suites often can't (or shouldn't) run as one pytest session: per-module
|
|
4
|
+
subprocesses give crash isolation (one CUDA abort no longer erases the whole
|
|
5
|
+
run's results), and big projects split GPU tests across invocations or
|
|
6
|
+
machines. Each invocation emits its own receipt; CI wants **one** artifact.
|
|
7
|
+
|
|
8
|
+
## Emit one receipt per shard
|
|
9
|
+
|
|
10
|
+
Point each invocation at its own output path:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pytest tests/gpu/test_a.py --gpu-proof-enable --gpu-proof-out=receipts/a.json
|
|
14
|
+
pytest tests/gpu/test_b.py --gpu-proof-enable --gpu-proof-out=receipts/b.json
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Every shard receipt is a complete, individually verifiable receipt.
|
|
18
|
+
|
|
19
|
+
## Merge
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
gpu-proof merge --out gpu-proof.json receipts/a.json receipts/b.json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`merge` unions the shards' `tests`, spans `session.started_at`/`ended_at`
|
|
26
|
+
across them, records per-shard provenance under `session.shards`
|
|
27
|
+
(`source`, `node_count`, timestamps, and each shard's recorded signer), and
|
|
28
|
+
**re-signs the merged payload with your local SSH key**. The result flows
|
|
29
|
+
through `gpu-proof verify` completely unchanged — same schema, same seven
|
|
30
|
+
checks.
|
|
31
|
+
|
|
32
|
+
Options:
|
|
33
|
+
|
|
34
|
+
- `--github-user USERNAME` — recorded signer identity for the merged receipt
|
|
35
|
+
(default: the first shard's `repo.github_username`).
|
|
36
|
+
- `--key PATH` — SSH private key (default: `git config user.signingKey`, then
|
|
37
|
+
`~/.ssh/id_ed25519` / `id_ecdsa` / `id_rsa`).
|
|
38
|
+
- `--unsigned` — write `signature: null`; verifies only with
|
|
39
|
+
`--allow-unsigned`, loudly.
|
|
40
|
+
|
|
41
|
+
## What merge refuses
|
|
42
|
+
|
|
43
|
+
A merged receipt must mean exactly what a single-session receipt means, so
|
|
44
|
+
`merge` hard-refuses shards that disagree on anything a receipt pins:
|
|
45
|
+
|
|
46
|
+
- `schema_version`, `repo.commit_sha`, `fingerprint` (digest + paths),
|
|
47
|
+
`mode`, or the `environment` the tests ran under (python/pytest/plugin
|
|
48
|
+
versions, platform);
|
|
49
|
+
- **duplicate node IDs across shards** — two shards attesting the same test is
|
|
50
|
+
a sharding bug in the runner, never something to dedupe silently.
|
|
51
|
+
|
|
52
|
+
`repo.dirty` is OR-ed: one dirty shard makes the merged attestation dirty, and
|
|
53
|
+
your verify-time dirty policy applies honestly. `gpu_info` is taken from the
|
|
54
|
+
first shard that has one, so a CPU-only shard doesn't erase the GPU record.
|
|
55
|
+
|
|
56
|
+
## Trust model
|
|
57
|
+
|
|
58
|
+
Consistent with the [security model](security_model.md): the merged receipt is
|
|
59
|
+
an **attestation by the merger**. Shard signatures are recorded as provenance
|
|
60
|
+
but not re-verified at merge time (merging is offline); the merged signature
|
|
61
|
+
is what CI verifies. If shards were signed by someone else, verification of
|
|
62
|
+
the merged receipt attests that *you* vouch for the union.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pytest-gpu-proof"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
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"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0"
|
|
@@ -72,8 +72,53 @@ def main():
|
|
|
72
72
|
"require_gpu = true in [tool.gpu_proof].",
|
|
73
73
|
)
|
|
74
74
|
|
|
75
|
+
# merge
|
|
76
|
+
mp = subparsers.add_parser(
|
|
77
|
+
"merge",
|
|
78
|
+
help="Merge shard receipts from ONE commit into a single re-signed receipt",
|
|
79
|
+
description=(
|
|
80
|
+
"Union the tests of N shard receipts (same commit SHA, fingerprint, "
|
|
81
|
+
"and environment) into one receipt, re-signed with your local SSH "
|
|
82
|
+
"key, that flows through `gpu-proof verify` unchanged. Shards that "
|
|
83
|
+
"disagree on anything a receipt pins are refused; duplicate node "
|
|
84
|
+
"IDs across shards are always an error."
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
mp.add_argument("shards", nargs="+", metavar="RECEIPT",
|
|
88
|
+
help="Shard receipt paths (two or more, typically)")
|
|
89
|
+
mp.add_argument("--out", required=True, metavar="PATH",
|
|
90
|
+
help="Path for the merged receipt")
|
|
91
|
+
mp.add_argument("--github-user", default=None, metavar="USERNAME",
|
|
92
|
+
help="Recorded signer identity for the merged receipt "
|
|
93
|
+
"(default: the first shard's repo.github_username)")
|
|
94
|
+
mp.add_argument("--key", default=None, metavar="PATH",
|
|
95
|
+
help="SSH private key to sign with (default: git "
|
|
96
|
+
"user.signingKey, then ~/.ssh/id_ed25519 etc.)")
|
|
97
|
+
mp.add_argument("--unsigned", action="store_true", default=False,
|
|
98
|
+
help="Write signature: null — the merged receipt then "
|
|
99
|
+
"verifies only with --allow-unsigned, loudly")
|
|
100
|
+
|
|
75
101
|
args = parser.parse_args()
|
|
76
102
|
|
|
103
|
+
if args.command == "merge":
|
|
104
|
+
from .merge import MergeError, merge_receipts
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
receipt = merge_receipts(
|
|
108
|
+
args.shards, args.out,
|
|
109
|
+
github_user=args.github_user,
|
|
110
|
+
key_path=args.key,
|
|
111
|
+
unsigned=args.unsigned,
|
|
112
|
+
)
|
|
113
|
+
except MergeError as e:
|
|
114
|
+
print(f"gpu-proof merge: {e}", file=sys.stderr)
|
|
115
|
+
sys.exit(1)
|
|
116
|
+
n = len(receipt.get("tests", []))
|
|
117
|
+
shards = len(receipt.get("session", {}).get("shards", []))
|
|
118
|
+
print(f"merged {shards} shard(s), {n} tests -> {args.out}"
|
|
119
|
+
+ (" (UNSIGNED)" if args.unsigned else ""))
|
|
120
|
+
sys.exit(0)
|
|
121
|
+
|
|
77
122
|
if args.command == "verify":
|
|
78
123
|
from .verify import verify_receipt
|
|
79
124
|
|
|
@@ -47,10 +47,13 @@ def load_config(pytest_config) -> GpuProofConfig:
|
|
|
47
47
|
return default
|
|
48
48
|
|
|
49
49
|
def resolve(opt_name, toml_key, default):
|
|
50
|
-
"""Precedence: CLI flag (when
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
"""Precedence: CLI flag (when explicitly passed), then [tool.gpu_proof]
|
|
51
|
+
in pyproject.toml, then the built-in default. Value-taking options
|
|
52
|
+
register with ``default=None`` so an explicit CLI value that happens to
|
|
53
|
+
equal the built-in default still wins — previously it was silently
|
|
54
|
+
ignored in favor of the toml value."""
|
|
55
|
+
cli = opt(opt_name, None)
|
|
56
|
+
if cli is not None:
|
|
54
57
|
return cli
|
|
55
58
|
toml_val = toml_cfg.get(toml_key)
|
|
56
59
|
if toml_val is not None:
|
|
@@ -74,7 +77,10 @@ def load_config(pytest_config) -> GpuProofConfig:
|
|
|
74
77
|
signing_backend=resolve("--gpu-proof-signing-backend", "signing_backend", "ed25519"),
|
|
75
78
|
policy_path=resolve("--gpu-proof-policy", "policy_path", None),
|
|
76
79
|
required_marker=resolve("--gpu-proof-required-marker", "required_marker", "gpu_proof"),
|
|
77
|
-
|
|
80
|
+
# store_true flag: False just means "not passed", so OR with the toml
|
|
81
|
+
# value rather than sentinel-resolving (a CLI flag can only turn it ON).
|
|
82
|
+
fail_on_skip=bool(opt("--gpu-proof-fail-on-skip", False)
|
|
83
|
+
or toml_cfg.get("fail_on_skip", False)),
|
|
78
84
|
fingerprint_paths=paths,
|
|
79
85
|
github_username=resolve("--gpu-proof-github-user", "github_username", None),
|
|
80
86
|
max_age_days=max_age_days,
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Merge shard receipts from one commit into a single re-signed receipt.
|
|
3
|
+
|
|
4
|
+
Motivation: large suites run their GPU tests as several pytest invocations
|
|
5
|
+
(per-module crash isolation, machine sharding). Each invocation emits its own
|
|
6
|
+
receipt; CI wants ONE artifact to verify. ``gpu-proof merge`` unions the shard
|
|
7
|
+
receipts' ``tests`` and re-signs the result with the merger's local SSH key —
|
|
8
|
+
so the merged receipt flows through the existing ``gpu-proof verify`` path
|
|
9
|
+
completely unchanged (schema_version stays "1"; the only addition is the
|
|
10
|
+
OPTIONAL ``session.shards`` provenance list, which the verifier ignores).
|
|
11
|
+
|
|
12
|
+
Trust model (consistent with docs/security_model.md): the merged receipt is an
|
|
13
|
+
attestation by the MERGER — shard signatures are recorded as provenance but are
|
|
14
|
+
NOT verified here (merge is offline by design); the merged receipt's signature
|
|
15
|
+
is what CI verifies. Merging refuses to combine shards that disagree on
|
|
16
|
+
anything a receipt pins: schema, commit SHA, fingerprint, mode, or the
|
|
17
|
+
environment the tests ran under. Duplicate node IDs are always an error — two
|
|
18
|
+
shards attesting the same test is a sharding bug, not a merge input.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
from typing import List, Optional
|
|
24
|
+
|
|
25
|
+
from .receipt import finalize_receipt, write_receipt
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MergeError(ValueError):
|
|
29
|
+
"""A refusal to merge, with an actionable message."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def load_receipt(path: str) -> dict:
|
|
33
|
+
try:
|
|
34
|
+
with open(path) as f:
|
|
35
|
+
data = json.load(f)
|
|
36
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
37
|
+
raise MergeError(f"{path}: not a readable receipt ({e})") from e
|
|
38
|
+
if not isinstance(data, dict) or "tests" not in data:
|
|
39
|
+
raise MergeError(f"{path}: not a gpu-proof receipt (no 'tests' key)")
|
|
40
|
+
return data
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _require_identical(receipts: List[dict], sources: List[str], getter, what: str):
|
|
44
|
+
values = [getter(r) for r in receipts]
|
|
45
|
+
first = values[0]
|
|
46
|
+
for src, val in zip(sources[1:], values[1:]):
|
|
47
|
+
if val != first:
|
|
48
|
+
raise MergeError(
|
|
49
|
+
f"shards disagree on {what}: {sources[0]}={first!r} vs {src}={val!r} — "
|
|
50
|
+
f"a merged receipt must come from ONE commit/config; re-run the "
|
|
51
|
+
f"divergent shard."
|
|
52
|
+
)
|
|
53
|
+
return first
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def merge_payloads(receipts: List[dict], sources: List[str]) -> dict:
|
|
57
|
+
"""Union shard payloads into one UNSIGNED payload. Raises MergeError on any
|
|
58
|
+
disagreement over what a receipt pins."""
|
|
59
|
+
if len(receipts) < 1:
|
|
60
|
+
raise MergeError("nothing to merge")
|
|
61
|
+
|
|
62
|
+
_require_identical(receipts, sources, lambda r: r.get("schema_version"), "schema_version")
|
|
63
|
+
if receipts[0].get("schema_version") != "1":
|
|
64
|
+
raise MergeError(
|
|
65
|
+
f"unsupported schema_version {receipts[0].get('schema_version')!r} "
|
|
66
|
+
f"(this version merges schema '1' receipts)"
|
|
67
|
+
)
|
|
68
|
+
_require_identical(receipts, sources, lambda r: r.get("repo", {}).get("commit_sha"),
|
|
69
|
+
"repo.commit_sha")
|
|
70
|
+
_require_identical(receipts, sources, lambda r: r.get("fingerprint"), "fingerprint")
|
|
71
|
+
_require_identical(receipts, sources, lambda r: r.get("mode"), "mode")
|
|
72
|
+
for key in ("python_version", "platform", "pytest_version", "plugin_version"):
|
|
73
|
+
_require_identical(receipts, sources,
|
|
74
|
+
lambda r, k=key: r.get("environment", {}).get(k),
|
|
75
|
+
f"environment.{key}")
|
|
76
|
+
|
|
77
|
+
# tests: union; duplicate node ids are a sharding bug, never silently deduped.
|
|
78
|
+
tests: List[dict] = []
|
|
79
|
+
seen: dict = {}
|
|
80
|
+
for src, r in zip(sources, receipts):
|
|
81
|
+
for t in r.get("tests", []):
|
|
82
|
+
nid = t.get("node_id")
|
|
83
|
+
if nid in seen:
|
|
84
|
+
raise MergeError(
|
|
85
|
+
f"duplicate node_id {nid!r} in {src} (already attested by "
|
|
86
|
+
f"{seen[nid]}) — shards must partition the suite."
|
|
87
|
+
)
|
|
88
|
+
seen[nid] = src
|
|
89
|
+
tests.append(t)
|
|
90
|
+
if not tests:
|
|
91
|
+
raise MergeError("merged receipt would contain zero tests")
|
|
92
|
+
|
|
93
|
+
sessions = [r.get("session", {}) for r in receipts]
|
|
94
|
+
started = min(s.get("started_at") for s in sessions)
|
|
95
|
+
ended = max(s.get("ended_at") for s in sessions)
|
|
96
|
+
|
|
97
|
+
merged = dict(receipts[0])
|
|
98
|
+
merged.pop("signature", None)
|
|
99
|
+
# repo: the commit SHA is asserted identical above; branch/remote come from
|
|
100
|
+
# the first shard. `dirty` is OR-ed — one dirty shard makes the merged
|
|
101
|
+
# attestation dirty, and the verifier's dirty policy then applies honestly.
|
|
102
|
+
merged["repo"] = dict(receipts[0].get("repo", {}))
|
|
103
|
+
merged["repo"]["dirty"] = any(r.get("repo", {}).get("dirty") for r in receipts)
|
|
104
|
+
# environment: fields asserted identical above; gpu_info from the first
|
|
105
|
+
# shard that has one (a CPU-only shard shouldn't erase the GPU record).
|
|
106
|
+
merged["environment"] = dict(receipts[0].get("environment", {}))
|
|
107
|
+
merged["environment"]["gpu_info"] = next(
|
|
108
|
+
(r["environment"]["gpu_info"] for r in receipts
|
|
109
|
+
if r.get("environment", {}).get("gpu_info")), None)
|
|
110
|
+
merged["tests"] = tests
|
|
111
|
+
merged["session"] = {
|
|
112
|
+
"started_at": started,
|
|
113
|
+
"ended_at": ended,
|
|
114
|
+
"node_ids": [t["node_id"] for t in tests],
|
|
115
|
+
# Provenance (additive; the verifier ignores unknown session keys).
|
|
116
|
+
# Shard signers are recorded, not verified — the merged signature is
|
|
117
|
+
# the attestation CI checks.
|
|
118
|
+
"shards": [
|
|
119
|
+
{
|
|
120
|
+
"source": os.path.basename(src),
|
|
121
|
+
"node_count": len(r.get("tests", [])),
|
|
122
|
+
"started_at": s.get("started_at"),
|
|
123
|
+
"ended_at": s.get("ended_at"),
|
|
124
|
+
"signer": (r.get("signature") or {}).get("signer"),
|
|
125
|
+
}
|
|
126
|
+
for src, r, s in zip(sources, receipts, sessions)
|
|
127
|
+
],
|
|
128
|
+
}
|
|
129
|
+
return merged
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def merge_receipts(
|
|
133
|
+
paths: List[str],
|
|
134
|
+
out: str,
|
|
135
|
+
*,
|
|
136
|
+
github_user: Optional[str] = None,
|
|
137
|
+
key_path: Optional[str] = None,
|
|
138
|
+
unsigned: bool = False,
|
|
139
|
+
) -> dict:
|
|
140
|
+
"""Merge receipts at ``paths`` and write the re-signed result to ``out``.
|
|
141
|
+
|
|
142
|
+
``github_user`` overrides the recorded signer identity (default: the first
|
|
143
|
+
shard's ``repo.github_username`` — correct when the merger is also the shard
|
|
144
|
+
runner). ``unsigned=True`` writes ``signature: null`` (verifies only with
|
|
145
|
+
``--allow-unsigned``, loudly, same as the plugin's 'none' backend).
|
|
146
|
+
"""
|
|
147
|
+
receipts = [load_receipt(p) for p in paths]
|
|
148
|
+
payload = merge_payloads(receipts, list(paths))
|
|
149
|
+
if github_user:
|
|
150
|
+
payload["repo"] = dict(payload.get("repo", {}))
|
|
151
|
+
payload["repo"]["github_username"] = github_user
|
|
152
|
+
|
|
153
|
+
if unsigned:
|
|
154
|
+
receipt = dict(payload)
|
|
155
|
+
receipt["signature"] = None
|
|
156
|
+
else:
|
|
157
|
+
from .signers.ed25519 import SSHSigner
|
|
158
|
+
signer = SSHSigner(key_path=key_path)
|
|
159
|
+
receipt = finalize_receipt(payload, signer)
|
|
160
|
+
write_receipt(receipt, out)
|
|
161
|
+
return receipt
|
|
@@ -201,13 +201,13 @@ def pytest_addoption(parser):
|
|
|
201
201
|
)
|
|
202
202
|
group.addoption(
|
|
203
203
|
"--gpu-proof-mode",
|
|
204
|
-
default=
|
|
204
|
+
default=None,
|
|
205
205
|
choices=["local", "ci-gpu"],
|
|
206
206
|
help="Execution mode: local (default) or ci-gpu",
|
|
207
207
|
)
|
|
208
208
|
group.addoption(
|
|
209
209
|
"--gpu-proof-out",
|
|
210
|
-
default=
|
|
210
|
+
default=None,
|
|
211
211
|
metavar="PATH",
|
|
212
212
|
help="Output path for the receipt JSON (default: gpu-proof.json)",
|
|
213
213
|
)
|
|
@@ -219,7 +219,7 @@ def pytest_addoption(parser):
|
|
|
219
219
|
)
|
|
220
220
|
group.addoption(
|
|
221
221
|
"--gpu-proof-signing-backend",
|
|
222
|
-
default=
|
|
222
|
+
default=None,
|
|
223
223
|
choices=["ed25519", "none"],
|
|
224
224
|
help="Signing backend (default: ed25519 via SSH key)",
|
|
225
225
|
)
|
|
@@ -231,7 +231,7 @@ def pytest_addoption(parser):
|
|
|
231
231
|
)
|
|
232
232
|
group.addoption(
|
|
233
233
|
"--gpu-proof-required-marker",
|
|
234
|
-
default=
|
|
234
|
+
default=None,
|
|
235
235
|
help="Marker name that flags a test for the receipt (default: gpu_proof)",
|
|
236
236
|
)
|
|
237
237
|
group.addoption(
|
|
@@ -242,7 +242,7 @@ def pytest_addoption(parser):
|
|
|
242
242
|
)
|
|
243
243
|
group.addoption(
|
|
244
244
|
"--gpu-proof-fingerprint-paths",
|
|
245
|
-
default=
|
|
245
|
+
default=None,
|
|
246
246
|
metavar="PATHS",
|
|
247
247
|
help="Comma-separated paths to fingerprint (default: src,tests)",
|
|
248
248
|
)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""gpu-proof merge: shard-union semantics, refusal matrix, and end-to-end
|
|
2
|
+
verification of a merged receipt."""
|
|
3
|
+
|
|
4
|
+
import datetime
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from unittest.mock import patch
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from cryptography.hazmat.primitives.serialization import (
|
|
11
|
+
Encoding,
|
|
12
|
+
NoEncryption,
|
|
13
|
+
PrivateFormat,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from pytest_gpu_proof.config import GpuProofConfig
|
|
17
|
+
from pytest_gpu_proof.merge import MergeError, merge_payloads, merge_receipts
|
|
18
|
+
from pytest_gpu_proof.receipt import build_receipt_payload, finalize_receipt, write_receipt
|
|
19
|
+
from pytest_gpu_proof.signers.ed25519 import SSHSigner, _verify_with_key
|
|
20
|
+
from pytest_gpu_proof.verify import verify_receipt
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def signer_with_key(tmp_path, ed25519_keypair):
|
|
24
|
+
private_key, public_key = ed25519_keypair
|
|
25
|
+
key_path = tmp_path / "id_ed25519"
|
|
26
|
+
key_path.write_bytes(
|
|
27
|
+
private_key.private_bytes(Encoding.PEM, PrivateFormat.OpenSSH, NoEncryption())
|
|
28
|
+
)
|
|
29
|
+
return SSHSigner(key_path=str(key_path)), public_key, str(key_path)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _utcstamp(minutes_ago: int = 0) -> str:
|
|
33
|
+
ts = datetime.datetime.now(datetime.UTC) - datetime.timedelta(minutes=minutes_ago)
|
|
34
|
+
return ts.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _result(node_id, outcome="passed"):
|
|
38
|
+
return {"node_id": node_id, "outcome": outcome, "duration_s": 0.01, "checks": []}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _shard(tmp_path, tmp_git_repo, signer, name, results, *,
|
|
42
|
+
started_ago=2, ended_ago=1, mutate=None, sign=True):
|
|
43
|
+
os.chdir(tmp_git_repo)
|
|
44
|
+
config = GpuProofConfig(enabled=True, fingerprint_paths=["src", "tests"])
|
|
45
|
+
payload = build_receipt_payload(
|
|
46
|
+
config, results, _utcstamp(started_ago), _utcstamp(ended_ago))
|
|
47
|
+
if mutate is not None:
|
|
48
|
+
mutate(payload)
|
|
49
|
+
if sign:
|
|
50
|
+
receipt = finalize_receipt(payload, signer)
|
|
51
|
+
else:
|
|
52
|
+
receipt = dict(payload)
|
|
53
|
+
receipt["signature"] = None
|
|
54
|
+
path = tmp_path / name
|
|
55
|
+
write_receipt(receipt, str(path))
|
|
56
|
+
return path
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _mock_github_keys(public_key):
|
|
60
|
+
def _fake_verify(data, signature, username):
|
|
61
|
+
return _verify_with_key(public_key, signature, data)
|
|
62
|
+
return patch("pytest_gpu_proof.verify.verify_with_github_keys",
|
|
63
|
+
side_effect=_fake_verify)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_happy_merge_verifies(tmp_path, tmp_git_repo, signer_with_key):
|
|
67
|
+
signer, public_key, key_path = signer_with_key
|
|
68
|
+
a = _shard(tmp_path, tmp_git_repo, signer, "a.json",
|
|
69
|
+
[_result("tests/test_add.py::test_add")], started_ago=10, ended_ago=8)
|
|
70
|
+
b = _shard(tmp_path, tmp_git_repo, signer, "b.json",
|
|
71
|
+
[_result("tests/test_add.py::test_sub")], started_ago=5, ended_ago=3)
|
|
72
|
+
out = tmp_path / "merged.json"
|
|
73
|
+
merged = merge_receipts([str(a), str(b)], str(out), key_path=key_path)
|
|
74
|
+
|
|
75
|
+
assert [t["node_id"] for t in merged["tests"]] == [
|
|
76
|
+
"tests/test_add.py::test_add", "tests/test_add.py::test_sub"]
|
|
77
|
+
sess = merged["session"]
|
|
78
|
+
# span = earliest start .. latest end across shards
|
|
79
|
+
assert sess["started_at"] == json.loads(a.read_text())["session"]["started_at"]
|
|
80
|
+
assert sess["ended_at"] == json.loads(b.read_text())["session"]["ended_at"]
|
|
81
|
+
assert [s["source"] for s in sess["shards"]] == ["a.json", "b.json"]
|
|
82
|
+
assert all(s["signer"] for s in sess["shards"])
|
|
83
|
+
|
|
84
|
+
# the merged receipt goes through the UNCHANGED verifier
|
|
85
|
+
os.chdir(tmp_git_repo)
|
|
86
|
+
with _mock_github_keys(public_key):
|
|
87
|
+
assert verify_receipt(receipt_path=str(out), policy_path=None,
|
|
88
|
+
repo_root=str(tmp_git_repo),
|
|
89
|
+
github_user_override="testuser",
|
|
90
|
+
max_age_days=None)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_refuses_commit_sha_mismatch(tmp_path, tmp_git_repo, signer_with_key):
|
|
94
|
+
signer, _, key_path = signer_with_key
|
|
95
|
+
a = _shard(tmp_path, tmp_git_repo, signer, "a.json",
|
|
96
|
+
[_result("t::a")])
|
|
97
|
+
b = _shard(tmp_path, tmp_git_repo, signer, "b.json",
|
|
98
|
+
[_result("t::b")],
|
|
99
|
+
mutate=lambda p: p["repo"].__setitem__("commit_sha", "deadbeef"))
|
|
100
|
+
with pytest.raises(MergeError, match="commit_sha"):
|
|
101
|
+
merge_payloads([json.loads(a.read_text()), json.loads(b.read_text())],
|
|
102
|
+
["a.json", "b.json"])
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_refuses_fingerprint_mismatch(tmp_path, tmp_git_repo, signer_with_key):
|
|
106
|
+
signer, _, key_path = signer_with_key
|
|
107
|
+
a = _shard(tmp_path, tmp_git_repo, signer, "a.json", [_result("t::a")])
|
|
108
|
+
b = _shard(tmp_path, tmp_git_repo, signer, "b.json", [_result("t::b")],
|
|
109
|
+
mutate=lambda p: p["fingerprint"].__setitem__("digest", "0" * 64))
|
|
110
|
+
with pytest.raises(MergeError, match="fingerprint"):
|
|
111
|
+
merge_payloads([json.loads(a.read_text()), json.loads(b.read_text())],
|
|
112
|
+
["a.json", "b.json"])
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_refuses_duplicate_node_id(tmp_path, tmp_git_repo, signer_with_key):
|
|
116
|
+
signer, _, key_path = signer_with_key
|
|
117
|
+
a = _shard(tmp_path, tmp_git_repo, signer, "a.json", [_result("t::same")])
|
|
118
|
+
b = _shard(tmp_path, tmp_git_repo, signer, "b.json", [_result("t::same")])
|
|
119
|
+
with pytest.raises(MergeError, match="duplicate node_id"):
|
|
120
|
+
merge_payloads([json.loads(a.read_text()), json.loads(b.read_text())],
|
|
121
|
+
["a.json", "b.json"])
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_refuses_environment_mismatch(tmp_path, tmp_git_repo, signer_with_key):
|
|
125
|
+
signer, _, key_path = signer_with_key
|
|
126
|
+
a = _shard(tmp_path, tmp_git_repo, signer, "a.json", [_result("t::a")])
|
|
127
|
+
b = _shard(tmp_path, tmp_git_repo, signer, "b.json", [_result("t::b")],
|
|
128
|
+
mutate=lambda p: p["environment"].__setitem__("pytest_version", "0.0"))
|
|
129
|
+
with pytest.raises(MergeError, match="environment.pytest_version"):
|
|
130
|
+
merge_payloads([json.loads(a.read_text()), json.loads(b.read_text())],
|
|
131
|
+
["a.json", "b.json"])
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_unsigned_shards_merge_and_dirty_ors(tmp_path, tmp_git_repo, signer_with_key):
|
|
135
|
+
signer, _, key_path = signer_with_key
|
|
136
|
+
a = _shard(tmp_path, tmp_git_repo, signer, "a.json", [_result("t::a")], sign=False)
|
|
137
|
+
b = _shard(tmp_path, tmp_git_repo, signer, "b.json", [_result("t::b")], sign=False,
|
|
138
|
+
mutate=lambda p: p["repo"].__setitem__("dirty", True))
|
|
139
|
+
merged = merge_payloads([json.loads(a.read_text()), json.loads(b.read_text())],
|
|
140
|
+
["a.json", "b.json"])
|
|
141
|
+
assert merged["repo"]["dirty"] is True
|
|
142
|
+
assert merged["session"]["shards"][0]["signer"] is None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_merged_unsigned_flag(tmp_path, tmp_git_repo, signer_with_key):
|
|
146
|
+
signer, _, key_path = signer_with_key
|
|
147
|
+
a = _shard(tmp_path, tmp_git_repo, signer, "a.json", [_result("t::a")])
|
|
148
|
+
out = tmp_path / "merged.json"
|
|
149
|
+
merged = merge_receipts([str(a)], str(out), unsigned=True)
|
|
150
|
+
assert merged["signature"] is None
|
|
151
|
+
assert json.loads(out.read_text())["signature"] is None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def test_gpu_info_survives_cpu_only_shard(tmp_path, tmp_git_repo, signer_with_key):
|
|
155
|
+
signer, _, key_path = signer_with_key
|
|
156
|
+
gpu = {"name": "FakeGPU", "driver_version": "1", "memory": "1 MiB"}
|
|
157
|
+
a = _shard(tmp_path, tmp_git_repo, signer, "a.json", [_result("t::a")],
|
|
158
|
+
mutate=lambda p: p["environment"].__setitem__("gpu_info", None))
|
|
159
|
+
b = _shard(tmp_path, tmp_git_repo, signer, "b.json", [_result("t::b")],
|
|
160
|
+
mutate=lambda p: p["environment"].__setitem__("gpu_info", gpu))
|
|
161
|
+
merged = merge_payloads([json.loads(a.read_text()), json.loads(b.read_text())],
|
|
162
|
+
["a.json", "b.json"])
|
|
163
|
+
assert merged["environment"]["gpu_info"] == gpu
|
|
@@ -274,3 +274,34 @@ def test_cli_overrides_toml_defaults(pytester):
|
|
|
274
274
|
result.assert_outcomes(passed=1)
|
|
275
275
|
assert (pytester.path / "cli-receipt.json").exists()
|
|
276
276
|
assert not (pytester.path / "toml-receipt.json").exists()
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def test_cli_equal_to_builtin_default_still_overrides_toml(pytester):
|
|
280
|
+
"""Tri-state regression (0.2.0): an EXPLICIT CLI value that happens to equal
|
|
281
|
+
the built-in default must beat [tool.gpu_proof] — previously it was
|
|
282
|
+
silently ignored because "set" was detected by comparing against the
|
|
283
|
+
built-in default. --gpu-proof-out=gpu-proof.json IS the built-in default."""
|
|
284
|
+
pytester.makepyprojecttoml(
|
|
285
|
+
"""
|
|
286
|
+
[tool.pytest.ini_options]
|
|
287
|
+
|
|
288
|
+
[tool.gpu_proof]
|
|
289
|
+
signing_backend = "none"
|
|
290
|
+
output = "toml-receipt.json"
|
|
291
|
+
"""
|
|
292
|
+
)
|
|
293
|
+
pytester.makepyfile(
|
|
294
|
+
"""
|
|
295
|
+
import pytest
|
|
296
|
+
|
|
297
|
+
@pytest.mark.gpu_proof
|
|
298
|
+
def test_ok():
|
|
299
|
+
assert True
|
|
300
|
+
"""
|
|
301
|
+
)
|
|
302
|
+
result = pytester.runpytest(
|
|
303
|
+
"--gpu-proof-enable", "--gpu-proof-out=gpu-proof.json"
|
|
304
|
+
)
|
|
305
|
+
result.assert_outcomes(passed=1)
|
|
306
|
+
assert (pytester.path / "gpu-proof.json").exists()
|
|
307
|
+
assert not (pytester.path / "toml-receipt.json").exists()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_ctypes_matmul/cuda_ctypes_matmul.py
RENAMED
|
File without changes
|
{pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_ctypes_matmul/matmul_kernel.cu
RENAMED
|
File without changes
|
|
File without changes
|
{pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_nanobind_matmul/CMakeLists.txt
RENAMED
|
File without changes
|
|
File without changes
|
{pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_nanobind_matmul/src/bindings.cpp
RENAMED
|
File without changes
|
{pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_nanobind_matmul/src/matmul_cuda.h
RENAMED
|
File without changes
|
{pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_nanobind_matmul/src/matmul_kernel.cu
RENAMED
|
File without changes
|
|
File without changes
|
{pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_pybind11_matmul/CMakeLists.txt
RENAMED
|
File without changes
|
|
File without changes
|
{pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_pybind11_matmul/src/bindings.cpp
RENAMED
|
File without changes
|
{pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_pybind11_matmul/src/matmul_cuda.h
RENAMED
|
File without changes
|
{pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/cuda_pybind11_matmul/src/matmul_kernel.cu
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/jax_ffi_cuda_matmul/CMakeLists.txt
RENAMED
|
File without changes
|
|
File without changes
|
{pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/jax_ffi_cuda_matmul/jax_ffi_matmul.py
RENAMED
|
File without changes
|
{pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/jax_ffi_cuda_matmul/src/matmul_ffi.cu
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/minimal_python_only/test_minimal.py
RENAMED
|
File without changes
|
{pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/wrapped_cuda_ctypes/fake_cuda_lib.py
RENAMED
|
File without changes
|
{pytest_gpu_proof-0.1.0 → pytest_gpu_proof-0.2.0}/examples/wrapped_cuda_ctypes/test_cuda_wrapper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|