Документация

Дообучение Laya как головы решений для браузерного агента

Проработанный и полностью воспроизводимый пример специализации Laya для семейства решений, которое она не может выполнять zero-shot: выбор следующего действия браузера (операция + целевой элемент) для browser-use/jev-ultrafast, чей формат запроса /v1/systemone совпадает с Agent.predict(state, questions). Всё ниже выполнялось на одной RTX 4070 Ti SUPER (16 ГБ) без платного API; веса, код и результаты по каждому запуску лежат на huggingface.co/cklxx/laya-browser.

Результат

typed-decisions, zero-shot дообученная
top-1 элемента на отложенных страницах (2,734 решения, ~45 кандидатов в каждом) 0.10 (случайно) 0.66 (421M) / 0.63 (322M)
точность операции (CLICK / TYPE_TEXT / SELECT / DONE) 0.54 0.88–0.89
16 реальных браузерных задач, по 3 запуска 0 % 62 % (322M), 50 % (421M)
задержка на шаг (3 вопроса, 30–65 кандидатов) 50–200 мс 41–50 мс (421M), 17–23 мс (322M)

Живой набор бимодален: 10 задач проходят 3/3 (навигация по категории / вкладке / странице, checkbox, <select>, поиск + отправка на некоторых сайтах), а 6 проваливают 3/3 (сценарии «набрать-затем-выбрать-подсказку», пагинация, требующая сначала прокрутки, Google Flights). Разброс между запусками на живых сайтах больше, чем разрыв между двумя бэкбонами, поэтому считайте их эквивалентными и выбирайте по задержке.

Чекпойнты — это обычные каталоги чекпойнтов Laya:

agent = laya.load("laya-browser/v10s")                       # after huggingface-cli download cklxx/laya-browser
agent.cfg["head_max_len"] = agent.cfg["head_max_len_train"]  # 768; the config records the input format too

Пайплайн

Каждый шаг — это скрипт в code/finetune/ репозитория на Hub; run_v10.sh / run_v10s.sh выполняют его от начала до конца.

  1. Обход 421 реальной страницы (Wikipedia, GitHub, HN, arXiv, HF, демо-магазины, сайты с обилием форм) с помощью DOM-ридера jev, сохраняя таблицу элементов и текст страницы.
  2. Обратная генерация целей (5,244): выберите элемент как ответ и попросите локальный Qwen3-8B написать цель, которую пользователь сформулировал бы, чтобы он ему понадобился. Ни одному учителю не нужно ничего решать, поэтому метки чистые.
  3. Реальные состояния DONE (700): выполните клик в браузере и запишите страницу приземления вместе с историей как случай DONE.
  4. Негативы из шага 2 (659): новые цели на этих страницах приземления с сохранённой историей, чтобы «наличие истории» перестало предсказывать DONE.
  5. Mind2Web (osunlp/Mind2Web, 7,296 шагов): кандидаты перерисованы как таблица элементов, история действий из action_reprs, типизированные значения показаны как текущее значение поля.
  6. Коррекции on-policy (DAgger, 177): запустите реальные задачи с текущей моделью, спрашивайте локальный LLM на каждом шаге, сохраняйте его вердикт вместе с собственным состоянием модели.
  7. Сборка → обучение → калибровка → оценка: рецепт RLCD от Laya (мягкие цели в виде gold-распределения + градиент политики по зашумлённым логитам + мягкая перекрёстная энтропия), одна GPU, без gradient checkpointing, 4 эпохи (~2 ч для 421M, ~1 ч для 322M), пост-хок температура, отложенные страницы / сайты для оценки.

Что важнее всего: формат входа

Когда состояние jev передаётся дословно (текст страницы + вся таблица элементов как JSON внутри state), окно в 1,024 токена обрезает большую часть таблицы, поэтому модель часто вообще не видит того кандидата, который должна выбрать. Перенос элементов из состояния в список вариантов (полная метка + роль + текущее значение, head_max_len 512 → 768; состояние хранит заголовок / URL / историю / 1.2–1.5k символов текста) дал больше, чем любое изменение данных: top-1 клика на Mind2Web 0.44 → 0.51, а живой набор 6/16 → 10/16 на тех же данных.

Что не сработало (чтобы вы не повторяли)

  • Шаблонные цели DONE («Open the page titled X, stop once it is open») утекают формулировкой; модель учится останавливаться, когда ⇒ DONE. Образцы DONE должны быть реальными страницами приземления после выполненного действия.
  • Если в каждом образце DONE ровно одно предшествующее действие, а в каждом образце клика — ни одного, модель учится любая история ⇒ DONE. Добавьте негативы из середины задачи.
  • Mind2Web сам по себе убивает DONE / TYPE_TEXT (там нет DONE, доминирует CLICK). Перевзвесьте редкие операции (DONE ×4, TYPE_TEXT / SELECT ×3).
  • Урезание текста страницы до 3,000 символов ничего не сэкономило (голова доминирует в последовательности) и стоило 0.04 top-1.
  • torch.compile на батчах переменной длины перекомпилирует под каждую форму: в 6 раз медленнее. Отключение gradient checkpointing было настоящим бесплатным выигрышем (1.25×).
  • Эскалация с gating по уверенности к локальному LLM на 8B или 27B сделала результаты хуже; на этих страницах дообученная модель 322M — лучший решатель (27B с бюджетом в 300 токенов на размышление: 0.861 точности операции / 0.603 top-1 при 4.7 с на шаг, против 0.890 / 0.623 при 21 мс). Для дальнейшего выигрыша от DAgger нужен более сильный учитель.
  • DOM-ридер jev скрывает поля паролей по замыслу и никогда не видит свёрнутые меню; некоторые «сбои» — это фреймворк, а не модель.

Воспроизведение

huggingface-cli download cklxx/laya-browser --local-dir laya-browser
cd laya-browser/code && uv sync --extra fast
uv run python verify.py v10s            # downloads the checkpoint, answers one recorded browser step

code/finetune/README.md в том репозитории содержит все промежуточные числа от первой попытки до финальной, а results/ хранит JSON-файлы набора по каждому запуску, стоящие за таблицей выше.