Free course · by the SutraCore team

Free NVIDIA Jetson AI course: 30 days, 16 steps

Orin NX 16GBJetPack 6.2.1CUDA 12.6TensorRT 10.3Python 3.10PyTorch 2.8
Step 02 of 16Day 2 · Tue 27 OctWeek 1Phase A · notebooks

CUDA, Python and JupyterLab

Install the exact GPU package set that matches your board (JetPack 6.2.1, CUDA 12.6.68, Python 3.10.12), lock it so nothing can silently replace it, and open JupyterLab from your laptop.

Runs on
Jetson terminal, then JupyterLab in your laptop browser
Notebooks
00_env_check.ipynb, 01_gpu_hello.ipynb
Creates
~/sutraai/.venv, constraints-jp621.txt, cuDSS + cuSPARSELt in /opt/nvidia, project folders, git repo
Time
2–3 hours

Where Where to implement

Jetson: jetson

  • venv: ~/sutraai/.venv (Python 3.10.12)
  • Version lock: ~/sutraai/constraints-jp621.txt
  • Notebooks: ~/sutraai/notebooks/

Laptop

  • SSH tunnel to port 8888
  • Browser at http://localhost:8888, or VS Code Remote-SSH

Versions Your package set

These wheels come from the Jetson AI Lab index for JetPack 6 / CUDA 12.6 and are built for Python 3.10 (cp310), which is exactly your board. torch 2.8.0 is the version NVIDIA forum users confirmed on JetPack 6.2. torchvision and torchaudio must match it.

PackageVersionComes fromWhy this one
torch2.8.0Jetson AI Lab jp6/cu126Built for CUDA 12.6 + cuDNN 9.3 on Orin (arch 8.7); classic ONNX exporter by default
torchvision0.23.0Jetson AI Lab jp6/cu126The release paired with torch 2.8
torchaudio2.8.0Jetson AI Lab jp6/cu126Paired with torch 2.8 (the steps use librosa for audio, so this is a backup)
onnxruntime-gpu1.23.0Jetson AI Lab jp6/cu126CUDA and TensorRT 10.3 execution providers on your GPU
numpy1.26.4PyPIThe last 1.x release; safe with every package in these steps
matplotlib · pillow · scipy3.10.9 · 11.3.0 · 1.15.3PyPIPinned so plots work with JupyterLab's inline backend
TensorRT (Python)10.3.0.30JetPack (system), linked into the venvThe only system Python package the venv needs (substep 2)
cuDSS (system library)0.7.1.4 · CUDA 12NVIDIA download, substep 3torch 2.8.0 needs libcudss.so.0; JetPack doesn't ship it
cuSPARSELt (system library)0.6.3.2NVIDIA download, substep 3torch 2.8.0 needs libcusparseLt.so.0; same version PyTorch uses with CUDA 12.6

The index also has newer sets (torch 2.9.1, 2.10.0, 2.11.0). Stay on 2.8.0 for the 3-week notebook phase. Change versions only between phases, and then change all three torch packages together.

Do Do it

1Put CUDA 12.6 on your PATH

Jetson terminal
echo 'export PATH=/usr/local/cuda/bin:$PATH' >> ~/.bashrc
echo 'export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc
source ~/.bashrc
nvcc --version | tail -n 2          # expect: Cuda compilation tools, release 12.6, V12.6.68
ls -l /usr/local/cuda               # points to /usr/local/cuda-12.6
Library on your boardWhat it does for you
CUDA 12.6.68Runs code on the GPU. Everything else sits on top of it.
cuDNN 9.3.0 / cuBLASFast neural-network layers and matrix maths used inside PyTorch
TensorRT 10.3.0.30Turns a trained model into a fast engine for this exact GPU, or for the Orin NX's DLA accelerators (Step 05)
VPI 3.2.4, DeepStreamCamera and video pipelines. Not needed until Month 3.
OpenCV 4.8.0 (no CUDA)Not used in these steps: images go through PIL and torchvision.

2Create the project, the venv and the version lock

