Most GPU projects follow the same path: prototype quickly, validate correctness, then move to a more persistent environment when compute, VRAM, or iteration speed becomes a bottleneck.
Google Colab is convenient for notebook-based experiments and quick CUDA builds. Brev — the cloud option used in my own workflow, not the only option — provides access to configurable cloud GPU instances that you can manage from a terminal or IDE.
This guide combines two practical cheatsheets: one for identifying GPUs, building CUDA/C++ projects, and managing Colab's ephemeral runtimes; the other for searching, provisioning, and operating Brev GPU instances. The commands are designed to be copied, adapted, and tested without assuming a particular project repository.
Mental model in one paragraph
Colab is an ephemeral notebook VM: free tier is usually a T4, Pro can give you L4/A100, there is no Docker daemon, and the filesystem resets on recycle. Brev is the opposite: you pick an exact GPU type, VRAM, provider and boot time, create a persistent box, SSH into it, and pay per hour. Prototype on Colab, scale on Brev.
Tested with
Colab GPU runtime (verify with !nvidia-smi and !nvcc --version — assignment varies), CMake 3.24+ for CMAKE_CUDA_ARCHITECTURES=native, Brev CLI v0.6.335 verified October 11, 2026. Brev flags and inventory change over time — run brev search --help and brev create --dry-run to confirm behavior on your version.
Table of Contents
- 1. Choosing: Colab vs Brev
- 2. Colab: Enable and Identify the GPU
- 3. CUDA Architectures (Applies to Both)
- 4. Colab Shell Basics and Python GPU Packages
- 5. Colab: Build Any CUDA C++ Project
- 6. Colab: Monitor, Persist, and Survive Resets
- 7. Brev: Search for GPUs
- 8. Brev: Filter by Model, VRAM, and Provider
- 9. Brev: Preset Recipes and JSON + jq
- 10. Brev: Create Instances Safely
- 11. Brev: Daily Use — Shell, Copy, Ports, List
- 12. Troubleshooting Both
- 13. Cross-Environment Reproducibility Checklist
- Conclusion
1. Choosing: Colab vs Brev
| Colab | Brev | |
|---|---|---|
| Best for | Free prototyping, notebooks, quick CUDA builds | Training, large VRAM, persistent boxes, multi-GPU |
| GPU choice | Assigned (T4 common, A100/L4 not guaranteed) | You choose, e.g.: T4, L4, A10G, A100, H100, L40S, RTX — examples only, inventory varies |
| Lifetime | Ephemeral; runtime limits and idle timeouts vary | Persistent until you stop/delete; stopping may end compute charges while attached storage and other resources can continue to incur costs — check pricing and storage-retention policy |
| Access | Browser notebook, !cmd shell |
SSH, VS Code / Cursor, port-forward, managed ports |
| Cost control | Quotas and disconnects | --dry-run for preview plus check of actual hourly price and disk config, per-hour pricing, stop when idle |
My default flow: validate the idea and the CUDA build on Colab, then reproduce it on a Brev box with the VRAM the model actually needs.
2. Colab: Enable and Identify the GPU
In the Colab UI:
Runtime → Change runtime type → Hardware accelerator: GPU → Save
Then confirm what you actually got — never assume T4. GPU assignment and installed CUDA tooling are separate checks: a GPU runtime does not guarantee nvcc is installed.
!nvidia-smi
!nvidia-smi --query-gpu=name,driver_version,memory.total,compute_cap --format=csv
!nvcc --version || echo "nvcc not installed on this runtime"
!python3 --version
import torch
print(torch.__version__)
print(torch.cuda.is_available())
print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else "no cuda")
TensorFlow / JAX alternates:
import tensorflow as tf; print(tf.config.list_physical_devices('GPU'))
import jax; print(jax.devices())
3. CUDA Architectures (Applies to Both)
A GPU architecture mismatch can cause no kernel image available at runtime; rebuild with the correct flag. Compute capability identifies hardware features, while CUDA toolkit and driver compatibility are separate constraints — matching the arch flag alone does not guarantee the toolkit/driver combination is supported. An illegal instruction error can have other causes, so check the surrounding error output before changing the architecture. Reference table:
| GPU | Compute Cap. | CMake flag |
|---|---|---|
| T4 (Colab free tier) | 7.5 | -DCMAKE_CUDA_ARCHITECTURES=75 |
| V100 | 7.0 | -DCMAKE_CUDA_ARCHITECTURES=70 |
| A100 | 8.0 | -DCMAKE_CUDA_ARCHITECTURES=80 |
| RTX 30xx / A10G / A40 | 8.6 | -DCMAKE_CUDA_ARCHITECTURES=86 |
| L4 / L40S / RTX 4090 | 8.9 | -DCMAKE_CUDA_ARCHITECTURES=89 |
| H100 | 9.0 | -DCMAKE_CUDA_ARCHITECTURES=90 |
| B100 / B200 (Blackwell DC) | 10.0 | -DCMAKE_CUDA_ARCHITECTURES=100 |
| RTX 5090 (Blackwell GeForce) | 12.0 | -DCMAKE_CUDA_ARCHITECTURES=120 |
Auto-detect instead of guessing (works on Colab and on any Brev box):
# Fast bash one-liner (uses primary GPU):
ARCH=$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader | head -n1 | tr -d '.')
echo "-DCMAKE_CUDA_ARCHITECTURES=${ARCH}"
Or via Python (multi-GPU safe, deduplicates capabilities):
%%bash
python3 - <<'PY'
import subprocess
result = subprocess.run(
['nvidia-smi', '--query-gpu=compute_cap', '--format=csv,noheader'],
check=True, capture_output=True, text=True,
)
archs = sorted(set(result.stdout.strip().replace('.', '').splitlines()))
print('-DCMAKE_CUDA_ARCHITECTURES=' + ';'.join(archs))
PY
On CMake ≥ 3.24 you can also delegate detection entirely — but native detects the GPU visible at configure time, so it is for local development, not portable binaries:
!cmake -S . -B build -DCMAKE_CUDA_ARCHITECTURES=native
- Local development:
native— fastest iteration on the current GPU. - Known deployment target: explicit arch, e.g.
-DCMAKE_CUDA_ARCHITECTURES=80for A100. - Multiple deployment targets: deliberate list, potentially including PTX for forward compatibility, e.g.
-DCMAKE_CUDA_ARCHITECTURES="75;80;90a".
For the full story on native vs all-major vs explicit lists, see my CMake CUDA Architecture Configuration guide.
Brev capability shortcut
Brev search exposes the same architecture as a filter: --min-capability 7.5 (Turing+), 8.0 (Ampere+), 8.9 (Ada+), 9.0 (Hopper and newer). Use it when you care about features (e.g. FP8, Transformer Engine) rather than a specific card name.
4. Colab Shell Basics and Python GPU Packages
Two rules: !cmd runs shell, %cd dir persists directory (!cd does not). Colab idle timeouts and maximum runtime vary with availability and usage.
!pwd && ls
%cd /content
!apt-get update -qq && apt-get install -y -qq cmake ninja-build
!cmake --version
First inspect what is already installed — Colab runtimes often ship with PyTorch:
!nvidia-smi --query-gpu=name,driver_version,memory.total --format=csv
!python -c "import torch; print(torch.__version__, torch.version.cuda, torch.cuda.is_available())"
Optional pinned environment (example only — match the wheel channel to your runtime and project; this can replace pre-installed packages):
!pip install -q "torch==2.3.1" "torchvision==0.18.1" --index-url https://download.pytorch.org/whl/cu121
!pip freeze | grep -Ei "torch|transformers|accelerate|datasets|cuda"
!python -c "import torch; print(torch.__version__, torch.version.cuda, torch.cuda.get_device_name(0))"
For a reproducible project, record pip freeze output plus nvidia-smi driver, nvcc --version, and cmake --version alongside the code.
Free VRAM before big jobs (empty_cache() releases unused cached allocations only — it does not free memory held by live tensors):
import torch
torch.cuda.empty_cache()
!nvidia-smi --query-gpu=memory.used,memory.total --format=csv
5. Colab: Build Any CUDA C++ Project
Generic recipe — replace the arch flag with the value from Section 3:
!git clone <repo-url>
%cd /content/<repo>
!cmake -S . -B build -DCMAKE_CUDA_ARCHITECTURES=75 -DCMAKE_BUILD_TYPE=Release
!cmake --build build -j$(nproc)
!./build/<binary>
Iterate on a single target instead of rebuilding everything:
!cmake --build build --target <target> -j$(nproc)
If the build gets killed mid-way, -j$(nproc) is usually too parallel for the available RAM — retry with -j2.
Validate against hidden env dependencies with a clean build or fresh clone before calling it done:
!rm -rf build && cmake -S . -B build -DCMAKE_CUDA_ARCHITECTURES=75 -DCMAKE_BUILD_TYPE=Release && cmake --build build -j2
# or: fresh clone in /tmp and build there to catch files only present in /content
6. Colab: Monitor, Persist, and Survive Resets
Live monitoring:
!nvidia-smi -l 1 # live (Ctrl-C to stop)
!nvidia-smi dmon -s u # util stream
!which ncu nsys nvprof || echo "no profilers on this runtime"
In-code timing (CUDA events / chrono around kernels) beats missing ncu on most Colab runtimes.
The filesystem resets. Build in /content (fast), store on Drive (durable):
from google.colab import drive
drive.mount('/content/drive')
!cp -r /content/drive/MyDrive/<src> /content/work
!df -h /content
!free -h
Drive is durable but slow — copy datasets and checkpoints to /content once per session and work there; avoid repeatedly copying multi-GB files in a loop if I/O becomes the bottleneck.
Quotas and resets:
!df -h
!free -h
# Runtime → Disconnect and delete runtime → factory reset when stuck
Expect on Colab
Idle disconnects, runtime limits, and GPU assignment vary. Free-tier notebooks can run for up to 12 hours depending on availability and usage; A100/L4 access is not guaranteed. If files vanished, the runtime recycled — re-clone and restore from Drive.
7. Brev: Search for GPUs
Commands below tested with Brev CLI v0.6.335 (October 11, 2026). Flags evolve — run brev search --help if a filter or sort key is rejected on your version.
GPU instances only. For CPU-only, replace brev search with brev search cpu.
brev search # all GPUs, cheapest first
brev search gpu # same (explicit)
brev search gpu --wide # +RAM, ARCH columns
brev search --json | head -n 40
Output columns include TYPE | GPU | COUNT | VRAM | TOTAL | $/HR | BOOT | FEATURES (use brev search gpu --wide for extra RAM, ARCH columns; exact columns vary by version — check brev search --help). Features: S = stoppable, R = rebootable, P = flex-ports.
Sort
brev search --sort price # default, cheapest first
brev search --sort vram
brev search --sort total-vram
brev search --sort boot-time # fastest boot first
brev search --sort gpu-count
brev search --sort vcpu
brev search --sort type
brev search --sort disk
brev search --sort price --desc # most expensive first
8. Brev: Filter by Model, VRAM, and Provider
Partial, case-insensitive match on GPU name. Models below are examples of what may be available — not guaranteed inventory:
brev search --gpu-name T4 # budget 16GB
brev search --gpu-name L4 # efficient 24GB
brev search --gpu-name A10G # balanced 24GB (e.g. g5.xlarge)
brev search --gpu-name A100 # 40/80GB training
brev search --gpu-name H100 # Hopper 80GB
brev search --gpu-name L40S # 48GB Ada
brev search --gpu-name RTX # RTX4090 / RTX6000Ada / RTX5090
brev search --gpu-name A4000 # cheap 16GB (e.g. hyperstack_A4000 — price varies, checked Oct 2026)
brev search --gpu-name A6000 # 48GB Ampere
Filter by VRAM, provider, disk, boot time, and features. --min-vram is the minimum per GPU; --min-total-vram is the sum across devices (COUNT x VRAM) — not a single unified pool. An 80 GB aggregate does not allow a single 80 GB allocation on one GPU:
brev search --min-vram 16 --sort price # per-GPU minimum
brev search --min-vram 24 --sort price
brev search --min-vram 40 --sort price
brev search --min-vram 80 --sort price # A100-80GB / H100
brev search --min-total-vram 48 --sort price # COUNT x VRAM (multi-GPU), e.g. 2x24GB
brev search --min-total-vram 160 --sort price # e.g. 2x80GB — per-GPU limits still apply
brev search --provider aws --sort price
brev search --provider gcp --sort price
brev search --gpu-name A100 --provider aws --sort price
brev search --min-disk 500 # GB
brev search --max-boot-time 3 --sort price # fast boot
brev search --max-boot-time 5 --sort price
brev search --stoppable --sort price
brev search --rebootable --sort price
brev search --flex-ports --sort price
Compute capability reference (same arch table as Section 3, Brev view):
| Cap | Arch | GPUs |
|---|---|---|
| 6.1 | Pascal | P4 (avoid for new work) |
| 7.5 | Turing | T4 |
| 8.0 | Ampere | A100, A10 |
| 8.6 | Ampere | A10G, A40, A4000, A6000, RTX30xx |
| 8.9 | Ada | L4, L40/L40S, RTX4090, RTX6000Ada |
| 9.0 | Hopper | H100 |
| 10.0 | Blackwell DC | B100/B200 |
| 12.0 | Blackwell | RTX 5090 |
brev search --min-capability 7.5 --sort price # Turing+
brev search --min-capability 8.0 --sort price # Ampere+
brev search --min-capability 8.9 --sort price # Ada+
brev search --min-capability 9.0 --sort price # Hopper and newer
9. Brev: Preset Recipes and JSON + jq
Recipes I reuse:
# dev: fast + cheap
brev search --max-boot-time 3 --sort price | head -10
# ML training: high VRAM
brev search --min-vram 40 --sort price | head -10
# large model: total VRAM
brev search --min-total-vram 80 --sort price | head -10
# production: specific GPU + fast boot
brev search --gpu-name A100 --max-boot-time 5 --sort price
# budget: cheapest usable
brev search --min-vram 16 --sort price | head -5
# Hopper and newer
brev search --min-capability 9.0 --sort price
# stoppable + VRAM (cost-saving training)
brev search --stoppable --min-total-vram 40 --sort price
Scriptable search with JSON (field names vary by CLI version — inspect brev search --json | jq '.[0]' first):
brev search --json | jq '.[0]'
brev search --json | jq -r '.[0] | keys'
# example only — verify keys on v0.6.335 before scripting:
brev search --gpu-name A100 --json | jq -r '.[] | "\(.type) \(.price_per_hour)/hr"'
10. Brev: Create Instances Safely
brev create my-box # smart defaults (~20GB VRAM, 500GB disk, cap 8.0+, <7m boot)
brev create my-box --type g5.xlarge # exact type
brev create my-box --type g5.xlarge,g5.2xlarge # fallback chain
brev create my-box --gpu-name A100 --min-vram 40 # filters directly on create
brev create my-cluster --count 3 --type g5.xlarge # multi (confirm first: cost x3)
brev create my-box --startup-script @setup.sh # local file, @ prefix
brev create my-box --startup-script 'pip install torch'
brev create my-box --dry-run # preview match, no cost — always run first, then still check hourly price + disk
brev search --gpu-name A100 | brev create my-box # pipe search -> create
brev search cpu --min-ram 64 | brev create my-cpu-box
Safety before create
Confirm first: delete, stop, --count >1, H100 / multi-GPU create (expensive). Always: --dry-run before create, then verify the actual hourly price and disk configuration in the search output — dry-run does not replace that check. Preview brev ls | awk ... before piping to stop/delete, show cost/type before create. Stopped instances may still accrue storage charges.
11. Brev: Daily Use — Shell, Copy, Ports, List
Shell and exec
brev shell my-box # interactive SSH
brev exec my-box "nvidia-smi" # one-shot command
brev exec my-box @setup.sh # run local script remotely
brev exec box1 box2 box3 "nvidia-smi" # multi-instance
Open in editor
brev open my-box # default editor
brev open my-box code # VS Code
brev open my-box cursor
brev open my-box windsurf
brev open my-box tmux
brev open --set-default cursor
Copy files
brev copy ./train.py my-box:/home/ubuntu/
brev copy my-box:/home/ubuntu/ckpt/ ./ckpt/
Port-forward (simple)
brev port-forward my-box -p 8888:8888 # jupyter (-p / --port, same flag on v0.6.335)
brev port-forward my-box -p 8000:8000
Managed ports (needs P / flex-ports)
brev ports ls my-box
brev ports ls my-box --json
brev ports get nport-abc123
brev ports open my-box 8080
brev ports open my-box 8000-8031
brev ports open my-box 3000 --protocol http --public
brev ports open my-box 8888 --protocol http --authorize me@example.com
brev ports open my-box 53 --protocol udp --allow 203.0.113.10/32
brev ports update nport-abc123 --destination-port 8081
brev ports update nport-abc123 --public
brev ports remove my-box 8080
brev ports rm nport-abc123
Public ports are public
Managed ports with --public can expose services to the internet. Avoid publishing unauthenticated Jupyter servers or dev endpoints — prefer --authorize me@example.com or IP allow-lists, and close ports when done.
List / manage
brev ls = cloud instances only. brev ls nodes = Brev Connect machines only (never in brev ls).
brev ls
brev ls instances
brev ls nodes
brev ls orgs
brev ls --json | jq -r '.workspaces[].name'
brev ls nodes --json | jq -r '.[].name'
brev ls nodes --json | jq -r '.[] | select(.status=="Connected") | .name'
brev stop my-box
brev start my-box
brev delete my-box
brev reset my-box # recover from errors
brev stop --all # all running (confirm first)
Bulk (preview before destructive):
brev ls | awk '/RUNNING/ {print $1}' # preview
brev ls | awk '/RUNNING/ {print $1}' | brev stop
brev ls | awk '/STOPPED/ {print $1}' | brev delete
brev ls | awk '/STOPPED/ {print $1}' | brev start
brev ls | grep "test-" | awk '{print $1}' | brev stop
brev ls | awk '/RUNNING/ {print $1}' | brev exec "nvidia-smi"
brev search --gpu-name A100 | brev create my-box | brev open cursor
brev create my-box | brev exec @setup.sh
Orgs:
brev org ls
brev org set my-org # alias: brev set my-org
brev invite
Once the box is up, the Colab checks from Section 2 apply verbatim — nvidia-smi, compute-cap query, and the CMake arch flag for whatever GPU Brev gave you.
12. Troubleshooting Both
Colab
| Symptom | Fix |
|---|---|
CUDA unknown / no GPU | Runtime → GPU type set? !nvidia-smi must list a GPU first |
no kernel image available | CUDA-arch mismatch — rebuild with Section 3 flag for this GPU |
CUDA error / toolkit-driver mismatch | nvcc --version vs driver in nvidia-smi — align toolkit/channel, reinstall wheel if needed |
illegal memory access / illegal instruction | Separate bug class — check indices/bounds, UVM, arch flag; do not assume arch fix alone |
CUDA out of memory | Smaller batch, release tensors, empty_cache() (cached-only), restart runtime |
nvcc missing | Normal on CPU runtime — switch to GPU runtime |
cmake old/missing | apt-get install -y cmake, need 3.24+ for native (3.20+ minimum for most CUDA projects otherwise) |
| Build killed mid-way | -j$(nproc) too parallel for RAM — try -j2; keep tab active |
| Profilers absent | which ncu check; fall back to in-app timing |
| Files vanished | Runtime recycled — re-clone, use Drive for durable storage |
Brev
brev ls # does it exist? RUNNING?
brev ls nodes # Connect machines live here, not in brev ls
brev org ls # wrong org?
brev refresh # fix SSH config
brev healthcheck
which code cursor # editor in PATH?
brev feedback "what failed, what you expected" # no secrets
| Symptom | Fix |
|---|---|
| Instance not found | brev ls + brev ls nodes + brev org ls |
| Create fails | Try another type: brev search --sort price; check quota |
| SSH fails | brev refresh, instance must be RUNNING |
| Editor won't open | which code, brev open --set-default code |
13. Cross-Environment Reproducibility Checklist
The move from Colab to Brev only pays off if the same project builds and behaves consistently in both. Run this before scaling:
- Record GPU model, compute capability (
nvidia-smi --query-gpu=compute_cap), driver (nvidia-smi), and CUDA toolkit (nvcc --version). - Pin compiler, CMake (3.24+ for
native), and Python deps (pip freeze). - Keep build configuration and architecture selection in a script or CMake preset — not a pasted shell line.
- Run the same correctness tests on both environments.
- Benchmark with warm-up iterations and CUDA events or another appropriate GPU timing method.
- Save test outputs, benchmark results, and checkpoints outside ephemeral storage (Drive on Colab, persistent disk/snapshot on Brev).
Conclusion
Colab minimizes setup friction; Brev provides a more persistent environment with configurable GPU resources. The useful skill is knowing when to move between them — and how to keep builds, tests, and benchmarks reproducible across both.
Identify the GPU, select an appropriate CUDA architecture, pin the environment, validate correctness, and measure performance before scaling up. Treat cloud costs and persistent storage as part of the engineering workflow, not an afterthought.
For production-oriented CUDA development, pair this cheatsheet with the CMake CUDA architecture guide to understand native builds, explicit architecture targets, and portable deployment.