Фаза 03 · урок 11

Введение в PyTorch

Цель урока: У вас есть работающий мини-фреймворк. Линейные слои, ReLU, dropout, batch norm, Adam, DataLoader , цикл обучения. Он обучает 4-слойную сеть на задаче классификации кругов на чистом Python.

Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.

Курс
AI Engineering from Scratch
Фаза
Основы глубокого обучения
Чтение
20 мин.
Проверено
Содержание урока
  1. Цели обучения
  2. Проблема
  3. Концепция
  4. Почему победил PyTorch
  5. Тензоры
  6. Autograd
  7. nn.Module
  8. Функции потерь и оптимизаторы
  9. Цикл обучения
  10. Dataset и DataLoader
  11. Обучение на GPU
  12. Сравнение: мини-фреймворк, PyTorch и JAX
  13. Соберите это
  14. Шаг 1: загрузите MNIST из необработанных файлов
  15. Шаг 2: определите модель
  16. Шаг 3: цикл обучения
  17. Шаг 4: свяжите всё вместе
  18. Используйте это
  19. Быстрое сравнение: мини-фреймворк и PyTorch
  20. Сохранение и загрузка моделей
  21. Планирование скорости обучения
  22. Выпустите это
  23. Упражнения
  24. Ключевые термины
  25. Дополнительное чтение

Вы построили двигатель из поршней и коленвалов. Теперь научитесь пользоваться тем, на котором ездит каждый.

Тип: Сборка Языки: Python Предварительные требования: Урок 03.10 (Создайте собственный мини-фреймворк) Время: ~75 минут

Цели обучения

  • Создавать и обучать нейронные сети с помощью PyTorch: nn.Module, nn.Sequential и autograd
  • Использовать тензоры PyTorch, ускорение на GPU и стандартный цикл обучения (zero_grad, forward, loss, backward, step)
  • Переносить компоненты своего мини-фреймворка, созданного с нуля, на их эквиваленты в PyTorch
  • Профилировать и сравнивать скорость обучения вашего чисто Python-фреймворка и PyTorch на одной задаче

Проблема

У вас есть работающий мини-фреймворк. Линейные слои, ReLU, dropout, batch norm, Adam, DataLoader, цикл обучения. Он обучает 4-слойную сеть на задаче классификации кругов на чистом Python.

Он также в 500 раз медленнее PyTorch на той же задаче.

Ваш мини-фреймворк обрабатывает по одному примеру за раз во вложенных циклах Python. PyTorch отправляет те же операции в оптимизированные ядра C++/CUDA, работающие на GPU. На одном NVIDIA A100 PyTorch обучает ResNet-50 (25,6 млн параметров) на ImageNet (1,28 млн изображений) примерно за 6 часов. На эту же задачу вашему фреймворку потребовалось бы примерно 3 000 часов — если бы он прежде не исчерпал память.

Скорость — не единственный разрыв. У вашего фреймворка нет поддержки GPU. Нет автоматического дифференцирования: вы вручную писали backward() для каждого модуля. Нет сериализации. Нет распределённого обучения. Нет смешанной точности. Нет способа отладить поток градиентов без операторов print.

PyTorch закрывает каждый из этих пробелов. И делает это, сохраняя точно такую же ментальную модель, которую вы уже построили: Module, forward(), parameters(), backward(), optimizer.step(). Концепции переносятся один к одному. Синтаксис почти идентичен. Разница в том, что PyTorch скрывает за тем же интерфейсом десятилетие системной инженерии, которую вы спроектировали с нуля.

Концепция

Почему победил PyTorch

В 2015 году TensorFlow требовал определить статический вычислительный граф до выполнения чего-либо. Вы строили граф, компилировали его, затем подавали в него данные. Отладка означала разглядывание визуализаций графа. Изменение архитектуры означало перестроить граф с нуля.

PyTorch появился в 2017 году с другой философией: немедленным выполнением (eager execution). Вы пишете Python. Он выполняется сразу. y = model(x) действительно вычисляет y прямо сейчас, а не «добавляет узел в граф, который когда-нибудь вычислит y». Поэтому работали стандартные инструменты отладки Python. Работал print(). Работал pdb. Работал if/else в вашем прямом проходе.

