HuggingFace Accelerate: Satu Kode PyTorch untuk CPU, Satu GPU, Multi-GPU, dan Mixed Precision

# HuggingFace Accelerate: Satu Kode PyTorch untuk CPU, Satu GPU, Multi-GPU, dan Mixed Precision Halo temen-temen, kali ini kita bahas library yang sering dipakai diam-diam di balik layar tapi jarang...

By Ruby Abdullah · · tutorial
acceleratepytorchdistributed-trainingmulti-gpuhuggingface

HuggingFace Accelerate: Satu Kode PyTorch untuk CPU, Satu GPU, Multi-GPU, dan Mixed Precision

Halo temen-temen, kali ini kita bahas library yang sering dipakai diam-diam di balik layar tapi jarang dipelajari langsung, namanya Accelerate dari HuggingFace. Kalau kalian pernah nulis training loop PyTorch yang jalan mulus di laptop, terus disuruh jalanin di server dengan 4 GPU, dan tiba-tiba harus belajar DistributedDataParallel, torchrun, rank, world size, dist.initprocessgroup, sampai pusing sendiri, nah Accelerate itu obatnya.

Filosofinya sederhana dan aku suka banget: kalian tetap nulis training loop PyTorch biasa, Accelerate cuma nambahin sekitar lima baris, dan setelah itu kode yang sama bisa jalan di CPU, satu GPU, banyak GPU dalam satu mesin, banyak mesin, Apple Silicon (MPS), sampai TPU. Tanpa if bertingkat, tanpa nulis dua versi kode.

Di tutorial ini kita bakal bahas instalasi dan konfigurasi, mengubah training loop biasa jadi versi Accelerate, mixed precision, gradient accumulation, cara benar menyimpan dan memuat checkpoint di lingkungan terdistribusi, logging yang nggak dobel, sampai integrasi DeepSpeed dan FSDP untuk model besar. Ayo mulai.

Introduction

Aku mau kalian paham dulu masalah apa yang sebenarnya diselesaikan Accelerate.

Kenapa Multi-GPU Itu Menyebalkan

Waktu kalian pindah dari satu GPU ke banyak GPU dengan PyTorch murni, ada banyak hal membosankan yang harus diurus manual.

Kalian harus inisialisasi process group, menentukan backend komunikasi seperti NCCL, menentukan rank tiap proses, dan menutup process group dengan benar di akhir.

Kalian harus mengganti DataLoader biasa jadi pakai DistributedSampler, dan jangan lupa memanggil setepoch() tiap epoch supaya shuffle-nya benar antar-proses.

Kalian harus membungkus model dengan DistributedDataParallel, lalu ingat bahwa model.module yang harus disimpan, bukan model.

Kalian harus memastikan hanya proses utama yang mencetak log, menyimpan file, dan mengirim metrik, supaya tidak ada delapan baris log identik atau delapan proses menulis ke file yang sama.

Dan kalau kalian mau mixed precision, kalian tambah lagi urusan GradScaler dan autocast.

Semua itu bukan bagian dari ide penelitian atau produk kalian. Itu cuma plumbing. Accelerate mengambil alih plumbing tersebut.

Apa yang Dilakukan Accelerate

Accelerate memberi kalian satu objek bernama Accelerator. Objek ini mendeteksi lingkungan eksekusi, menyiapkan model, optimizer, dan dataloader supaya cocok dengan lingkungan tersebut, lalu menyediakan pengganti untuk beberapa operasi yang perlu sadar konteks distribusi, terutama backward(), penyimpanan checkpoint, dan pencetakan log.

Yang penting dipahami: Accelerate bukan framework training seperti PyTorch Lightning. Dia tidak mengambil alih loop kalian. Kalian tetap pegang kendali penuh atas urutan operasi. Dia cuma menghapus kode boilerplate distribusi.

Instalasi dan Konfigurasi

python -m venv venv

source venv/bin/activate # Linux / Mac

venv\Scripts\activate # Windows

pip install torch torchvision

pip install accelerate

Setelah itu jalankan wizard konfigurasi sekali saja di mesin tersebut:

accelerate config

Kalian akan ditanya beberapa hal: jalan di satu mesin atau banyak mesin, berapa GPU, mau pakai mixed precision apa (no, fp16, atau bf16), mau pakai DeepSpeed atau FSDP atau tidak. Jawabannya disimpan di file YAML di ~/.cache/huggingface/accelerate/defaultconfig.yaml.

Kalau kalian buru-buru dan cuma mau setting default yang masuk akal:

