Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions phases/00-setup-and-tooling/01-dev-environment/code/verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,22 @@
("Rust (cargo)", lambda: shutil.which("cargo") is not None, None),
]

def _gpu_backend():
"""Return a human label for the active GPU backend, or None if CPU-only."""
torch = __import__("torch")
if torch.cuda.is_available():
return f"CUDA — {torch.cuda.get_device_name(0)}"
if torch.backends.mps.is_available():
return "MPS — Apple Silicon"
return None


GPU_CHECKS = [
("PyTorch", lambda: __import__("torch"), None),
(
"CUDA",
lambda: __import__("torch").cuda.is_available(),
lambda: __import__("torch").cuda.get_device_name(0) if __import__("torch").cuda.is_available() else "Not available",
"GPU acceleration (CUDA or MPS)",
lambda: _gpu_backend() is not None,
lambda: _gpu_backend() or "Not available",
),
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
import sys


def _sync(backend):
"""Block until queued GPU work finishes, for accurate timing."""
if backend == "cuda":
import torch
torch.cuda.synchronize()
elif backend == "mps":
import torch
torch.mps.synchronize()


def check_gpu():
try:
import torch
Expand All @@ -12,18 +22,26 @@ def check_gpu():
print("=== GPU Check ===\n")
print(f"PyTorch version: {torch.__version__}")
print(f"CUDA available: {torch.cuda.is_available()}")
print(f"MPS (Apple Silicon) available: {torch.backends.mps.is_available()}")

if not torch.cuda.is_available():
if torch.cuda.is_available():
backend = "cuda"
elif torch.backends.mps.is_available():
backend = "mps"
else:
print("\nNo GPU detected. That's fine for most lessons.")
print("For GPU-heavy lessons, use Google Colab (free).")
return

print(f"CUDA version: {torch.version.cuda}")
print(f"GPU: {torch.cuda.get_device_name(0)}")

props = torch.cuda.get_device_properties(0)
print(f"Memory: {props.total_memory / 1e9:.1f} GB")
print(f"Compute capability: {props.major}.{props.minor}")
props = None
if backend == "cuda":
print(f"CUDA version: {torch.version.cuda}")
print(f"GPU: {torch.cuda.get_device_name(0)}")
props = torch.cuda.get_device_properties(0)
print(f"Memory: {props.total_memory / 1e9:.1f} GB")
print(f"Compute capability: {props.major}.{props.minor}")
else:
print("GPU: Apple Silicon (MPS) — unified memory shared with system RAM")

print("\n=== CPU vs GPU Benchmark ===\n")
size = 4000
Expand All @@ -36,21 +54,25 @@ def check_gpu():
cpu_time = time.time() - start
print(f"CPU matrix multiply ({size}x{size}): {cpu_time:.3f}s")

a_gpu = a.to("cuda")
b_gpu = b.to("cuda")
torch.cuda.synchronize()
a_gpu = a.to(backend)
b_gpu = b.to(backend)
_sync(backend)

start = time.time()
_ = a_gpu @ b_gpu
torch.cuda.synchronize()
_sync(backend)
gpu_time = time.time() - start
print(f"GPU matrix multiply ({size}x{size}): {gpu_time:.3f}s")
print(f"Speedup: {cpu_time / gpu_time:.0f}x")

vram_gb = props.total_memory / 1e9
params_fp16 = vram_gb * 1e9 / 2
params_billions = params_fp16 / 1e9
print(f"\nEstimated max model size (fp16): ~{params_billions:.0f}B parameters")
if props is not None:
vram_gb = props.total_memory / 1e9
params_fp16 = vram_gb * 1e9 / 2
params_billions = params_fp16 / 1e9
print(f"\nEstimated max model size (fp16): ~{params_billions:.0f}B parameters")
else:
print("\nApple Silicon shares memory with the system; max model size depends")
print("on total RAM, not a dedicated VRAM pool.")


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,15 @@ def demo_device_checking():

check_devices(model, t1, t2)

accel = None
if torch.cuda.is_available():
model_gpu = model.cuda()
accel = "cuda"
elif torch.backends.mps.is_available():
accel = "mps"
if accel:
model_gpu = model.to(accel)
t_cpu = torch.randn(4, 10)
t_gpu = torch.randn(4, 10).cuda()
t_gpu = torch.randn(4, 10).to(accel)
print(" With mixed devices:")
check_devices(model_gpu, t_cpu, t_gpu)

Expand All @@ -224,26 +229,40 @@ def demo_gradient_health():
def demo_gpu_memory():
print("\n--- 8. GPU Memory Summary ---")

if not torch.cuda.is_available():
print(" No GPU available. Skipping GPU memory demo.")
print(" On a GPU machine, torch.cuda.memory_summary() shows:")
print(" - Allocated memory per block size")
print(" - Cached (reserved) memory")
print(" - Peak memory usage")
if torch.cuda.is_available():
print(f" GPU: {torch.cuda.get_device_name(0)}")
print(f" Allocated: {torch.cuda.memory_allocated() / 1e6:.1f} MB")
print(f" Cached: {torch.cuda.memory_reserved() / 1e6:.1f} MB")

large_tensor = torch.randn(10000, 10000, device="cuda")
print(f" After 10k x 10k tensor:")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove no-op f-strings to satisfy Ruff F541.

These four strings have no interpolation and trigger lint errors.

Suggested patch
-        print(f"  After 10k x 10k tensor:")
+        print("  After 10k x 10k tensor:")
...
-        print(f"  After cleanup:")
+        print("  After cleanup:")
...
-        print(f"  After 10k x 10k tensor:")
+        print("  After 10k x 10k tensor:")
...
-        print(f"  After cleanup:")
+        print("  After cleanup:")

Also applies to: 243-243, 252-252, 257-257

🧰 Tools
🪛 Ruff (0.15.15)

[error] 238-238: f-string without any placeholders

Remove extraneous f prefix

(F541)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phases/00-setup-and-tooling/12-debugging-and-profiling/code/debug_tools.py`
at line 238, Remove the f-string prefix from four print statements in
debug_tools.py that contain no variable interpolation, as these trigger Ruff
F541 lint errors. At line 238, convert the f-string in the print statement (" 
After 10k x 10k tensor:") to a regular string by removing the f prefix. Apply
the same fix at lines 243, 252, and 257 where similar f-strings with no
interpolation exist. Each of these four locations should have the f prefix
removed from the string literal to satisfy the linter.

Source: Linters/SAST tools

print(f" Allocated: {torch.cuda.memory_allocated() / 1e6:.1f} MB")

del large_tensor
torch.cuda.empty_cache()
print(f" After cleanup:")
print(f" Allocated: {torch.cuda.memory_allocated() / 1e6:.1f} MB")
return

print(f" GPU: {torch.cuda.get_device_name(0)}")
print(f" Allocated: {torch.cuda.memory_allocated() / 1e6:.1f} MB")
print(f" Cached: {torch.cuda.memory_reserved() / 1e6:.1f} MB")
if torch.backends.mps.is_available():
print(" GPU: Apple Silicon (MPS) — unified memory")
print(f" Allocated: {torch.mps.current_allocated_memory() / 1e6:.1f} MB")

large_tensor = torch.randn(10000, 10000, device="mps")
print(f" After 10k x 10k tensor:")
print(f" Allocated: {torch.mps.current_allocated_memory() / 1e6:.1f} MB")

large_tensor = torch.randn(10000, 10000, device="cuda")
print(f" After 10k x 10k tensor:")
print(f" Allocated: {torch.cuda.memory_allocated() / 1e6:.1f} MB")
del large_tensor
torch.mps.empty_cache()
print(f" After cleanup:")
print(f" Allocated: {torch.mps.current_allocated_memory() / 1e6:.1f} MB")
return

del large_tensor
torch.cuda.empty_cache()
print(f" After cleanup:")
print(f" Allocated: {torch.cuda.memory_allocated() / 1e6:.1f} MB")
print(" No GPU available. Skipping GPU memory demo.")
print(" On a GPU machine, torch.cuda.memory_summary() shows:")
print(" - Allocated memory per block size")
print(" - Cached (reserved) memory")
print(" - Peak memory usage")


def demo_logging():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@
import time


def get_device():
"""Pick the best available accelerator: CUDA, then Apple Silicon (MPS), then CPU."""
if torch.cuda.is_available():
return torch.device("cuda")
if torch.backends.mps.is_available():
return torch.device("mps")
return torch.device("cpu")


MNIST_BASE_URL = "https://storage.googleapis.com/cvdf-datasets/mnist/"
MNIST_FILES = [
"train-images-idx3-ubyte.gz",
Expand Down Expand Up @@ -297,11 +306,11 @@ def demo_autograd():
print(" Introduction to PyTorch -- Phase 3, Lesson 11")
print("=" * 60)

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device = get_device()
print(f"\n PyTorch version: {torch.__version__}")
print(f" Device: {device}")
print(f" CUDA available: {torch.cuda.is_available()}")
if torch.cuda.is_available():
print(f" Accelerator: {device.type}")
if device.type == "cuda":
print(f" GPU: {torch.cuda.get_device_name(0)}")

demo_tensor_basics()
Expand Down
11 changes: 10 additions & 1 deletion phases/04-computer-vision/04-image-classification/code/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@
from torch.optim.lr_scheduler import CosineAnnealingLR


def get_device():
"""Pick the best available accelerator: CUDA, then Apple Silicon (MPS), then CPU."""
if torch.cuda.is_available():
return torch.device("cuda")
if torch.backends.mps.is_available():
return torch.device("mps")
return torch.device("cpu")


def synthetic_cifar(num_per_class=300, num_classes=10, seed=0):
rng = np.random.default_rng(seed)
X = []
Expand Down Expand Up @@ -192,7 +201,7 @@ def main():
train_loader = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=0)
val_loader = DataLoader(val_ds, batch_size=256, shuffle=False, num_workers=0)

device = "cuda" if torch.cuda.is_available() else "cpu"
device = get_device()
model = MiniClassifier(num_classes=10).to(device)
optimizer = SGD(model.parameters(), lr=0.05, momentum=0.9, weight_decay=5e-4, nesterov=True)
scheduler = CosineAnnealingLR(optimizer, T_max=5)
Expand Down
11 changes: 10 additions & 1 deletion phases/04-computer-vision/05-transfer-learning/code/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@
from torchvision.models import resnet18, ResNet18_Weights


def get_device():
"""Pick the best available accelerator: CUDA, then Apple Silicon (MPS), then CPU."""
if torch.cuda.is_available():
return torch.device("cuda")
if torch.backends.mps.is_available():
return torch.device("mps")
return torch.device("cpu")


def synthetic_dataset(num_per_class=100, num_classes=10, size=224, seed=0):
rng = np.random.default_rng(seed)
X = np.empty((num_per_class * num_classes, size, size, 3), dtype=np.float32)
Expand Down Expand Up @@ -141,7 +150,7 @@ def main():
train_loader = DataLoader(train_ds, batch_size=16, shuffle=True, num_workers=0)
val_loader = DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=0)

device = "cuda" if torch.cuda.is_available() else "cpu"
device = get_device()
print(f"device: {device}")

print("\n[feature extraction] freeze backbone, train head only")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@
from torch.optim import Adam


def get_device():
"""Pick the best available accelerator: CUDA, then Apple Silicon (MPS), then CPU."""
if torch.cuda.is_available():
return torch.device("cuda")
if torch.backends.mps.is_available():
return torch.device("mps")
return torch.device("cpu")


class DoubleConv(nn.Module):
def __init__(self, in_c, out_c):
super().__init__()
Expand Down Expand Up @@ -149,7 +158,7 @@ def main():
train_loader = DataLoader(train_ds, batch_size=8, shuffle=True)
val_loader = DataLoader(val_ds, batch_size=8, shuffle=False)

device = "cuda" if torch.cuda.is_available() else "cpu"
device = get_device()
num_classes = 3
model = UNet(in_channels=3, num_classes=num_classes, base=16).to(device)
optimizer = Adam(model.parameters(), lr=1e-3)
Expand Down
11 changes: 10 additions & 1 deletion phases/04-computer-vision/09-image-generation-gans/code/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@
from torch.nn.utils import spectral_norm


def get_device():
"""Pick the best available accelerator: CUDA, then Apple Silicon (MPS), then CPU."""
if torch.cuda.is_available():
return torch.device("cuda")
if torch.backends.mps.is_available():
return torch.device("mps")
return torch.device("cpu")


class Generator(nn.Module):
def __init__(self, z_dim=64, img_channels=3, feat=32):
super().__init__()
Expand Down Expand Up @@ -96,7 +105,7 @@ def sample(G, n=8, z_dim=64, device="cpu"):

def main():
torch.manual_seed(0)
device = "cuda" if torch.cuda.is_available() else "cpu"
device = get_device()
z_dim = 64

data = synthetic_circles(num=400)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@
from torch.utils.data import DataLoader, TensorDataset


def get_device():
"""Pick the best available accelerator: CUDA, then Apple Silicon (MPS), then CPU."""
if torch.cuda.is_available():
return torch.device("cuda")
if torch.backends.mps.is_available():
return torch.device("mps")
return torch.device("cpu")


def linear_beta_schedule(T=1000, beta_start=1e-4, beta_end=2e-2):
return torch.linspace(beta_start, beta_end, T)

Expand Down Expand Up @@ -137,7 +146,7 @@ def synthetic_circles(num=200, size=16, seed=0):

def main():
torch.manual_seed(0)
device = "cuda" if torch.cuda.is_available() else "cpu"
device = get_device()
T = 200

schedule = precompute_schedule(linear_beta_schedule(T=T, beta_start=1e-4, beta_end=0.04))
Expand Down
12 changes: 8 additions & 4 deletions phases/04-computer-vision/11-stable-diffusion/code/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,20 @@ def text_to_image_stub(prompt, seed=42):
if not has_diffusers():
print(" diffusers not installed. `pip install diffusers transformers accelerate` to run.")
return None
if not torch.cuda.is_available():
print(" CUDA not available; running SD on CPU is extremely slow. Skipping real call.")
if torch.cuda.is_available():
device = "cuda"
elif torch.backends.mps.is_available():
device = "mps"
else:
print(" No GPU (CUDA/MPS); running SD on CPU is extremely slow. Skipping real call.")
return None
from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
pipe = StableDiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
torch_dtype=torch.float16,
).to("cuda")
).to(device)
pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
gen = torch.Generator("cuda").manual_seed(seed)
gen = torch.Generator(device).manual_seed(seed)
out = pipe(prompt, guidance_scale=7.5, num_inference_steps=25, generator=gen).images[0]
path = os.path.expanduser("~/sd_demo.png")
out.save(path)
Expand Down
Loading