К 2020 году рынок высказался. Доля PyTorch в исследовательских статьях по ML выросла с 7% (2017) до более 75% (2022). Meta, Google DeepMind, OpenAI, Anthropic и Hugging Face используют PyTorch как основной фреймворк. TensorFlow 2.x в ответ внедрил немедленное выполнение — молчаливое признание правильности дизайна PyTorch.

Урок: опыт разработчика накапливается. Фреймворк, который на 10% медленнее, но на 50% быстрее отлаживается, побеждает каждый раз.

Тензоры

Тензор — это многомерный массив с тремя критически важными свойствами: формой (shape), типом данных (dtype) и устройством (device).

import torch

x = torch.zeros(3, 4)           # shape: (3, 4), dtype: float32, device: cpu
x = torch.randn(2, 3, 224, 224) # batch of 2 RGB images, 224x224
x = torch.tensor([1, 2, 3])     # from a Python list

Форма — это размерность. Скаляр имеет форму (), вектор — (n,), матрица — (m, n), пакет изображений — (batch, channels, height, width).

Dtype управляет точностью и памятью.

dtype Биты Диапазон Вариант использования
float32 32 ~7 десятичных знаков Обучение по умолчанию
float16 16 ~3,3 десятичных знака Смешанная точность
bfloat16 16 Тот же диапазон, что у float32, меньшая точность Обучение LLM
int8 8 от -128 до 127 Квантованный инференс

Устройство определяет, где происходит вычисление.

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
x = torch.randn(3, 4, device=device)
x = x.to("cuda")
x = x.cpu()

Каждая операция требует, чтобы все тензоры находились на одном устройстве. Это ошибка PyTorch №1, с которой сталкиваются начинающие: RuntimeError: Expected all tensors to be on the same device. Исправьте её, переместив всё на одно устройство до вычисления.

Изменение формы выполняется за константное время: меняются метаданные, а не данные.

x = torch.randn(2, 3, 4)
x.view(2, 12)      # reshape to (2, 12) -- must be contiguous
x.reshape(6, 4)    # reshape to (6, 4) -- works always
x.permute(2, 0, 1) # reorder dimensions
x.unsqueeze(0)     # add dimension: (1, 2, 3, 4)
x.squeeze()        # remove size-1 dimensions

Autograd

Ваш мини-фреймворк требовал реализовать backward() для каждого модуля. PyTorch не требует. Он записывает каждую операцию над тензорами в ориентированный ациклический граф (вычислительный граф), а затем проходит этот граф в обратном порядке, чтобы автоматически вычислить градиенты.

Диаграмма к уроку «Введение в PyTorch»

Ключевое отличие от вашего фреймворка: PyTorch использует ленточное автоматическое дифференцирование (tape-based autodiff). Каждая операция добавляется на «ленту» во время прямого прохода. Вызов .backward() воспроизводит ленту в обратном порядке.

x = torch.randn(3, requires_grad=True)
y = x ** 2 + 3 * x
z = y.sum()
z.backward()
print(x.grad)  # dz/dx = 2x + 3

Три правила autograd:

  1. Только листовые тензоры с requires_grad=True накапливают градиенты.
  2. По умолчанию градиенты накапливаются — вызывайте optimizer.zero_grad() перед каждым обратным проходом.
  3. torch.no_grad() отключает отслеживание градиентов (используйте при оценке).

nn.Module

nn.Module — базовый класс для каждого компонента нейронной сети в PyTorch. Вы уже строили эту абстракцию в уроке 10. Версия PyTorch добавляет автоматическую регистрацию параметров, рекурсивное обнаружение модулей, управление устройствами и сериализацию state_dict.

import torch.nn as nn

class MLP(nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim):
        super().__init__()
        self.layer1 = nn.Linear(input_dim, hidden_dim)
        self.relu = nn.ReLU()
        self.layer2 = nn.Linear(hidden_dim, output_dim)

    def forward(self, x):
        x = self.layer1(x)
        x = self.relu(x)
        x = self.layer2(x)
        return x

Когда вы присваиваете nn.Module или nn.Parameter атрибуту в __init__, PyTorch автоматически регистрирует его. model.parameters() рекурсивно собирает каждый зарегистрированный параметр. Поэтому вам никогда не нужно вручную собирать веса, как в мини-фреймворке.