accelerate config default

Cek hasil deteksi lingkungan kalian:

accelerate env

Dari Loop Biasa ke Loop Accelerate

Ini bagian intinya. Aku tunjukin dulu versi PyTorch biasa, lalu versi Accelerate, biar kalian lihat persis apa yang berubah.

Versi PyTorch biasa:

import torch

import torch.nn as nn

device = "cuda" if torch.cuda.isavailable() else "cpu"

model = MyModel().to(device)

optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4)

criterion = nn.CrossEntropyLoss()

for epoch in range(EPOCHS):

model.train()

for x, y in trainloader:

x, y = x.to(device), y.to(device)

optimizer.zerograd()

loss = criterion(model(x), y)

loss.backward()

optimizer.step()

Versi Accelerate:

import torch

import torch.nn as nn

from accelerate import Accelerator

accelerator = Accelerator()

model = MyModel()

optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4)

criterion = nn.CrossEntropyLoss()

model, optimizer, trainloader = accelerator.prepare(model, optimizer, trainloader)

for epoch in range(EPOCHS):

model.train()

for x, y in trainloader:

optimizer.zerograd()

loss = criterion(model(x), y)

accelerator.backward(loss)

optimizer.step()

Perbedaannya cuma empat hal.

Kalian membuat Accelerator().

Kalian menghapus semua .to(device). Accelerate yang memindahkan model dan batch ke perangkat yang benar.

Kalian memanggil accelerator.prepare() untuk model, optimizer, dan dataloader. Di sinilah pembungkusan DDP dan penggantian sampler terjadi.

Kalian mengganti loss.backward() jadi accelerator.backward(loss). Ini yang mengurus scaling gradien untuk mixed precision dan sinkronisasi antar-proses.

Sudah. Kode ini sekarang jalan di CPU, satu GPU, atau delapan GPU tanpa diubah.

Menjalankan Training

Untuk menjalankan, jangan pakai python train.py, pakai launcher:

accelerate launch train.py

Kalau mau override konfigurasi dari command line:

# Paksa 4 GPU dengan bf16

accelerate launch --numprocesses=4 --mixedprecision=bf16 train.py

Paksa jalan di CPU untuk debugging

accelerate launch --cpu train.py

Tips dari pengalaman: waktu debugging, jalanin dulu dengan satu proses. Bug logika jauh lebih gampang dibaca kalau log-nya nggak tumpang tindih dari delapan proses.

Mixed Precision

Mixed precision itu melatih model dengan sebagian operasi memakai presisi 16 bit, bukan 32 bit. Hasilnya lebih hemat memori dan biasanya lebih cepat di GPU modern.

Di Accelerate, kalian tidak perlu menyentuh GradScaler atau autocast sama sekali. Cukup deklarasikan di konstruktor atau di command line.

accelerator = Accelerator(mixedprecision="bf16")

Pilih bf16 kalau GPU kalian Ampere ke atas (A100, RTX 30 series, RTX 40 series, H100). Pilih fp16 untuk GPU yang lebih lama seperti V100 atau T4. bf16 lebih stabil karena rentang eksponennya sama dengan fp32, jadi jarang kena masalah overflow.

Gradient Accumulation

Kalau GPU kalian kecil tapi kalian butuh batch size besar secara efektif, gradient accumulation adalah jawabannya. Accelerate menyediakannya lewat context manager yang rapi.

from accelerate import Accelerator

accelerator = Accelerator(gradientaccumulationsteps=4)

model, optimizer, trainloader = accelerator.prepare(model, optimizer, trainloader)

for x, y in trainloader:

with accelerator.accumulate(model):

loss = criterion(model(x), y)

accelerator.backward(loss)

optimizer.step()

scheduler.step()

optimizer.zerograd()

Yang menyenangkan, kalian tetap menulis optimizer.step() di dalam loop seperti biasa. Accelerate yang memutuskan kapan langkah itu benar-benar dieksekusi dan kapan hanya mengakumulasi gradien. Dia juga otomatis mematikan sinkronisasi gradien antar-GPU pada langkah yang belum saatnya update, yang menghemat bandwidth komunikasi.

Logging dan Print yang Tidak Dobel

Di lingkungan multi-proses, print() biasa akan dieksekusi oleh semua proses. Kalau kalian punya 8 GPU, satu baris log jadi 8 baris.

# Hanya dicetak oleh proses utama

accelerator.print(f"epoch {epoch} loss {loss.item():.4f}")

Blok yang hanya dijalankan proses utama

if accelerator.ismainprocess:

kirimnotifikasitelegram("training selesai")

Tunggu semua proses sampai di titik ini

accelerator.waitforeveryone()

waitforeveryone() itu penting sebelum operasi seperti menyimpan file atau memuat file yang baru ditulis proses utama. Tanpa itu, proses lain bisa mencoba membaca file yang belum selesai ditulis.

Menggabungkan Metrik dari Semua Proses

Waktu evaluasi, tiap proses cuma melihat sebagian data. Kalau kalian hitung akurasi lokal saja, angkanya tidak mewakili seluruh dataset. Accelerate menyediakan gatherformetrics yang juga otomatis membuang duplikat dari padding batch terakhir.

model.eval()

benar = 0

total = 0

for x, y in evalloader:

with torch.nograd():

pred = model(x).argmax(dim=-1)

pred, y = accelerator.gatherformetrics((pred, y))

benar += (pred == y).sum().item()

total += y.numel()

accelerator.print(f"akurasi: {benar/total:.4f}")

Ini salah satu fitur yang paling menyelamatkan, karena bug metrik di lingkungan distribusi itu sunyi. Angkanya kelihatan wajar tapi salah.

Menyimpan dan Memuat Model

Ini area yang paling sering bikin orang tersandung. Model yang sudah di-prepare itu dibungkus DDP, jadi kalau kalian simpan langsung, key-nya akan punya prefix module. dan nanti susah dimuat.

Cara yang benar untuk menyimpan bobot final:

accelerator.waitforeveryone()

unwrapped = accelerator.unwrapmodel(model)

accelerator.save(unwrapped.statedict(), "modelfinal.pth")

accelerator.save sudah pintar, dia hanya menulis dari proses utama, jadi kalian tidak perlu membungkusnya dengan if ismainprocess.

Untuk checkpoint lengkap yang bisa melanjutkan training (termasuk state optimizer, scheduler, dan random state), pakai API state:

# Daftarkan objek tambahan yang ingin ikut disimpan

accelerator.registerforcheckpointing(scheduler)

Simpan

accelerator.savestate("checkpoints/epoch5")

Muat kembali

accelerator.loadstate("checkpoints/epoch5")

Ini jauh lebih andal daripada menyusun dictionary checkpoint manual, terutama kalau kalian nanti pindah ke DeepSpeed atau FSDP yang punya format sharded sendiri.

Contoh Lengkap

Ini contoh utuh yang bisa langsung kalian jalankan sebagai kerangka.

import torch

import torch.nn as nn

from torch.utils.data import DataLoader, TensorDataset

from accelerate import Accelerator

EPOCHS = 5

def main():

accelerator = Accelerator(mixedprecision="bf16", gradientaccumulationsteps=2)

# Data dummy: 2000 sampel, 20 fitur, 3 kelas

x = torch.randn(2000, 20)

y = torch.randint(0, 3, (2000,))

ds = TensorDataset(x, y)

trainloader = DataLoader(ds, batchsize=32, shuffle=True)

model = nn.Sequential(

nn.Linear(20, 128), nn.ReLU(),

nn.Linear(128, 64), nn.ReLU(),

nn.Linear(64, 3),

)

optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3)

scheduler = torch.optim.lrscheduler.CosineAnnealingLR(optimizer, Tmax=EPOCHS)

criterion = nn.CrossEntropyLoss()

model, optimizer, trainloader, scheduler = accelerator.prepare(

model, optimizer, trainloader, scheduler

)

for epoch in range(EPOCHS):

model.train()

for xb, yb in trainloader:

with accelerator.accumulate(model):

loss = criterion(model(xb), yb)

accelerator.backward(loss)

optimizer.step()

optimizer.zerograd()

scheduler.step()

accelerator.print(f"epoch {epoch+1}/{EPOCHS} loss {loss.item():.4f}")

accelerator.waitforeveryone()

unwrapped = accelerator.unwrapmodel(model)

accelerator.save(unwrapped.statedict(), "modelfinal.pth")

accelerator.print("selesai, model tersimpan")

if name == "main":

main()

Jalankan dengan:

accelerate launch contoh.py

Integrasi Tracker Eksperimen

Accelerate punya pembungkus untuk beberapa tracker populer, sehingga logging hanya terjadi di proses utama secara otomatis.

accelerator = Accelerator(logwith="wandb")

accelerator.inittrackers("proyek-klasifikasi", config={"lr": 1e-3, "epochs": 5})

accelerator.log({"trainloss": loss.item(), "epoch": epoch}, step=globalstep)

