Гайд · TNWS AI

Как ограничить глобальную норму градиентов в JAX

4 мин—

Проверяемый gradient clipping по общей L2-норме PyTree: tree.leaves, единый scale, норма 13→5 и защита от деления на ноль.

Задача и применимость

Актуальный запрос: «как сделать gradient clipping global norm JAX pytree». Ограничить общую L2-норму всех листьев gradient pytree одним коэффициентом, сохранив направление вектора градиента. Это не jnp.clip поэлементно: сначала суммируются квадраты всех листьев, затем каждый лист умножается на min(1, max_norm/(norm+eps)). Единый scale принципиален для сохранения относительных компонентов.

Материал применим к JAX 0.11.2 и jaxlib 0.11.2. JAX предоставляет массивы, автоматическое дифференцирование и программные преобразования; он не является готовой библиотекой слоёв, датасетов или полного тренировочного цикла. Пример намеренно мал и выполняется локально на доступном backend без загрузки весов и внешних данных. Численный критерий доказывает поведение конкретного кода, но не качество рабочей нейросети.

Пошаговое решение

  1. Получите листья градиентов через jax.tree.leaves.
  2. Сложите vdot каждого листа и возьмите квадратный корень.
  3. Рассчитайте единый scale с верхней границей один.
  4. Примените scale через jax.tree.map и повторно измерьте норму.

Готовый проверяемый шаблон

import jax
import jax.numpy as jnp

grads = {"w": jnp.array([3., 4.]), "b": jnp.array(12.)}
leaves = jax.tree.leaves(grads)
global_norm = jnp.sqrt(sum(jnp.vdot(x, x) for x in leaves))
max_norm = 5.
scale = jnp.minimum(1., max_norm / (global_norm + 1e-6))
clipped = jax.tree.map(lambda x: x * scale, grads)
clipped_norm = jnp.sqrt(sum(jnp.vdot(x, x) for x in jax.tree.leaves(clipped)))
assert jnp.allclose(global_norm, 13.)
assert clipped_norm <= max_norm + 1e-5
assert jax.tree.structure(clipped) == jax.tree.structure(grads)
print(global_norm, clipped_norm, scale)

Вход и ожидаемый результат

Лист [3,4] имеет квадрат нормы 25, scalar leaf 12 — 144; общая норма равна 13. При пороге 5 единый коэффициент около 5/13 уменьшает итоговую норму до 4.9999995 и сохраняет pytree.

Как проверить результат

Проверьте три случая: норма выше порога, ниже порога и все нулевые листья. В первом итоговая норма не превышает max_norm; во втором scale равен единице; в третьем нет NaN. Логируйте долю шагов с clipping, а не чувствительные значения градиентов.

Перенос на настоящую нейросеть

Для рабочей модели сначала зафиксируйте контракт параметров и batch: pytree structure, формы, dtype, порядок признаков и семантику выходов. Генерацию случайностей оформите как явный поток keys. Разделите чистые функции forward, loss и update, чтобы их можно было независимо тестировать, дифференцировать и компилировать. Данные train, validation и test не должны смешиваться. Любое изменение формы, precision, backend или версии JAX проверяйте на сохранённом обезличенном regression-наборе до развёртывания.

Критерии приёмки

  1. Все формы и dtype совпадают с контрактом.
  2. Каждый PRNG subkey используется в одной логической случайной операции.
  3. Loss и gradients конечны на положительном тесте.
  4. Негативный вход вызывает ожидаемую ошибку, а не случайный output.
  5. Обычный и преобразованный через jit/vmap путь численно согласованы, когда это применимо.
  6. Устройство, версии JAX/jaxlib и численный допуск записаны в отчёте.

Регрессионный протокол

Храните несколько малых входов: стандартный batch, один объект, нули, граничные значения и заведомо невалидную форму. Сравнивайте outputs и loss с эталоном через allclose с явно выбранным допуском. После смены backend проверяйте допустимую численную погрешность, а не требуйте безусловной битовой идентичности. Производительность измеряйте после компиляционного прогрева и завершения асинхронной работы. Не выбирайте лучший единичный замер: сообщайте распределение задержки и условия машины.

Диагностика расхождений

Если результат изменился, локализуйте слой: данные и preprocessing, key-поток, shapes/dtype, forward, loss, gradients, update, затем jit и backend. Сначала выполните функцию без jit на минимальном входе, затем верните transformations по одной. Сравните tree structure и каждый leaf. Не увеличивайте допуск и не применяйте nan_to_num, пока не выяснено, не скрывает ли это неверную форму, переполнение или повтор ключа.

Контроль после внедрения

В эксплуатации ограничьте допустимые batch shapes и объём входа, чтобы запрос не исчерпал память и не создал бесконечный поток перекомпиляций. Собирайте агрегированные метрики latency, compile count, долю невалидных входов и численные ошибки без записи исходных чувствительных данных. Сохраняйте предыдущий проверенный артефакт и код параметров для отката. Автоматическая замена весов требует той же независимой проверки, что и обычный выпуск.

Границы применимости

Пример на CPU не доказывает свойства GPU или TPU, а маленький массив не предсказывает производительность большой модели. JIT может ускорить повторяющиеся вычисления, но стоимость компиляции, передача данных и новые формы способны ухудшить end-to-end время. Autodiff возвращает производную написанной функции, а не подтверждает правильность математической постановки. Для критичных решений нужны предметные метрики, мониторинг и человеческий контроль.

Типичные ошибки и чего не делать

  • Не заменяйте global norm clipping поэлементным jnp.clip.
  • Не используйте отдельный scale для каждого leaf, если нужен общий векторный порог.
  • Не скрывайте постоянный взрыв градиентов одним clipping без поиска причины.

Не публикуйте только удачный вывод без негативного теста и версий среды. Не используйте тестовую выборку для настройки модели. Не загружайте непроверенные параметры и не передавайте в диагностику персональные данные. Для важных решений предусмотрите безопасный отказ.

FAQ

Сохраняется ли направление градиента?

Да, все листья умножаются на один положительный коэффициент.

Зачем epsilon?

Чтобы избежать деления на ноль при нулевой норме.

Меняется ли pytree?

Структура и формы листьев сохраняются.

Официальные источники

Частые вопросы

Сохраняется ли направление градиента?

Да, все листья умножаются на один положительный коэффициент.

Зачем epsilon?

Чтобы избежать деления на ноль при нулевой норме.

Меняется ли pytree?

Структура и формы листьев сохраняются.

Читайте также

Комментарии

Пока тихо. Скажите первое слово