Ключевые строительные блоки:

Модуль Что делает Параметры
nn.Linear(in, out) Wx + b in*out + out
nn.Conv2d(in_ch, out_ch, k) Двумерная свёртка in_chout_chk*k + out_ch
nn.BatchNorm1d(features) Нормализует активации 2 * features
nn.Dropout(p) Случайное зануление 0
nn.ReLU() max(0, x) 0
nn.GELU() Gaussian error linear 0
nn.Embedding(vocab, dim) Таблица поиска vocab * dim
nn.LayerNorm(dim) Нормализация по примеру 2 * dim

Функции потерь и оптимизаторы

PyTorch поставляет готовые к промышленному использованию версии всего, что вы построили.

Функции потерь (из torch.nn):

Функция потерь Задача Вход
nn.MSELoss() Регрессия Любая форма
nn.CrossEntropyLoss() Многоклассовая классификация Логиты (не softmax)
nn.BCEWithLogitsLoss() Бинарная классификация Логиты (не sigmoid)
nn.L1Loss() Регрессия (робастная) Любая форма
nn.CTCLoss() Выравнивание последовательностей Логарифмы вероятностей

Примечание: CrossEntropyLoss внутренне объединяет LogSoftmax + NLLLoss. Передавайте исходные логиты, а не выходы softmax. Это распространённая ошибка, которая незаметно производит неправильные градиенты.

Оптимизаторы (из torch.optim):

Оптимизатор Когда использовать Типичная LR
SGD(params, lr, momentum) CNN, хорошо настроенные конвейеры 0.01–0.1
Adam(params, lr) Стартовая точка по умолчанию 1e-3
AdamW(params, lr, weight_decay) Трансформеры, дообучение 1e-4–1e-3
LBFGS(params) Малый масштаб, второй порядок 1.0

Цикл обучения

Каждый цикл обучения PyTorch следует одному и тому же пятишаговому шаблону. Вы уже знаете его из урока 10.

Диаграмма к уроку «Введение в PyTorch»

Канонический шаблон:

for epoch in range(num_epochs):
    model.train()
    for inputs, targets in train_loader:
        inputs, targets = inputs.to(device), targets.to(device)
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, targets)
        loss.backward()
        optimizer.step()

Пять строк внутри цикла пакета. Пять строк, которые обучили GPT-4, Stable Diffusion и LLaMA. Меняется архитектура. Меняются данные. Эти пять строк не меняются.

Dataset и DataLoader

Dataset в PyTorch — абстрактный класс с двумя методами: __len__ и __getitem__. DataLoader оборачивает его, добавляя пакетирование, перемешивание и многопроцессную загрузку данных.

from torch.utils.data import Dataset, DataLoader

class MNISTDataset(Dataset):
    def __init__(self, images, labels):
        self.images = images
        self.labels = labels

    def __len__(self):
        return len(self.labels)

    def __getitem__(self, idx):
        return self.images[idx], self.labels[idx]

loader = DataLoader(dataset, batch_size=64, shuffle=True, num_workers=4)

num_workers=4 запускает 4 процесса, параллельно загружающих данные, пока GPU обучается на текущем пакете. На нагрузках, ограниченных диском (крупные изображения, аудио), уже одно это может удвоить скорость обучения.

Обучение на GPU

Перемещение модели на GPU:

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)

Это рекурсивно переносит каждый параметр и буфер на GPU. Затем перемещайте каждый пакет при обучении:

inputs, targets = inputs.to(device), targets.to(device)

Смешанная точность сокращает потребление памяти вдвое и удваивает пропускную способность на современных GPU (A100, H100, RTX 4090), выполняя прямой/обратный проход в float16 при сохранении основных весов в float32:

from torch.amp import autocast, GradScaler

scaler = GradScaler()
for inputs, targets in loader:
    with autocast(device_type="cuda"):
        outputs = model(inputs)
        loss = criterion(outputs, targets)
    scaler.scale(loss).backward()
    scaler.step(optimizer)
    scaler.update()
    optimizer.zero_grad()

Сравнение: мини-фреймворк, PyTorch и JAX