Jetson terminal
cd ~/sutraai
mkdir -p notebooks sutra_core configs data/raw data/processed models exports/golden reports
touch sutra_core/__init__.py
python3 --version                                   # expect: Python 3.10.12
python3 -m venv .venv                               # isolated: Ubuntu's old Python packages stay out
cat > constraints-jp621.txt <<'EOF'
# SutraAI tested set: Jetson Orin NX 16GB · JetPack 6.2.1 · CUDA 12.6.68 · cuDNN 9.3.0 · TensorRT 10.3.0.30 · Python 3.10.12
torch==2.8.0
torchvision==0.23.0
torchaudio==2.8.0
onnxruntime-gpu==1.23.0
numpy==1.26.4
matplotlib==3.10.9
pillow==11.3.0
scipy==1.15.3
EOF
echo 'export PIP_CONSTRAINT=$HOME/sutraai/constraints-jp621.txt' >> .venv/bin/activate   # every pip install in this venv obeys the lock
source .venv/bin/activate
pip install --upgrade pip
pip install numpy==1.26.4

# Link only JetPack's TensorRT 10.3 Python bindings into the venv (nothing else from Ubuntu)
TRT_DIR=$(/usr/bin/python3 -c "import tensorrt, os; print(os.path.dirname(os.path.dirname(tensorrt.__file__)))")
echo "$TRT_DIR"                                     # expect: /usr/lib/python3.10/dist-packages
SP="$VIRTUAL_ENV/lib/python3.10/site-packages"
for p in "$TRT_DIR"/tensorrt*; do ln -sfn "$p" "$SP/$(basename "$p")"; done
python -c "import tensorrt; print(tensorrt.__version__)"                          # expect: 10.3.0.30
python -c "import sys; print([p for p in sys.path if 'dist-packages' in p])"      # expect: []

Why an isolated venv: Ubuntu keeps its own old Python packages in /usr/lib/python3/dist-packages (matplotlib 3.5, numpy 1.21, scipy, Pillow…). If the venv can see them, pip treats them as "already installed", and you end up with half-new, half-old libraries: errors like 'RcParams' object has no attribute '_get' or Unable to import Axes3D. An isolated venv sees none of them. The one system package you do need, TensorRT, is linked in by name.

Why the lock matters: packages like timm or sentence-transformers depend on torch. Without the lock, pip can replace your GPU torch with a CPU-only one from normal PyPI. With PIP_CONSTRAINT set, pip refuses any other version instead.

Already made your venv with --system-site-packages (the earlier version of this page)? Switch it to isolated instead of starting over. Your torch install is kept.

Jetson terminalone-time switch for an existing ~/sutraai/.venv
cd ~/sutraai && source .venv/bin/activate
SP="$VIRTUAL_ENV/lib/python3.10/site-packages"

# 1) stop seeing Ubuntu's Python packages
sed -i 's/^include-system-site-packages = .*/include-system-site-packages = false/' .venv/pyvenv.cfg
grep include-system .venv/pyvenv.cfg                 # expect: include-system-site-packages = false

# 2) remove the two mpl_toolkits workaround files (no longer needed)
rm -f "$SP/00-sutraai-mpl-toolkits.pth" "$SP/mpl_toolkits/__init__.py"

# 3) link only TensorRT
TRT_DIR=$(/usr/bin/python3 -c "import tensorrt, os; print(os.path.dirname(os.path.dirname(tensorrt.__file__)))")
for p in "$TRT_DIR"/tensorrt*; do ln -sfn "$p" "$SP/$(basename "$p")"; done

# 4) install what pip skipped earlier because Ubuntu "already had it" (installed packages are kept)
pip install torch==2.8.0 torchvision==0.23.0 torchaudio==2.8.0 onnxruntime-gpu==1.23.0 --index-url https://pypi.jetson-ai-lab.io/jp6/cu126/+simple/
pip install jupyterlab ipykernel ipywidgets matplotlib pandas scikit-learn tqdm pyyaml \
            timm onnx librosa soundfile openai sentence-transformers thop nbstripout ai-edge-litert
