Merge pull request #141 from elder-plinius/docs/31-jetson-support-architecture

This commit is contained in:
Joseph Magly
2026-08-21 20:15:46 -04:00
37 changed files with 1819 additions and 22 deletions
@@ -0,0 +1,102 @@
# ADR-001: Use a JetPack-pinned runtime for Jetson support
Status: Proposed
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
uv lock. Its hardware CUDA gate is explicitly x64 and replaces that locked
package with an upstream cu130 build. Application device discovery already
delegates to `torch.cuda.is_available()`.
Jetson is therefore not primarily an application detection change. It is a
platform-runtime problem: the Jetson module, JetPack/L4T release, Python,
CUDA-family libraries, and PyTorch build or container must be compatible. A
generic ARM64 build can detect Python packaging defects but cannot establish
that this runtime works on Jetson hardware.
## Options considered
| Option | Benefit | Limitation | Decision |
| --- | --- | --- | --- |
| Generic ARM64 build only | Cheap packaging and CPU portability signal | Exercises no Jetson CUDA, L4T, or device memory behavior | Keep as preflight only |
| Native Jetson virtual environment | Direct and simple hardware validation | Host packages drift and the default lock can replace vendor PyTorch | Supported fallback |
| JetPack-compatible container on Jetson hardware | Reproducible runtime boundary and retained image provenance | Requires per-JetPack maintenance and a physical Jetson runner | Preferred |
| Build PyTorch and the complete CUDA stack from source | Maximum version control | High build cost and long-term platform maintenance burden | Reject for the initial tier |
`jetson-containers` may accelerate prototyping, but it is a community project,
not the project's vendor trust anchor. A supported image must ultimately pin
audited base-image and package provenance.
## Decision
1. Keep the default PR environment and lock CPU-only.
2. Treat generic ARM64 CI as packaging preflight, never Jetson support evidence.
3. Define one exact initial matrix only after a runner is available: Jetson
module, JetPack patch, L4T, Python, PyTorch image or wheel, CUDA, cuDNN,
TensorRT, and bitsandbytes status.
4. Prefer an image derived from a JetPack-compatible NVIDIA PyTorch runtime and
run it on physical Jetson hardware. Preserve the image digest and binary
hashes in release evidence.
5. Add a separate `jetson-runtime` conditional gate. Its exact base labels are
`self-hosted`, `linux`, `ARM64`, and `jetson`; tier labels identify the module
and JetPack major version.
6. Run the hardware lane only by trusted manual, scheduled, or release
dispatch. Never execute untrusted pull-request code on the persistent
self-hosted Jetson runner.
7. Do not install the generic PyPI Linux-aarch64 bitsandbytes wheel on Jetson.
Upstream documents that wheel as SBSA/server ARM and requires a Jetson source
build. Quantization remains unsupported until a pinned source build passes
the exact hardware gate.
## Consequences
- Jetson support will have a narrower, explicit matrix rather than a broad ARM
claim.
- The implementation needs a Jetson-specific constraints/profile boundary so
`uv sync` cannot replace vendor PyTorch or select an incompatible
bitsandbytes wheel.
- Cross-building an ARM64 image may shorten image assembly, but only an
on-device run supplies CUDA support evidence.
- Vendor upgrades become release-affecting changes and must produce fresh gate
evidence.
## Delivery and migration
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. (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. (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
release commit.
## Rollback
If the Jetson lane regresses, disable the affected matrix entry and remove its
support claim while retaining the generic ARM64 preflight. Revert the
Jetson-specific image/profile independently; do not change or weaken the
default CPU lock or x64 CUDA gate. Restore a matrix only after fresh physical
Jetson evidence passes.
## Review record
The 2026-08-21 review panel covered NVIDIA/platform compatibility,
build/maintainability, test/release evidence, and security/supply chain. It
required the Jetson-specific bitsandbytes source-build rule, physical-hardware
container evidence, one canonical public plan, exact gate naming, and an
untrusted-code restriction for the self-hosted runner.
@@ -0,0 +1,14 @@
ref_id: REF-BITSANDBYTES-INSTALL
grade: LOW
baseline: LOW
source_type: library_documentation
upgrades: []
downgrades:
- Library support matrix is not Jetson-specific OBLITERATUS runtime evidence.
allowed_language:
- "bitsandbytes documents generic Linux aarch64 CUDA wheels for SBSA/server ARM..."
- "Jetson requires a pinned source build that must be proven on the selected stack..."
forbidden_language:
- "bitsandbytes is confirmed for OBLITERATUS on Jetson..."
- "Jetson quantization support can be claimed without hardware evidence..."
- "The generic Linux aarch64 wheel is compatible with Jetson..."
@@ -0,0 +1,13 @@
ref_id: REF-GITHUB-SELF-HOSTED
grade: MODERATE
baseline: LOW
source_type: platform_documentation
upgrades:
- Primary GitHub documentation for the runner trust boundary used by the proposed workflow.
downgrades:
- Documentation establishes platform risk, not proof of this repository's future runner configuration.
allowed_language:
- "GitHub warns that persistent self-hosted runners can be compromised by untrusted workflow code..."
- "The Jetson lane must be limited to trusted refs and maintainer-controlled dispatch..."
forbidden_language:
- "A runner label alone safely isolates untrusted pull requests..."
@@ -0,0 +1,14 @@
ref_id: REF-JETPACK-62
grade: MODERATE
baseline: LOW
source_type: vendor_documentation
upgrades:
- Primary NVIDIA release notes for the recommended JetPack 6.2 target tier.
downgrades:
- Release notes establish platform stack contents, not OBLITERATUS runtime support.
allowed_language:
- "NVIDIA lists JetPack 6.2 as..."
- "JetPack 6.2 is an appropriate explicit platform tier..."
forbidden_language:
- "JetPack 6.2 support is certified..."
- "Generic ARM evidence proves this stack..."
@@ -0,0 +1,13 @@
ref_id: REF-JETPACK-7-DOWNLOADS
grade: MODERATE
baseline: LOW
source_type: vendor_documentation
upgrades:
- Current NVIDIA release/download page for JetPack 7 stack contents.
downgrades:
- Download page is not project runtime evidence.
allowed_language:
- "NVIDIA currently lists..."
- "JetPack 7 should be a separate evidence tier..."
forbidden_language:
- "JetPack 7 works with OBLITERATUS..."
@@ -0,0 +1,14 @@
ref_id: REF-JETSON-PYTORCH-INSTALL
grade: MODERATE
baseline: LOW
source_type: vendor_documentation
upgrades:
- Primary vendor documentation for Jetson PyTorch installation.
downgrades:
- Vendor documentation can change without project-controlled reproducibility.
allowed_language:
- "NVIDIA documents..."
- "NVIDIA's install guide states..."
forbidden_language:
- "OBLITERATUS supports Jetson..."
- "This guarantees compatibility..."
@@ -0,0 +1,14 @@
ref_id: REF-JETSON-PYTORCH-RELEASES
grade: MODERATE
baseline: LOW
source_type: vendor_documentation
upgrades:
- Primary vendor compatibility table for Jetson PyTorch releases.
downgrades:
- Release tables are necessary but not sufficient runtime evidence.
allowed_language:
- "NVIDIA maps..."
- "The compatibility table lists..."
forbidden_language:
- "All listed combinations work for OBLITERATUS..."
- "No hardware testing is needed..."
@@ -0,0 +1,13 @@
ref_id: REF-UV-PYTORCH
grade: MODERATE
baseline: LOW
source_type: tool_documentation
upgrades:
- Primary tool documentation for uv PyTorch resolution behavior.
downgrades:
- Packaging behavior still needs validation in this repository's lock model.
allowed_language:
- "uv documents..."
- "PyTorch accelerator variants need explicit resolver handling..."
forbidden_language:
- "uv automatically solves Jetson packaging..."
@@ -0,0 +1,11 @@
id: REF-BITSANDBYTES-INSTALL
title: bitsandbytes Installation Guide
source_type: library_documentation
publisher: Hugging Face
url: https://huggingface.co/docs/bitsandbytes/installation
accessed_at: "2026-08-21T23:31:01Z"
relevant_claims:
- bitsandbytes supports NVIDIA CUDA GPUs with compute capability 6.0 or newer.
- Linux aarch64 CUDA wheels are documented for CUDA Toolkit 11.8 through 13.2 with specific SM targets.
- NVIDIA Jetson L4T/JetPack requires a source build; the published Linux aarch64 wheels target SBSA/server ARM and are not Jetson-compatible.
- LLM.int8 requires Turing-class or newer hardware, while NF4/FP4 quantization requires Pascal-class or newer hardware.
@@ -0,0 +1,10 @@
id: REF-GITHUB-SELF-HOSTED
title: Self-hosted runners reference
source_type: platform_documentation
publisher: GitHub
url: https://docs.github.com/en/actions/reference/security/secure-use#hardening-for-self-hosted-runners
accessed_at: "2026-08-21T23:55:00Z"
relevant_claims:
- GitHub warns that self-hosted runners do not provide clean, ephemeral isolation for every job.
- GitHub recommends using self-hosted runners only with private repositories because repository forks can execute dangerous code through pull requests.
- A persistent Jetson runner must not execute untrusted pull-request code.
@@ -0,0 +1,10 @@
id: REF-JETPACK-62
title: JetPack 6.2 Release Notes
source_type: vendor_documentation
publisher: NVIDIA
url: https://docs.nvidia.com/jetson/archives/jetpack-archived/jetpack-62/release-notes/index.html
accessed_at: "2026-08-21T23:31:01Z"
relevant_claims:
- JetPack 6.2 includes Jetson Linux 36.4.3.
- JetPack 6.2 includes a compute stack with CUDA 12.6, TensorRT 10.3, cuDNN 9.3, VPI 3.2, DLA 3.1, and DLFW 24.0.
- JetPack 6.2 targets Jetson Orin modules and includes updated Super Mode behavior for Orin Nano and Orin NX modules.
@@ -0,0 +1,10 @@
id: REF-JETPACK-7-DOWNLOADS
title: NVIDIA JetPack SDK Downloads and Notes
source_type: vendor_documentation
publisher: NVIDIA
url: https://developer.nvidia.com/embedded/jetpack/downloads
accessed_at: "2026-08-21T23:31:01Z"
relevant_claims:
- The current JetPack 7.2.1 release is paired with Jetson Linux 39.2.1.
- JetPack 7.2.1 lists CUDA 13.2.1, TensorRT 10.16.2, and cuDNN 9.20.0.
- JetPack 7 uses an Ubuntu 24.04 L4T base and aligns Jetson software with SBSA.
@@ -0,0 +1,11 @@
id: REF-JETSON-PYTORCH-INSTALL
title: Installing PyTorch for Jetson Platform
source_type: vendor_documentation
publisher: NVIDIA
url: https://docs.nvidia.com/deeplearning/frameworks/install-pytorch-jetson-platform/index.html
accessed_at: "2026-08-21T23:31:01Z"
relevant_claims:
- NVIDIA provides Jetson PyTorch pip wheels with GPU acceleration and cuDNN support.
- The packages are intended to be installed on top of a specified JetPack version.
- Installation prerequisites include JetPack on the Jetson device and system packages.
- PyTorch installation verification starts by importing torch on the Jetson platform.
@@ -0,0 +1,11 @@
id: REF-JETSON-PYTORCH-RELEASES
title: PyTorch for Jetson Platform Release Notes
source_type: vendor_documentation
publisher: NVIDIA
url: https://docs.nvidia.com/deeplearning/frameworks/install-pytorch-jetson-platform-release-notes/pytorch-jetson-rel.html
accessed_at: "2026-08-21T23:31:01Z"
relevant_claims:
- NVIDIA's compatibility table maps PyTorch versions to NVIDIA framework containers or wheels and JetPack versions.
- JetPack 6.2 entries map to NVIDIA framework containers 25.02 through 25.06.
- JetPack 7.x entries map to NVIDIA framework containers 25.08 and later.
- NVIDIA notes that standalone iGPU containers are no longer produced starting with the 26.03 release.
@@ -0,0 +1,11 @@
id: REF-UV-PYTORCH
title: Using uv with PyTorch
source_type: tool_documentation
publisher: Astral
url: https://docs.astral.sh/uv/guides/integration/pytorch/
accessed_at: "2026-08-21T23:31:01Z"
relevant_claims:
- uv can manage PyTorch dependencies while controlling accelerator selection.
- PyTorch wheels use dedicated indexes outside PyPI for many builds.
- PyTorch encodes accelerator builds in local version specifiers such as +cpu and +cu130.
- Different PyTorch accelerator builds are published on different indexes.
+286
View File
@@ -0,0 +1,286 @@
# Jetson support acceptance and release-validation strategy
Date: 2026-08-21
Scope: OBLITERATUS issue #31
Status: assessment, not implementation
## Executive conclusion
Issue #31 cannot be validated or released on a generic ARM builder alone.
Generic ARM64 CI can cover repository-level portability, importability,
packaging, and CPU-only behavior, but Jetson support depends on the NVIDIA
JetPack software stack and on execution on Jetson-class hardware or a JetPack
container running on that hardware.
The lowest-risk support model is:
1. keep the default PR gate CPU-only and deterministic;
2. add a Jetson-specific conditional gate on a self-hosted ARM64 Jetson runner;
3. treat generic ARM64 CI as a preflight layer, not as acceptance evidence;
4. require real-device smoke evidence before declaring Jetson support.
## What the current repo policy already says
The project context already establishes that the default PR baseline is
CPU-safe, deterministic, and must not require accelerator, network, or remote
credentials. Conditional accelerator checks are explicitly outside the default
CPU job.
The current conditional policy already has:
- a CUDA gate on `self-hosted, linux, x64, cuda`;
- a documented waiver model for unavailable accelerator lanes;
- a GitHub Actions routing model that relies on runner labels and groups.
That means Jetson support should be added as a new conditional lane, not folded
into the standard CPU PR job.
## Evidence-based split: generic ARM64 vs Jetson device
### Generic ARM64 builders can validate
- Python packaging and metadata.
- Pure-Python imports and CLI/help entry points.
- CPU-only unit and boundary tests.
- Static/configuration logic that does not require CUDA or JetPack runtime
libraries.
- The fact that a workflow can target `ARM64` self-hosted runners.
This is enough for portability regressions, but not enough for Jetson runtime
support.
### Jetson hardware or JetPack container is required for
- CUDA device discovery.
- CUDA execution and memory behavior.
- cuDNN / TensorRT / JetPack-specific library compatibility.
- Any claim that NVIDIA-provided Jetson PyTorch wheels run correctly.
- Any claim that the installed runtime actually sees a Jetson GPU.
NVIDIAs Jetson PyTorch installation guide says the provided wheels are meant
to be installed on top of a specified JetPack version on a Jetson device, and
verification is done by importing `torch` on the Jetson platform.
NVIDIAs Jetson Linux validation guide also says CUDA samples can be run
natively on the target or inside the JetPack container, which is the right
acceptance bar for hardware-backed validation.
## Support tiers
### Tier 0: source-only portability
Purpose: prove the codebase does not contain obvious ARM-incompatible
assumptions.
Environment:
- GitHub-hosted Linux or self-hosted ARM64 runner;
- CPU-only;
- no NVIDIA driver, no JetPack, no CUDA.
Checks:
- `python -m build --sdist --wheel`
- install the built wheel into a clean venv
- `python -m obliteratus --help`
- import and module smoke tests
- all CPU-only pytest markers
Acceptance:
- no architecture-specific syntax/runtime breakage;
- no packaging or import regressions.
### Tier 1: Jetson preflight on generic ARM64
Purpose: catch obvious Jetson-adjacent integration mistakes before touching
hardware.
Environment:
- ARM64 Linux runner without JetPack;
- optional cross-check against Jetson-targeted config files and dependency
pins.
Checks:
- validate Jetson-specific config manifests and workflow wiring;
- verify that Jetson jobs are gated behind dedicated labels and environment
variables;
- confirm that no Jetson-only dependency is pulled into the default PR job.
Acceptance:
- the repo can express a Jetson lane cleanly;
- default CI remains CPU-only.
### Tier 2: Jetson hardware smoke
Purpose: prove the runtime actually works on a Jetson device.
Environment:
- self-hosted Linux ARM64 Jetson runner with the exact labels `self-hosted`,
`linux`, `ARM64`, and `jetson`;
- JetPack installed and active;
- NVIDIA runtime / CUDA stack available.
Checks:
- `torch` import from the JetPack-aligned wheel set;
- `torch.cuda.is_available()` and device enumeration;
- a tiny tensor operation on GPU;
- a minimal model-load or model-adapter smoke if the feature requires it;
- a short CUDA sample or equivalent library smoke;
- optional containerized run using the JetPack-compatible container on that
Jetson hardware.
Acceptance:
- the device is recognized as CUDA-capable;
- the relevant Jetson wheel/container stack works on the intended JetPack
version;
- the workflow emits logs and artifacts that identify the exact JetPack / CUDA /
wheel set used.
### Tier 3: release validation
Purpose: certify a tagged release candidate.
Environment:
- Tier 2 Jetson hardware;
- pinned dependency set and reproducible artifacts;
- retained logs and hashes.
Checks:
- Tier 2 smoke;
- the repos full release-validation suite;
- supply-chain checks for vendor artifacts;
- any release note or compatibility matrix update.
Acceptance:
- tagged releases declare Jetson support only when Tier 2 and Tier 3 pass
against the exact tagged commit.
## Recommended runner labels
Use separate labels so the workflow can route cleanly and fail obviously if the
hardware is absent.
- `self-hosted, linux, ARM64, jetson`
- optional refinement: `self-hosted, linux, ARM64, jetson, orin`
- optional refinement: `self-hosted, linux, ARM64, jetson, jetpack-<major>`
Keep the generic ARM64 preflight on a separate label such as:
- `self-hosted, linux, ARM64, arm64-preflight`
This avoids accidentally treating a generic ARM machine as a Jetson target.
GitHubs self-hosted runner routing is label-based, and jobs remain queued if
no matching runner is online. That makes an explicit Jetson label the right
mechanism for a physical-device lane.
## Exact acceptance gates
Jetson support should be considered ready only when all of these are true:
1. The default PR gate still passes with no Jetson dependency.
2. A dedicated `jetson-runtime` job passes on physical Jetson hardware, either
natively or inside the pinned JetPack-compatible container.
3. The exact supported JetPack version is documented.
4. The exact NVIDIA wheel/container provenance is pinned and hashed.
5. The repo has a regression test that fails if Jetson detection or the CUDA
smoke is broken.
6. The release notes state the supported JetPack / CUDA / TensorRT matrix.
7. The workflow retains the named `conditional-jetson-<run-attempt>` log and
environment artifact for 30 days against the exact candidate commit.
## Memory and thermal constraints
Jetson support should assume small-device variability, even on higher-memory
SKUs.
Practical guardrails:
- use tiny models and short sequences for smoke tests;
- cap wall time tightly;
- assert memory-sensitive paths with low-footprint fixtures;
- avoid benchmark-length runs on the Jetson lane;
- collect `nvidia-smi`-equivalent or device telemetry only if the platform
exposes it;
- do not rely on long repeated sweeps for acceptance.
If a test is intended to validate memory pressure, it should do so with a
deterministic threshold and a short timeout, not with a human-observed
benchmark.
## Rollback and waiver policy
If Jetson hardware is unavailable, the repo should:
- keep the Jetson lane optional and separately labeled;
- preserve the generic ARM64 preflight;
- publish a time-bounded waiver in the existing conditional-policy pattern;
- avoid claiming Jetson support in release notes.
If a Jetson regression is found after support is published:
- revert or patch the exact regression on the supported branch;
- keep the Jetson lane red until the hardware smoke passes again;
- do not widen the default PR gate to hide the failure.
## Supply-chain and security considerations
Jetson support increases supply-chain risk because the runtime depends on vendor
wheels and platform-specific system packages.
Controls to require:
- pin exact NVIDIA wheel URLs and versions;
- verify hashes for every downloaded binary artifact;
- prefer official JetPack / NVIDIA container images over ad hoc mixed library
stacks;
- do not mix arbitrary patched CUDA/TensorRT libraries into the supported
matrix;
- record the exact JetPack release, CUDA version, and TensorRT version in the
evidence trail;
- treat any vendor wheel update as a release-affecting change.
- build bitsandbytes from a pinned source revision for Jetson, record the build
inputs and hash, and never substitute the generic SBSA Linux-aarch64 wheel;
- never execute untrusted pull-request code on the persistent self-hosted
Jetson runner.
NVIDIAs TensorRT documentation explicitly notes that JetPack deployments must
remain on a TensorRT 10.x release supported by the JetPack version. That means
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
`jetson` runner label.
3. Add a minimal Jetson smoke test that checks import, CUDA discovery, and one
tiny GPU operation.
4. Add a generic ARM64 preflight job to catch packaging and configuration
regressions early.
5. Document the supported JetPack / CUDA / TensorRT matrix in the repo docs.
6. Promote Jetson claims only after a real-device run has passed on the exact
release candidate.
## Source URLs
- https://docs.nvidia.com/deeplearning/frameworks/install-pytorch-jetson-platform/index.html
- https://docs.nvidia.com/jetson/archives/r36.5/DeveloperGuide/SD/TestPlanValidation.html
- https://docs.nvidia.com/deeplearning/tensorrt/latest/installing-tensorrt/installing.html
- https://docs.github.com/en/actions/reference/runners/self-hosted-runners
- https://docs.github.com/en/actions/reference/runners/github-hosted-runners
+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"
]
},
{
+16 -3
View File
@@ -70,16 +70,29 @@ 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. 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
MPS uses a runner labeled `self-hosted`, `macOS`, `ARM64`, and `mps`; enable its
+215
View File
@@ -0,0 +1,215 @@
# NVIDIA Jetson support plan
Issue: https://github.com/elder-plinius/OBLITERATUS/issues/31
## Status
Native Jetson AGX support is not claimed yet. OBLITERATUS should treat Jetson
as a dedicated conditional runtime lane, not as part of the default pull-request
gate and not as a generic Linux ARM build.
The current OBLITERATUS CUDA path delegates discovery to PyTorch through
`torch.cuda.is_available()`. If a Jetson AGX host reports no CUDA inside
OBLITERATUS, the first thing to verify is the JetPack/L4T/PyTorch/container
stack, because NVIDIA publishes Jetson-specific PyTorch builds intended for
specified JetPack versions.
## Decision
Support Jetson through a JetPack-pinned runtime contract:
- Keep ordinary PR CI CPU-only, offline, and architecture-neutral.
- Add a Jetson conditional gate once a Jetson runner is available.
- Prefer an NVIDIA-supported Jetson PyTorch container or NVIDIA Jetson PyTorch
wheel for the exact JetPack release under test.
- Do not use the existing x64 CUDA gate as Jetson evidence.
- Do not treat a generic `linux/arm64` build as evidence that CUDA works on
Jetson.
## Why generic ARM is insufficient
Jetson support couples at least five moving pieces:
- Jetson hardware family and compute capability.
- JetPack version.
- Jetson Linux/L4T version and Ubuntu base image.
- CUDA, cuDNN, TensorRT, and related NVIDIA libraries.
- PyTorch build or container version.
NVIDIA's Jetson PyTorch documentation says the PyTorch packages are installed
on top of a specified JetPack version, and the compatibility table maps PyTorch
versions to NVIDIA framework containers/wheels and JetPack versions. A generic
ARM build can prove that Python code imports on `aarch64`; it cannot prove that
CUDA, cuDNN, TensorRT, or PyTorch CUDA dispatch works on Jetson.
## Proposed initial support matrix
Start with the hardware reported in issue #31: Jetson AGX devices with 64 GB
unified memory.
This is a planning target, not a current support claim. The implementation must
replace the JetPack family with the exact patch installed on the available
runner before publishing compatibility:
| Tier | Hardware | JetPack | OS / CUDA baseline | Evidence requirement |
| --- | --- | --- | --- | --- |
| Initial candidate | Jetson AGX Orin 64 GB | 6.2.x, exact patch TBD | L4T/CUDA values from the selected patch | Native Jetson runner or pinned compatible container on Jetson hardware |
| Evaluate later | Jetson AGX Thor | 7.x, exact release TBD | Select only after NVIDIA's PyTorch compatibility table covers the release | Separate runner and evidence before claiming support |
| Legacy | Jetson AGX Xavier | 5.1.x | Jetson Linux 35.x / Ubuntu 20.04 / CUDA 11.x family | Defer unless a maintainer/user provides hardware and demand |
Do not collapse these tiers into one "ARM64" support claim.
## Installation shape
The generic local Dockerfile uses `python:3.11-slim` and is not the Jetson
runtime image. A Jetson runtime should use one of these approaches:
1. Start from an NVIDIA Jetson-compatible PyTorch framework container for the
selected JetPack version, then install OBLITERATUS without replacing the
container's validated PyTorch stack.
2. On a flashed Jetson host, install the NVIDIA Jetson PyTorch wheel matching
the installed JetPack release, then install OBLITERATUS in a virtual
environment without allowing dependency resolution to replace `torch`.
The lock policy should make the Jetson torch source explicit. The existing
Linux PR lock intentionally uses CPU-only PyTorch. A Jetson install path needs
an override or separate constraints file that preserves NVIDIA's Jetson PyTorch
runtime. It must also prevent the generic PyPI Linux-aarch64 bitsandbytes wheel
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:
- Gate id: `jetson-runtime`
- Runner labels: `self-hosted`, `linux`, `ARM64`, `jetson`
- Optional labels by tier: `orin`, `jetpack-6` or `thor`, `jetpack-7`
- Trigger: manual dispatch and release/scheduled validation only
- Evidence retention: same 30-day conditional-evidence policy as other hardware
gates
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.
- `torch.cuda.is_available()` is true.
- `torch.version.cuda` is not `None`.
- `torch.cuda.get_device_name(0)` identifies the Jetson GPU class.
- OBLITERATUS resolves `device=auto` to `cuda`.
- A small CUDA tensor operation completes with finite output.
- The existing offloaded-surgery CUDA probe passes.
- A pinned, source-built `bitsandbytes` NF4/4-bit path is proven on that exact
Jetson stack, or bitsandbytes is documented as unsupported for the tier.
- A tiny Hugging Face model run passes only when the model-download gate is
explicitly selected and the runner has the required account/cache policy.
## Acceptance criteria
Jetson support can be claimed for a tier only after all of the following are
true:
- The supported Jetson module and JetPack version are named in this document.
- The install instructions pin the JetPack-compatible PyTorch container or
wheel source.
- The Jetson conditional gate produces non-skipped green evidence on the exact
commit being claimed.
- The named `jetson-runtime` workflow job uses the documented runner labels and
retains `conditional-jetson-<run-attempt>` logs and environment metadata for
30 days.
- The release notes distinguish generic ARM importability from Jetson CUDA
support.
- The docs state memory expectations for 64 GB unified memory and recommend
small models for validation before large ablation runs.
## Operational notes
Jetson's unified memory is shared by the OS, CUDA, model weights, activations,
and file cache. Treat "64 GB" as a capacity class, not guaranteed usable model
memory. Use small models for smoke tests, then move larger GPU validation to
dedicated CUDA hosts such as Titan when those resources are available.
The Jetson hardware probe does not require a Hugging Face login. Model download
testing remains the separate, explicitly selected `model-download-runtime`
gate; credentials are relevant only when that selected model itself requires
them.
## Sources
- [NVIDIA: Installing PyTorch for Jetson Platform](https://docs.nvidia.com/deeplearning/frameworks/install-pytorch-jetson-platform/index.html)
- [NVIDIA: PyTorch for Jetson compatibility table](https://docs.nvidia.com/deeplearning/frameworks/install-pytorch-jetson-platform-release-notes/pytorch-jetson-rel.html)
- [NVIDIA: JetPack 6.2 release notes](https://docs.nvidia.com/jetson/archives/jetpack-archived/jetpack-62/release-notes/index.html)
- [NVIDIA: current JetPack downloads and notes](https://developer.nvidia.com/embedded/jetpack/downloads)
- [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 = [