Возможность Мини-фреймворк (L10) PyTorch JAX
Автодифференцирование Ручной backward() Ленточный autograd Функциональные преобразования
Выполнение Немедленное (циклы Python) Немедленное (ядра C++) Трассировка + JIT-компиляция
Поддержка GPU Нет Да (CUDA, ROCm, MPS) Да (CUDA, TPU)
Скорость (MNIST MLP) ~300 с/эпоху ~0,5 с/эпоху ~0,3 с/эпоху
Система модулей Пользовательский класс Module nn.Module Функции без состояния (Flax/Equinox)
Отладка print() print(), pdb, breakpoint() Сложнее (JIT-трассировка ломает print)
Экосистема Нет Hugging Face, Lightning, timm Flax, Optax, Orbax
Кривая обучения Вы её построили Умеренная Крутая (функциональная парадигма)
Использование в продакшене Игрушечные задачи Meta, OpenAI, Anthropic, HF Google DeepMind, Midjourney
dropout-mask

Соберите это

3-слойный MLP, обученный на MNIST только с помощью примитивов PyTorch. Без высокоуровневых обёрток. Без torchvision.datasets. Мы сами загрузим и разберём необработанные данные.

Шаг 1: загрузите MNIST из необработанных файлов

MNIST поставляется в 4 сжатых gzip-файлах: обучающие изображения (60 000 x 28 x 28), обучающие метки, тестовые изображения (10 000 x 28 x 28), тестовые метки. Мы загружаем их и разбираем двоичный формат.

import torch
import torch.nn as nn
import struct
import gzip
import urllib.request
import os

def download_mnist(path="./mnist_data"):
    base_url = "https://storage.googleapis.com/cvdf-datasets/mnist/"
    files = [
        "train-images-idx3-ubyte.gz",
        "train-labels-idx1-ubyte.gz",
        "t10k-images-idx3-ubyte.gz",
        "t10k-labels-idx1-ubyte.gz",
    ]
    os.makedirs(path, exist_ok=True)
    for f in files:
        filepath = os.path.join(path, f)
        if not os.path.exists(filepath):
            urllib.request.urlretrieve(base_url + f, filepath)

def load_images(filepath):
    with gzip.open(filepath, "rb") as f:
        magic, num, rows, cols = struct.unpack(">IIII", f.read(16))
        data = f.read()
        images = torch.frombuffer(bytearray(data), dtype=torch.uint8)
        images = images.reshape(num, rows * cols).float() / 255.0
    return images

def load_labels(filepath):
    with gzip.open(filepath, "rb") as f:
        magic, num = struct.unpack(">II", f.read(8))
        data = f.read()
        labels = torch.frombuffer(bytearray(data), dtype=torch.uint8).long()
    return labels

Шаг 2: определите модель

3-слойный MLP: 784 -> 256 -> 128 -> 10. Активации ReLU. Dropout для регуляризации. Без batch norm, чтобы оставить пример простым.

class MNISTModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(784, 256),
            nn.ReLU(),
            nn.Dropout(0.2),
            nn.Linear(256, 128),
            nn.ReLU(),
            nn.Dropout(0.2),
            nn.Linear(128, 10),
        )

    def forward(self, x):
        return self.net(x)

Выходной слой выдаёт 10 необработанных логитов (по одному на цифру). Без softmax: CrossEntropyLoss обрабатывает его внутри.

Число параметров: 784256 + 256 + 256128 + 128 + 128*10 + 10 = 235 146. По современным меркам крошечная модель. У GPT-2 small 124M. Эта обучается за секунды.

Шаг 3: цикл обучения

Канонический шаблон прямой проход — потеря — обратный проход — шаг.

def train_one_epoch(model, loader, criterion, optimizer, device):
    model.train()
    total_loss = 0
    correct = 0
    total = 0
    for images, labels in loader:
        images, labels = images.to(device), labels.to(device)
        optimizer.zero_grad()
        outputs = model(images)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        total_loss += loss.item() * images.size(0)
        _, predicted = outputs.max(1)
        correct += predicted.eq(labels).sum().item()
        total += labels.size(0)
    return total_loss / total, correct / total


