Kita kini memegang sembilan teknik yang masing-masing menyerang satu sisi konsumsi memori: dari aritmetika 16 × P state optimizer, akumulasi gradien, hitung ulang aktivasi, hingga pembagian data, model, pipa, parameter, dan penulisan ulang attention. Persoalan praktisnya tinggal satu: dari semua pilihan itu, mulai dari mana, dan bagaimana kita tahu suatu langkah benar-benar berdampak tanpa menebak? Jawabannya adalah siklus ukur–ubah–ukur kembali: pantau memori lebih dulu, terapkan satu teknik pada satu waktu, lalu bandingkan angkanya. Chapter penutup ini merangkai kotak perkakas menjadi daftar periksa terurut, membedah alat pemantauan CPU dan GPU, lalu menutup dengan pengukuran nyata dua konfigurasi pelatihan yang menggabungkan tiga teknik sekaligus.
Alat Pemantauan yang Tepat untuk Objek yang Tepat #
Di GPU, PyTorch menyediakan torch.cuda.memory_allocated() dan torch.cuda.max_memory_allocated() — byte teralokasi saat itu dan puncaknya sejak penanda reset_peak_memory_stats(). Di CPU tidak ada padanan langsung, dan jebakan pertamanya adalah salah alat: tracemalloc milik Python hanya melihat alokasi lewat pengalokas Python, sedangkan tensor torch dialokasikan di luar jalur itu. Konsekuensya kami uji nyata pada berkas di bawah. Untuk tensor CPU, pemindaian gc.get_objects() plus element_size() × numel() menjadi padanan yang jujur — pola yang sudah dipakai di chapter Activation Checkpointing
dan Memory Management
.
"""Toolkit optimasi memori: pemantauan nyata dan dua konfigurasi pelatihan."""
import gc
import tracemalloc
import warnings
from contextlib import nullcontext
import torch
import torch.utils.checkpoint as cp
warnings.filterwarnings("ignore")
torch.manual_seed(3)
def memori_tensor_hidup():
"""Total byte seluruh tensor torch yang masih direferensikan Python."""
total = 0
for obj in gc.get_objects():
if isinstance(obj, torch.Tensor):
total += obj.element_size() * obj.numel()
return total
# ---------- 1. Pemantauan: tracemalloc vs tensor torch ----------
print("=== Pemantauan memori: tracemalloc (alat Python) ===")
tracemalloc.start()
daftar = [0.0] * 1_000_000 # list Python ~8 MB
_, puncak_list = tracemalloc.get_traced_memory()
t = torch.zeros(2_000_000) # tensor 8 MB di luar alokator Python
_, puncak_tensor = tracemalloc.get_traced_memory()
print(f"list 1 juta float : tracemalloc melihat {puncak_list:,} byte")
print(f"tensor 8 MB : tracemalloc hanya melihat {puncak_tensor - puncak_list:,} byte")
print(f"tensor sesungguhnya: {t.numel() * t.element_size():,} byte (tak terlacak)")
tracemalloc.stop()
del daftar, t
gc.collect() # lepas referensi sebelum mengukur pelatihan
print("padanan CPU untuk tensor: element_size() * numel() lewat pemindaian gc")
# ---------- 2. Dua konfigurasi pelatihan diukur nyata ----------
class Blok(torch.nn.Module):
def __init__(self, d):
super().__init__()
self.fc1 = torch.nn.Linear(d, d)
self.fc2 = torch.nn.Linear(d, d)
def forward(self, x):
return x + self.fc2(torch.nn.functional.gelu(self.fc1(x)))
def bangun(n_blok=10, d=256):
torch.manual_seed(3)
return torch.nn.ModuleList(Blok(d) for _ in range(n_blok))
N = 128
X = torch.randn(N, 256)
T = torch.randn(N, 256)
def latih_dan_ukur(autocast, checkpoint, micro, n_micro, n_epoch=2):
blok = bangun()
opt = torch.optim.SGD(blok.parameters(), lr=0.01)
dasar = memori_tensor_hidup()
puncak = dasar
for _ in range(n_epoch):
for awal in range(0, N, micro * n_micro): # batch efektif 32
opt.zero_grad()
for langkah in range(n_micro):
idx = slice(awal + langkah * micro, awal + (langkah + 1) * micro)
konteks = (
torch.autocast("cpu", dtype=torch.bfloat16)
if autocast
else nullcontext()
)
with konteks:
h = X[idx]
for b in blok:
h = (
cp.checkpoint(b, h, use_reentrant=False)
if checkpoint
else b(h)
)
puncak = max(puncak, memori_tensor_hidup())
loss = ((h.float() - T[idx]) ** 2).mean() / n_micro
loss.backward()
opt.step()
with torch.no_grad():
h = X
for b in blok:
h = b(h)
mse = float(((h - T) ** 2).mean())
return dasar, puncak, mse
print()
print("=== Dua konfigurasi (batch efektif 32, 10 blok, 2 epoch) ===")
d1, p1, mse1 = latih_dan_ukur(autocast=False, checkpoint=False, micro=32, n_micro=1)
print(f"baseline fp32 batch 32 : dasar {d1:,} | puncak {p1:,} "
f"| aktivasi {p1 - d1:,} byte | MSE {mse1:.4f}")
d2, p2, mse2 = latih_dan_ukur(autocast=True, checkpoint=True, micro=8, n_micro=4)
print(f"hemat bf16+ckpt+akum 4x8 : dasar {d2:,} | puncak {p2:,} "
f"| aktivasi {p2 - d2:,} byte | MSE {mse2:.4f}")
print()
print(f"aktivasi tertahan turun : {(p1 - d1) / (p2 - d2):.1f}x lebih kecil")
print(f"MSE kedua konfigurasi : {mse1:.4f} vs {mse2:.4f} (kualitas terjaga)")
print(f"gradien akumulasi disimpan di sini; state SGD hanya 8P tanpa momentum")Output yang Dihasilkan:
=== Pemantauan memori: tracemalloc (alat Python) ===
list 1 juta float : tracemalloc melihat 8,000,064 byte
tensor 8 MB : tracemalloc hanya melihat 128 byte
tensor sesungguhnya: 8,000,000 byte (tak terlacak)
padanan CPU untuk tensor: element_size() * numel() lewat pemindaian gc
=== Dua konfigurasi (batch efektif 32, 10 blok, 2 epoch) ===
baseline fp32 batch 32 : dasar 5,525,504 | puncak 6,541,316 | aktivasi 1,015,812 byte | MSE 2.4730
hemat bf16+ckpt+akum 4x8 : dasar 5,525,504 | puncak 5,666,180 | aktivasi 140,676 byte | MSE 2.4730
aktivasi tertahan turun : 7.2x lebih kecil
MSE kedua konfigurasi : 2.4730 vs 2.4730 (kualitas terjaga)
gradien akumulasi disimpan di sini; state SGD hanya 8P tanpa momentumMari kita telaah kode di atas baris per baris:
- Baris 24-37 (jebakan tracemalloc): list Python 1 juta float terlihat penuh oleh tracemalloc (8.000.064 byte), sedangkan tensor
torch.zeros(2_000_000)yang juga berukuran 8 MB hanya menambah 128 byte jejak — alokasi tensor torch tidak lewat pengalokas Python. Kesimpulan praktisnya: di CPU, ukur tensor denganelement_size() × numel(), bukan tracemalloc; di GPU, cukuptorch.cuda.memory_allocated(). - Baris 41-49 (
Blok): blok residual yang sama dengan chapter checkpointing — dua layerLineardan satu GELU per blok — sehingga angka pengukurannya dapat dibandingkan langsung dengan bab tersebut. - Baris 61-84 (satu fungsi, dua konfigurasi): parameter
autocast,checkpoint,micro,n_micromengubah jalur tanpa mengubah struktur. Konfigurasi hemat menggabungkan tiga teknik:torch.autocast("cpu", dtype=torch.bfloat16)(aktivasi berpindah ke 2 byte per elemen),cp.checkpointper blok (aktivasi di dalam blok dibuang), dan akumulasi 4×8 (aktivasi per forward hanya untuk 8 sampel). - Baris 84 (titik ukur puncak):
memori_tensor_hidup()dipanggil setelah setiap forward micro-batch dan nilainya diambil maksimumnya — puncak yang terukur mencakup aktivasi tertahan, gradien yang sedang terkumpul, dan parameter; puncak singkat selama backward recompute tidak terpindai penuh, sehingga angka ini batas bawah yang konsisten, bukan batas atas mutlak. - Baris 96-109 (hasil): aktivasi tertahan turun dari 1.015.812 byte menjadi 140.676 byte — 7,2x lebih hemat — dengan MSE akhir 2,4730 di kedua konfigurasi: kombinasi tiga teknik memangkas memori tanpa merusak hasil pelatihan pada contoh ini.
Urutan Praktis dan Daftar Periksa #
| Urutan coba | Teknik | Yang dipangkas | Biaya | Kapan berhenti |
|---|---|---|---|---|
| 1 | mixed precision (bfloat16/float16) | separuh aktivasi | galat pembulatan kecil | bila masih jebol |
| 2 | gradient accumulation | aktivasi sebanding micro-batch | waktu sedikit naik | bila masih jebol |
| 3 | activation checkpointing | mayoritas aktivasi | forward 2x | bila masih jebol |
| 4 | parameter sharding (FSDP/ZeRO) | state optimizer & parameter | komunikasi naik tajam | bila masih jebol |
| 5 | efficient attention | memori skor n² | butuh perangkat didukung | konteks panjang |
Urutan itu mengikuti dua prinsip: teknologi termurah diulang lebih dulu (biaya waktu dan kompleksitas naik ke bawah tabel), dan setiap langkah diukur ulang sebelum melangkah. Dua kebiasaan penutup melengkapi daftar: lepas tensor besar dengan del lalu gc.collect() begitu tidak dipakai — dan di GPU tambahkan torch.cuda.empty_cache() hanya di titik istirahat (batch eval, akhir epoch), karena pemanggilan berlebihan justru memaksa alokasi ulang dan memperlambat pelatihan; perilaku caching allocator-nya dibahas di docs.pytorch.org/docs/stable/notes/cuda.html
.
Section ini menutup perjalanan teknik pelatihan bersumber daya terbatas. Dengan memori terkendali, langkah berikutnya adalah membawa model terlatih keluar dari notebook: mengemasnya, menjalankannya di perangkat target, dan menjaga kualitasnya di dunia produksi — pokok bahasan section Deployment dan Inference .