feat: add Jetson contributor validation path

This commit is contained in:
Joseph Magly
2026-08-21 20:12:35 -04:00
parent 1e870ccee0
commit 06e80af7b6
23 changed files with 1128 additions and 30 deletions
@@ -5,6 +5,11 @@ Date: 2026-08-21
Issue: https://github.com/elder-plinius/OBLITERATUS/issues/31
Public plan: [docs/platforms/jetson.md](../../../docs/platforms/jetson.md)
Implementation status: generic ARM64 preflight, vendor-PyTorch-preserving
bootstrap, physical-device gate, sanitized evidence collector, manual runner
workflow, and contributor issue form are implemented. The ADR remains Proposed
until physical-device evidence fixes the initial supported matrix.
## Context
OBLITERATUS currently resolves CPU-only PyTorch for Linux through its default
@@ -69,10 +74,12 @@ audited base-image and package provenance.
1. Inventory the available Jetson and record its exact JetPack/L4T stack.
2. Select and pin the initial Orin/JetPack 6.2.x matrix at an exact patch.
3. Add generic ARM64 package/import preflight without changing default PR
dependencies.
4. Add the Jetson constraints/profile and preferred container recipe.
dependencies. (Implemented.)
4. Add the Jetson constraints/profile and preferred container recipe. (The
native/container bootstrap boundary is implemented; an exact image remains
dependent on the selected hardware matrix.)
5. Add the `jetson-runtime` policy entry, test probe, trusted workflow job, and
retained evidence artifact.
retained evidence artifact. (Implemented.)
6. Validate CUDA discovery, device selection, a tiny CUDA operation, and the
offloaded-surgery probe. Validate source-built bitsandbytes separately.
7. Claim support only for matrices with fresh green evidence on the exact
@@ -260,6 +260,11 @@ the Jetson compatibility matrix must be version-pinned, not “latest by default
## Recommended implementation path
Repository status as of 2026-08-21: steps 2 through 4 are implemented with a
manual-only physical runner, a hosted ARM64 preflight, CPU-testable policy
contracts, and a sanitized contributor evidence flow. Physical Jetson results
are still required before completing steps 5 and 6 or publishing support.
1. Keep issue #31 open as a feature/specification item until the acceptance
matrix is merged.
2. Add a Jetson conditional policy entry and workflow job with an explicit
+77
View File
@@ -0,0 +1,77 @@
name: Jetson runtime report
description: Report a physical Jetson bootstrap, build, or CUDA validation result
title: "[Jetson] "
labels:
- enhancement
body:
- type: markdown
attributes:
value: |
Thank you for testing OBLITERATUS on physical Jetson hardware. Run the
documented `jetson-runtime` probe and attach its sanitized report. The
report intentionally omits host identity, network, serial, token, and
local-path data.
- type: dropdown
id: device
attributes:
label: Jetson device
options:
- Jetson AGX Orin
- Jetson Orin NX
- Jetson Orin Nano
- Jetson AGX Xavier
- Jetson AGX Thor
validations:
required: true
- type: input
id: jetpack
attributes:
label: Exact JetPack version
description: Include the patch version, for example 6.2.1.
validations:
required: true
- type: input
id: commit
attributes:
label: Exact OBLITERATUS commit
description: Paste the 40-character commit SHA that was tested.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Reproduction commands
description: Provide the smallest command sequence that demonstrates the result.
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected result
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual result
description: Include the complete error text, but remove any secrets before submitting.
validations:
required: true
- type: textarea
id: evidence
attributes:
label: Sanitized Jetson evidence
description: Attach conditional-evidence/jetson-report.json or paste its JSON contents.
validations:
required: true
- type: checkboxes
id: confirmations
attributes:
label: Confirmations
options:
- label: I ran this validation on physical Jetson hardware.
required: true
- label: I reviewed the report and removed any secrets or personal identifiers.
required: true
validations:
required: true
+1 -1
View File
@@ -1,6 +1,6 @@
self-hosted-runner:
# Project-owned capability labels used by conditional test runners.
labels: [cuda, mps, mlx]
labels: [cuda, jetson, mps, mlx]
# Configuration variables in array of strings defined in your repository or
# organization. `null` means disabling configuration variables check.
+39
View File
@@ -225,6 +225,45 @@ jobs:
scripts/check_supply_chain_policy.py
scripts/gemma4_12b_recursive_loop.py || true
arm64-preflight:
name: Linux ARM64 preflight
if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
runs-on: ubuntu-24.04-arm
timeout-minutes: 10
env:
CUDA_VISIBLE_DEVICES: ""
HF_DATASETS_OFFLINE: "1"
HF_HUB_DISABLE_TELEMETRY: "1"
HF_HUB_OFFLINE: "1"
TRANSFORMERS_OFFLINE: "1"
TEST_ENV: /tmp/obliteratus-arm64-preflight
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
cache: pip
cache-dependency-path: |
pyproject.toml
uv.lock
- name: Install locked CPU runtime
run: |
python -m pip install "uv==${UV_VERSION}"
UV_PROJECT_ENVIRONMENT="$TEST_ENV" \
uv sync --locked --no-default-groups --extra dev --no-editable
- name: Verify ARM64 package and core runtime
run: |
"$TEST_ENV/bin/python" -c \
'import platform; assert platform.machine().lower() in {"aarch64", "arm64"}'
"$TEST_ENV/bin/python" -m build --wheel
"$TEST_ENV/bin/python" -c 'import obliteratus; print(obliteratus.__version__)'
"$TEST_ENV/bin/python" -m obliteratus --help
"$TEST_ENV/bin/python" -m pytest \
tests/test_module_imports.py tests/test_device_boundaries.py \
tests/test_jetson_support_tooling.py -q --no-cov
pr-core:
name: Pull request core
if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
+55 -3
View File
@@ -23,6 +23,10 @@ on:
description: Run CUDA and bitsandbytes on the labeled self-hosted runner
type: boolean
default: false
run_jetson:
description: Run Jetson CUDA on the trusted labeled physical runner
type: boolean
default: false
run_mps:
description: Run MPS on the labeled Apple Silicon runner
type: boolean
@@ -239,7 +243,7 @@ jobs:
run: |
python -m pip install "uv==${UV_VERSION}"
UV_PROJECT_ENVIRONMENT="$CONDITIONAL_ENV" \
uv sync --locked --no-default-groups --extra dev --no-editable
uv sync --locked --no-default-groups --extra dev --extra quantization --no-editable
CUDA_TORCH_VERSION="$("$CONDITIONAL_ENV/bin/python" -c \
'import torch; print(torch.__version__.split("+", 1)[0])')"
UV_TORCH_BACKEND=cu130 uv pip install \
@@ -264,6 +268,52 @@ jobs:
if-no-files-found: error
retention-days: 30
jetson:
name: NVIDIA Jetson runtime
needs: policy
if: github.event_name == 'workflow_dispatch' && inputs.run_jetson
runs-on: [self-hosted, linux, ARM64, jetson]
timeout-minutes: 45
env:
JETSON_ENV: /tmp/obliteratus-jetson-${{ github.run_id }}-${{ github.run_attempt }}
JETSON_TOOLS: /tmp/obliteratus-jetson-tools-${{ github.run_id }}-${{ github.run_attempt }}
steps:
- name: Check out trusted candidate
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pinned bootstrap tooling outside the JetPack runtime
run: |
python3 -m venv "$JETSON_TOOLS"
"$JETSON_TOOLS/bin/python" -m pip install "uv==${UV_VERSION}"
- name: Preserve JetPack PyTorch and install OBLITERATUS
run: >-
python3 scripts/setup_jetson.py
--python python3
--uv-python "$JETSON_TOOLS/bin/python"
--venv "$JETSON_ENV"
- name: Run physical Jetson CUDA probe
run: >-
"$JETSON_ENV/bin/python" scripts/run_conditional_gate.py jetson-runtime
- name: Collect sanitized Jetson evidence
if: always()
run: |
JETSON_PYTHON=python3
if [ -x "$JETSON_ENV/bin/python" ]; then
JETSON_PYTHON="$JETSON_ENV/bin/python"
fi
"$JETSON_PYTHON" scripts/jetson_support.py \
--check \
--gate-evidence conditional-evidence/jetson-runtime.json \
--output conditional-evidence/jetson-report.json \
--issue-body conditional-evidence/jetson-issue.md
- name: Upload Jetson evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: conditional-jetson-${{ github.run_attempt }}
path: conditional-evidence/
if-no-files-found: error
retention-days: 30
mps:
name: Apple MPS runtime
needs: policy
@@ -393,20 +443,22 @@ jobs:
summary:
name: Conditional result and freshness summary
if: always()
needs: [policy, model_runtime, network_services, operator_ui, cuda, mps, mlx, remote]
needs: [policy, model_runtime, network_services, operator_ui, cuda, jetson, mps, mlx, remote]
runs-on: ubuntu-latest
timeout-minutes: 5
env:
CONDITIONAL_RESULTS: >-
{"policy":"${{ needs.policy.result }}","model_runtime":"${{ needs.model_runtime.result }}",
"network_services":"${{ needs.network_services.result }}","operator_ui":"${{ needs.operator_ui.result }}",
"cuda":"${{ needs.cuda.result }}","mps":"${{ needs.mps.result }}","mlx":"${{ needs.mlx.result }}",
"cuda":"${{ needs.cuda.result }}","jetson":"${{ needs.jetson.result }}",
"mps":"${{ needs.mps.result }}","mlx":"${{ needs.mlx.result }}",
"remote":"${{ needs.remote.result }}"}
CONDITIONAL_SELECTED: >-
{"model_runtime":${{ github.event_name != 'workflow_dispatch' || inputs.run_model }},
"network_services":${{ github.event_name != 'workflow_dispatch' || inputs.run_network }},
"operator_ui":${{ github.event_name != 'workflow_dispatch' || inputs.run_ui }},
"cuda":${{ (github.event_name == 'workflow_dispatch' && inputs.run_cuda) || (github.event_name != 'workflow_dispatch' && vars.ENABLE_CUDA_GATE == 'true') }},
"jetson":${{ github.event_name == 'workflow_dispatch' && inputs.run_jetson }},
"mps":${{ (github.event_name == 'workflow_dispatch' && inputs.run_mps) || (github.event_name != 'workflow_dispatch' && vars.ENABLE_MPS_GATE == 'true') }},
"mlx":${{ (github.event_name == 'workflow_dispatch' && inputs.run_mlx) || (github.event_name != 'workflow_dispatch' && vars.ENABLE_MLX_GATE == 'true') }},
"remote":${{ (github.event_name == 'workflow_dispatch' && inputs.run_remote) || (github.event_name != 'workflow_dispatch' && vars.ENABLE_REMOTE_GATE == 'true') }}}
+1
View File
@@ -8,6 +8,7 @@ build/
.eggs/
*.egg
.venv/
.venv-jetson/
venv/
env/
.env
+9 -2
View File
@@ -206,8 +206,8 @@ them in the managed environment.
## Conditional and hardware testing
GPU, MPS, MLX, model-download, external-evaluation, network, operator-UI, and
remote-execution checks are conditional release or risk-surface gates. A unit test
GPU, Jetson, MPS, MLX, model-download, external-evaluation, network, operator-UI,
and remote-execution checks are conditional release or risk-surface gates. A unit test
with a mocked device is still required; hardware evidence complements deterministic
contract coverage and never replaces it.
@@ -222,6 +222,13 @@ Current operator hardware includes Titan for CUDA/bitsandbytes probes and Mutsu,
machines are attached to a public pull-request workflow; CI or a maintainer will
record whether the mapped conditional gate ran.
Jetson contributors do not need project-owned hardware access. Follow the
[Jetson contributor bootstrap](docs/platforms/jetson.md#experimental-contributor-bootstrap)
on a physical device, then submit the generated sanitized evidence through the
[Jetson runtime report](https://github.com/elder-plinius/OBLITERATUS/issues/new?template=jetson-runtime.yml). Maintainers
will reproduce, add missing test depth, and integrate compatible changes. Never
attach a contributor-controlled runner to untrusted pull-request execution.
## Security and supply-chain expectations
- Treat issue text, pull requests, patches, model repositories, checkpoints,
+15 -3
View File
@@ -162,6 +162,11 @@ obliteratus ui --auth user:pass # add basic auth
The `obliteratus ui` command adds a Rich terminal startup with GPU detection and hardware-appropriate model recommendations. You can also run `python app.py` directly (same thing the Space uses).
Install `.[spaces,quantization]` instead when the UI must load supported
bitsandbytes 8-bit or 4-bit models. Jetson users must follow the dedicated
[Jetson bootstrap](docs/platforms/jetson.md); its bitsandbytes path is not yet
supported.
### 3. Google Colab (free GPU)
[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/elder-plinius/OBLITERATUS/blob/main/notebooks/abliterate.ipynb)
@@ -457,6 +462,12 @@ This sets `CUDA_VISIBLE_DEVICES` before CUDA initializes. The model is then shar
The `--dtype` flag controls the precision of model weights, which directly determines how much VRAM you need. Lower precision means smaller memory footprint at the cost of some numerical fidelity:
Install the optional backend before selecting a bitsandbytes mode:
```bash
pip install -e ".[quantization]"
```
| Dtype | Bytes/param | 7B model | 70B model | 405B model |
|-------|-----------|---------|----------|-----------|
| `float32` | 4 | 28 GB | 280 GB | 1620 GB |
@@ -783,9 +794,10 @@ run the full Python matrix with 75% repository statement coverage, 60% repositor
branch coverage, touched-module regression checks, mature-scope floors,
deterministic property/order-repeat checks, selective mutation, package contracts,
Windows portability, and supply-chain certification.
Eight environment-bound test files run through the separately documented conditional
workflow for model downloads, network services, operator UI, CUDA, bitsandbytes, MPS,
MLX, and least-privileged remote execution.
Nine environment-bound test files run through the separately documented conditional
workflow for model downloads, network services, operator UI, CUDA, bitsandbytes,
physical Jetson CUDA, MPS, MLX, and least-privileged remote execution. The Jetson
lane is a trusted manual hardware probe; hosted ARM64 CI checks portability only.
## License
+13
View File
@@ -154,6 +154,19 @@
"expected_cost": "included in cuda",
"coverage_paths": ["obliteratus/models/loader.py"]
},
{
"id": "jetson-runtime",
"job": "jetson",
"marker": "gpu",
"runner": "self-hosted, linux, ARM64, jetson",
"prerequisites": "trusted manual dispatch on physical Jetson hardware with a JetPack-aligned CUDA PyTorch runtime",
"expected_cost": "under 30 self-hosted runner-minutes",
"coverage_paths": [
"obliteratus/device.py",
"obliteratus/models/loader.py",
"obliteratus/models/offload_surgery.py"
]
},
{
"id": "mps-runtime",
"job": "mps",
+4
View File
@@ -13,13 +13,17 @@
".github/workflows/**",
"ci/**",
"scripts/check_*.py",
"scripts/jetson_*.py",
"scripts/select_pr_tests.py",
"scripts/setup_jetson.py",
"pyproject.toml",
"uv.lock"
],
"infrastructure_tests": [
"tests/test_aiwg_workspace_contracts.py",
"tests/test_ci_policy.py",
"tests/test_conditional_gate_scripts.py",
"tests/test_jetson_support_tooling.py",
"tests/test_pr_test_selection.py",
"tests/test_quality_policy.py",
"tests/test_quality_gate_scripts.py",
+8 -3
View File
@@ -78,7 +78,8 @@
"tests/test_offload_surgery.py",
"tests/test_persistence_contracts.py",
"tests/test_persistence_pipeline.py",
"tests/conditional/test_cuda_runtime.py"
"tests/conditional/test_cuda_runtime.py",
"tests/conditional/test_jetson_runtime.py"
]
},
{
@@ -134,7 +135,8 @@
"tests/test_model_profile.py",
"tests/test_model_profile_contracts.py",
"tests/test_runtime_contracts.py",
"tests/test_study_presets.py"
"tests/test_study_presets.py",
"tests/conditional/test_jetson_runtime.py"
]
},
{
@@ -442,6 +444,7 @@
],
"conditional_gates": [
"cuda-runtime",
"jetson-runtime",
"mps-runtime"
]
},
@@ -466,6 +469,7 @@
"conditional_gates": [
"model-download-runtime",
"cuda-runtime",
"jetson-runtime",
"bitsandbytes-runtime"
]
},
@@ -489,7 +493,8 @@
"tests/conditional/test_cuda_runtime.py"
],
"conditional_gates": [
"cuda-runtime"
"cuda-runtime",
"jetson-runtime"
]
},
{
+15 -8
View File
@@ -70,21 +70,28 @@ probes.
For an operator run on the labeled machine:
```bash
uv sync --locked --extra dev
uv sync --locked --extra dev --extra quantization
CUDA_TORCH_VERSION="$(.venv/bin/python -c \
'import torch; print(torch.__version__.split("+", 1)[0])')"
UV_TORCH_BACKEND=cu130 uv pip install --python .venv/bin/python \
--reinstall-package torch "torch==$CUDA_TORCH_VERSION"
uv pip check --python .venv/bin/python
uv run --extra dev python scripts/run_conditional_gate.py cuda-runtime
uv run --extra dev python scripts/run_conditional_gate.py bitsandbytes-runtime
uv run --extra dev --extra quantization python scripts/run_conditional_gate.py cuda-runtime
uv run --extra dev --extra quantization python scripts/run_conditional_gate.py bitsandbytes-runtime
```
Jetson CUDA support is tracked separately from this generic x64 CUDA lane. A
generic Linux ARM build can prove package portability, but it does not prove
Jetson GPU support because Jetson depends on a JetPack/L4T-matched CUDA, cuDNN,
and PyTorch runtime. The support plan, recommended container path, and acceptance
criteria are documented in [NVIDIA Jetson support plan](platforms/jetson.md).
Jetson CUDA support is tracked separately from this generic x64 CUDA lane. The
mandatory `Linux ARM64 preflight` uses GitHub's hosted `ubuntu-24.04-arm` runner
to prove locked CPU packaging, imports, CLI startup, and Jetson tooling contracts.
It is not GPU evidence. Jetson depends on a JetPack/L4T-matched CUDA, cuDNN, and
PyTorch runtime.
Physical testing uses only a trusted manual dispatch on the labels `self-hosted`,
`linux`, `ARM64`, and `jetson`. The job preserves NVIDIA's vendor PyTorch, runs
`jetson-runtime`, and uploads the sanitized `conditional-jetson-<run-attempt>`
artifact for 30 days. It never runs for a pull request, schedule, or release.
Contributor bootstrap, runner isolation, reporting commands, and acceptance
criteria are documented in the [NVIDIA Jetson support plan](platforms/jetson.md).
## Apple MPS and MLX
+67
View File
@@ -79,6 +79,41 @@ from being selected: upstream documents that wheel as SBSA/server ARM and says
Jetson L4T/JetPack requires a source build. Until a pinned source build passes
on the selected device, bitsandbytes is unsupported for that tier.
### Experimental contributor bootstrap
Start with NVIDIA's PyTorch wheel or PyTorch iGPU container for the exact
JetPack patch installed on the device. Confirm that `python3 -c 'import torch;
assert torch.cuda.is_available()'` succeeds before installing OBLITERATUS. Then,
from a checkout of the exact commit under test, run:
```bash
python3 -m venv .venv-jetson-tools
.venv-jetson-tools/bin/python -m pip install "uv==0.12.4"
.venv-jetson-tools/bin/python scripts/setup_jetson.py \
--python python3 \
--uv-python .venv-jetson-tools/bin/python \
--venv .venv-jetson
.venv-jetson/bin/python scripts/run_conditional_gate.py jetson-runtime
.venv-jetson/bin/python scripts/jetson_support.py \
--check \
--gate-evidence conditional-evidence/jetson-runtime.json \
--output conditional-evidence/jetson-report.json \
--issue-body conditional-evidence/jetson-issue.md
```
The bootstrap validates ARM64, L4T, and CUDA before changing the environment.
It creates a virtual environment with `--system-site-packages`, exports the
committed lock, and installs locked OBLITERATUS dependencies without replacing
the vendor `torch`. It also excludes bitsandbytes. The generic bitsandbytes
package is now an explicit `quantization` extra for supported non-Jetson
environments; Jetson quantization remains a separate source-build milestone.
For JetPack 6.2, NVIDIA publishes the
`nvcr.io/nvidia/pytorch:25.06-py3-igpu` container. Run it only on Jetson hardware
with the NVIDIA runtime, mount a reviewed checkout, and use the same bootstrap
inside the container. Match other JetPack releases through NVIDIA's
compatibility table rather than substituting a `latest` tag.
## Conditional gate
Add a new gate instead of modifying the x64 CUDA gate:
@@ -93,6 +128,35 @@ Add a new gate instead of modifying the x64 CUDA gate:
The job must run only from a trusted ref or reviewed maintainer dispatch. A
persistent self-hosted Jetson must never execute untrusted pull-request code.
### Attaching a contributor-owned runner
Register the runner using GitHub's self-hosted runner instructions, on the
Jetson itself, and add the custom label `jetson`. GitHub supplies the
`self-hosted`, `linux`, and `ARM64` default labels. Verify that the repository
shows exactly these required labels before dispatching the job:
```text
self-hosted, linux, ARM64, jetson
```
Use a dedicated, non-personal runner account and a disposable or resettable
workspace. Do not place Hugging Face, SSH, cloud, or signing credentials on the
runner. Only a maintainer should manually dispatch `Conditional tests` against
a reviewed commit; the Jetson job is deliberately unavailable to pull-request,
scheduled, and release triggers. Remove the runner registration token after
setup and keep the runner offline when it is not being used for reviewed work.
### Reporting results without a project-owned Jetson
Open the [Jetson runtime report](https://github.com/elder-plinius/OBLITERATUS/issues/new?template=jetson-runtime.yml)
issue form and attach `conditional-evidence/jetson-report.json`, or paste the
generated `conditional-evidence/jetson-issue.md`. The collector reports only an
allow-listed architecture, OS/JetPack, PyTorch/CUDA, device-class, test-result,
and commit profile. It excludes environment variables, hostnames, usernames,
network addresses, device serials, tokens, and local filesystem paths. Review
the file yourself before publishing it. A failed report is useful evidence and
does not imply that the contributor must diagnose the compatibility problem.
The gate should verify:
- `platform.machine()` is `aarch64` or equivalent ARM64.
@@ -146,3 +210,6 @@ them.
- [Astral: Using uv with PyTorch](https://docs.astral.sh/uv/guides/integration/pytorch/)
- [Hugging Face: bitsandbytes installation guide](https://huggingface.co/docs/bitsandbytes/installation)
- [GitHub: secure use of self-hosted runners](https://docs.github.com/en/actions/reference/security/secure-use#hardening-for-self-hosted-runners)
- [GitHub: use self-hosted runner labels](https://docs.github.com/en/actions/how-tos/manage-runners/self-hosted-runners/use-in-a-workflow)
- [GitHub: hosted ARM64 runners](https://docs.github.com/en/actions/reference/runners/github-hosted-runners)
- [NVIDIA: PyTorch 25.06 for JetPack 6.2](https://docs.nvidia.com/deeplearning/frameworks/pytorch-release-notes/rel-25-06.html)
+1 -1
View File
@@ -34,7 +34,6 @@ dependencies = [
"numpy>=1.24",
"scikit-learn>=1.3",
"tqdm>=4.64",
"bitsandbytes>=0.46.1",
]
[project.urls]
@@ -51,6 +50,7 @@ dev = [
"pytest-cov==7.1.0",
"ruff==0.16.2",
]
quantization = ["bitsandbytes>=0.46.1"]
spaces = ["gradio>=6.7,<7.0"]
[dependency-groups]
+254
View File
@@ -0,0 +1,254 @@
#!/usr/bin/env python3
"""Collect privacy-safe Jetson runtime evidence for CI and issue reports."""
from __future__ import annotations
import argparse
import importlib.metadata
import json
import os
import platform
import re
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Sequence
ISSUE_URL = "https://github.com/elder-plinius/OBLITERATUS/issues/31"
JETSON_RELEASE = Path("/etc/nv_tegra_release")
OS_RELEASE = Path("/etc/os-release")
SHA = re.compile(r"^[0-9a-f]{40}$")
def _read_first_line(path: Path, *, limit: int = 500) -> str | None:
try:
return path.read_text(encoding="utf-8", errors="replace").splitlines()[0][:limit]
except (OSError, IndexError):
return None
def _os_release(path: Path) -> dict[str, str]:
try:
lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
except OSError:
return {}
values: dict[str, str] = {}
for line in lines:
key, separator, value = line.partition("=")
if separator and key in {"ID", "VERSION_ID", "PRETTY_NAME"}:
values[key.lower()] = value.strip().strip('"')[:200]
return values
def _capture(command: Sequence[str], *, timeout: int = 10) -> str | None:
try:
result = subprocess.run(
command,
check=False,
capture_output=True,
text=True,
timeout=timeout,
)
except (OSError, subprocess.TimeoutExpired):
return None
if result.returncode != 0:
return None
value = result.stdout.strip()
return value[:500] or None
def _candidate_sha() -> str:
candidate = os.environ.get("GITHUB_SHA", "")
if SHA.fullmatch(candidate):
return candidate
local = _capture(["git", "rev-parse", "HEAD"])
return local if local is not None and SHA.fullmatch(local) else "local"
def collect_host_facts(
*,
tegra_release: Path = JETSON_RELEASE,
os_release: Path = OS_RELEASE,
) -> dict[str, object]:
"""Return an allow-listed host profile without identity or network data."""
return {
"architecture": platform.machine(),
"python_version": platform.python_version(),
"os": _os_release(os_release),
"l4t_release": _read_first_line(tegra_release),
"jetpack_package": _capture(
["dpkg-query", "-W", "-f=${Version}", "nvidia-jetpack"],
),
}
def collect_runtime_facts() -> dict[str, object]:
"""Return an allow-listed PyTorch/GPU profile without serials or file paths."""
facts: dict[str, object] = {
"torch_imported": False,
"torch_version": None,
"torch_cuda_version": None,
"cuda_available": False,
"cuda_device_count": 0,
"device_name": None,
"compute_capability": None,
"total_memory_gb": None,
"bitsandbytes_version": None,
}
try:
import torch
except Exception as exc: # pragma: no cover - exact vendor loader failures vary
facts["torch_import_error"] = type(exc).__name__
return facts
facts.update({
"torch_imported": True,
"torch_version": str(torch.__version__),
"torch_cuda_version": torch.version.cuda,
"cuda_available": bool(torch.cuda.is_available()),
"cuda_device_count": int(torch.cuda.device_count()),
})
if facts["cuda_available"] and facts["cuda_device_count"]:
properties = torch.cuda.get_device_properties(0)
facts.update({
"device_name": str(properties.name)[:200],
"compute_capability": list(torch.cuda.get_device_capability(0)),
"total_memory_gb": round(properties.total_memory / 1024 ** 3, 2),
})
try:
facts["bitsandbytes_version"] = importlib.metadata.version("bitsandbytes")
except importlib.metadata.PackageNotFoundError:
pass
return facts
def validate_report(report: dict[str, object]) -> tuple[list[str], list[str]]:
"""Return blocking errors and non-blocking compatibility warnings."""
host = report["host"]
runtime = report["runtime"]
assert isinstance(host, dict)
assert isinstance(runtime, dict)
errors: list[str] = []
warnings: list[str] = []
if str(host.get("architecture", "")).lower() not in {"aarch64", "arm64"}:
errors.append("host architecture is not ARM64")
if not host.get("l4t_release"):
errors.append("/etc/nv_tegra_release is unavailable; this is not a Jetson L4T runtime")
if not runtime.get("torch_imported"):
errors.append("PyTorch could not be imported from the JetPack-aligned runtime")
elif not runtime.get("torch_cuda_version"):
errors.append("PyTorch is not a CUDA build")
elif not runtime.get("cuda_available"):
errors.append("PyTorch cannot access the Jetson CUDA device")
if runtime.get("bitsandbytes_version"):
warnings.append(
"bitsandbytes is installed but remains unsupported until its pinned Jetson "
"source build passes the separate quantization probe",
)
return errors, warnings
def _gate_summary(path: Path | None) -> dict[str, object] | None:
if path is None:
return None
try:
value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return {"status": "unavailable"}
if not isinstance(value, dict):
return {"status": "invalid"}
summary: dict[str, object] = {}
for key in ("gate", "status"):
if isinstance(value.get(key), str):
summary[key] = value[key][:100]
git_sha = value.get("git_sha")
if isinstance(git_sha, str) and (SHA.fullmatch(git_sha) or git_sha == "local"):
summary["git_sha"] = git_sha
counts = value.get("counts")
if isinstance(counts, dict):
summary["counts"] = {
key: counts[key]
for key in ("tests", "failures", "errors", "skipped")
if isinstance(counts.get(key), int) and counts[key] >= 0
}
return summary
def build_report(*, gate_evidence: Path | None = None) -> dict[str, object]:
report: dict[str, object] = {
"schema_version": 1,
"generated_at": datetime.now(timezone.utc).isoformat(),
"issue": ISSUE_URL,
"git_sha": _candidate_sha(),
"host": collect_host_facts(),
"runtime": collect_runtime_facts(),
}
errors, warnings = validate_report(report)
report["validation"] = {"errors": errors, "warnings": warnings}
gate = _gate_summary(gate_evidence)
if gate is not None:
report["gate_evidence"] = gate
return report
def issue_body(report: dict[str, object]) -> str:
return "\n".join([
"## Jetson runtime report",
"",
"### What happened",
"<!-- Describe the command, expected result, and actual result. -->",
"",
"### Reproduction",
"<!-- Add the smallest command that reproduces the problem. -->",
"",
"### Sanitized environment evidence",
"",
"```json",
json.dumps(report, indent=2, sort_keys=True),
"```",
"",
"This report intentionally excludes environment variables, hostnames, usernames,",
"network addresses, GPU serials, tokens, and local filesystem paths.",
"",
])
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--output",
type=Path,
default=Path("conditional-evidence/jetson-report.json"),
)
parser.add_argument("--gate-evidence", type=Path)
parser.add_argument("--issue-body", type=Path)
parser.add_argument("--check", action="store_true")
args = parser.parse_args()
report = build_report(gate_evidence=args.gate_evidence)
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n")
if args.issue_body is not None:
args.issue_body.parent.mkdir(parents=True, exist_ok=True)
args.issue_body.write_text(issue_body(report), encoding="utf-8")
validation = report["validation"]
assert isinstance(validation, dict)
errors = validation["errors"]
warnings = validation["warnings"]
assert isinstance(errors, list)
assert isinstance(warnings, list)
for warning in warnings:
print(f"WARNING: {warning}", file=sys.stderr)
for error in errors:
print(f"ERROR: {error}", file=sys.stderr)
print(args.output)
return 2 if args.check and errors else 0
if __name__ == "__main__":
raise SystemExit(main())
+10 -2
View File
@@ -7,6 +7,7 @@ import argparse
import importlib.util
import json
import os
import platform
import subprocess
import sys
from datetime import datetime, timezone
@@ -21,24 +22,31 @@ GATES = {
"operator-ui": "tests/conditional/test_operator_ui.py",
"cuda-runtime": "tests/conditional/test_cuda_runtime.py",
"bitsandbytes-runtime": "tests/conditional/test_cuda_runtime.py",
"jetson-runtime": "tests/conditional/test_jetson_runtime.py",
"mps-runtime": "tests/conditional/test_mps_runtime.py",
"mlx-runtime": "tests/conditional/test_mlx_runtime.py",
"remote-execution": "tests/conditional/test_remote_runtime.py",
}
JETSON_RELEASE = Path("/etc/nv_tegra_release")
def missing_prerequisites(gate: str) -> list[str]:
missing: list[str] = []
if gate in {"cuda-runtime", "bitsandbytes-runtime", "mps-runtime"}:
if gate in {"cuda-runtime", "bitsandbytes-runtime", "jetson-runtime", "mps-runtime"}:
import torch
if gate.startswith("cuda") or gate.startswith("bitsandbytes"):
if gate.startswith(("cuda", "bitsandbytes", "jetson")):
if not torch.cuda.is_available():
missing.append("a CUDA-capable PyTorch runtime")
elif not (hasattr(torch.backends, "mps") and torch.backends.mps.is_available()):
missing.append("an available Apple MPS backend")
if gate == "bitsandbytes-runtime" and importlib.util.find_spec("bitsandbytes") is None:
missing.append("bitsandbytes")
if gate == "jetson-runtime":
if platform.machine().lower() not in {"aarch64", "arm64"}:
missing.append("an ARM64 host")
if not JETSON_RELEASE.is_file():
missing.append("a Jetson L4T runtime")
if gate == "mlx-runtime":
for module in ("mlx", "mlx_lm"):
if importlib.util.find_spec(module) is None:
+190
View File
@@ -0,0 +1,190 @@
#!/usr/bin/env python3
"""Create an OBLITERATUS venv without replacing JetPack's PyTorch runtime."""
from __future__ import annotations
import argparse
import re
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Sequence
EXCLUDED_PACKAGES = {"bitsandbytes", "torch"}
NORMALIZE = re.compile(r"[-_.]+")
def _run(command: Sequence[str], *, cwd: Path) -> None:
print("+ " + " ".join(command))
subprocess.run(command, cwd=cwd, check=True)
def _require_new_or_reusable_venv(venv: Path, *, reuse: bool) -> None:
if not venv.exists():
return
if not reuse:
raise ValueError(f"virtual environment already exists: {venv}; pass --reuse to use it")
config = venv / "pyvenv.cfg"
try:
contents = config.read_text(encoding="utf-8").lower()
except OSError as exc:
raise ValueError(f"existing path is not a reusable virtual environment: {venv}") from exc
if "include-system-site-packages = true" not in contents:
raise ValueError(f"existing virtual environment does not expose JetPack packages: {venv}")
def _require_safe_target(venv: Path, project: Path) -> None:
resolved = venv.resolve()
forbidden = {Path("/").resolve(), Path.home().resolve(), project.resolve()}
if resolved in forbidden:
raise ValueError(f"refusing unsafe virtual environment target: {resolved}")
def _require_exclusions(requirements: Path) -> None:
emitted: set[str] = set()
for raw_line in requirements.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith(("#", "--")):
continue
name = re.split(r"[<>=!~;@\[]", line, maxsplit=1)[0].strip()
emitted.add(NORMALIZE.sub("-", name).lower())
unexpected = sorted(EXCLUDED_PACKAGES & emitted)
if unexpected:
raise RuntimeError(f"Jetson export contains forbidden packages: {unexpected}")
def prepare(
*,
project: Path,
venv: Path,
python: str,
uv_python: str,
reuse: bool,
) -> None:
project = project.resolve()
support_script = project / "scripts" / "jetson_support.py"
if not support_script.is_file() or not (project / "uv.lock").is_file():
raise ValueError(f"not an OBLITERATUS checkout: {project}")
_require_safe_target(venv, project)
_require_new_or_reusable_venv(venv, reuse=reuse)
with tempfile.TemporaryDirectory(prefix="obliteratus-jetson-") as temp_value:
temp = Path(temp_value)
_run(
[python, str(support_script), "--check", "--output", str(temp / "host.json")],
cwd=project,
)
_run([uv_python, "-m", "uv", "--version"], cwd=project)
if not venv.exists():
_run([python, "-m", "venv", "--system-site-packages", str(venv)], cwd=project)
target_python = venv / "bin" / "python"
_run(
[
str(target_python),
str(support_script),
"--check",
"--output",
str(temp / "venv.json"),
],
cwd=project,
)
requirements = temp / "requirements-jetson.txt"
_run(
[
uv_python,
"-m",
"uv",
"export",
"--locked",
"--no-default-groups",
"--extra",
"dev",
"--no-emit-project",
"--no-emit-package",
"torch",
"--no-emit-package",
"bitsandbytes",
"--no-annotate",
"--no-header",
"--no-hashes",
"--output-file",
str(requirements),
],
cwd=project,
)
_require_exclusions(requirements)
_run(
[
uv_python,
"-m",
"uv",
"pip",
"install",
"--python",
str(target_python),
"--no-deps",
"--requirements",
str(requirements),
],
cwd=project,
)
_run(
[
uv_python,
"-m",
"uv",
"pip",
"install",
"--python",
str(target_python),
"--no-deps",
"--editable",
str(project),
],
cwd=project,
)
_run(
[uv_python, "-m", "uv", "pip", "check", "--python", str(target_python)],
cwd=project,
)
print("Jetson environment prepared without replacing vendor PyTorch.")
print(f"Run: {target_python} scripts/run_conditional_gate.py jetson-runtime")
print(
f"Then: {target_python} scripts/jetson_support.py --check "
"--gate-evidence conditional-evidence/jetson-runtime.json "
"--issue-body conditional-evidence/jetson-issue.md",
)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--project", type=Path, default=Path(__file__).parents[1])
parser.add_argument("--venv", type=Path, default=Path(".venv-jetson"))
parser.add_argument("--python", default=sys.executable)
parser.add_argument(
"--uv-python",
default=sys.executable,
help="interpreter containing the pinned uv module (defaults to this interpreter)",
)
parser.add_argument("--reuse", action="store_true")
args = parser.parse_args()
try:
prepare(
project=args.project,
venv=args.venv,
python=args.python,
uv_python=args.uv_python,
reuse=args.reuse,
)
except (OSError, RuntimeError, subprocess.CalledProcessError, ValueError) as exc:
print(f"ERROR: {exc}", file=sys.stderr)
return 2
return 0
if __name__ == "__main__":
raise SystemExit(main())
+53
View File
@@ -0,0 +1,53 @@
"""Physical NVIDIA Jetson CUDA placement and operation probe."""
from __future__ import annotations
import platform
from pathlib import Path
import pytest
import torch
import torch.nn as nn
from accelerate.hooks import AlignDevicesHook, add_hook_to_module
from obliteratus import device
from obliteratus.abliterate import AbliterationPipeline
pytestmark = pytest.mark.gpu
def test_jetson_cuda_runtime_contract():
assert platform.machine().lower() in {"aarch64", "arm64"}
assert Path("/etc/nv_tegra_release").is_file()
assert torch.version.cuda is not None
assert torch.cuda.is_available()
assert torch.cuda.device_count() > 0
assert device.is_cuda()
assert device.get_device("auto") == "cuda"
tensor = torch.arange(16, device="cuda", dtype=torch.float32).reshape(4, 4)
result = tensor @ tensor.T
assert result.device.type == "cuda"
assert torch.isfinite(result).all()
def test_jetson_cuda_offloaded_surgery_contract():
module = nn.Module()
module.proj = nn.Linear(4, 4, bias=False)
original = module.proj.weight.detach().clone()
hook = AlignDevicesHook(execution_device="cuda", offload=True)
add_hook_to_module(module.proj, hook)
count = AbliterationPipeline._project_out_advanced(
module,
torch.tensor([[1.0], [0.0], [0.0], [0.0]], device="cuda"),
["proj"],
)
output = module.proj(torch.ones(1, 4, device="cuda"))
expected = original.clone()
expected[:, 0] = 0
assert count == 1
assert output.device.type == "cuda"
assert module.proj.weight.device.type == "meta"
torch.testing.assert_close(hook.weights_map["weight"], expected)
+18
View File
@@ -129,6 +129,24 @@ def test_pull_request_gate_is_fast_risk_mapped_and_uses_shared_floor():
assert "tests/conditional/" in policy["excluded_test_prefixes"]
def test_arm64_preflight_proves_portability_without_claiming_jetson_cuda():
workflow = WORKFLOW.read_text(encoding="utf-8")
arm = workflow.split(" arm64-preflight:\n", maxsplit=1)[1].split(
" pr-core:\n",
maxsplit=1,
)[0]
assert "runs-on: ubuntu-24.04-arm" in arm
assert "github.event_name == 'pull_request'" in arm
assert "github.event_name == 'push' && github.ref == 'refs/heads/main'" in arm
assert "-m build --wheel" in arm
assert "import obliteratus" in arm
assert "-m obliteratus --help" in arm
assert "tests/test_device_boundaries.py" in arm
assert "tests/test_jetson_support_tooling.py" in arm
assert "jetson-runtime" not in arm
def test_release_depth_jobs_only_run_for_tags_or_manual_validation():
workflow = WORKFLOW.read_text(encoding="utf-8")
release_condition = (
+23
View File
@@ -42,6 +42,7 @@ def test_cuda_job_replaces_locked_cpu_torch_with_same_version_cuda_build():
cuda_job = workflow.split(" cuda:\n", maxsplit=1)[1].split(" mps:\n", maxsplit=1)[0]
assert "torch.__version__.split" in cuda_job
assert "--extra quantization" in cuda_job
assert "UV_TORCH_BACKEND=cu130 uv pip install" in cuda_job
assert "--reinstall-package torch" in cuda_job
assert '"torch==$CUDA_TORCH_VERSION"' in cuda_job
@@ -49,6 +50,28 @@ def test_cuda_job_replaces_locked_cpu_torch_with_same_version_cuda_build():
assert 'uv pip check --python "$CONDITIONAL_ENV/bin/python"' in cuda_job
def test_jetson_job_is_manual_physical_trusted_and_retains_sanitized_evidence():
workflow = (ROOT / ".github" / "workflows" / "conditional-tests.yml").read_text()
jetson_job = workflow.split(" jetson:\n", maxsplit=1)[1].split(
" mps:\n",
maxsplit=1,
)[0]
assert "github.event_name == 'workflow_dispatch' && inputs.run_jetson" in jetson_job
assert "runs-on: [self-hosted, linux, ARM64, jetson]" in jetson_job
assert "scripts/setup_jetson.py" in jetson_job
assert "scripts/run_conditional_gate.py jetson-runtime" in jetson_job
assert "scripts/jetson_support.py" in jetson_job
assert "conditional-jetson-${{ github.run_attempt }}" in jetson_job
assert "retention-days: 30" in jetson_job
assert "actions/setup-python" not in jetson_job
policy = json.loads((ROOT / "ci" / "conditional-test-policy.json").read_text())
gate = next(value for value in policy["gates"] if value["id"] == "jetson-runtime")
assert gate["job"] == "jetson"
assert gate["runner"] == "self-hosted, linux, ARM64, jetson"
def test_policy_rejects_unknown_cpu_exclusion_gate(tmp_path):
policy = json.loads((ROOT / "ci" / "conditional-test-policy.json").read_text())
quality = {
+254
View File
@@ -0,0 +1,254 @@
"""CPU-testable contracts for the experimental Jetson support path."""
from __future__ import annotations
import importlib.metadata
import json
import sys
import tomllib
from pathlib import Path
from types import SimpleNamespace
import pytest
import yaml
from scripts import jetson_support
from scripts import run_conditional_gate
from scripts import setup_jetson
ROOT = Path(__file__).parents[1]
def test_host_evidence_is_allow_listed(monkeypatch, tmp_path):
tegra = tmp_path / "nv_tegra_release"
tegra.write_text("# R36 (release), REVISION: 4.3\nserial=secret\n")
os_release = tmp_path / "os-release"
os_release.write_text(
'ID=ubuntu\nVERSION_ID="22.04"\nPRETTY_NAME="Ubuntu 22.04"\nSECRET=value\n'
)
monkeypatch.setattr(jetson_support.platform, "machine", lambda: "aarch64")
monkeypatch.setattr(jetson_support.platform, "python_version", lambda: "3.10.12")
monkeypatch.setattr(jetson_support, "_capture", lambda command, **kwargs: "6.2+b17")
facts = jetson_support.collect_host_facts(
tegra_release=tegra,
os_release=os_release,
)
assert facts == {
"architecture": "aarch64",
"python_version": "3.10.12",
"os": {
"id": "ubuntu",
"version_id": "22.04",
"pretty_name": "Ubuntu 22.04",
},
"l4t_release": "# R36 (release), REVISION: 4.3",
"jetpack_package": "6.2+b17",
}
assert "secret" not in json.dumps(facts).lower()
def test_runtime_evidence_reports_cuda_without_device_identity(monkeypatch):
properties = SimpleNamespace(name="Orin", total_memory=64 * 1024**3)
fake_cuda = SimpleNamespace(
is_available=lambda: True,
device_count=lambda: 1,
get_device_properties=lambda _index: properties,
get_device_capability=lambda _index: (8, 7),
)
fake_torch = SimpleNamespace(
__version__="2.8.0a0+nv25.06",
version=SimpleNamespace(cuda="12.6"),
cuda=fake_cuda,
)
monkeypatch.setitem(sys.modules, "torch", fake_torch)
def missing_package(_name):
raise importlib.metadata.PackageNotFoundError
monkeypatch.setattr(jetson_support.importlib.metadata, "version", missing_package)
facts = jetson_support.collect_runtime_facts()
assert facts["cuda_available"] is True
assert facts["device_name"] == "Orin"
assert facts["compute_capability"] == [8, 7]
assert facts["total_memory_gb"] == 64.0
assert set(facts) == {
"torch_imported",
"torch_version",
"torch_cuda_version",
"cuda_available",
"cuda_device_count",
"device_name",
"compute_capability",
"total_memory_gb",
"bitsandbytes_version",
}
def test_report_validation_blocks_non_jetson_or_non_cuda_and_warns_on_bnb():
report = {
"host": {"architecture": "x86_64", "l4t_release": None},
"runtime": {
"torch_imported": True,
"torch_cuda_version": None,
"cuda_available": False,
"bitsandbytes_version": "0.47.0",
},
}
errors, warnings = jetson_support.validate_report(report)
assert len(errors) == 3
assert any("ARM64" in error for error in errors)
assert any("Jetson L4T" in error for error in errors)
assert any("not a CUDA build" in error for error in errors)
assert warnings and "unsupported" in warnings[0]
def test_gate_evidence_and_issue_body_cannot_copy_arbitrary_fields(tmp_path):
evidence = tmp_path / "gate.json"
evidence.write_text(json.dumps({
"gate": "jetson-runtime",
"status": "passed",
"git_sha": "a" * 40,
"counts": {"tests": 1, "token": "nested-secret"},
"token": "must-not-escape",
"hostname": "must-not-escape",
}))
summary = jetson_support._gate_summary(evidence)
body = jetson_support.issue_body({"gate_evidence": summary})
assert summary == {
"gate": "jetson-runtime",
"status": "passed",
"git_sha": "a" * 40,
"counts": {"tests": 1},
}
assert "must-not-escape" not in body
assert "nested-secret" not in body
assert "excludes environment variables" in body
def test_dependency_export_rejects_torch_and_bitsandbytes(tmp_path):
requirements = tmp_path / "requirements.txt"
requirements.write_text("transformers==4.56.0\npytest==8.4.1\n")
setup_jetson._require_exclusions(requirements)
for forbidden in (
"torch==2.8.0",
"torch @ https://example.invalid/torch.whl",
"bitsandbytes[diagnostics]==0.47.0",
):
requirements.write_text(forbidden + "\n")
with pytest.raises(RuntimeError, match="forbidden packages"):
setup_jetson._require_exclusions(requirements)
def test_jetson_bootstrap_preserves_vendor_runtime_and_uses_locked_no_deps(
monkeypatch,
tmp_path,
):
project = tmp_path / "project"
(project / "scripts").mkdir(parents=True)
(project / "scripts" / "jetson_support.py").write_text("# fixture\n")
(project / "uv.lock").write_text("# fixture\n")
venv = tmp_path / "jetson-venv"
commands: list[list[str]] = []
def fake_run(command, *, cwd):
command = list(command)
commands.append(command)
if command[1:4] == ["-m", "venv", "--system-site-packages"]:
(venv / "bin").mkdir(parents=True)
(venv / "pyvenv.cfg").write_text("include-system-site-packages = true\n")
if "--output-file" in command:
output = Path(command[command.index("--output-file") + 1])
output.write_text("transformers==4.56.0\n")
monkeypatch.setattr(setup_jetson, "_run", fake_run)
setup_jetson.prepare(
project=project,
venv=venv,
python="vendor-python",
uv_python="tool-python",
reuse=False,
)
assert commands[0][0] == "vendor-python"
assert "--check" in commands[0]
export = next(command for command in commands if "export" in command)
assert export.count("--no-emit-package") == 2
assert "torch" in export and "bitsandbytes" in export
installs = [command for command in commands if "install" in command]
assert len(installs) == 2
assert all("--no-deps" in command for command in installs)
assert any("check" in command for command in commands)
def test_bootstrap_rejects_unsafe_or_non_vendor_reusable_targets(tmp_path):
project = tmp_path / "project"
project.mkdir()
with pytest.raises(ValueError, match="unsafe"):
setup_jetson._require_safe_target(project, project)
existing = tmp_path / "existing"
existing.mkdir()
(existing / "pyvenv.cfg").write_text("include-system-site-packages = false\n")
with pytest.raises(ValueError, match="does not expose JetPack"):
setup_jetson._require_new_or_reusable_venv(existing, reuse=True)
def test_bitsandbytes_is_opt_in_for_quantization_only():
metadata = tomllib.loads((ROOT / "pyproject.toml").read_text())
base = metadata["project"]["dependencies"]
extras = metadata["project"]["optional-dependencies"]
assert not any(value.startswith("bitsandbytes") for value in base)
assert extras["quantization"] == ["bitsandbytes>=0.46.1"]
def test_jetson_issue_form_requires_reproducible_sanitized_hardware_evidence():
form = yaml.safe_load(
(ROOT / ".github" / "ISSUE_TEMPLATE" / "jetson-runtime.yml").read_text(),
)
fields = {value.get("id"): value for value in form["body"] if value.get("id")}
assert set(fields) == {
"device",
"jetpack",
"commit",
"reproduction",
"expected",
"actual",
"evidence",
"confirmations",
}
assert all(value.get("validations", {}).get("required") for value in fields.values())
confirmations = fields["confirmations"]["attributes"]["options"]
assert all(option["required"] for option in confirmations)
assert any("secrets" in option["label"] for option in confirmations)
def test_jetson_conditional_prerequisites_are_physical_and_cuda(monkeypatch, tmp_path):
fake_torch = SimpleNamespace(cuda=SimpleNamespace(is_available=lambda: True))
monkeypatch.setitem(sys.modules, "torch", fake_torch)
monkeypatch.setattr(run_conditional_gate.platform, "machine", lambda: "aarch64")
tegra = tmp_path / "nv_tegra_release"
tegra.write_text("# R36\n")
monkeypatch.setattr(run_conditional_gate, "JETSON_RELEASE", tegra)
assert run_conditional_gate.missing_prerequisites("jetson-runtime") == []
monkeypatch.setattr(run_conditional_gate.platform, "machine", lambda: "x86_64")
fake_torch.cuda.is_available = lambda: False
tegra.unlink()
assert run_conditional_gate.missing_prerequisites("jetson-runtime") == [
"a CUDA-capable PyTorch runtime",
"an ARM64 host",
"a Jetson L4T runtime",
]
Generated
+6 -4
View File
@@ -15,7 +15,7 @@ resolution-markers = [
]
[options]
exclude-newer = "2026-08-11T18:17:23.461940614Z"
exclude-newer = "2026-08-19T00:05:48.361273363Z"
exclude-newer-span = "P3D"
[manifest]
@@ -2602,7 +2602,6 @@ version = "0.1.2"
source = { editable = "." }
dependencies = [
{ name = "accelerate" },
{ name = "bitsandbytes" },
{ name = "datasets" },
{ name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "matplotlib", version = "3.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
@@ -2631,6 +2630,9 @@ dev = [
{ name = "pytest-cov" },
{ name = "ruff" },
]
quantization = [
{ name = "bitsandbytes" },
]
spaces = [
{ name = "gradio" },
]
@@ -2652,7 +2654,7 @@ quality = [
[package.metadata]
requires-dist = [
{ name = "accelerate", specifier = ">=0.24" },
{ name = "bitsandbytes", specifier = ">=0.46.1" },
{ name = "bitsandbytes", marker = "extra == 'quantization'", specifier = ">=0.46.1" },
{ name = "build", marker = "extra == 'dev'", specifier = "==1.2.2.post1" },
{ name = "datasets", specifier = ">=2.14" },
{ name = "gradio", marker = "extra == 'spaces'", specifier = ">=6.7,<7.0" },
@@ -2674,7 +2676,7 @@ requires-dist = [
{ name = "tqdm", specifier = ">=4.64" },
{ name = "transformers", specifier = ">=4.40" },
]
provides-extras = ["dev", "spaces"]
provides-extras = ["dev", "quantization", "spaces"]
[package.metadata.requires-dev]
ci = [