no_grad dan detach

Mode inferensi dengan no_grad dan penghentian pelacakan dengan detach.

Daftar isi

Topik ini membahas dua cara menghentikan pelacakan autograd, yaitu konteks torch.no_grad() yang menonaktifkan pencatatan untuk seluruh blok kode dan method detach() yang memutus satu tensor dari riwayat operasinya. Keduanya lazim digunakan pada tahap inferensi (inference), evaluasi, dan penyalinan bobot, yaitu situasi ketika gradien tidak lagi dibutuhkan. Pemahaman atas perbedaan keduanya menjaga memori tetap hemat sekaligus mencegah perubahan yang tidak disengaja pada tensor asal.

Mode inferensi dengan no_grad#

Selama model digunakan untuk memprediksi, tidak ada parameter yang perlu diperbarui, sehingga graf komputasi kehilangan kegunaannya. Pencatatan graf tetap memakan memori dan waktu apabila dibiarkan berjalan. Konteks torch.no_grad() menonaktifkan pencatatan untuk semua operasi di dalam bloknya, sehingga hasil operasinya tidak membawa grad_fn dan tidak menyimpan riwayat apa pun:

python no-grad-demo.py
import torch

w = torch.tensor(2.0, requires_grad=True)

y = w * 3
print("di luar no_grad: y.requires_grad =", y.requires_grad,
      "| y.grad_fn =", type(y.grad_fn).__name__)

with torch.no_grad():
    z = w * 3
    print("di dalam no_grad: z.requires_grad =", z.requires_grad,
          "| z.grad_fn =", z.grad_fn)
text Keluaran
di luar no_grad: y.requires_grad = True | y.grad_fn = MulBackward0
di dalam no_grad: z.requires_grad = False | z.grad_fn = None

Tensor z lahir dari operasi yang sama dengan y, yaitu perkalian dengan 3, tetapi statusnya sama sekali berbeda karena pencatatan dimatikan pada saat ia dihitung. Perbedaan ini murni menyangkut waktu pembuatan, bukan isi tensor. Nilai z sama dengan nilai y, sedangkan riwayat pelacakannya tidak ada.

Pola evaluasi pada model#

Konteks torch.no_grad() biasanya membungkus seluruh perulangan evaluasi, bukan satu operasi saja. Program berikut menggunakan layer nn.Linear kecil sebagai model dan menjalankannya dalam mode inferensi:

python inferensi-model.py
import torch
import torch.nn as nn

torch.manual_seed(0)

model = nn.Linear(2, 1)
x = torch.randn(4, 2)

with torch.no_grad():
    y = model(x)

print("bentuk y                   =", tuple(y.shape))
print("y.requires_grad            =", y.requires_grad)
print("y.grad_fn                  =", y.grad_fn)
print("model.weight.requires_grad =", model.weight.requires_grad)
text Keluaran
bentuk y                   = (4, 1)
y.requires_grad            = False
y.grad_fn                  = None
model.weight.requires_grad = True

Perhatikan bahwa model.weight tetap berstatus requires_grad=True karena hakikatnya parameter, sedangkan keluaran y tidak membawa grad_fn sama sekali karena seluruh operasinya dijalankan di dalam konteks tanpa pencatatan. Kondisi inilah yang diharapkan pada evaluasi, yaitu parameter tetap utuh, tetapi tidak ada graf yang menumpuk di memori. Method model.eval() yang sering menyertai pola ini mengatur perilaku layer tertentu seperti dropout, sedangkan no_grad() mengatur pencatatan graf, sehingga keduanya saling melengkapi dan bukan saling menggantikan.

Memutus riwayat dengan detach()#

Method detach() bekerja pada satu tensor, bukan satu blok kode. Ia menghasilkan tensor baru yang berbagi penyimpanan dengan tensor asal, tetapi terputus dari riwayat operasinya, sehingga membawa status requires_grad=False dan is_leaf=True:

python detach-demo.py
import torch

w = torch.tensor(2.0, requires_grad=True)
y = w * 3

d = y.detach()

print("y.requires_grad =", y.requires_grad)
print("d.requires_grad =", d.requires_grad)
print("d.is_leaf       =", d.is_leaf)
print("nilai d         =", d.item())

try:
    d.sum().backward()
except RuntimeError as e:
    print("RuntimeError:", e)
text Keluaran
y.requires_grad = True
d.requires_grad = False
d.is_leaf       = True
nilai d         = 6.0
RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn

Percobaan backward() pada tensor hasil detach() gagal dengan pesan “element 0 of tensors does not require grad and does not have a grad_fn” karena tensor itu memang tidak terhubung ke graf mana pun. Kegagalan ini wajar dan justru menegaskan fungsinya, yaitu memutus sambungan secara sadar.

Pola detach().clone() di atas adalah bentuk baku untuk mengambil salinan bobot yang bebas pelacakan, misalnya saat menyimpan bobot sementara atau saat memindahkan nilai antara model. Apabila berbagi penyimpanan justru diinginkan untuk menghemat memori pada data yang tidak akan diubah, detach() saja sudah cukup.

Memilih antara no_grad dan detach#

Tabel berikut merangkum perbedaan keduanya:

Aspektorch.no_grad()detach()
Cakupanseluruh operasi di dalam bloksatu tensor tertentu
Bentukcontext managermethod pada tensor
Hasiloperasi tidak membentuk graftensor putus dari riwayat
Penggunaan lazimevaluasi, inferensi, pembaruan parameterpenyalinan bobot, pemutusan cabang tertentu

Keduanya bukan pilihan yang saling menggantikan. no_grad() adalah alat tingkat blok yang praktis untuk membungkus tahap utuh, sedangkan detach() adalah alat tingkat tensor untuk kasus yang memerlukan ketelitian, misalnya memutus satu cabang perhitungan sambil membiarkan cabang lain tetap terlacak. Dalam praktiknya, inferensi dan evaluasi menggunakan no_grad(), sedangkan manipulasi bobot dan pembersihan keluaran menggunakan detach().

Kesalahan umum#

Kesalahan pertama adalah memanggil backward() di dalam konteks no_grad(). Tidak ada tensor yang terlacak di dalam blok itu, sehingga pemanggilan gagal dengan pesan “element 0 of tensors does not require grad and does not have a grad_fn”. Perbaikannya adalah memindahkan pemanggilan keluar dari konteks, karena penghitungan gradien memang tidak boleh dilakukan di dalam no_grad().

Kesalahan kedua adalah menganggap detach() sebagai penyalin data. Tensor hasilnya berbagi penyimpanan dengan tensor asalnya, sehingga pengubahan melalui operasi in-place pada salah satunya ikut mengubah yang lain tanpa pesan apa pun. Perbaikannya adalah menambahkan .clone() setiap kali salinan yang berdiri sendiri dibutuhkan.

Kesalahan ketiga adalah lupa membungkus perulangan evaluasi dengan no_grad(). Tidak ada error yang muncul, tetapi memori membengkak karena graf menumpuk setiap iterasi dan proses ikut melambat. Karena gejalanya tidak langsung terlihat, kebiasaan membungkus evaluasi sebaiknya ditetapkan sejak pola kode pertama kali ditulis.

Ringkasan#

  • torch.no_grad() menonaktifkan pencatatan graf untuk seluruh blok kode, sehingga cocok untuk inferensi dan evaluasi.
  • detach() memutus satu tensor dari riwayat operasinya tanpa mengubah nilai, dan hasilnya berbagi penyimpanan dengan tensor asalnya.
  • Penyalinan bobot yang bebas pelacakan ditulis detach().clone(), bukan detach() saja.
  • Keduanya saling melengkapi, yaitu no_grad() untuk tingkat blok dan detach() untuk tingkat tensor.

Referensi#