Mục lục
- Mục tiêu bài học
- Vì sao DL cần GPU
- CUDA là gì
- Hardware: consumer, datacenter, MPS, TPU
- Kiểm tra GPU available
- torch.device
- Pattern device-agnostic
- Move tensor sang GPU
- Tạo tensor trực tiếp trên GPU
- Tensor + model phải cùng device
- Move model
- Mixed precision (FP16 / BF16)
- Memory management
- CUDA Out of Memory
- NumPy không support GPU
- Speed test CPU vs GPU
- Multi-GPU preview
- Code Python
- Bài tập
- Tóm tắt
Mục tiêu bài học
Sau bài học, bạn sẽ:
- Hiểu vì sao DL (CNN, Transformer, LLM) cần GPU và GPU nhanh hơn CPU bao nhiêu trong phép tensor.
- Biết CUDA là gì, mối quan hệ PyTorch → CUDA → GPU NVIDIA.
- Phân biệt consumer GPU (RTX), datacenter (A100, H100), Apple Silicon (MPS), TPU.
- Viết code device-agnostic:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu"). - Chuyển tensor và model qua lại CPU ↔ GPU, hiểu khác biệt
tensor.to()(trả tensor mới) vàmodel.to()(in-place). - Dùng mixed precision (
autocast+GradScaler) để giảm RAM và tăng tốc. - Đọc
torch.cuda.memory_allocated, xử lý CUDA Out of Memory (OOM) bằng giảm batch / gradient accumulation / checkpointing / FP16. - Biết multi-GPU ở mức preview:
DataParallel,DistributedDataParallel, Lightning, Accelerate.
Vì sao DL cần GPU
Phép cốt lõi của neural network là matrix multiplication giữa hai tensor lớn. Một forward pass của ResNet-50 cho batch 32 ảnh đã có vài tỷ phép nhân-cộng (FLOPs); một bước train Transformer base với sequence dài 512 lên đến hàng chục tỷ FLOPs.
CPU thiết kế cho ít core mạnh, latency thấp, branch prediction tốt — phù hợp logic phức tạp tuần tự. GPU thiết kế ngược lại: hàng nghìn core đơn giản, throughput cao, song song hóa hàng nghìn phép tính cùng dạng. Matrix multiply là bài toán "embarrassingly parallel" — mỗi phần tử output là một dot product độc lập — đúng "sở trường" của GPU.
Hệ quả thực tế:
- Train ResNet-50 trên ImageNet với CPU mất nhiều ngày đến vài tuần; trên 1 GPU consumer (RTX 4090) mất nhiều giờ; trên 8x A100 mất khoảng 1 giờ.
- Train một Transformer base trên CPU gần như không khả thi nếu corpus đủ lớn.
- LLM (GPT, LLaMA…) cần multi-GPU, thậm chí multi-node — model parameter vượt RAM của một GPU đơn lẻ.
Khoảng cách tốc độ matmul giữa CPU và một GPU consumer rơi vào tầm 10–100x với tensor đủ lớn (xem section 16). Với tensor nhỏ, overhead launch kernel + copy memory có thể khiến GPU chậm hơn CPU — GPU chỉ "đáng" khi workload đủ lớn.
CUDA là gì
CUDA = Compute Unified Device Architecture — platform tính toán song song của NVIDIA, ra mắt 2007. CUDA gồm:
- Driver + runtime cho GPU NVIDIA.
- Toolkit (compiler
nvcc, profiler, libraries). - Thư viện chuyên dụng:
cuBLAS(BLAS),cuDNN(deep learning primitives),NCCL(multi-GPU collective).
Stack thực tế khi bạn gọi a @ b trên GPU trong PyTorch:
Python (a @ b)
→ PyTorch ATen dispatcher (chọn kernel theo device + dtype)
→ cuBLAS / cuDNN
→ CUDA runtime
→ GPU driver
→ SM (Streaming Multiprocessor) chạy kernel
Bạn không cần viết kernel CUDA tay; PyTorch wrap sẵn. Quan trọng: CUDA chỉ chạy trên GPU NVIDIA. AMD GPU dùng ROCm (có PyTorch ROCm build), Intel GPU dùng XPU backend, Apple Silicon dùng MPS — mỗi backend là một dispatch path riêng trong PyTorch, nhưng API tensor (cho đa số trường hợp) thì giống nhau.
Hardware: consumer, datacenter, MPS, TPU
Consumer GPU (NVIDIA, dùng cho học và project cá nhân)
- RTX 3060 / 4060: 8–12GB VRAM. Đủ chạy hầu hết model dạy học (ResNet, BERT-base inference, một số fine-tune small LLM với LoRA).
- RTX 4070 / 4080: 12–16GB.
- RTX 4090: 24GB. Đủ fine-tune model 7B với LoRA / QLoRA, train CNN cỡ trung.
Datacenter GPU (NVIDIA, dùng cho production / large-scale)
- A100: 40GB hoặc 80GB HBM2e. Standard cho train model lớn 2020–2023.
- H100: 80GB HBM3, hỗ trợ FP8, Transformer Engine. Standard cho LLM training hiện tại.
- H200 / B200 (Blackwell): generation mới hơn, RAM lớn hơn.
Apple Silicon — MPS (Metal Performance Shaders)
- M1 / M2 / M3 / M4 — GPU tích hợp dùng unified memory (CPU + GPU dùng chung RAM).
- PyTorch hỗ trợ backend
"mps"từ v1.12 (2022). Không phải tất cả op đều có MPS kernel — một số op vẫn fallback về CPU. - Đủ cho học và prototype; chậm hơn RTX cùng phân khúc giá ở workload heavy.
TPU (Google)
- ASIC chuyên cho tensor op. Mạnh với TensorFlow / JAX.
- PyTorch chạy TPU qua PyTorch-XLA (compile graph qua XLA). Code phải viết hơi khác (XLA device, lazy execution).
Free cloud — học không cần mua hardware
- Google Colab free tier: GPU T4 (16GB), session ngắn, ngắt nhanh khi idle. Pro / Pro+ trả phí có A100, L4.
- Kaggle Notebooks: P100 (16GB) hoặc T4 x2, 30h/tuần GPU free, có session dài hơn Colab free.
- Lightning AI Studios, SageMaker Studio Lab: có free tier với GPU.
Kiểm tra GPU available
import torch
print(torch.__version__) # vd: 2.4.0+cu121
print(torch.cuda.is_available()) # True nếu có GPU NVIDIA + CUDA driver
print(torch.cuda.device_count()) # số GPU thấy được
if torch.cuda.is_available():
print(torch.cuda.get_device_name(0)) # vd: NVIDIA GeForce RTX 4090
print(torch.cuda.get_device_capability(0)) # vd: (8, 9) — compute capability
print(torch.cuda.get_device_properties(0)) # name, total_memory, multi_processor_count, ...
Trên Apple Silicon, kiểm tra MPS:
print(torch.backends.mps.is_available()) # True trên M1/M2/M3/M4 với macOS 12.3+
print(torch.backends.mps.is_built()) # True nếu PyTorch build có MPS
is_available() trả False thường vì: chưa cài bản PyTorch + CUDA (CPU-only build), driver NVIDIA chưa cài / cũ, hoặc đang chạy trong container không expose GPU. Vào pytorch.org/get-started để lấy lệnh cài đúng cho hệ CUDA của máy.
torch.device
torch.device là object đại diện cho nơi tensor sống. Các giá trị phổ biến:
torch.device("cpu") # CPU
torch.device("cuda") # GPU mặc định (cuda:0)
torch.device("cuda:0") # GPU thứ 0
torch.device("cuda:1") # GPU thứ 1 (nếu có)
torch.device("mps") # Apple Silicon GPU
Mỗi tensor có thuộc tính .device:
x = torch.tensor([1.0, 2.0, 3.0])
print(x.device) # cpu
y = x.to("cuda")
print(y.device) # cuda:0
Lưu ý: "cuda" luôn map về cuda:0 trừ khi bạn set torch.cuda.set_device(i) hoặc dùng env var CUDA_VISIBLE_DEVICES.
Pattern device-agnostic
Code chạy được ở mọi máy — Colab T4, máy local có / không GPU, MacBook M-series — chỉ cần một dòng:
import torch
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("Using device:", device)
Phiên bản hỗ trợ cả MPS:
if torch.cuda.is_available():
device = torch.device("cuda")
elif torch.backends.mps.is_available():
device = torch.device("mps")
else:
device = torch.device("cpu")
Sau đó mọi nơi cần biết device thì dùng biến device, không hard-code "cuda". Đây là pattern chuẩn trong mọi tutorial / template PyTorch.
Move tensor sang GPU
Ba cách phổ biến:
x = torch.randn(3, 4) # CPU tensor
# 1) .to(device) — recommended, flexible (CPU/CUDA/MPS đều dùng được)
x_gpu = x.to(device)
# 2) .cuda() — shortcut, CHỈ work với CUDA
x_cuda = x.cuda() # tương đương x.to("cuda")
# 3) .cpu() — quay về CPU
x_back = x_gpu.cpu()
Quan trọng: tensor.to() không in-place — nó trả về tensor mới ở device đích. Phải gán lại:
x.to(device) # SAI — kết quả bị bỏ
x = x.to(device) # ĐÚNG
Nếu tensor đã ở đúng device, .to() trả lại chính tensor đó (no-op, không copy) — viết phòng hờ không tốn gì.
to() cũng nhận dtype: x.to(device, dtype=torch.float16) chuyển cả device và dtype trong một call.
Tạo tensor trực tiếp trên GPU
Mọi factory function (zeros, ones, randn, arange, empty…) nhận tham số device:
x = torch.zeros(3, 4, device="cuda")
y = torch.randn(3, 4, device=device)
z = torch.arange(10, device=device)
Cách này nhanh hơn tạo trên CPU rồi .to(device) vì tránh được bước copy host → device. Với tensor lớn (vài chục MB trở lên) hoặc trong hot loop, khác biệt rõ rệt.
Reuse device từ tensor / model có sẵn:
noise = torch.randn_like(x) # cùng shape, dtype, device với x
mask = torch.zeros(x.size(0), device=x.device)
Tensor + model phải cùng device
Đây là bug phổ biến nhất khi mới làm việc với GPU. Mọi tensor tham gia một phép tính phải ở cùng device (và thường cùng dtype). Nếu mismatch:
RuntimeError: Expected all tensors to be on the same device,
but found at least two devices, cuda:0 and cpu!
Tình huống điển hình: model đã .to("cuda") nhưng input batch quên move:
model = MyModel().to(device)
inputs = torch.randn(32, 784) # vẫn ở CPU
out = model(inputs) # RuntimeError
Fix:
for inputs, targets in dataloader:
inputs = inputs.to(device) # bắt buộc gán lại
targets = targets.to(device)
out = model(inputs)
loss = loss_fn(out, targets)
...
Khác biệt then chốt cần nhớ:
model.to(device)— in-place: move toàn bộ parameter và buffer của module. Không cần gán lại, nhưng viếtmodel = model.to(device)cũng đúng.tensor.to(device)— không in-place: trả tensor mới, phải gán lại.
Lý do: nn.Module.to() override để di chuyển param và đăng ký device cho module; còn Tensor.to() giữ semantic functional (như NumPy).
Move model
import torch.nn as nn
class MyModel(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 256)
self.fc2 = nn.Linear(256, 10)
def forward(self, x):
return self.fc2(torch.relu(self.fc1(x)))
model = MyModel().to(device) # move parameter sang device
print(next(model.parameters()).device)
Lưu checkpoint thì save từ CPU để file portable:
torch.save(model.state_dict(), "model.pt")
# Load: tạo model rỗng, load state, rồi mới to(device)
model = MyModel()
model.load_state_dict(torch.load("model.pt", map_location="cpu"))
model.to(device)
Tham số map_location trong torch.load quan trọng: nếu checkpoint lưu trên cuda:0 và bạn load trên máy CPU-only, không có map_location="cpu" sẽ lỗi.
Mixed precision (FP16 / BF16)
Tensor mặc định float32 (FP32, 4 byte / phần tử). GPU hiện đại (Tensor Core từ Volta trở đi) chạy FP16 / BF16 nhanh hơn nhiều, RAM giảm gần một nửa.
- FP16: 16 bit, range hẹp — dễ overflow / underflow trong loss và gradient.
- BF16 (bfloat16): 16 bit, cùng range với FP32 (8 bit exponent), precision thấp hơn FP16 nhưng stable hơn khi train. Yêu cầu Ampere (A100, RTX 30xx) trở lên.
Pattern chuẩn: autocast cho forward, GradScaler cho backward (chỉ cần cho FP16, BF16 không cần):
from torch.amp import autocast, GradScaler
scaler = GradScaler("cuda") # PyTorch 2.0+ API
for inputs, targets in dataloader:
inputs = inputs.to(device)
targets = targets.to(device)
optimizer.zero_grad()
# autocast: ops chạy FP16/BF16 khi an toàn, FP32 cho ops cần precision
with autocast(device_type="cuda", dtype=torch.float16):
out = model(inputs)
loss = loss_fn(out, targets)
# GradScaler: scale loss để gradient FP16 không bị underflow
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
API cũ torch.cuda.amp.autocast() vẫn chạy nhưng đã deprecated; PyTorch 2.0+ thống nhất sang torch.amp.autocast(device_type=...) để hỗ trợ CPU autocast, MPS, v.v.
Với BF16, bỏ GradScaler:
with autocast(device_type="cuda", dtype=torch.bfloat16):
out = model(inputs)
loss = loss_fn(out, targets)
loss.backward()
optimizer.step()
Tham khảo PyTorch AMP recipes cho chi tiết edge case (gradient clipping, multi-GPU, multiple loss).
Memory management
PyTorch dùng caching allocator: khi free một tensor, RAM không trả về hệ điều hành mà giữ trong cache để reuse cho tensor sau (tránh overhead cudaMalloc).
print(torch.cuda.memory_allocated() / 1e9, "GB") # đang dùng (sống)
print(torch.cuda.memory_reserved() / 1e9, "GB") # tổng cache PyTorch giữ
print(torch.cuda.max_memory_allocated() / 1e9, "GB") # peak từ đầu run
torch.cuda.reset_peak_memory_stats() # reset peak counter
print(torch.cuda.memory_summary(abbreviated=True)) # bảng chi tiết
Giải phóng cache (KHÔNG giúp giảm RAM của model / tensor đang sống):
del big_tensor # bỏ reference
torch.cuda.empty_cache() # trả cache về OS / driver
Hiểu lầm thường gặp: empty_cache() KHÔNG "fix" OOM của model lớn. Nó chỉ trả phần cache chưa được PyTorch tái sử dụng. Nếu model + activation đang chiếm chỗ thật, gọi empty_cache() bao nhiêu lần cũng vô ích.
nvidia-smi báo VRAM bao gồm cả cache PyTorch — đừng panic nếu số cao hơn memory_allocated().
CUDA Out of Memory
RuntimeError: CUDA out of memory. Tried to allocate 2.00 GiB
(GPU 0; 24.00 GiB total capacity; 22.10 GiB already allocated;
1.50 GiB free; 22.50 GiB reserved in total by PyTorch)
Nguyên nhân điển hình:
- Batch size quá lớn — phổ biến nhất. Activation cho mỗi sample nhân với batch size.
- Model quá lớn cho VRAM — tổng parameter + gradient + optimizer state (Adam giữ 2 buffer / param) đã vượt RAM.
- Quên detach tensor không cần grad —
loss_total += losssẽ giữ cả computation graph qua nhiều iteration; phảiloss.item()hoặcloss.detach(). - Giữ reference dư — biến trỏ vào tensor trên GPU không bị free.
- Memory fragmentation — cache có đủ tổng RAM nhưng không có block liên tục đủ lớn.
Cách giải quyết, từ rẻ đến đắt:
- Giảm batch size — thử half rồi quarter.
- Gradient accumulation — chia batch lớn thành nhiều micro-batch, cộng dồn gradient trước khi
optimizer.step(). Hiệu quả như batch lớn nhưng RAM bằng micro-batch. - Mixed precision (FP16 / BF16) — giảm ~50% RAM cho activation và một phần parameter.
- Gradient checkpointing (
torch.utils.checkpoint) — không lưu activation, tính lại trong backward. Đổi compute lấy RAM. - Model parallel / sharding — chia model qua nhiều GPU (FSDP, DeepSpeed ZeRO). Dành cho khi 1 GPU không chứa nổi.
- Hạ optimizer — Adam → SGD (giảm 2/3 optimizer state), hoặc 8-bit Adam.
Snippet gradient accumulation:
accum_steps = 4
optimizer.zero_grad()
for i, (x, y) in enumerate(loader):
x, y = x.to(device), y.to(device)
loss = loss_fn(model(x), y) / accum_steps
loss.backward()
if (i + 1) % accum_steps == 0:
optimizer.step()
optimizer.zero_grad()
NumPy không support GPU
NumPy ndarray sống trên CPU RAM. Không có cách convert tensor CUDA → ndarray trực tiếp:
x = torch.randn(3, 4, device="cuda")
x.numpy() # TypeError: can't convert cuda:0 device type tensor to numpy
x.cpu().numpy() # OK — copy về CPU trước
Ngược lại từ ndarray sang tensor GPU phải qua hai bước (PyTorch tự lo):
import numpy as np
arr = np.array([1.0, 2.0, 3.0])
t = torch.from_numpy(arr).to(device)
Thư viện tương đương NumPy nhưng chạy GPU: CuPy (API gần giống NumPy, chạy CUDA). PyTorch tensor cũng đã đủ dùng cho hầu hết workflow DL.
Speed test CPU vs GPU
import time
import torch
N = 10000
a = torch.randn(N, N)
b = torch.randn(N, N)
# CPU
start = time.time()
c = a @ b
print("CPU:", time.time() - start, "s")
# GPU
a = a.cuda()
b = b.cuda()
# Warm-up — lần matmul đầu tiên tốn extra do cuBLAS load + autotune
_ = a @ b
torch.cuda.synchronize()
start = time.time()
c = a @ b
torch.cuda.synchronize()
print("GPU:", time.time() - start, "s")
Vì sao cần torch.cuda.synchronize(): GPU op chạy bất đồng bộ — Python a @ b chỉ enqueue kernel rồi trả về ngay, kernel có thể chưa chạy xong. Không có synchronize() thì time.time() đo thời gian enqueue, không phải thời gian tính. Số đo sẽ vô nghĩa nhỏ.
Quy luật:
N = 100: GPU thường chậm hơn CPU (overhead kernel launch).N = 1000: bắt đầu hòa.N = 10000: GPU nhanh hơn 20–100x tùy hardware và dtype.
Đo nghiêm túc dùng torch.utils.benchmark.Timer (handle warm-up, sync, repeat) thay vì time.time().
Multi-GPU preview
Đây là preview — đi sâu vào các bài training nâng cao sau.
nn.DataParallel(DP): wrapper đơn giản, 1 process điều khiển N GPU. Có bottleneck ở GPU 0 (gather output), không khuyến nghị cho production. PyTorch docs khuyên dùng DDP thay thế.nn.parallel.DistributedDataParallel(DDP): N process, mỗi process 1 GPU; sync gradient qua NCCL. Standard cho multi-GPU và multi-node. Phức tạp hơn (cầntorchrun, init process group), nhưng scale gần linear.- PyTorch Lightning: wrap training loop, một flag
strategy="ddp"là chạy được multi-GPU. - Hugging Face Accelerate: vài dòng config CLI (
accelerate config) để chạy cùng code trên CPU / 1 GPU / multi-GPU / TPU. - FSDP (Fully Sharded Data Parallel) trong PyTorch và DeepSpeed ZeRO: shard parameter + gradient + optimizer state qua nhiều GPU — cho phép train model lớn hơn RAM một GPU.
Skeleton DDP đơn giản:
# train.py — chạy bằng: torchrun --nproc_per_node=2 train.py
import os, torch
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDP
dist.init_process_group("nccl")
local_rank = int(os.environ["LOCAL_RANK"])
torch.cuda.set_device(local_rank)
device = torch.device("cuda", local_rank)
model = MyModel().to(device)
model = DDP(model, device_ids=[local_rank])
# ... training loop bình thường
dist.destroy_process_group()
Code Python — đầy đủ một flow
import time
import torch
import torch.nn as nn
# 1) Chọn device
if torch.cuda.is_available():
device = torch.device("cuda")
elif torch.backends.mps.is_available():
device = torch.device("mps")
else:
device = torch.device("cpu")
print("Using device:", device)
# 2) Tạo tensor trực tiếp trên device
x = torch.randn(1024, 784, device=device)
y = torch.randint(0, 10, (1024,), device=device)
# 3) Model trên device
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Linear(784, 256), nn.ReLU(),
nn.Linear(256, 10),
)
def forward(self, x):
return self.net(x)
model = MLP().to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
loss_fn = nn.CrossEntropyLoss()
# 4) Một bước train ngắn
out = model(x)
loss = loss_fn(out, y)
loss.backward()
optimizer.step()
print("Loss:", loss.item())
# 5) Speed test matmul (chỉ chạy nếu CUDA)
if device.type == "cuda":
N = 5000
a = torch.randn(N, N, device=device)
b = torch.randn(N, N, device=device)
# warm-up
_ = a @ b
torch.cuda.synchronize()
start = time.time()
for _ in range(5):
_ = a @ b
torch.cuda.synchronize()
gpu_t = (time.time() - start) / 5
print(f"GPU matmul {N}x{N}: {gpu_t*1000:.2f} ms")
a_cpu, b_cpu = a.cpu(), b.cpu()
start = time.time()
_ = a_cpu @ b_cpu
cpu_t = time.time() - start
print(f"CPU matmul {N}x{N}: {cpu_t*1000:.2f} ms")
print(f"Speedup: {cpu_t / gpu_t:.1f}x")
# 6) Memory snapshot (chỉ CUDA)
if device.type == "cuda":
print(f"Allocated: {torch.cuda.memory_allocated()/1e9:.2f} GB")
print(f"Reserved : {torch.cuda.memory_reserved()/1e9:.2f} GB")
# 7) Demo OOM-safe pattern: try/except, dọn rồi giảm batch
def train_step(batch):
try:
out = model(batch.to(device))
loss = loss_fn(out, torch.zeros(batch.size(0), dtype=torch.long, device=device))
loss.backward()
optimizer.step()
optimizer.zero_grad()
return loss.item()
except torch.cuda.OutOfMemoryError:
optimizer.zero_grad()
torch.cuda.empty_cache()
raise # caller giảm batch và retry
Bài tập
- Mở Google Colab, chọn Runtime → Change runtime type → T4 GPU. In
torch.cuda.is_available(),torch.cuda.get_device_name(0),torch.cuda.get_device_properties(0). - Speed test matmul 5000×5000 CPU vs GPU. Nhớ
torch.cuda.synchronize()trước và sau khi đo. So sánh speedup; thử thêmN=500và quan sát: vớiNnhỏ, GPU có thể chậm hơn — giải thích. - Tạo tensor
x = torch.arange(10, device="cpu"), move sang GPU bằng.to(device), thực hiệnx = x * 2 + 1, move về CPU, verify giá trị bằngprinthoặc so sánh với tính tay. - Tự kích hoạt CUDA Out of Memory: tạo tensor lớn lặp lại trong loop (vd
tensors.append(torch.randn(10000, 10000, device="cuda"))) cho đến khitorch.cuda.OutOfMemoryError. Inmemory_allocatedtrước lúc lỗi. Sau đó dọn (del tensors,torch.cuda.empty_cache()) và xác nhận RAM giảm. - Viết một training loop ngắn với
autocast(device_type="cuda", dtype=torch.float16)+GradScalercho MLP trên dữ liệu giả. So sánhmemory_allocatedpeak với bản FP32.
Gợi ý câu 2 — vì sao GPU chậm với N nhỏ
Mỗi kernel CUDA có overhead launch cố định (~vài μs đến vài chục μs) và overhead copy / dispatch trong PyTorch. Với N=500, matmul thực chỉ tốn vài μs trên GPU — overhead launch ngang bằng hoặc lớn hơn phần tính. CPU làm thẳng trong cache L1/L2 không có overhead này. GPU chỉ "thắng" rõ rệt khi compute đủ lớn để che overhead.
Tóm tắt
- GPU song song hóa matrix multiplication — train DL nhanh hơn CPU 10–100x với workload đủ lớn. LLM bắt buộc multi-GPU.
- CUDA là stack tính toán song song của NVIDIA; PyTorch dispatch qua cuBLAS / cuDNN xuống CUDA kernel. AMD dùng ROCm, Apple dùng MPS, Google dùng TPU (qua XLA).
- Kiểm tra GPU:
torch.cuda.is_available(),torch.cuda.device_count(),torch.cuda.get_device_name(). - Pattern device-agnostic:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu"). - Move tensor:
x = x.to(device)(phải gán lại). Move model:model.to(device)(in-place). Tạo trực tiếp trên GPU:torch.zeros(..., device=device)— nhanh hơn tạo CPU rồi.to(). - Tensor + model phải cùng device — mismatch raise
RuntimeError. Trong train loop, luôninputs = inputs.to(device),targets = targets.to(device). - Mixed precision với
torch.amp.autocast(device_type="cuda", dtype=torch.float16/bfloat16); FP16 kèmGradScaler, BF16 không cần. - Memory:
memory_allocated(sống),memory_reserved(cache).empty_cache()trả cache, KHÔNG giảm RAM của tensor đang sống. - CUDA OOM fix theo thứ tự: giảm batch → gradient accumulation → mixed precision → gradient checkpointing → model sharding (FSDP / DeepSpeed).
- NumPy chỉ ở CPU — phải
.cpu().numpy()để convert tensor CUDA. - Speed test GPU phải
torch.cuda.synchronize()trước và sau khi đo, vì kernel async. - Multi-GPU production dùng DDP (qua
torchrun) hoặc wrapper như Lightning / Accelerate.DataParallelđơn giản nhưng có bottleneck.
- PyTorch Docs - CUDA semantics
- PyTorch Docs - torch.cuda
- PyTorch Docs - torch.device
- PyTorch Docs - Tensor.to
- PyTorch Docs - nn.Module.to
- PyTorch Docs - Automatic Mixed Precision
- PyTorch Docs - AMP examples
- PyTorch Docs - MPS backend
- PyTorch Docs - DistributedDataParallel
- PyTorch Docs - FullyShardedDataParallel
- PyTorch Docs - Gradient Checkpointing
- PyTorch - Install (chọn CUDA version)
- NVIDIA - CUDA Toolkit
- PyTorch-XLA - chạy PyTorch trên TPU
- PyTorch Lightning Docs
- Hugging Face Accelerate Docs
- DeepSpeed
