Гайд · TNWS AI

Как задать straight-through gradient через jax.custom_jvp

4 мин—

Проверяемый surrogate gradient для round в JAX 0.11.2: custom_jvp, отдельные primal и tangent, forward 2 и производная 1.

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

Актуальный запрос: «как задать straight through estimator JAX custom jvp». Задать пользовательское правило прямой производной для недифференцируемого округления. Forward остаётся round, а tangent намеренно проходит как у identity — это straight-through estimator (STE). Такой gradient является оптимизационной аппроксимацией, а не истинной производной round, поэтому правило должно быть явно задокументировано и проверено отдельно.

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

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

  1. Оберните функцию округления декоратором jax.custom_jvp.
  2. Опишите defjvp с кортежами primals и tangents.
  3. Верните настоящее округлённое значение и surrogate tangent без изменения.
  4. Проверьте forward, grad и jvp на контролируемой точке.

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

import jax
import jax.numpy as jnp

@jax.custom_jvp
def ste_round(x):
    return jnp.round(x)

@ste_round.defjvp
def ste_round_jvp(primals, tangents):
    (x,), (x_dot,) = primals, tangents
    return ste_round(x), x_dot

x = jnp.array(1.7)
value = ste_round(x)
gradient = jax.grad(ste_round)(x)
_, tangent = jax.jvp(ste_round, (x,), (jnp.array(3.),))
assert value == 2.
assert gradient == 1.
assert tangent == 3.
print(value, gradient, tangent)

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

Вход 1,7 округляется до 2 в forward pass. Благодаря пользовательскому JVP grad возвращает 1, а входной tangent 3 выходит как 3. Это ожидаемое поведение STE, но математическая производная round почти везде равна нулю.

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

Проверяйте primal output независимо от tangent. Зафиксируйте тесты слева, справа и на границе округления, затем сравните обучение с baseline без STE. Не применяйте finite differences как доказательство surrogate gradient: они проверяют исходную функцию, а не выбранное оптимизационное соглашение.

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

Для рабочей модели сначала зафиксируйте контракт параметров и 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 возвращает производную написанной функции, а не подтверждает правильность математической постановки. Для критичных решений нужны предметные метрики, мониторинг и человеческий контроль.

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

  • Не называйте surrogate gradient точной производной round.
  • Не возвращайте tangent несовместимой формы или структуры.
  • Не внедряйте custom_jvp без теста композиции с jit, vmap и grad, которые использует модель.

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

FAQ

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

Нет, значение по-прежнему вычисляет jnp.round.

Почему grad равен единице?

Это явно заданное surrogate-правило STE.

Подходит ли правило любой модели?

Нет, его пользу и смещение проверяют экспериментально для конкретной задачи.

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

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

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

Нет, значение по-прежнему вычисляет `jnp.round`.

Почему grad равен единице?

Это явно заданное surrogate-правило STE.

Подходит ли правило любой модели?

Нет, его пользу и смещение проверяют экспериментально для конкретной задачи.

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

Комментарии

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