def evaluate(model, loader, criterion, device):
    model.eval()
    total_loss = 0
    correct = 0
    total = 0
    with torch.no_grad():
        for images, labels in loader:
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)
            loss = criterion(outputs, labels)
            total_loss += loss.item() * images.size(0)
            _, predicted = outputs.max(1)
            correct += predicted.eq(labels).sum().item()
            total += labels.size(0)
    return total_loss / total, correct / total

Обратите внимание на torch.no_grad() при оценке. Он отключает autograd, уменьшая потребление памяти и ускоряя инференс. Без него PyTorch строит вычислительный граф, который вы никогда не используете.

Шаг 4: свяжите всё вместе

def main():
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    download_mnist()
    train_images = load_images("./mnist_data/train-images-idx3-ubyte.gz")
    train_labels = load_labels("./mnist_data/train-labels-idx1-ubyte.gz")
    test_images = load_images("./mnist_data/t10k-images-idx3-ubyte.gz")
    test_labels = load_labels("./mnist_data/t10k-labels-idx1-ubyte.gz")

    train_dataset = torch.utils.data.TensorDataset(train_images, train_labels)
    test_dataset = torch.utils.data.TensorDataset(test_images, test_labels)
    train_loader = torch.utils.data.DataLoader(
        train_dataset, batch_size=64, shuffle=True
    )
    test_loader = torch.utils.data.DataLoader(
        test_dataset, batch_size=256, shuffle=False
    )

    model = MNISTModel().to(device)
    criterion = nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

    num_params = sum(p.numel() for p in model.parameters())
    print(f"Device: {device}")
    print(f"Parameters: {num_params:,}")
    print(f"Train samples: {len(train_dataset):,}")
    print(f"Test samples: {len(test_dataset):,}")
    print()

    for epoch in range(10):
        train_loss, train_acc = train_one_epoch(
            model, train_loader, criterion, optimizer, device
        )
        test_loss, test_acc = evaluate(
            model, test_loader, criterion, device
        )
        print(
            f"Epoch {epoch+1:2d} | "
            f"Train Loss: {train_loss:.4f} | Train Acc: {train_acc:.4f} | "
            f"Test Loss: {test_loss:.4f} | Test Acc: {test_acc:.4f}"
        )

    torch.save(model.state_dict(), "mnist_mlp.pt")
    print(f"\nModel saved to mnist_mlp.pt")
    print(f"Final test accuracy: {test_acc:.4f}")

Ожидаемый результат после 10 эпох: ~97,8% точности на тесте. Время обучения на CPU: ~30 секунд. На GPU: ~5 секунд. В вашем мини-фреймворке с той же архитектурой: ~45 минут.

Используйте это

Быстрое сравнение: мини-фреймворк и PyTorch

Мини-фреймворк (урок 10) PyTorch
model = Sequential(Linear(784, 256), ReLU(), ...) model = nn.Sequential(nn.Linear(784, 256), nn.ReLU(), ...)
pred = model.forward(x) pred = model(x)
optimizer.zero_grad() optimizer.zero_grad()
grad = criterion.backward() then model.backward(grad) loss.backward()
optimizer.step() optimizer.step()
Нет GPU model.to("cuda")
Ручной обратный проход для каждого модуля Autograd обрабатывает всё

Интерфейс почти идентичен. Разница — во всём, что находится под капотом.

Сохранение и загрузка моделей

torch.save(model.state_dict(), "model.pt")

model = MNISTModel()
model.load_state_dict(torch.load("model.pt", weights_only=True))
model.eval()

Всегда сохраняйте state_dict() (словарь параметров), а не объект модели. Сохранение объекта модели использует pickle, который ломается при рефакторинге кода. State dict переносимы.

Планирование скорости обучения

scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
    optimizer, T_max=10
)
for epoch in range(10):
    train_one_epoch(model, train_loader, criterion, optimizer, device)
    scheduler.step()

PyTorch поставляет более 15 планировщиков: StepLR, ExponentialLR, CosineAnnealingLR, OneCycleLR, ReduceLROnPlateau. Все подключаются к одному интерфейсу оптимизатора.

Выпустите это

Этот урок создаёт два артефакта:

  • outputs/prompt-pytorch-debugger.md — промпт для диагностики распространённых сбоев обучения PyTorch
  • outputs/skill-pytorch-patterns.md — справочник-практика по шаблонам обучения PyTorch