pip check                                            # expect: No broken requirements found.

# 5) verify
python -c "import sys; print([p for p in sys.path if 'dist-packages' in p])"      # expect: []
python -c "import torch, tensorrt, matplotlib; print(torch.__version__, torch.cuda.is_available(), tensorrt.__version__, matplotlib.__version__)"
# expect: 2.8.0 True 10.3.0.30 3.10.9

Then restart JupyterLab (in its tmux window: Ctrl+C, then jupyter lab --no-browser --port 8888 again), restart each notebook kernel, and run the NB00 test cell (substep 7). It should end with ✅.

3Add the NVIDIA libraries the torch 2.8 wheel needs

The Jetson torch 2.8.0 wheel is linked against libraries that JetPack 6.2.1 doesn't install: cuDSS (sparse solver), cuSPARSELt (sparse matmul) and OpenBLAS. Without them, the install finishes fine but import torch fails with ImportError: libcudss.so.0: cannot open shared object file (or the same for libcusparseLt.so.0). This is the fix NVIDIA staff give on the forum. Run it once; it survives reboots and works in notebooks too.

Jetson terminal
# a) OpenBLAS + OpenMP from Ubuntu
sudo apt-get update
sudo apt-get install -y libopenblas-dev libomp-dev

# b) cuDSS 0.7.1 for Jetson (CUDA 12)  →  provides libcudss.so.0
mkdir -p ~/downloads && cd ~/downloads
wget https://developer.download.nvidia.com/compute/cudss/redist/libcudss/linux-aarch64/libcudss-linux-aarch64-0.7.1.4_cuda12-archive.tar.xz
echo "f283c31b6badf4a5277014295705eac8e6e28f27de30d746719ea1cef7a750b8  libcudss-linux-aarch64-0.7.1.4_cuda12-archive.tar.xz" | sha256sum -c   # expect: OK
sudo mkdir -p /opt/nvidia
sudo tar xf libcudss-linux-aarch64-0.7.1.4_cuda12-archive.tar.xz -C /opt/nvidia
dirname "$(find /opt/nvidia/libcudss-linux-aarch64-0.7.1.4_cuda12-archive -name 'libcudss.so.0' | head -n1)" | sudo tee /etc/ld.so.conf.d/cudss.conf

# c) cuSPARSELt 0.6.3 for Jetson (the version PyTorch uses with CUDA 12.6)  →  provides libcusparseLt.so.0
wget https://developer.download.nvidia.com/compute/cusparselt/redist/libcusparse_lt/linux-aarch64/libcusparse_lt-linux-aarch64-0.6.3.2-archive.tar.xz
echo "91501d0c05d1ff0dd67399ecd7c1bf76a620e842dce54ae4c8a1f07cec0673e5  libcusparse_lt-linux-aarch64-0.6.3.2-archive.tar.xz" | sha256sum -c   # expect: OK
sudo tar xf libcusparse_lt-linux-aarch64-0.6.3.2-archive.tar.xz -C /opt/nvidia
dirname "$(find /opt/nvidia/libcusparse_lt-linux-aarch64-0.6.3.2-archive -name 'libcusparseLt.so.0' | head -n1)" | sudo tee /etc/ld.so.conf.d/cusparselt.conf

# d) register both with the system loader
sudo ldconfig
ldconfig -p | grep -E "libcudss.so.0|libcusparseLt.so.0|libopenblas.so.0"   # expect 3+ lines
cd ~/sutraai

Why /opt/nvidia + /etc/ld.so.conf.d and not LD_LIBRARY_PATH: the loader finds them everywhere (SSH shell, tmux, Jupyter kernels, systemd services) without editing any environment. To undo: delete the two .conf files and the two folders, then sudo ldconfig.

4Install the GPU wheels for JetPack 6 / CUDA 12.6

