Documentação

Ajuste fino do Laya como cabeça de decisão de um agente de navegador

Um exemplo trabalhado e totalmente reproduzível de especialização do Laya numa família de decisões que ele não consegue fazer zero-shot: escolher a próxima ação do navegador (operação + elemento alvo) para o browser-use/jev-ultrafast, cujo formato de pedido /v1/systemone é igual a Agent.predict(state, questions). Tudo o que se segue correu numa única RTX 4070 Ti SUPER (16 GB) sem API paga; os pesos, o código e os resultados por execução estão em huggingface.co/cklxx/laya-browser.

Resultado

typed-decisions, zero-shot ajuste fino
top-1 de elemento em páginas reservadas (2,734 decisões, ~45 candidatos cada) 0.10 (acaso) 0.66 (421M) / 0.63 (322M)
precisão da operação (CLICK / TYPE_TEXT / SELECT / DONE) 0.54 0.88–0.89
16 tarefas reais de navegador, 3 execuções cada 0 % 62 % (322M), 50 % (421M)
latência por passo (3 perguntas, 30–65 candidatos) 50–200 ms 41–50 ms (421M), 17–23 ms (322M)

A suite em direto é bimodal: 10 tarefas passam 3/3 (navegação por categoria / separador / página, checkbox, <select>, pesquisa + submissão em alguns sites) e 6 falham 3/3 (fluxos de escrever e depois escolher uma sugestão, paginação que precisa de um scroll primeiro, Google Flights). A variância entre execuções em sites em direto é maior do que a diferença entre os dois backbones, por isso trata-os como equivalentes e escolhe pela latência.

Os checkpoints são diretórios de checkpoint normais do 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

Pipeline

Cada passo é um script em code/finetune/ do repositório do Hub; run_v10.sh / run_v10s.sh executam-no de ponta a ponta.

  1. Rastrear 421 páginas reais (Wikipedia, GitHub, HN, arXiv, HF, lojas de demonstração, sites de teste com muitos formulários) com o leitor de DOM do jev, mantendo a tabela de elementos e o texto da página.
  2. Gerar objetivos inversamente (5,244): escolhe um elemento como resposta, pede a um Qwen3-8B local que escreva o objetivo que um utilizador enunciaria para precisar dele. Nenhum professor tem de resolver nada, por isso as etiquetas são limpas.
  3. Estados DONE reais (700): executa o clique no navegador e regista a página de destino com o histórico como um caso DONE.
  4. Negativos do passo 2 (659): novos objetivos nessas páginas de destino com o histórico mantido, para que «ter um histórico» deixe de prever DONE.
  5. Mind2Web (osunlp/Mind2Web, 7,296 passos): candidatos renderizados novamente como tabela de elementos, histórico de ações a partir de action_reprs, valores tipados mostrados como o valor atual do campo.
  6. Correções on-policy (DAgger, 177): executa tarefas reais com o modelo atual, pergunta a um LLM local a cada passo e guarda o seu veredicto com o estado do próprio modelo.
  7. Construir → treinar → calibrar → avaliar: a receita RLCD do Laya (alvos suaves de distribuição gold + gradiente de política com logits ruidosos + CE suave), uma única GPU, sem gradient checkpointing, 4 épocas (~2 h para 421M, ~1 h para 322M), temperatura post-hoc, páginas/sites reservados para avaliação.

O que mais importou: o formato de entrada

Com o estado do jev passado à letra (texto da página + toda a tabela de elementos como JSON dentro de state), a janela de 1,024 tokens trunca a maior parte da tabela, por isso o modelo muitas vezes nunca vê o candidato que devia escolher. Mover os elementos para fora do estado e para a lista de opções (etiqueta completa + função + valor atual, head_max_len 512 → 768; o estado mantém o título / URL / histórico / 1.2–1.5k caracteres de texto) valeu mais do que qualquer alteração nos dados: top-1 de clique no Mind2Web 0.44 → 0.51 e a suite em direto 6/16 → 10/16 nos mesmos dados.

O que não funcionou (para não o repetires)

  • Objetivos DONE em modelo («Abre a página com o título X, para quando estiver aberta») deixam escapar o fraseado; o modelo aprende parar quando ⇒ DONE. As amostras DONE têm de ser páginas de destino reais após uma ação executada.
  • Se cada amostra DONE tiver exatamente uma ação anterior e cada amostra de clique nenhuma, o modelo aprende qualquer histórico ⇒ DONE. Acrescenta negativos a meio da tarefa.
  • Só o Mind2Web mata DONE / TYPE_TEXT (não há DONE lá, CLICK domina). Repondera as operações raras (DONE ×4, TYPE_TEXT / SELECT ×3).
  • Truncar o texto da página para 3,000 caracteres não poupou nada (a cabeça domina a sequência) e custou 0.04 de top-1.
  • O torch.compile em lotes de comprimento variável recompila por forma: 6× mais lento. Desligar o gradient checkpointing foi o verdadeiro ganho grátis (1.25×).
  • O escalonamento com gate de confiança para um LLM local de 8B ou 27B tornou os resultados piores; nestas páginas o modelo de 322M com ajuste fino é o melhor decisor (27B com um orçamento de raciocínio de 300 tokens: 0.861 de precisão de operação / 0.603 de top-1 a 4.7 s por passo, contra 0.890 / 0.623 a 21 ms). É preciso um professor mais forte para mais ganhos com DAgger.
  • O leitor de DOM do jev esconde os campos de palavra-passe por design e nunca vê menus recolhidos; algumas «falhas» são a framework, não o modelo.

Reproduzir

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

O code/finetune/README.md desse repositório tem todos os números intermédios, da primeira tentativa à final, e results/ contém os JSONs da suite por execução por detrás da tabela acima.