Упражнения

  1. Добавьте batch normalization. Вставьте nn.BatchNorm1d после каждого линейного слоя (перед активацией). Сравните точность теста и скорость обучения с версией только с dropout. Batch norm должен достичь 98%+ за меньшее число эпох.

  2. Реализуйте поиск скорости обучения. Обучайте одну эпоху с экспоненциально возрастающей скоростью обучения (от 1e-7 до 1.0). Постройте функцию потерь относительно LR. Оптимальная LR находится непосредственно перед тем, как функция потерь начинает расти. Используйте это для выбора лучшей LR для модели MNIST.

  3. Перенесите на GPU со смешанной точностью. Добавьте torch.amp.autocast и GradScaler в цикл обучения. Измерьте пропускную способность (примеров в секунду) с смешанной точностью и без неё на GPU. На A100 ожидайте ~2x ускорение.

  4. Создайте пользовательский Dataset. Скачайте Fashion-MNIST (тот же формат, что и MNIST, но с предметами одежды). Реализуйте класс FashionMNISTDataset(Dataset) с __getitem__ и __len__. Обучите тот же MLP и сравните точность. Fashion-MNIST сложнее: ожидайте ~88% против ~98%.

  5. Замените Adam на SGD + momentum. Обучайте с SGD(params, lr=0.01, momentum=0.9). Сравните кривые сходимости. Затем добавьте планировщик CosineAnnealingLR и проверьте, догонит ли SGD Adam к 10-й эпохе.

Ключевые термины

Термин Как обычно говорят Что это действительно означает
Тензор «Многомерный массив» Типизированный массив, учитывающий устройство и имеющий поддержку автоматического дифференцирования в каждой операции
Autograd «Автоматическое обратное распространение» Ленточная система, записывающая операции во время прямого прохода, затем воспроизводящая их в обратном порядке для вычисления точных градиентов
nn.Module «Слой» Базовый класс для любого блока дифференцируемых вычислений: регистрирует параметры, поддерживает вложение, обрабатывает режимы train/eval
state_dict «Веса модели» OrderedDict, сопоставляющий имена параметров с тензорами: переносимое сериализуемое представление обученной модели
.backward() «Вычислить градиенты» Обойти вычислительный граф в обратном порядке, вычисляя и накапливая градиенты для каждого листового тензора с requires_grad=True
.to(device) «Перенести на GPU» Рекурсивно перенести все параметры и буферы на указанное устройство (CPU, CUDA, MPS)
DataLoader «Конвейер данных» Итератор, который пакетирует, перемешивает и при необходимости параллелит загрузку данных из Dataset
Смешанная точность «Использовать float16» Обучать с float16 в прямом/обратном проходе для скорости, сохраняя основные веса float32 для численной стабильности
Немедленное выполнение «Выполнить сейчас» Операции выполняются сразу при вызове, а не откладываются до последующей компиляции: ключевой выбор дизайна, отличающий PyTorch от TF 1.x
zero_grad «Сбросить градиенты» Установить все градиенты параметров в ноль перед следующим обратным проходом, поскольку PyTorch по умолчанию накапливает градиенты

Дополнительное чтение

  • Paszke et al., «PyTorch: An Imperative Style, High-Performance Deep Learning Library» (2019) — оригинальная статья, объясняющая компромиссы дизайна PyTorch
  • PyTorch Tutorials: «Learning PyTorch with Examples» (https://pytorch.org/tutorials/beginner/pytorch_with_examples.html) — официальный путь от тензоров к nn.Module
  • PyTorch Performance Tuning Guide (https://pytorch.org/tutorials/recipes/recipes/tuning_guide.html) — смешанная точность, воркеры DataLoader, закреплённая память и другие оптимизации для продакшена
  • Horace He, «Making Deep Learning Go Brrrr» (https://horace.io/brrr_intro.html) — почему обучение на GPU быстрое, с оптимизациями, специфичными для PyTorch

Источник: Introduction to PyTorch 03.10 — Создайте собственный мини-фреймворк · Фаза 3 — Основы глубокого обучения · Полный каталог · 03.12 — Введение в JAX