Модуль 12. Инфраструктура RL для LLM: память, роллауты и асинхронность
Сначала попробуйте решить упражнение. Ответы, подробные решения и проектные ориентиры раскрываются отдельно и сохраняют своё состояние при переключении RU/EN.
Скачать файлы курса (.zip) 382,1 КБАрхив содержит файлы на обоих языках и вспомогательные материалы с сохранением структуры каталогов.
Логические роли и процессы
В одном эксперименте один процесс последовательно генерирует ответы, вычисляет награду и выполняет шаг оптимизатора. В другом эксперименте эти операции распределены между четырьмя службами.
-
Назовите четыре логические роли RL-контура.
-
Обязаны ли они быть четырьмя отдельными процессами?
-
Какой инвариант данных должен сохраняться при любом размещении?
-
Почему строка ответа и одно число награды не образуют достаточный контракт роллаута?
Показать ответ и решение
Четыре основные роли:
-
тренер вычисляет функцию потерь, градиенты и шаг оптимизатора;
-
движок генерации порождает действия политики и обслуживает кэш ключей и значений (
key–value cache, KV-кэш); -
проверяющая система или среда вычисляет исход и служебный статус;
-
оркестратор выбирает запросы, формирует группы, управляет очередями, версиями и восстановлением.
Они не обязаны быть отдельными процессами. На одном графическом процессоре (graphics processing unit, GPU) один процесс может по очереди выполнять все роли; на кластере одна роль может быть распределена между десятками работников.
Инвариант: каждая обучающая запись однозначно связывает действия с фактической политикой поведения и награду — с версией проверяющей системы или среды.
Строка и одно число не сохраняют токенизацию, маску токенов политики, логарифмы вероятностей поведения, версию весов, параметры декодирования, причину остановки и происхождение награды. Без них невозможно надёжно восстановить функцию потерь и причину сбоя.
Две независимые оси
Система использует отдельный сервер vLLM, но тренер запрашивает один пакет, ждёт его целиком, обучается, синхронизирует веса и только затем запрашивает следующий.
-
Является ли размещение совместным или раздельным?
-
Является ли логический алгоритм синхронным или асинхронным?
-
Почему фраза «отдельный сервер автоматически даёт асинхронность» неверна?
-
Приведите пример совместного размещения с буферизацией и пример раздельного размещения со строгим барьером.
Показать ответ и решение
-
Размещение раздельное: генерация работает на отдельном сервере.
-
Логический режим синхронный: между пакетами стоит строгий барьер.
-
Асинхронность определяется не адресом процесса, а порядком сбора и потребления данных. Отдельный сервер может обслуживать строго последовательный протокол.
-
Совместное размещение с буферизацией: один GPU по очереди генерирует и обучается, пока центральный процессор (
central processing unit, CPU) готовит следующий набор запросов. Раздельное размещение со строгим барьером: отдельный пул vLLM генерирует пакет, затем ждёт завершения обучения и загрузки новой версии.
Профиль памяти
Коллега утверждает: «Adam всегда требует 16 байт на параметр, поэтому другого расчёта не нужно».
-
Объясните, какие объекты могут образовать эти 16 байт.
-
Назовите не менее четырёх причин, по которым фактический профиль отличается.
-
Почему деление логической памяти на число участников полностью шардированного параллелизма данных (
Fully Sharded Data Parallel, FSDP) не даёт точный пик памяти? -
Что меняет низкоранговая адаптация (
Low-Rank Adaptation, LoRA), а что она не устраняет?
Показать ответ и решение
Распространённый 16-байтовый профиль может состоять из:
- 2 байт bfloat16-параметра;
- 2 байт bfloat16-градиента;
- 8 байт двух float32-моментов Adam;
- 4 байт отдельной float32-мастер-копии.
Профиль меняется из-за точности градиентов, наличия мастер-копии, вида оптимизатора, квантования состояний, выгрузки на CPU, обучения только адаптеров, шардирования и особенностей реализации.
Деление на число участников полностью шардированного параллелизма данных (Fully Sharded Data Parallel, FSDP) не учитывает реплицируемые объекты, временную сборку модулей, коммуникационные буферы, активации, логиты, фрагментацию и момент синхронизации.
Низкоранговая адаптация (Low-Rank Adaptation, LoRA) уменьшает число параметров с градиентами и состояниями оптимизатора. Она не устраняет базовые веса, активации прямого прохода, KV-кэш, временные буферы и необходимость доставить согласованную версию адаптеров движку генерации.
KV-кэш и длина роллаутов
Размер группы в групповой относительной оптимизации политики (Group Relative Policy Optimization, GRPO) увеличили с 8 до 32 при той же максимальной длине ответа.
-
Как изменится полезная память кэша ключей и значений (
key–value cache, KV-кэш), если все последовательности активны одновременно? -
Почему реальный рост может не совпасть с идеальной линейной формулой?
-
Какие два механизма движка уменьшают потери памяти или повторную работу, но не отменяют хранения ключей и значений?
-
Почему кэш общего префикса нельзя без проверки переносить через обновление весов?
Показать ответ и решение
-
Для групповой относительной оптимизации политики (
Group Relative Policy Optimization, GRPO) в идеальной формуле память вырастет в четыре раза: число одновременно активных последовательностей увеличилось с 8 до 32. -
Реальный рост зависит от завершившихся последовательностей, распределения длин, страниц распределителя памяти, тензорного параллелизма, резервов и повторного использования префикса.
-
PagedAttention уменьшает фрагментацию управляемого KV-кэша; повторное использование префиксов или RadixAttention переиспользует общую запросную часть.
-
Ключи и значения зависят от параметров модели. После обновления весов старый кэш представляет скрытые состояния старой политики и не является кэшем новой версии.
Атомарная версия политики
Длинный ответ начал генерироваться на версии 120. После половины токенов движок загрузил версию 121. В журнал записали только policy_version=121 и итоговый текст.
-
В чём ошибка контракта?
-
Назовите два безопасных режима обновления.
-
Какие данные понадобятся, если обновление в середине ответа всё же разрешено?
-
Почему одинаковые веса при разных
top_pили температуре всё равно задают разные политики поведения?
Показать ответ и решение
В журнале одна версия приписана ответу, который фактически породила смесь двух наборов весов. Поэтому знаменатель отношения вероятностей и воспроизведение политики поведения неоднозначны.
Безопасные режимы:
- завершить уже начатые ответы версией 120 и переключать только новые запросы;
- хранить версию и логарифмы вероятностей каждого действия или хода, если смена между ходами является частью протокола.
При смене внутри ответа нужны поточечные версии, фактические логарифмы вероятностей после всех преобразований декодирования, атомарный порядок обновления и маска действий.
Температура, top-p и маски логитов меняют распределение после весов. Поэтому одинаковый трансформер не означает одинаковую политику поведения.
Задержка и рассогласование
Сразу после синхронизации движок и тренер имеют одну версию весов, но их логарифмы вероятностей одного и того же ответа немного различаются. Через десять шагов тренера очередь содержит ответы версии на восемь шагов старше.
-
Как называется первая проблема?
-
Как называется вторая?
-
Какой контрольный эксперимент разделяет эти причины?
-
Почему эффективный размер выборки (
effective sample size, ESS) является диагностикой, но не доказательством несмещённости обновления?
Показать ответ и решение
Первая проблема — рассогласование обучения и генерации (training–inference mismatch, TIM): версия одна, численные вероятности различаются.
Вторая — задержка политики (policy lag): данные породила более старая версия.
Контроль: сразу после синхронизации, при нулевой задержке, прогнать одни и те же токены через движок и тренер и сравнить логарифмы вероятностей токен за токеном. Затем отдельно изменить возраст версии.
Эффективный размер выборки (effective sample size, ESS) показывает концентрацию весов: равные веса дают полный размер выборки, доминирующий вес уменьшает его. Но высокий ESS не доказывает правильность знаменателя, отсутствие смещения из-за клиппирования, корректность масок или валидность выбранного оценивателя для данных другой политики.
Очередь и медленные хвосты
Генераторы производят 14 миллионов токенов в минуту, а тренер устойчиво потребляет 10 миллионов. Одновременно 99-й процентиль (p99) длительности роллаута в шесть раз больше медианы.
-
Какое первое условие устойчивости очереди нарушено?
-
Что такое обратное давление (
backpressure) в этой системе? -
Почему ограничение только числом записей слабее ограничения токенами или оценочной стоимостью?
-
Почему безусловное отбрасывание самых медленных эпизодов может сместить обучающее распределение?
Показать ответ и решение
-
Нарушено условие : скорость поступления 14 млн токенов/мин выше скорости потребления 10 млн.
-
Обратное давление ограничивает генераторы, когда очередь достигает порога: уменьшает выдачу задач, приостанавливает работников или меняет приоритет.
-
Две записи могут отличаться по длине в сотни раз. Число записей плохо описывает память, вычислительную стоимость и возраст данных; токены или оценочная стоимость ближе к физической нагрузке.
-
Длинные эпизоды могут быть систематически сложнее. Их отбрасывание меняет распределение задач и оставляет тренеру преимущественно простые примеры.
Контрольная точка и выбор стека
Команда сохранила только model.safetensors, затем перезапустила обучение с нулевым состоянием оптимизатора и новой версией проверяющей системы. В отчёте это назвали продолжением того же запуска.
-
Почему это неверно?
-
Перечислите не менее восьми компонентов семантически полной контрольной точки RL-запуска.
-
Как проверить восстановление до настоящей аварии?
-
Почему TRL, verl, OpenRLHF, NeMo RL, PRIME-RL, SkyRL и AReaL нельзя ранжировать одной строкой «лучше–хуже» без контракта задачи?
Показать ответ и решение
Это новый запуск с инициализацией из старых весов, а не продолжение прежнего процесса оптимизации. Сброшены моменты Adam, планировщик и контракт награды.
Семантически полная контрольная точка обычно включает:
- веса модели и адаптеров;
- состояние оптимизатора;
- состояние планировщика скорости обучения;
- глобальный шаг и версию политики;
- состояния генераторов случайных чисел;
- положение в наборе данных;
- состояние учебной программы или планировщика запросов;
- параметры нормировки наград и преимуществ;
- версии токенизатора и шаблона диалога;
- версию проверяющей системы и среды;
- конфигурацию декодирования;
- схему роллаута;
- распределённую раскладку и рецепт восстановления очереди.
Проверка восстановления: сохранить состояние, принудительно остановить процессы, восстановить систему, выполнить контрольный пакет и сравнить версии, токенизацию, конечность потерь и статистику с допустимым диапазоном.
Библиотеки и системы оптимизируют разные уровни: готовый тренер, оркестрацию ролей, полностью асинхронный поток, агентные среды или конкретную систему распределённого обучения. Сравнение без модели, масштаба, синхронности, типа среды, требований к отказоустойчивости и навыков команды не имеет однозначного смысла.
Полное обучение и адаптеры
Модель содержит параметров. Базовые веса хранятся в bfloat16, градиенты — в bfloat16, два момента Adam — в float32, отдельная мастер-копия — в float32.
-
Найдите число байт на обучаемый параметр.
-
Найдите логическую память полного обучения в GB.
-
Найдите память только базовых bfloat16-весов в GB.
-
Для низкоранговой адаптации (LoRA) добавляются и обучаются параметров адаптеров, но все базовые веса остаются в памяти. Найдите логическую память при том же профиле состояний адаптера.
-
Почему ни одно из этих чисел ещё не является пиком памяти графического процессора?
Показать ответ и решение
На обучаемый параметр приходится
Полное обучение:
Базовые bfloat16-веса:
Для LoRA базовые веса занимают 14 GB. На каждый из 20 млн обучаемых параметров учитываются сам вес адаптера, градиент, два момента и мастер-копия:
Дополнительная память:
Итого:
Пик дополнительно содержит активации, временные буферы, логиты, KV-кэш, фрагментацию, реплики и фазу передачи весов.
KV-кэш
Для учебной архитектуры заданы:
-
Сколько байт требует один сохранённый токен для ключей и значений?
-
Сколько кибибайт (KiB) это составляет?
-
Найдите память для 64 последовательностей по 4096 токенов.
-
Выразите результат в гибибайтах (GiB).
-
Каков идеализированный локальный объём при равномерном шардировании KV-кэша по четырём участникам тензорного параллелизма?
Показать ответ и решение
На один токен:
Поскольку один кибибайт (KiB) равен байтам,
Число токенов:
Память:
Здесь GiB — гибибайт, равный байтам. При идеальном равномерном шардировании по четырём участникам:
Это полезная нагрузка без страниц, таблиц блоков, выравнивания и временных тензоров.
Последовательный и перекрытый конвейер
Генерация и проверка одного пакета занимают с, обучение и синхронизация — с.
-
Найдите длительность одного последовательного пакета.
-
Найдите период идеального двухступенчатого конвейера после заполнения.
-
Найдите полное время 20 пакетов в последовательном режиме.
-
Найдите полное время 20 пакетов в идеальном конвейере.
-
Найдите ускорение по полному времени и предельное ускорение по периоду.
-
Почему ускорение не близко к двум?
Показать ответ и решение
Один последовательный пакет:
Период после заполнения:
Двадцать последовательных пакетов:
Идеальный конвейер:
Ускорение по полному времени:
Предельное ускорение по периоду:
Этапы несбалансированы: 90 секунд генерации нельзя скрыть десятисекундным обучением. Почти весь период определяется генерацией.
Медленный роллаут
Восемь параллельных роллаутов заняли
-
Найдите суммарную работу в GPU-секундах.
-
Найдите время синхронной группы.
-
Найдите доступную ёмкость прямоугольника «8 исполнителей × время группы».
-
Найдите утилизацию и долю простоя.
-
Как изменится утилизация, если роллаут длительностью 20 с можно безопасно ограничить 8 секундами? Объясните, почему такая операция не всегда статистически нейтральна.
Показать ответ и решение
Суммарная работа:
Группа завершается через
Ёмкость:
Утилизация:
Простой:
После ограничения последнего роллаута 8 секундами работа равна 43 GPU-с, время группы — 8 с, ёмкость — 64 GPU-с:
Утилизация выросла, но если длинные ответы соответствуют трудным задачам, правило усечения изменяет распределение данных и наблюдаемую награду.
Отношения вероятностей и ESS
Для четырёх последовательностей даны логарифмы отношений вероятностей:
-
Найдите веса .
-
Найдите ESS.
-
Найдите нормированный ESS.
-
Сравните с ESS четырёх одинаковых весов.
-
Как изменится ответ, если последний логарифм отношения клиппировать сверху значением ?
-
Что эти числа говорят и чего не доказывают?
Показать ответ и решение
Веса:
Сумма равна 13, сумма квадратов —
Поэтому
Нормированный ESS:
Для четырёх одинаковых весов ESS равен 4, нормированный — 1.
После клиппирования последнего логарифма сверху все веса равны :
нормированный ESS равен примерно 0.942.
Клиппирование уменьшило концентрацию, но изменило оцениватель. ESS описывает распределение весов, а не гарантирует корректную целевую функцию или хорошее качество политики.
Синхронизация и контрольные точки
Синхронизация весов занимает 8 секунд. В учебной модели цена единицы средней задержки равна одной секунде на обновление. Рассматриваются интервалы синхронизации , а приближённая цена одного обновления равна
-
Найдите для всех кандидатов и выберите минимум.
-
Каковы максимальная и стационарная средняя задержки при выбранном ?
-
Для 10 обновлений выпишите точную последовательность задержек и её среднее.
Отдельно: запись контрольной точки занимает с, среднее время между отказами равно с.
- Найдите приближённый оптимальный интервал
-
Переведите его в минуты.
-
Какие существенные расходы эта простая модель не учитывает?
Показать ответ и решение
Вычислим:
| 1 | 8.0 | 0.0 | 8.0 |
| 2 | 4.0 | 0.5 | 4.5 |
| 4 | 2.0 | 1.5 | 3.5 |
| 8 | 1.0 | 3.5 | 4.5 |
| 16 | 0.5 | 7.5 | 8.0 |
Минимум среди кандидатов достигается при .
Стационарная последовательность задержек равна , поэтому максимум — 3, среднее —
Для 10 обновлений:
Сумма равна 13, точное конечновыборочное среднее — 1.3.
Оптимальный интервал между контрольными точками:
В минутах:
Модель не учитывает время перезапуска, асинхронную запись, коррелированные отказы, стоимость сети и хранилища, частично потерянные очереди, несовместимость распределённой раскладки и изменение риска во времени.
Память модели и KV-кэша
Реализуйте функции:
def model_state_bytes(
total_parameters: int,
trainable_parameters: int,
parameter_bytes: int,
gradient_bytes: int,
optimizer_state_bytes: int,
master_parameter_bytes: int,
) -> int:
...
def kv_cache_bytes(
num_sequences: int,
tokens_per_sequence: int,
num_layers: int,
num_kv_heads: int,
head_dim: int,
element_bytes: int,
) -> int:
...Проверьте положительность размеров, условие 0 <= trainable_parameters <= total_parameters и опубликованные числа B1–B2.
Открытые проверки:
profile = model_state_bytes(
7_000_000_000,
trainable_parameters=7_000_000_000,
parameter_bytes=2,
gradient_bytes=2,
optimizer_state_bytes=8,
master_parameter_bytes=4,
)
assert profile == 112_000_000_000
lora = model_state_bytes(
7_020_000_000,
trainable_parameters=20_000_000,
parameter_bytes=2,
gradient_bytes=2,
optimizer_state_bytes=8,
master_parameter_bytes=4,
)
assert lora == 14_320_000_000
assert lora < profileПоказать ответ и решение
def model_state_bytes(
total_parameters,
trainable_parameters,
parameter_bytes,
gradient_bytes,
optimizer_state_bytes,
master_parameter_bytes,
):
integers = [
total_parameters,
trainable_parameters,
parameter_bytes,
gradient_bytes,
optimizer_state_bytes,
master_parameter_bytes,
]
if any(isinstance(x, bool) or not isinstance(x, int) for x in integers):
raise ValueError("all arguments must be integers")
if total_parameters <= 0 or not 0 <= trainable_parameters <= total_parameters:
raise ValueError("invalid parameter counts")
if min(integers[2:]) < 0:
raise ValueError("byte counts must be non-negative")
base = total_parameters * parameter_bytes
trainable_extra = trainable_parameters * (
gradient_bytes + optimizer_state_bytes + master_parameter_bytes
)
return base + trainable_extra
def kv_cache_bytes(
num_sequences,
tokens_per_sequence,
num_layers,
num_kv_heads,
head_dim,
element_bytes,
):
values = [
num_sequences,
tokens_per_sequence,
num_layers,
num_kv_heads,
head_dim,
element_bytes,
]
if any(isinstance(x, bool) or not isinstance(x, int) or x <= 0 for x in values):
raise ValueError("all dimensions must be positive integers")
return (
2
* num_sequences
* tokens_per_sequence
* num_layers
* num_kv_heads
* head_dim
* element_bytes
)Проверка:
assert model_state_bytes(7_000_000_000, 7_000_000_000, 2, 2, 8, 4) == 112_000_000_000
assert model_state_bytes(7_020_000_000, 20_000_000, 2, 2, 8, 4) == 14_320_000_000
assert kv_cache_bytes(64, 4096, 32, 8, 128, 2) == 32 * 1024**3Время конвейера
Реализуйте:
def pipeline_report(rollout_seconds: float, train_seconds: float, batches: int) -> dict[str, float]:
"""Верните sequential_total, pipeline_total, period и speedup."""Для , , функция должна вернуть последовательное время 2000 с и конвейерное время 1810 с.
Открытые проверки:
pipe_one = pipeline_report(90.0, 10.0, 1)
assert pipe_one["pipeline_total"] == 100.0
assert pipe_one["speedup"] == 1.0Показать ответ и решение
def pipeline_report(rollout_seconds, train_seconds, batches):
if rollout_seconds <= 0 or train_seconds <= 0:
raise ValueError("stage times must be positive")
if isinstance(batches, bool) or not isinstance(batches, int) or batches <= 0:
raise ValueError("batches must be a positive integer")
sequential = batches * (rollout_seconds + train_seconds)
period = max(rollout_seconds, train_seconds)
pipelined = rollout_seconds + train_seconds + (batches - 1) * period
return {
"sequential_total": sequential,
"pipeline_total": pipelined,
"period": period,
"speedup": sequential / pipelined,
}report = pipeline_report(90.0, 10.0, 20)
assert report["sequential_total"] == 2000.0
assert report["pipeline_total"] == 1810.0Веса важности и ESS
Реализуйте:
def importance_weights(current_logprobs, behavior_logprobs, max_log_ratio=None):
...
def effective_sample_size(weights, normalized=False):
...Требования:
- входы одномерны и одинаковой длины;
- логарифмы вероятностей конечны и не положительны;
- веса неотрицательны;
- нулевой суммарный квадрат весов вызывает
ValueError; - необязательное клиппирование выполняется в логарифмическом пространстве.
Открытые проверки:
new = np.array([-0.4, -0.6])
old = np.array([-0.5, -0.5])
w = importance_weights(new, old)
assert np.allclose(w, np.exp(new - old))
assert np.allclose(importance_weights(old, old), 1.0)Показать ответ и решение
import numpy as np
def importance_weights(current_logprobs, behavior_logprobs, max_log_ratio=None):
current = np.asarray(current_logprobs, dtype=float)
behavior = np.asarray(behavior_logprobs, dtype=float)
if current.ndim != 1 or current.shape != behavior.shape or current.size == 0:
raise ValueError("inputs must be non-empty 1-D arrays of equal shape")
if not np.isfinite(current).all() or not np.isfinite(behavior).all():
raise ValueError("log probabilities must be finite")
if np.any(current > 0) or np.any(behavior > 0):
raise ValueError("log probabilities must be non-positive")
log_ratio = current - behavior
if max_log_ratio is not None:
if not np.isfinite(max_log_ratio):
raise ValueError("max_log_ratio must be finite")
log_ratio = np.minimum(log_ratio, max_log_ratio)
return np.exp(log_ratio)
def effective_sample_size(weights, normalized=False):
w = np.asarray(weights, dtype=float)
if w.ndim != 1 or w.size == 0 or not np.isfinite(w).all():
raise ValueError("weights must be a finite non-empty vector")
if np.any(w < 0):
raise ValueError("weights must be non-negative")
denom = float(np.dot(w, w))
if denom == 0:
raise ValueError("at least one weight must be positive")
ess = float(w.sum() ** 2 / denom)
return ess / w.size if normalized else essПроверка контракта роллаута
Реализуйте функцию, которая возвращает список ошибок для записи с обязательными полями:
sample_id, prompt_id, policy_version,
prompt_tokens, response_tokens, behavior_logprobs,
decoding, reward, stop_reason, verifier_versionПроверьте типы, неотрицательность версии и идентификаторов токенов, конечность логарифмов вероятностей и их выравнивание с response_tokens.
Сигнатура:
def rollout_contract_errors(record: Mapping[str, Any]) -> list[str]:
...Связь с предыдущей практикой. Сопоставьте эту запись одного продолжения с многоходовой траекторией из задания C5 Модуля 11 (
validate_rollout_record). Переиспользуйте правила проверки, но явно преобразуйте формат: одного добавленияsample_id,verifier_versionиdecodingнедостаточно. Опишите соответствие полей и сведения, которые нужно сохранить в исходном агентном журнале.
Открытые проверки:
record = {
"sample_id": "s1", "prompt_id": "q1", "policy_version": 1,
"prompt_tokens": [1, 2], "response_tokens": [3, 4],
"behavior_logprobs": [-0.1, -0.2], "decoding": {"temperature": 1.0},
"reward": {"task": 1.0}, "stop_reason": "eos", "verifier_version": "v1",
}
assert rollout_contract_errors(record) == []
assert rollout_contract_errors({}) != []
assert any("missing fields" in e for e in rollout_contract_errors({}))Показать ответ и решение
import math
REQUIRED = {
"sample_id", "prompt_id", "policy_version",
"prompt_tokens", "response_tokens", "behavior_logprobs",
"decoding", "reward", "stop_reason", "verifier_version",
}
def rollout_contract_errors(record):
if not isinstance(record, dict):
return ["record must be a dict"]
errors = []
missing = sorted(REQUIRED - set(record))
if missing:
errors.append("missing fields: " + ", ".join(missing))
version = record.get("policy_version")
if isinstance(version, bool) or not isinstance(version, int) or version < 0:
errors.append("policy_version must be a non-negative integer")
for key in ("prompt_tokens", "response_tokens"):
tokens = record.get(key)
if not isinstance(tokens, list) or any(
isinstance(token, bool) or not isinstance(token, int) or token < 0
for token in (tokens or [])
):
errors.append(f"invalid {key}")
logps = record.get("behavior_logprobs")
if not isinstance(logps, list) or any(
not isinstance(value, (int, float))
or not math.isfinite(value)
or value > 0
for value in (logps or [])
):
errors.append("invalid behavior_logprobs")
elif isinstance(record.get("response_tokens"), list) and len(logps) != len(record["response_tokens"]):
errors.append("logprobs do not align with response_tokens")
return errorsПолная эталонная версия дополнительно проверяет строки, вложенные отображения и выдаёт более точные сообщения.
В М11 token_ids, policy_mask и turn_ids описывают всю траекторию, а policy_versions — версии её ходов. Здесь выберите одно продолжение, созданное одной поведенческой версией: prompt_tokens — контекст перед его генерацией, response_tokens — выбранные политикой токены этого продолжения, behavior_logprobs выровнены именно с ними. Одиночная числовая policy_version требует явного реестра версий; строку версии нельзя молча приводить к числу. prompt_id задаётся при формировании запроса. Преобразование rewards в reward и выбор stop_reason требуют объявленного правила кредитования и остановки данного продолжения; итог эпизода не следует автоматически приписывать каждому ходу. Идентификаторы среды, действия, наблюдения, происхождение токенов и флаги всей траектории остаются в исходном журнале, связанном с инфраструктурной записью. Это проекция на другую единицу данных, а не замена полного агентного контракта.
Периодическая синхронизация и контрольные точки
Реализуйте:
def synchronization_schedule(sync_every: int, updates: int) -> list[int]:
"""Верните задержки 0, 1, ..., sync_every-1 с периодическим сбросом."""
def optimal_checkpoint_interval(checkpoint_seconds: float, mtbf_seconds: float) -> float:
...Проверьте B6 и граничные случаи sync_every=1, updates=1.
Открытые проверки:
schedule = synchronization_schedule(4, 10)
assert schedule == [0, 1, 2, 3, 0, 1, 2, 3, 0, 1]Показать ответ и решение
from math import sqrt
def synchronization_schedule(sync_every, updates):
if isinstance(sync_every, bool) or not isinstance(sync_every, int) or sync_every <= 0:
raise ValueError("sync_every must be positive")
if isinstance(updates, bool) or not isinstance(updates, int) or updates <= 0:
raise ValueError("updates must be positive")
return [step % sync_every for step in range(updates)]
def optimal_checkpoint_interval(checkpoint_seconds, mtbf_seconds):
if checkpoint_seconds <= 0 or mtbf_seconds <= 0:
raise ValueError("times must be positive")
return sqrt(2 * checkpoint_seconds * mtbf_seconds)assert synchronization_schedule(1, 4) == [0, 0, 0, 0]
assert synchronization_schedule(4, 10) == [0, 1, 2, 3, 0, 1, 2, 3, 0, 1]
assert abs(optimal_checkpoint_interval(120, 86400) - 4553.679830642467) < 1e-9Диагностика по измерениям
Реализуйте правило, которое получает:
metrics = {
"trainer_idle_fraction": ...,
"rollout_idle_fraction": ...,
"verifier_fraction": ...,
"weight_sync_fraction": ...,
"queue_growth_per_minute": ...,
"normalized_ess": ...,
"oom_events": ...,
}и возвращает проверяемые гипотезы, а не окончательный диагноз. Например:
- большой простой тренера при неполной очереди → проверить генерацию, верификацию и подачу запросов;
- большая доля синхронизации → измерить передачу и преобразование весов;
- низкий ESS → разделить задержку политики и рассогласование обучения с генерацией;
- нехватка памяти графического процессора (
out of memory, OOM) → построить память по фазам.
Добавьте проверки диапазонов и тест для нейтрального случая, где явного узкого места нет.
Открытые проверки:
neutral = diagnose_pipeline({
"trainer_idle_fraction": 0.05, "rollout_idle_fraction": 0.05,
"verifier_fraction": 0.05, "weight_sync_fraction": 0.05,
"queue_growth_per_minute": 0.0, "normalized_ess": 0.9, "oom_events": 0,
})
assert neutral == ["inspect a stage-level trace"]
starved = diagnose_pipeline({
"trainer_idle_fraction": 0.6, "rollout_idle_fraction": 0.05,
"verifier_fraction": 0.1, "weight_sync_fraction": 0.05,
"queue_growth_per_minute": 0.0, "normalized_ess": 0.9, "oom_events": 0,
})
assert len(starved) == 1Показать ответ и решение
def diagnose_pipeline(metrics):
required = {
"trainer_idle_fraction",
"rollout_idle_fraction",
"verifier_fraction",
"weight_sync_fraction",
"queue_growth_per_minute",
"normalized_ess",
"oom_events",
}
missing = required - set(metrics)
if missing:
raise ValueError("missing metrics: " + ", ".join(sorted(missing)))
bounded = required - {"queue_growth_per_minute", "oom_events"}
for key in bounded:
if not 0 <= metrics[key] <= 1:
raise ValueError(f"{key} must lie in [0, 1]")
if metrics["oom_events"] < 0:
raise ValueError("oom_events must be non-negative")
hypotheses = []
if metrics["trainer_idle_fraction"] > 0.25 and metrics["queue_growth_per_minute"] <= 0:
hypotheses.append("check rollout, verifier, and input supply")
if metrics["verifier_fraction"] > 0.30:
hypotheses.append("profile the verifier critical path")
if metrics["weight_sync_fraction"] > 0.20:
hypotheses.append("profile weight transfer and conversion")
if metrics["normalized_ess"] < 0.50:
hypotheses.append("separate policy lag from training-inference mismatch")
if metrics["oom_events"] > 0:
hypotheses.append("measure phase-specific peaks, KV cache, activations, and transfer buffers")
if not hypotheses:
hypotheses.append("inspect a stage-level trace")
return hypothesesГлавный принцип решения: выводы сформулированы как гипотезы, которые можно проверить измерением. По одной агрегированной метрике нельзя надёжно определить причину.