accelerator.endtraining()

Ganti "wandb" dengan "tensorboard", "mlflow", atau "cometml" sesuai kebutuhan, tanpa mengubah baris accelerator.log.

Model Besar: DeepSpeed dan FSDP

Kalau model kalian tidak muat di satu GPU, kalian butuh sharding parameter. Bagusnya, kalian tidak perlu mengubah kode training sama sekali, cukup ubah konfigurasi.

Jalankan accelerate config, lalu jawab ya waktu ditanya soal DeepSpeed atau FSDP, dan pilih tahap ZeRO yang diinginkan. Setelah itu accelerate launch train.py yang sama akan menjalankan training tersharding.

Dua catatan penting untuk kasus ini. Pertama, dengan DeepSpeed ZeRO-3 atau FSDP, penyimpanan checkpoint harus lewat accelerator.savestate, karena statedict biasa mungkin hanya berisi shard lokal. Kedua, unwrapmodel perlu argumen tambahan pada beberapa konfigurasi untuk mengumpulkan parameter penuh, jadi baca peringatan yang muncul di log.

Tips dan Best Practice

Selalu bungkus kode kalian dalam fungsi main() dan blok if name == "main":. Di lingkungan multi-proses, kode di level modul bisa dieksekusi berkali-kali dengan efek samping yang tidak kalian harapkan.

Jangan panggil .to(device) manual setelah prepare. Kalau kalian butuh device saat ini untuk membuat tensor baru, pakai accelerator.device.

Ingat bahwa batchsize di DataLoader adalah batch per proses. Dengan 4 GPU dan batch size 32, batch efektif kalian adalah 128. Sesuaikan learning rate kalau perlu.

Kalau memakai scheduler per-step (bukan per-epoch), masukkan scheduler ke prepare supaya Accelerate menyesuaikan jumlah langkahnya dengan jumlah proses.

Debug dulu di satu proses. Baru setelah loop kalian benar, naikkan ke multi-GPU.

Hati-hati dengan operasi yang memakai indeks batch global, misalnya menulis file per sampel. Di multi-proses, indeks lokal tidak unik secara global.

Kesimpulan

Accelerate adalah jalan tengah yang pas antara PyTorch murni yang terlalu manual dan framework besar yang mengambil alih semuanya. Ringkasan yang perlu kalian bawa:

Kalian tetap menulis training loop PyTorch biasa, hanya menambahkan Accelerator(), prepare(), dan accelerator.backward().

Kode yang sama berjalan di CPU, satu GPU, multi-GPU, multi-mesin, MPS, dan TPU tanpa percabangan kode.

Mixed precision dan gradient accumulation menjadi opsi konfigurasi, bukan kode tambahan yang rawan bug.

Gunakan accelerator.print, ismainprocess, dan waitforeveryone supaya log dan penulisan file tidak kacau di multi-proses.

Gunakan gatherformetrics untuk evaluasi yang benar, dan savestate atau unwrap_model untuk penyimpanan yang benar.

Untuk model besar, DeepSpeed dan FSDP tinggal diaktifkan lewat konfigurasi tanpa menyentuh kode training.

Coba ambil satu skrip training lama kalian, ubah lima baris sesuai panduan di atas, lalu jalankan dengan accelerate launch. Rasanya menyenangkan waktu sadar skrip yang tadinya cuma jalan di laptop sekarang bisa langsung dipakai di server multi-GPU tanpa ditulis ulang. Selamat mencoba.

Artikel Terkait

DeepSpeed: Melatih Model Raksasa di GPU Terbatas dengan ZeRO dan Offload

DeepSpeed: Melatih Model Raksasa di GPU Terbatas dengan ZeRO dan Offload Halo temen-temen, kali ini kita masuk ke topik ...

Depth Anything V2: Prediksi Kedalaman dari Satu Gambar dengan Python

Depth Anything V2: Prediksi Kedalaman dari Satu Gambar dengan Python Halo temen-temen, di tutorial kali ini aku mau ngaj...

timm: Gudang 1000+ Model Computer Vision Siap Pakai di PyTorch

timm: Gudang 1000+ Model Computer Vision Siap Pakai di PyTorch Halo temen-temen, di tutorial kali ini aku mau ngajak kal...

DINOv2: Panduan Lengkap Vision Foundation Model dari Meta AI untuk Embedding Gambar Tanpa Label

DINOv2: Panduan Lengkap Vision Foundation Model dari Meta AI untuk Embedding Gambar Tanpa Label Halo temen-temen, di tut...