Jetson terminal
cd ~/sutraai && source .venv/bin/activate
pip install --no-cache-dir torch==2.8.0 torchvision==0.23.0 torchaudio==2.8.0 --index-url https://pypi.jetson-ai-lab.io/jp6/cu126/+simple/
pip install --no-cache-dir onnxruntime-gpu==1.23.0 --index-url https://pypi.jetson-ai-lab.io/jp6/cu126/+simple/

# check that every library torch links against is found (runs without importing torch)
TORCH_LIB=$(python -c "import importlib.util, pathlib; print(pathlib.Path(importlib.util.find_spec('torch').origin).parent / 'lib')")
for f in "$TORCH_LIB"/*.so*; do ldd "$f"; done | grep "not found" | sort -u          # expect: no output

python -c "import torch, torchvision, numpy; print(torch.__version__, torchvision.__version__, numpy.__version__, torch.version.cuda, torch.cuda.is_available())"
# expect: 2.8.0 0.23.0 1.26.4 12.6 True

If the ldd line prints anything, the library it names is still missing: libcudss.so.0 or libcusparseLt.so.0 → redo substep 3 and check that sha256sum said OK; libopenblas.so.0 → sudo apt-get install -y libopenblas-dev; libnccl.so.2 → see the "If it breaks" table below.

The last value must be True. If it's False, a CPU wheel slipped in. Run pip uninstall -y torch torchvision torchaudio and repeat this substep.

If pip hangs or the index doesn't respond, retry in a few minutes, or try another network (some home routers block the port). Use the .io address, not the old .dev one. You can browse the available wheels at pypi.jetson-ai-lab.io/jp6/cu126.

5Install the rest of the toolkit (the lock stays in force)

Jetson terminal
pip install jupyterlab ipykernel ipywidgets matplotlib pandas scikit-learn tqdm pyyaml \
            timm onnx librosa soundfile openai sentence-transformers thop nbstripout ai-edge-litert
python -m ipykernel install --user --name sutraai --display-name "SutraAI (venv · torch 2.8 · CUDA 12.6)"
python -c "import torch; print(torch.__version__, torch.cuda.is_available())"     # still 2.8.0 True
pip check                                                                        # expect: No broken requirements found.

# isolation check: every package the notebooks import must come from the venv
python - <<'EOF'
import importlib
for name in ["numpy", "matplotlib", "mpl_toolkits.mplot3d", "PIL", "scipy", "sklearn", "pandas", "torch", "torchvision", "onnxruntime", "tensorrt"]:
    try:
        m = importlib.import_module(name)
    except Exception as e:
        print(f"{name:22} FAILED: {type(e).__name__}: {e}"); continue
    where = "venv" if "/.venv/" in (m.__file__ or "") else "OUTSIDE THE VENV  <- check .venv/pyvenv.cfg (substep 2)"
    print(f"{name:22} {str(getattr(m, '__version__', '')):12} {where}")
EOF
# expect: every line ends with "venv" (tensorrt too: it's linked in)

If pip stops with a conflict that mentions numpy or torch, the lock is doing its job: a package wanted a different version. Copy the error into BUILDLOG and install that package with an older release (for example pip install "somepkg<X") rather than removing the lock.

Recommended: a free Hugging Face token. Pretrained weights (Step 04's MobileNet via timm, Step 11's Nemotron and embedding models) download from the Hugging Face Hub. Without a token, downloads still work but print "You are sending unauthenticated requests to the HF Hub" and get lower rate limits. Create a Read token at huggingface.co/settings/tokens, then:

Jetson terminal
source ~/sutraai/.venv/bin/activate
hf auth login        # paste the token when asked; if asked "Add token as git credential?", answer n
hf auth whoami       # expect: your Hugging Face username

The token is saved in ~/.cache/huggingface/token, outside the project, so it never lands in git. Never paste it into a notebook. Restart the notebook kernel afterwards.

6Start JupyterLab inside tmux

Jetson terminal
tmux new -s lab                    # detach with Ctrl+B then D; come back with: tmux attach -t lab
cd ~/sutraai && source .venv/bin/activate
jupyter lab --no-browser --port 8888
Laptop terminal
ssh -N -L 8888:localhost:8888 <your-user>@<jetson-ip>      # or your Tailscale address when away from home
# then open http://localhost:8888 and paste the token printed in the Jetson terminal

In JupyterLab, create notebooks in notebooks/ and choose the SutraAI (venv · torch 2.8 · CUDA 12.6) kernel.

7NB00: environment check with your exact versions

Notebook cellnotebooks/00_env_check.ipynb
import platform, numpy, torch, torchvision, onnxruntime as ort, matplotlib
print("Python     ", platform.python_version())                       # 3.10.12
print("numpy      ", numpy.__version__)                               # 1.26.4
print("matplotlib ", matplotlib.__version__, matplotlib.__file__)     # 3.10.9 …/.venv/lib/python3.10/site-packages/…
print("torch      ", torch.__version__, "| CUDA available:", torch.cuda.is_available())   # 2.8.0 True
print("torchvision", torchvision.__version__)                         # 0.23.0
print("CUDA (torch build):", torch.version.cuda)                      # 12.6
print("cuDNN      ", torch.backends.cudnn.version())                  # 9.3.x reported as 903xx
print("GPU        ", torch.cuda.get_device_name(0))                   # Orin
free, total = torch.cuda.mem_get_info()
print(f"GPU-visible memory: {free/1e9:.1f} GB free of {total/1e9:.1f} GB")   # about 15 GB total on Orin NX 16GB
try:
    import tensorrt
    print("TensorRT   ", tensorrt.__version__)                         # 10.3.0.30
except ImportError:
    print("TensorRT not visible: run the TensorRT link lines in substep 2")
print("ONNX Runtime", ort.__version__, ort.get_available_providers())  # 1.23.0, includes CUDAExecutionProvider

8NB01: feel the GPU

Notebook cellnotebooks/01_gpu_hello.ipynb
import torch, time

def bench(device, n=2048, reps=10, dtype=torch.float32):
    a = torch.randn(n, n, device=device, dtype=dtype)
    b = torch.randn(n, n, device=device, dtype=dtype)
    for _ in range(2): a @ b                          # warm-up
    if device == "cuda": torch.cuda.synchronize()
    t = time.perf_counter()
    for _ in range(reps): a @ b
    if device == "cuda": torch.cuda.synchronize()
    return (time.perf_counter() - t) / reps * 1000    # ms per multiply

cpu, gpu, gpu16 = bench("cpu"), bench("cuda"), bench("cuda", dtype=torch.float16)
print(f"CPU (8 cores) {cpu:.1f} ms | GPU fp32 {gpu:.1f} ms | GPU fp16 {gpu16:.1f} ms")

A tensor is an array that can live on the GPU. .to("cuda") moves it there. fp16 uses half the memory and runs on the Tensor Cores. Run it once at 15 W and once at MAXN SUPER to see what the power mode does.

9Version control from day one

Jetson terminal
cd ~/sutraai && git init
nbstripout --install                       # strips notebook outputs on commit
printf ".venv/\n.venv-convert/\ndata/\nmodels/\nexports/\n*.engine\n" > .gitignore
pip freeze > requirements-lock.txt         # full snapshot of what actually got installed
git add . && git commit -m "Step 02: environment ready (JetPack 6.2.1, CUDA 12.6.68, torch 2.8.0)"

Test Test it

Add this as the last cell of NB00, then run Kernel → Restart Kernel and Run All Cells:

Notebook cellnotebooks/00_env_check.ipynb (last cell)
assert platform.python_version().startswith("3.10"), "use the venv made with Python 3.10"
assert torch.__version__.startswith("2.8.0"), f"expected torch 2.8.0, got {torch.__version__}"
assert torchvision.__version__.startswith("0.23"), f"expected torchvision 0.23.x, got {torchvision.__version__}"
assert torch.version.cuda.startswith("12.6"), f"torch built for CUDA {torch.version.cuda}, board has 12.6"
assert torch.cuda.is_available(), "PyTorch can't see the GPU"
assert numpy.__version__ == "1.26.4", numpy.__version__
assert "/.venv/" in matplotlib.__file__, "matplotlib comes from outside the venv: switch the venv to isolated (substep 2), then restart the kernel"
assert tensorrt.__version__.startswith("10.3"), tensorrt.__version__
assert "CUDAExecutionProvider" in ort.get_available_providers(), "ONNX Runtime has no CUDA provider"
print("✅ environment matches JetPack 6.2.1 / CUDA 12.6.68")
Pass: NB00 shows Python 3.10.12, numpy 1.26.4, torch 2.8.0, torchvision 0.23.0, CUDA 12.6, TensorRT 10.3.0.30, ONNX Runtime 1.23.0 with the CUDA provider, a GPU named Orin with about 15 GB visible, and the last line "✅ environment matches JetPack 6.2.1 / CUDA 12.6.68". In NB01, GPU fp32 is many times faster than the CPU, and fp16 is faster again.

Done Done when

  • nvcc --version reports release 12.6, V12.6.68
  • constraints-jp621.txt created and PIP_CONSTRAINT set in the venv
  • cuDSS and cuSPARSELt installed; ldconfig -p lists libcudss.so.0 and libcusparseLt.so.0
  • torch 2.8.0 / torchvision 0.23.0 / onnxruntime-gpu 1.23.0 installed from the jp6/cu126 index; the ldd … | grep "not found" check prints nothing
  • torch.cuda.is_available() is True inside the notebook kernel; pip check is clean
  • JupyterLab opens through the SSH tunnel and survives an SSH disconnect (tmux)
  • NB00 and NB01 pass Restart and Run All
  • requirements-lock.txt saved and first git commit done

If it breaks

SymptomFix
ImportError: libcudss.so.0: cannot open shared object filecuDSS isn't installed (JetPack doesn't include it). Do substep 3b, then sudo ldconfig.
ImportError: libcusparseLt.so.0: cannot open shared object filecuSPARSELt isn't installed. Do substep 3c, then sudo ldconfig.
ImportError: libopenblas.so.0sudo apt-get install -y libopenblas-dev
ImportError: libnccl.so.2 or undefined symbol: ncclCommWindowDeregisterYou got an early copy of the torch 2.8.0 wheel; NVIDIA has since replaced it on the index. Reinstall: pip install --force-reinstall --no-cache-dir --no-deps torch==2.8.0 --index-url https://pypi.jetson-ai-lab.io/jp6/cu126/+simple/
Any error whose traceback shows /usr/lib/python3/dist-packages: 'RcParams' object has no attribute '_get', Unable to import Axes3D, cannot import name 'docstring'The venv can still see Ubuntu's old Python packages. Run the one-time switch in substep 2 (sets include-system-site-packages = false), restart JupyterLab and the kernel.
The isolation check prints OUTSIDE THE VENVSame cause: check that .venv/pyvenv.cfg says include-system-site-packages = false.
sha256sum says FAILEDDownload was cut off. Delete the .tar.xz and run the wget line again.
pip hangs with no outputThe network is blocking the index (seen on some home routers). Try another network or a phone hotspot.
cuda.is_available() is FalseCPU wheel installed. Uninstall torch, torchvision and torchaudio; reinstall the pinned versions from the jp6/cu126 index with --no-cache-dir.
operator torchvision::nms does not existtorch and torchvision don't match. Install torch 2.8.0 and torchvision 0.23.0 together in one command from the same index.
pip says a package needs numpy 2Install an older release of that package that supports numpy 1.26, or keep it in the separate converter venv (Step 05).
TensorRT import fails in the venvThe link is missing. Run the TRT_DIR=… / for p in …tensorrt* lines from substep 2.
Index returns 404 or times outRetry later; use pypi.jetson-ai-lab.io (not .dev).
Browser can't reach 8888Tunnel not running, or JupyterLab is on a different port; check the Jetson terminal output.

Learn more