چیتشیت
JAX در برابر PyTorch
JAX against PyTorch
مشتقگیری خودکار نواری و بلادرنگ در برابر توابع خالص با ردگیری و کامپایلشده. پیادهسازی یک MLP مشابه برای MNIST در هر دو فریمورک همراه با APIها و نکات مهم.
فهرست مطالب
مقایسه شانه به شانه
مقایسه| معیار | PyTorch | JAX |
|---|---|---|
| مشتقگیری خودکار | مشتقگیری خودکار مبتنی بر نوار (Tape) | تبدیلهای تابعی |
| اجرا | بلادرنگ / Eager (کرنلهای C++) | ردگیری محاسبات + کامپایلشده با JIT |
| مدل | nn.Module | تابع خالص: f(params, x) -> y |
| پشتیبانی از TPU | نگهداریشده توسط جامعه کاربری (torch_xla) | درجه یک (پیشفرض و بومی) |
| پشتیبانی از GPU | بهترین در این رده (CUDA بومی) | خوب (CUDA از طریق XLA) |
| دیباگ | آسان (بلادرنگ، خط به خط) | دشوار (ردگیری محاسبات + کامپایل) |
| سرعت (MLP روی MNIST) | ~0.5 ثانیه بر ایپاک | ~0.3 ثانیه بر ایپاک |
| چنددستگاهی | FSDP | jax.pmap(f) |
وظیفه یکسان، دو API
مقایسه| وظیفه | PyTorch | JAX |
|---|---|---|
| محاسبه گرادیانها | loss.backward() | jax.grad(loss_fn)(params, x, y) |
| صفر کردن گرادیانها | optimizer.zero_grad() | نیاز نیست. گرادیانها مقدار بازگشتی هستند |
| بهروزرسانی پارامترها | optimizer.step() | optax.apply_updates(params, updates) |
| بهینهساز Adam | torch.optim.Adam(model.parameters(), lr=1e-3) | optax.adam(learning_rate=1e-3) |
| دستهبندی روی نمونهها | for x in batch: | jax.vmap(f) |
| نگهداری پارامترها | model.parameters() | پایتری تغییرناپذیر از آرایهها |
| بذردهی تصادفی | torch.manual_seed | jax.random.PRNGKey(42) |
| ذخیره وزنها | torch.save(model.state_dict(), "model.pt") | checkpointer.save('/tmp/model', params) |
دو مدل اجرا
معمارییک گام آموزش JAX
گامبهگام- init_paramsدستور
random.split(key, 3)سه کلید میدهد. مقیاس He برابر باjnp.sqrt(2.0 / 784)است. - optimizer.initدستور
opt_state = optimizer.init(params)وضعیت بهینهساز Adam را نگه میدارد. - jax.value_and_gradتابع زیان (loss) و گرادیانها را در یک دور رفت برمیگرداند.
- optimizer.updateگرادیانها را به مقادیر بهروزرسانی و یک
opt_stateجدید تبدیل میکند. - optax.apply_updatesپارامترهای جدید را برمیگرداند. هیچ متغیری مستقیماً دستکاری نمیشود.
- نخستین فراخوانیجیاکس
train_stepرا ردگیری و به XLA کامپایل میکند. ایپاک اول کند است. - فراخوانیهای بعدیپایتون را دور میزند. ایپاکهای 2 تا 10 سریع هستند. 10 ایپاک حدود 97٪ دقت آزمون میدهد.
حلقه آموزش MLP برای MNIST در PyTorch
کدpython
model = MNISTModel().to(device) # 784, 256, 128, 10
criterion = nn.CrossEntropyLoss() # لاجیتهای خام را دریافت میکند
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
for epoch in range(10):
model.train() # دراپاوت فعال است
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad() # گرادیانها انباشته میشوند
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward() # نوار محاسباتی را بازپخش میکند
optimizer.step()
torch.save(model.state_dict(), "mnist_mlp.pt") # ~97.8٪ دقت آزمون
حلقه آموزش MLP برای MNIST در JAX
کدpython
optimizer = optax.adam(learning_rate=1e-3)
@jax.jit # کامپایل به XLA
def train_step(params, opt_state, x, y):
loss, grads = jax.value_and_grad(loss_fn)(params, x, y)
updates, opt_state = optimizer.update(grads, opt_state, params)
params = optax.apply_updates(params, updates)
return params, opt_state, loss # خروجی وضعیت جدید
key = random.PRNGKey(0)
params = init_params(key) # پایتری با دیکشنریهای تودرتو
opt_state = optimizer.init(params)
for epoch in range(n_epochs):
key, subkey = random.split(key) # کلید جدید در هر ایپاک
perm = random.permutation(subkey, len(X_train))
params, opt_state, loss = train_step(params, opt_state, xb, yb) # اندازه دسته: 128
تبدیلهای JAX
کار و کد| وظیفه | کد |
|---|---|
| مشتقگیری از یک تابع | jax.grad(f) |
| مشتق مرتبه دوم | jax.grad(jax.grad(f)) |
| دستهبندی تابعی که تکنمونه میگیرد | jax.vmap(predict, in_axes=(None, 0)) |
| گرادیانها به ازای هر نمونه | jax.vmap(jax.grad(loss_fn), in_axes=(None, 0, 0)) |
| توزیع دسته بین دستگاهها | jax.pmap(train_step, axis_name='devices') |
| بهروزرسانی تمام برگها | jax.tree.map(lambda p, g: p - lr * g, params, grads) |
| مقداردهی به یک عنصر | a = a.at[0].set(5) |
| برش گرادیان، سپس Adam | optax.chain(optax.clip_by_global_norm(1.0), optax.adam(1e-3)) |
نصب و اجرا
دستورها| دستور | عملکرد |
|---|---|
pip install jax jaxlib optax flax | نصب JAX، Optax و Flax |
pip install jax[cuda12] | نصب JAX با پشتیبانی از GPU |
pip install jax[tpu] -f <url> | نصب JAX برای TPU روی Google Cloud |
XLA_PYTHON_CLIENT_PREALLOCATE=false | غیرفعالسازی تخصیص اولیه 75٪ از حافظه GPU |
jax.debug.print() | چاپ پیام داخل یک تابع جیتشده |
fast_fn(x).block_until_ready() | انتظار برای نتیجه قبل از متوقف کردن تایمر |
torch.load("model.pt", weights_only=True) | بارگذاری دیکشنری وضعیت پارامترها ذخیرهشده |
نکات و خطاهای رایج
عیبیابیExpected all tensors to be on the same device- علت
- مدل و دسته روی دستگاههای متفاوتی هستند
- راهحل
- انتقال هر دو با استفاده از
.to(device)
گرادیانهای نادرست بدون هیچ خطا
- علت
- ارسال خروجی سافتمکس به
CrossEntropyLoss - راهحل
- ارسال لاجیتهای خام
گام اول کند است
- علت
- کامپایل JIT در نخستین فراخوانی اتفاق میافتد
- راهحل
- گرمکردن اولیه قبل از بنچمارک
خطای
if x > 0درون تابع جیتشده- علت
- جریان کنترل برنامه به مقدار تحت ردگیری وابسته است
- راهحل
- استفاده از
jax.lax.cond
کندی حلقههای پایتون درون تابع جیتشده
- علت
- حلقه زدن روی آرایههای JAX
- راهحل
- استفاده از
jax.lax.scanیاjax.lax.fori_loop
خطای
a[0] = 5- علت
- آرایههای JAX تغییرناپذیر هستند
- راهحل
- نوشتن
a = a.at[0].set(5)
کدام را چه زمانی انتخاب کنیم
نکتهها- از PyTorch استفاده کنید، مگر اینکه دلیل مشخصی برای رفتن به سراغ JAX داشته باشید.
- از PyTorch برای دیباگ خط به خط استفاده کنید؛
print()،pdbوbreakpoint()بهخوبی کار میکنند. - از PyTorch برای ساخت پیشنمونه با سرعت بالا و دسترسی به اکوسیستم HuggingFace استفاده کنید.
- از JAX زمانی استفاده کنید که به TPU دسترسی دارید.
- از JAX برای محاسبه گرادیانها به ازای هر نمونه استفاده کنید.
- از JAX برای آموزش چنددستگاهی در مقیاسهای بسیار بزرگ استفاده کنید.
پرسشها
پرسش و پاسخ- چرا JAX به توابع خالص نیاز دارد؟
- برای کامپایل JIT ضروری است. ورودیهای یکسان باید همیشه خروجیهای یکسان تولید کنند.
- چرا در JAX دستوری به نام
zero_gradوجود ندارد؟ - تابع
jax.gradگرادیانها را بهصورت مقادیر خروجی برمیگرداند. تنسورها گراف محاسباتی ذخیره نمیکنند. - چرا باید
state_dict()را ذخیره کرد نه خود شیء مدل را؟ - شیء مدل از pickle استفاده میکند. با بازآرایی کد (Refactor)، ساختار pickle میشکند.
- چرا در ارزیابی از
torch.no_grad()استفاده میشود؟ - مشتقگیری خودکار را غیرفعال میکند و میزان مصرف حافظه کاهش مییابد.
- چه مفاهیمی از مینیفریمورک متناظر هستند؟
- کلاسهای
SequentialوAdam، توابعzero_grad،backward،step،trainوeval.