ドキュメント

ブラウザエージェントの意思決定ヘッドとしての Laya のファインチューニング

ブラウザエージェントの意思決定ヘッドとしての Laya のファインチューニング

ゼロショットではできない意思決定ファミリー向けに Laya を特化させる、再現可能な実例です。browser-use/jev-ultrafast の次のブラウザ操作(操作 + 対象要素)を選びます。このリクエスト形式 /v1/systemone は Agent.predict(state, questions) と同じです。以下はすべて RTX 4070 Ti SUPER(16 GB)1 台で、有料 API なしで実行しました。重み・コード・実行ごとの結果は huggingface.co/cklxx/laya-browser にあります。

結果

typed-decisions、ゼロショット ファインチューニング後
未見ページでの要素 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 ms 41–50 ms(421M)、17–23 ms(322M)

実サイトのスイートは二峰性です。10 件のタスクは 3/3 で成功し(カテゴリ / タブ / ページのナビゲーション、チェックボックス、<select>、一部サイトでの検索 + 送信)、6 件は 3/3 で失敗します(入力を打ってから候補を選ぶフロー、先にスクロールが必要なページネーション、Google Flights)。実サイトでの実行ごとのばらつきは、2 つのバックボーン間の差より大きいので、両者は同等とみなし、レイテンシで選んでください。

チェックポイントは通常の 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

パイプライン

各ステップは Hub リポジトリの code/finetune/ にあるスクリプトで、run_v10.sh / run_v10s.sh が最初から最後まで実行します。

  1. クロール —— jev の DOM リーダーで実ページ 421 件(Wikipedia、GitHub、HN、arXiv、HF、デモショップ、フォームの多いテストサイト)を取得し、要素テーブルとページテキストを保持します。
  2. 目標を逆生成(5,244 件)—— ある要素を答えとして選び、それを必要とする状況をユーザーが述べる目標文をローカルの Qwen3-8B に書かせます。教師に何かを解かせる必要がないので、ラベルはクリーンです。
  3. 実際の DONE 状態(700 件)—— ブラウザでクリックを実行し、履歴付きでランディングページを DONE のケースとして記録します。
  4. ステップ 2 のネガティブ(659 件)—— それらのランディングページで履歴を保ったまま新しい目標を作り、「履歴がある」ことが DONE を予測しなくなるようにします。
  5. Mind2Web(osunlp/Mind2Web、7,296 ステップ)—— 候補を要素テーブルとして再レンダリングし、操作履歴を action_reprs から取り、型付きの値はそのフィールドの現在値として示します。
  6. オンポリシー修正(DAgger、177 件)—— 現在のモデルで実タスクを実行し、各ステップでローカル LLM に尋ね、その判定をモデル自身の state とともに保持します。
  7. 構築 → 学習 → キャリブレーション → 評価 —— Laya の RLCD レシピ(正解分布のソフトターゲット + ノイズ付きロジットの方策勾配 + ソフト CE)。GPU 1 台、勾配チェックポインティングなし、4 エポック(421M で約 2 時間、322M で約 1 時間)、事後 temperature、評価には未見ページ / ウェブサイトを使用します。

最も効いたもの:入力形式

jev の state をそのまま渡すと(ページテキスト + 要素テーブル全体を JSON として state に入れる)、1,024 トークンの窓ではテーブルの大部分が切り捨てられ、モデルは選ぶべき候補をそもそも見られないことがよくあります。要素を state の外に出して選択肢リストへ移すこと(完全なラベル + role + 現在値、head_max_len を 512 → 768 に。state には title / URL / history / 1.2〜1.5k 文字のテキストを残す)は、どんなデータ変更よりも効きました。Mind2Web のクリック top-1 は 0.44 → 0.51、実サイトスイートは同じデータで 6/16 → 10/16 になりました。

うまくいかなかったこと(同じ轍を踏まないために)

  • テンプレート化した DONE 目標(「X というタイトルのページを開き、開いたら停止する」)は言い回しが漏れます。モデルは停止条件 ⇒ DONE を学びます。DONE のサンプルは、操作を実行した後の実際のランディングページでなければなりません。
  • すべての DONE サンプルが直前の操作をちょうど 1 つ持ち、すべてのクリックサンプルが 0 個だと、モデルは履歴があれば ⇒ DONE を学びます。タスク途中のネガティブを加えてください。
  • Mind2Web だけでは DONE / TYPE_TEXT が壊れます(DONE がなく、CLICK が支配的)。まれな操作は再重み付けします(DONE ×4、TYPE_TEXT / SELECT ×3)。
  • ページテキストを 3,000 文字に切り詰めても何も節約できず(ヘッドが系列を支配するため)、top-1 が 0.04 下がりました。
  • 可変長バッチでの torch.compile は形状ごとに再コンパイルし、6 倍遅くなります。本当の無料の利得は勾配チェックポインティングを切ることでした(1.25 倍)。
  • 信頼度でゲートしてローカルの 8B や 27B の LLM にエスカレーションすると結果は悪化しました。これらのページでは、ファインチューニング済みの 322M モデルのほうが優れた判断者です(27B、思考予算 300 トークン:op acc 0.861 / top-1 0.603、ステップあたり 4.7 秒。対して 0.890 / 0.623、21 ms)。DAgger のさらなる利得にはより強い教師が必要です。
  • jev の DOM リーダーはパスワードフィールドを設計上隠し、折りたたまれたメニューも見えません。「失敗」の一部はモデルではなくフレームワークによるものです。

再現

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 があります。