MobileGym: A Verifiable and Highly Parallel Simulation Platform for Mobile GUI Agent Research

Paper: arXiv:2605.26114 Code: Purewhiter/mobilegym Code reference: main @ 8cf1e6af (2026-05-27)

1. Motivation (研究动机)

Mobile GUI agent 现在已经能从截图和自然语言指令出发操作手机,但训练与评测环境仍然卡在一个根本矛盾上:真实日常 App 覆盖面高但不可控,emulator 环境可复现但重、慢、覆盖窄。AndroidWorld / AndroidLab 这类 emulator benchmark 可以做较稳定的系统工具或开源 App 评测,但每个实例通常需要数 GB 内存,在线 RL 很难并行扩展;MobileBench-OL 这类真机 benchmark 能覆盖真实日常 App,却要面对账号、后端状态、App 版本漂移、真实资金/消息/删除操作等不可控因素,几乎无法安全地 reset、fork 和并行 rollout。

论文把这个问题拆成四个结构性障碍:

  • unreadable:日常 App 的余额、订单、私信、设置等关键状态不容易通过 adb / accessibility tree 可靠读出;VLM judge 只能看离散截图,既贵又容易误判。
  • unwritable:可复现实验需要把环境恢复到某个初始状态,但真实 App 的状态分散在本地缓存、私有存储和远程服务里,无法像游戏环境一样随意写入。
  • unforkable:GRPO 等 group-based RL 需要从同一个初始状态复制出多条 rollout;真实手机和 App 后端很难提供低成本、严格相同的状态分叉。
  • irreversible:发消息、转账、注销账号、删除数据等操作不能在真实系统中任意试错,这让训练和安全评估都受限。

MobileGym 试图解决的不是“再做一个更强 agent”,而是给移动 GUI agent 提供一个可验证、可写入、可 fork、低成本并行的研究环境。它的目标是保留 agent 真实需要学习的 interaction surface:屏幕、触控/输入反馈、导航、跨 App 跳转、任务相关状态变化,而不是复刻 Android 内核或真实商业后端。这个问题值得研究,因为一旦环境可控,移动 GUI agent 才能像 web/game/robotics simulator 那样进行大规模在线 RL、可复现实验、side-effect 安全诊断和低成本 benchmark 迭代。

2. Idea (核心思想)

核心洞察是:GUI agent 看到的是截图,执行的是离散动作;因此训练环境不必复刻真实 App 后端,只要复刻足够真实的交互语义,并把完整环境状态显式表示成结构化 JSON,就能同时获得交互保真和可验证性。MobileGym 用 browser-hosted Android-like simulator 承载 28 个模拟 App,把 world data、per-environment runtime overlay 和 OS runtime state 分层,让同一份结构化状态既能渲染界面,又能 reset / snapshot / fork / diff / judge。

关键创新可以概括为三点:第一,用结构化状态替代黑盒 App 后端,使 benchmark verdict 和 RL reward 可以由 deterministic state-based judge 给出;第二,用轻量浏览器实例替代 Android emulator,使单机可托管数百个并行环境,论文报告约 400 MB/instance、约 3 s cold start;第三,用 AnswerSheet 协议把 query task 的答案提交也变成 GUI 表单状态,避免自由文本匹配把“同义答案”误拒或把“推理里泄露 gold answer”误接收。

与 AndroidWorld / AndroidLab 的主要差异是:后者在 emulator 上评测真实/开源系统组件,verification 通常依赖 adb、UI tree、LLM verifier 或 app snapshot;MobileGym 则主动构造“可读写的模拟日常 App”,用 JSON state 做 ms-level snapshot/restore 和全环境 diff。与 MobileBench-OL 的差异是:MobileBench-OL 接触真实 App,但真机状态不可 fork、XPath rule 脆弱、无法做大规模并行 RL;MobileGym 牺牲真实后端,换取可控、可并行和可验证。

3. Method (方法)

3.1 Overall framework:从 task template 到 rollout / judge / reward

Figure 2 解读:这张图展示 MobileGym 的闭环。左侧先把自然语言 task template 绑定参数并 patch 到初始 structured state,例如把联系人、消息、订单等写入 runtime overlay;中间把同一个初始状态 clone 成多条并行 rollout,agent 只看到 screenshots,并通过 Tap / Type / Swipe / Home / Back / Wait / Drag / Complete 等动作交互;右侧用 state diff 检查最终状态是否满足预期目标、是否有 unexpected side effects;最后把判断结果转成 benchmark metrics 和 RL reward。这里最关键的是 state 同时服务于渲染、fork、verification 和 reward,不再需要额外的截图 VLM judge 作为主路径。

MobileGym 的执行单元是 browser page / context / browser 中的一个 Android-like simulator。论文报告在一个 server 上可跑数百个并行实例;released code 中 bench_env/env/pool.py 提供 pagescontextsbrowsers 三种 isolation,bench_env/run.py 暴露 --parallel--processes--isolation--browsers,对应论文中“低成本并行 rollout”的工程入口。

3.2 Interaction fidelity:只复刻 agent 需要交互的表面

Figure 1 解读:示例屏幕说明 MobileGym 并不是抽象文本环境,而是保留移动端 GUI 的视觉和操作界面:launcher、消息页、状态栏、按钮、输入框等都以截图形式暴露给 agent。agent 仍然需要从视觉状态中定位目标、点击、输入、返回、切换 App;但研究者在背后可以直接控制 JSON state。这种折中使环境既不像纯 API benchmark 那样脱离 GUI,又不像真机/模拟器那样被黑盒状态和真实后端锁住。

论文称目标是 interaction fidelity,不是 pixel-perfect Android 或真实商业后端复刻。它实现的是 agent 会接触到的交互语义:视觉 screen、touch/type response、navigation、cross-app handoff、permission / notification / keyboard / back-key dispatch、task-relevant state transition。这样做的直觉是:如果策略学习的关键是“看见页面状态 → 执行 GUI 动作 → 观察页面变化”,那么环境需要准确建模这些可观测/可操作的闭环,而不是复制不可观测的服务端实现。

3.3 Layered state model:World Data + Runtime Overlay + OS Runtime

Figure 3 解读:MobileGym 把状态分为三层。World Data 是大规模、基本只读的公共实体,例如帖子、商品、车站、地点;Runtime Overlay 是每个环境实例中可变的小状态,例如当前用户资料、草稿、点赞、设置、发送消息、对 world entity 的覆盖;OS Runtime 提供任务栈、生命周期、back dispatch、键盘、通知、共享资源、权限和 intent routing。App view 在渲染时把 runtime overlay 覆盖到 world data 上,而 benchmark 的 snapshot / reset / judge 主要信任 structured state 中可变的 runtime 部分。

这种分层有两个好处。第一,snapshot 很小:只复制每个 rollout 真正会变的 overlay,而不是复制完整 App 数据集。第二,judge 更精确:由于 agent 的写操作最终都会落到 runtime state,MobileGym 可以比较 episode 初始和终止状态,判断哪些 diff 是 task expected changes,哪些是 unexpected side effects。例如 agent 完成订票但顺手改了网络设置,截图 judge 可能看不到,state diff 可以直接发现。

released code 对应关系比较清楚:docs/platform/architecture.md 明确说明 __SIM__.getState() 返回一个包含 osapps 的 JSON;bench_env/env/mobile_gym.pyget_state() / set_state() 通过 __SIM__ 读取和 patch 状态;apps/*/data/defaults.jsonos/data/defaults.json 和各 App 的 state/navigation 文件承载 world/default/runtime 结构。

3.4 Task template 与参数化实例化

MobileGym-Bench 包含 416 个 parameterized task templates,分为 256 test 和 160 train,覆盖 28 个 App(12 everyday + 16 system)。每个 template 不是一个固定实例,而是在运行时通过三类变化实例化:

  • instruction variation:同一个目标有多个语义等价表述;
  • parameter sampling:slot values 从 curated sets、数值范围或当前环境状态中采样;
  • environment configuration:rollout 前把联系人、订单历史、消息会话、系统设置等注入初始状态。

论文报告有限参数范围已经可产生超过 27,000 个 distinct task instances,还不包括连续范围。released code 中 bench_env/task/sampler.pyTaskSampler 负责从 schema、env state 或 custom sampler 中采样;bench_env/task/base.pyBaseTask.setup() 顺序是 reset → warm/open apps → _prepare()get_state() → sample params → _post_sample() → initial observation。这个顺序保证 sampler 能看到 App store 已经创建后的默认状态,_post_sample() 也能根据最终参数写入任务相关初始状态。

3.5 State-based judging 与 diagnostics

MobileGym 的主评测路径是 programmatic state verification。每个 task 实现 check_goals(input) 或继承 CriteriaTask / AnswerTask 的通用逻辑,输入包含初始 state、终止 state、route 和 answer state。JudgeResult 不是只返回 success,而是拆成:

  • success:目标状态是否达成;
  • clean:是否没有 unexpected state changes;
  • progress:goal checks 通过比例;
  • issues:未通过的具体字段;
  • warnings:未预期 state diff。

最终 benchmark metrics 包括 SR(Success Rate)、PR(Progress Rate)、FC(False Complete:agent 说 COMPLETE 但没有真正成功)、OT(Overdue Termination:目标已达成但 agent 继续到截断)和 USE(Unexpected Side Effects)。released code 中 EpisodeResult.success 要求 stop_reason == COMPLETEjudge.passed,而 judge.passed = success and clean,这意味着“达成目标但产生副作用”不会被算作完整成功。

3.6 AnswerSheet:把 query answer 变成 GUI 表单状态

Figure 4 解读:传统 query task 经常要求 agent 最后输出自由文本,再由 string similarity 或 substring heuristic 判断答案;这会误拒等价表达,也会误收“推理文本中包含 gold answer 但最终答案不规范”的情况。AnswerSheet 把回答动作改成打开一个 GUI 表单 App,字段有类型和格式 hint,agent 必须像真实手机填表一样提交。judge 检查的是 answer_sheet app 的 typed state,而不是聊天输出文本。

在 released code 中,Controller.setup() 如果检测到 task 有 answer_fields 且环境支持 state injection,就向 apps.answer_sheet 注入 questionhintfields、空 answerssubmitted=False,并把 task instruction 追加“打开 答题卡 APP 在里面回答问题并提交”。Evaluator._evaluate_with_state() 随后读取 answer_sheet state;若是无自定义 check_goalsAnswerTask,会走 grounded exact field comparison;若 task 同时有 custom goals,则把表单答案 hydrate 成 JudgeInput.answer 后再让 task 自己评测。论文 Appendix 还指出 AnswerSheet 对弱模型是 conservative gate:在 19 个 AnswerSheet task 上 trained model 的 sim/real 表现接近(sim 71.1% vs real 73.7%),而 base model 在 real free-text 下高于 simulation AnswerSheet,这说明它更像格式门槛,不是泄题。

3.7 RL reward:PR-shaped dense signal + penalties

论文中的 GRPO 训练 reward 来自结构化 rollout artifact。令 为目标检查通过比例;AnswerSheet task 如果提交了表单但任何 answer field 错误,就去掉 answer_sheet.submitted 这类 bookkeeping check 后重新计算 ,避免“提交错误表单”拿到额外进度分。随后使用乘法惩罚: 这里 goal success 表示目标状态达成,clean 表示没有 unexpected state changes,false complete 表示 agent 用 COMPLETE 结束但不是 full success,post-success abort 表示已达目标却用 ABORT 结束,overdue 表示已达目标但持续行动直到截断。直觉上,reward 既鼓励部分进度,又避免 agent 通过乱改状态、错误提交、提前宣称完成或达成后不停止来刷分。

论文公式与 released code 实现差异:当前 Purewhiter/mobilegym release 包含 simulator、benchmark runner、state judge、task definitions 和 README 中的训练 recipe,但未包含 GRPO 训练 launch script / experiment config;因此上面的 GRPO hyperparameters 与 reward 公式来自论文 Appendix G / README,而不是可复现的训练脚本。另一个细节是论文 Appendix 写通用模型评测使用 loop_detect=100.8 s action wait;released CLI 的 bench_env/run.py / bench_env/config.py 默认 loop_detect=0(released code default loop_detect=0),--delay-after-action parser default 为 1.0,所以复现实验时应显式设置与论文一致的运行参数,不能只依赖 CLI default。

3.8 Python-style pseudocode:关键组件

3.8.1 参数化 task setup

async def setup_mobilegym_task(task, env):
    # bench_env/task/base.py::BaseTask.setup
    await env.reset(app_ids=task.apps or None)
 
    if len(task.apps) > 1:
        await env.warm_apps(task.apps)
    elif len(task.apps) == 1:
        await env.open_app(task.apps[0], wait_stable=True)
 
    await task._prepare(env)
    state = await env.get_state(required_apps=task.apps or None)
 
    if task.sampler and env.supports_state_injection:
        sampled = task.sampler.sample(state, task=task)
        for name, value in sampled.params.items():
            if name not in task.user_params:
                task.params[name] = value
 
    if env.supports_state_injection:
        await task._post_sample(env)
 
    return await env.get_observation()

3.8.2 AnswerSheet 注入与 grounded evaluation

async def inject_answersheet_if_needed(task, env, eval_mode="grounded"):
    # bench_env/runner/base.py::Controller.setup
    obs = await task.setup(env)
    if eval_mode == "grounded" and task.answer_fields and env.supports_state_injection:
        await env.set_state({
            "apps": {
                "answer_sheet": {
                    "question": task.resolve_answer_question() or task.description,
                    "hint": getattr(task, "answer_hint", None),
                    "fields": task.resolve_answer_fields(),
                    "answers": {},
                    "submitted": False,
                }
            }
        }, deep=True, reload=False)
        task.task_name = task.description + " 然后打开 答题卡 APP 在里面回答问题并提交"
    return obs
 
 
def evaluate_answersheet_task(task, init_obs, final_obs, exec_result):
    sheet_state = final_obs.state["apps"].get("answer_sheet", {})
    judge_input = JudgeInput(init_obs=init_obs, last_obs=final_obs)
 
    if task.has_answer_fields() and task.has_no_custom_check_goals():
        checks = build_grounded_checks(task, judge_input, sheet_state)
        return task._evaluate_with_checks(judge_input, checks)
 
    if sheet_state.get("submitted"):
        judge_input.answer = flatten(sheet_state.get("answers", {}))
    return task.evaluate(judge_input)

3.8.3 state diff judge 与 side-effect filtering

def evaluate_with_state_diff(task, init_state, final_state):
    # bench_env/task/base.py + bench_env/task/judge.py
    judge_input = JudgeInput(init_obs=init_state, last_obs=final_state)
    checks = task.check_goals(judge_input)
 
    passed = [c["passed"] for c in checks]
    goal_success = all(passed) if passed else task.is_successful(judge_input)
    progress = sum(passed) / max(len(passed), 1)
 
    all_diffs = StateComparator.diff_states(init_state, final_state)
    expected = task.get_expected_changes()
    unexpected = StateComparator.filter_unexpected_changes(all_diffs, expected)
 
    return JudgeResult(
        success=goal_success,
        clean=(len(unexpected) == 0),
        progress=progress,
        issues=[c for c in checks if not c["passed"]],
        warnings=unexpected,
    )

3.8.4 并行 rollout pool

async def run_parallel_benchmark(tasks, agent_factory, env_url, parallel=96):
    # bench_env/env/pool.py + bench_env/runner/parallel.py
    async with EnvPool(url=env_url, n=parallel, isolation="pages") as pool:
        queue = asyncio.Queue()
        for task in tasks:
            queue.put_nowait(task)
 
        async def worker(env):
            agent = agent_factory()
            while not queue.empty():
                task = await queue.get()
                init_obs, _ = await Controller.setup(env, task, eval_mode="grounded")
                exec_result, init_obs, final_obs, episode, task = await Controller.run(
                    env, agent, task, init_obs,
                    max_steps=task.max_steps,
                    loop_threshold=10,
                )
                judge = await Evaluator(judge_mode="state").evaluate(
                    task, init_obs, final_obs, exec_result, episode
                )
                yield EpisodeResult.from_parts(task, exec_result, judge)
 
        return await asyncio.gather(*(worker(env) for env in pool))

3.8.5 GRPO reward shaping(论文配置;released repo 未给训练脚本)

def mobilegym_dense_reward(judge, exec_result, answer_sheet_wrong=False):
    # Paper Appendix G reward; not found as a released training script.
    p = judge.progress
    if answer_sheet_wrong:
        p = recompute_progress_without_bookkeeping_submit(judge)
 
    reward = p
    if judge.success and not judge.clean:
        reward *= 0.8
    if exec_result.stop_reason == "COMPLETE" and not (judge.success and judge.clean) and p > 0:
        reward *= 0.8
    if judge.success and exec_result.stop_reason == "ABORT":
        reward *= 0.5
    if exec_result.truncated and judge.success:
        reward *= 0.5
    return reward

3.9 Code-to-paper mapping

Code reference: main @ 8cf1e6af (2026-05-27) — pseudocode and mapping based on this commit

Paper ConceptSource FileKey Class/Function
Browser-hosted simulator / screenshot-only interactionbench_env/env/mobile_gym.py, bench_env/env/base.pyMobileGymEnv, ActionType, Observation, StepResult
Parallel lightweight rollout poolbench_env/env/pool.py, bench_env/runner/parallel.pyEnvPool, ParallelRunner.from_config
Unified benchmark CLI and runtime parametersbench_env/run.py, bench_env/config.pycreate_parser, RunnerConfig.from_args
Task lifecycle and parameterized instantiationbench_env/task/base.py, bench_env/task/sampler.pyBaseTask.setup, TaskSampler.sample, _prepare, _post_sample
Deterministic state-based judgingbench_env/task/base.py, bench_env/task/judge.pyBaseTask.evaluate, _evaluate_with_checks, JudgeResult, StateComparator.diff_states
AnswerSheet grounded query checkingbench_env/runner/base.py, bench_env/task/common_tasks.pyController.setup, Evaluator._evaluate_with_state, AnswerTask, build_grounded_checks
False Complete / Overdue / USE diagnosticsbench_env/runner/base.py, bench_env/task/judge.pyEpisodeResult.false_complete, EpisodeResult.overdue_termination, StateComparator.filter_unexpected_changes
Train/test/payment/high-risk split managementbench_env/splits.py, bench_env/splits/*.txtresolve_split, base_task_id
Concrete task templates and expected state changesbench_env/task/*/tasks.pye.g. Reddit_CreatePostToCommunity, CriteriaTask, AnswerTask
App navigation and runtime data modelapps/*/navigation.declaration.ts, apps/*/data/defaults.json, system/*/navigation.declaration.ts, os/data/defaults.jsondeclarative routes, default data, per-app runtime stores
GRPO training recipe / reward formulaPaper Appendix G, README.mdreleased repo gives recipe summary, but no training launch/config script found

4. Experimental Setup (实验设置)

4.1 Dataset / benchmark scale

MobileGym-Bench 共 416 个 parameterized task templates:256 test + 160 train,二者严格 disjoint。它覆盖 28 个模拟 App,其中 12 个 everyday apps 和 16 个 system apps。论文 Appendix 的 app coverage 表按类别列出:社交/内容类包括 WeChat、Reddit、X、RedBook、Bilibili、WeChat Reading、Spotify;交易/出行类包括 Alipay、eBay、Railway12306、Map;生产力/系统类包括 Calendar、Notes、Calculator、Browser、File Manager、Clock、Theme Store、SMS、Weather、Compass、Gallery、Settings、Contacts 等。

Test256 的维度分布是:Difficulty 为 L1=20L2=73L3=83L4=80;Objective 为 operate=170query=48hybrid=38;Composition 为 atomic=22sequential=110transfer=56deep_dive=68。Difficulty 不是人工直觉标签,而是用 8 个 reference models 的 mean SR / PR 做 post-hoc empirical calibration:L1 要求 SR ≥ 75% 且 PR ≥ 75%L2 是剩余任务中 SR ≥ 25% 且 PR ≥ 50%L3 是剩余任务中 SR > 0 且 PR ≥ 25%L4 是其余任务。

4.2 Baselines

论文评测 9 个 agent:

  • proprietary models:Gemini 3.1 Pro、Doubao-Seed-2.0-Pro、Qwen3.6-Plus;
  • open-source GUI-specialized models:AutoGLM-Phone-9B、UI-TARS-1.5-8B、UI-Venus-1.5-8B、GUI-Owl-1.5-8B-Think、Step-GUI-4B;
  • open-source generalist base:Qwen3-VL-4B-Instruct,也是 Sim-to-Real GRPO 训练的初始 policy。

Open-source models 用 4 trials 并重新采样参数;proprietary models 因 API cost 主要用 single run,Gemini 3.1 Pro 作为最强模型额外跑一次估计 variation。

4.3 Metrics

  • SR (Success Rate):最终任务被 deterministic judge 判为成功且 clean 的比例,是主指标。
  • PR (Progress Rate):sub-goal / check 通过比例,给部分完成提供密集信号。
  • FC (False Complete):agent 发出 COMPLETE,但目标未达成或有 unexpected side effects。
  • OT (Overdue Termination):目标已达成,但 agent 未正确停止,继续行动直到 step limit 或 loop termination。
  • USE (Unexpected Side Effects):终止状态相对初始状态出现了不属于 expected outcome 的 state mutation。

4.4 Inference and training config

一般模型的 inference 配置由论文 Appendix G 给出:temperature=0.1top_p=0.95frequency_penalty=0max_tokens=4096、单步 LLM call timeout 300 s、动作后等待 0.8 sloop_detect=10。截图以 1080 x 2400 physical resolution 提供,坐标归一化到 0--1000

Sim-to-Real 训练用 Qwen3-VL-4B-Instruct 作为初始 policy,在 MobileGym 的 160 train templates 上做 GRPO 10 steps。硬件和并行度是 single node、3 张 RTX Pro 6000、96 个并行环境实例。关键超参:vLLM asynchronous mode、group size 、train batch size 12、PPO mini-batch size 12、per-GPU micro-batch size 2、learning rate 1e-6、gradient clipping 1.0、KL coefficient 0.01、DAPO-style asymmetric clipping 0.2/0.28、maximum prompt length 32768、maximum response length 1024、rollout maximum model length 40960、training-time decoding temperature 0.7、validation temperature 0.1

需要注意:这些训练配置在论文 Appendix G / README 中出现,但 released repo main@8cf1e6af 未包含完整 GRPO training launch script / experiment config;因此目前只能把它们视为 paper-reported recipe,而不是已经由公开训练脚本校验过的配置。

5. Experimental Results (实验结果)

5.1 Benchmark main results:强模型仍未饱和,L4 是 frontier discriminator

主要结果在 256-task test set 上。关键数值如下:

模型SRPRL1L2L3L4FCOTUSE
Gemini 3.1 Pro58.8 ±1.472.197.583.663.321.934.00.25.5
Doubao-Seed-2.0-Pro52.0†63.6100.093.248.26.233.60.44.7
Qwen3.6-Plus45.7†59.2100.078.144.63.834.00.414.5
AutoGLM-Phone-9B20.0 ±1.335.386.233.69.61.939.60.612.6
UI-TARS-1.5-8B13.8 ±1.726.377.521.93.01.638.60.211.0
UI-Venus-1.5-8B15.4 ±2.428.385.021.96.01.922.90.57.7
GUI-Owl-1.5-8B-Think15.1 ±0.928.876.226.04.21.230.40.914.1
Step-GUI-4B12.9 ±1.125.783.817.82.41.637.00.87.6
Qwen3-VL-4B-Instruct9.4 ±0.620.171.212.30.60.315.90.410.0

结论有两层。第一,MobileGym-Bench 没有被当前模型打穿:总体 SR 从 9.4%58.8%,L4 上只有 Gemini 3.1 Pro 还能保持 21.9%,其他 proprietary models 最高 6.2%,开源 GUI specialist 最高 1.9%。第二,USE 不随能力单调下降:9 个模型的 USE 从 4.7%14.5%,说明“完成任务”和“不乱改状态”是两个不同能力维度,必须靠 full-environment state diff 才能测出来。

5.2 与已有环境的资源/能力比较

论文 Table 1 的核心对比是:AndroidWorld / AndroidLab / MobileWorld 主要基于 emulator,MobileBench-OL 基于 real device,而 MobileGym 基于 browser。MobileGym 覆盖 28 / 416 templates,verification 是 state-based programmatic,支持 full environment state comparison、JSON ms-level snapshot/restore、full JSON state customization 和 online RL-ready。资源上,MobileGym 报告约 400 MB/instance、约 50 MB core disk footprint、约 3 s cold start;AndroidWorld 对应约 4.5 GB memory、约 20 GB disk、约 78 s cold start,AndroidLab 约 6 GB memory、约 9 GB disk,MobileWorld 至少继承 AndroidWorld 的 ≥4.5 GB / ≥20 GB

效率实验进一步报告:256 个并行实例在单 server 上 CPU 使用率 <10%,RAM 约 100 GB,完整 256-task benchmark 约 6 分钟完成。作为对比,MAI-UI 为达到 512 个并行 Android-emulator instances 需要 10 台 bare-metal cloud servers(总计 960 vCPUs、3840 GB RAM)。这说明 MobileGym 的主要价值不是单个环境更真实,而是让 online RL 的吞吐变得接近“普通 browser workload”。

5.3 GRPO simulation-side gain

用 Qwen3-VL-4B-Instruct 在 160-task train set 上做 10-step GRPO 后,256-task test set 上整体 SR 从 9.4 ±0.6 提升到 22.2 ±1.2,增益 +12.8 pt。分 difficulty:

模型L1 (20)L2 (73)L3 (83)L4 (80)All
Qwen3-VL-4B-Instruct base71.212.30.60.39.4 ±0.6
Qwen3-VL-4B-10s trained92.537.711.71.222.2 ±1.2
Δ training gain+21.3+25.4+11.1+0.9+12.8

这个分解说明训练最有效的位置是 base model 已有中等能力的 L1/L2/L3,尤其 L2 的 +25.4 pt;L4 只涨 +0.9 pt,表明最难任务仍主要受模型基础能力限制。trained 4B 在 L1—L3 上超过 9B AutoGLM-Phone-9B,但双方在 L4 仍接近零。

5.4 Sim-to-Real transfer

Figure 5 解读:这张图按 outcome bucket 比较 simulation 与 real-device 上 base/trained 的 SR。headline subset 是 59 个 signal-bucket tasks:simulation 中 base 到 trained 从 33.9%76.7%,增益 +42.8 pt;真实 Redmi Note 12 Turbo 上从 32.2%72.9%,增益 +40.7 pt,保留了 simulation-side gain 的 95.1%。图的意义不是证明 MobileGym 覆盖所有真实手机分布,而是说明这种 interaction-fidelity simulator 学到的策略变化可以在真实设备上保留大部分。

real-device 评估设计比较谨慎:先用 base/trained 各 4 次 simulator rollout 把 256 个 test tasks 分桶,Uplift 26、Stable-pass 21、Mid 20、Regression 0、Stable-fail 189。从前三个 signal buckets 选择 67 个任务,其中 8 个因真实设备无法等价复现(例如不可逆账号级修改、非可回滚消费操作、需要合成历史状态)被剔除,最终 59 个 headline signal tasks;另外从 Stable-fail 随机采样 15 个作 negative-control,真实设备上 base/trained 都是 0/15 成功。

5.5 OOD case study:Reddit flair requirement

Figure 6 解读:这个真实设备案例来自 Reddit_CreatePostToCommunity。目标社区要求发帖前必须选 flair。base model 从 step 10 开始反复点击灰色 Post,直到 60 step budget 用完;trained model 第 13/14 步也先点击失败,但第 15 步注意到 Add tags & flair 上的星号和灰色按钮状态,推断还有必填项,进入 tag selector,选择 Tech,第 18 步看到 Post 变蓝后提交,并在 22 步完成。这个例子支持论文观点:MobileGym 的训练可能学到可迁移的交互 primitive,例如识别 disabled state、必填标记和重新规划,而不是只记住模拟器里的具体页面。

5.6 VLM judge audit 与 code-level judging 的成本优势

真实设备评估需要 VLM judge,但论文手工审计 59-task signal subset 的 118 条轨迹后发现 Qwen3.6-Plus judge 有 12/118 = 10.2% 误判:base 5/59,trained 7/59。用 GPT-5.4 重新 judge 同一批轨迹,aggregate error rate 仍是 12/118 = 10.2%,只是错误分布不同。这说明误判不是某个 judge 模型的偶然问题,而是 screenshot/VLM 判断本身在复杂 GUI trajectory 上不稳定。MobileGym 的 state-based judge 在 simulator 内避免了这类 API cost 和视觉误判。

成本表也量化了这点:如果 256-task full evaluation 都改用 VLM judge,估计 Qwen3.6-Plus 约 USD 2.6,GPT-5.4 约 USD 23;GRPO 100 steps / 9.6K trajectories 约 USD 100 / USD 8501K steps / 96K trajectories 约 USD 1,000 / USD 8.5K10K steps / 960K trajectories 约 USD 10K / USD 85K。code-level judging 在这些规模下 API cost 是 USD 0,并且没有云设备/模拟器租赁成本。

5.7 Limitations

论文自己列出的限制主要有三类。第一,视觉建模仍有差异:MobileGym 与真实 App 的细微布局、动画、App-specific icons 不完全一致,对强依赖精确图标识别的任务可能影响 transfer。第二,后端与动态内容是可控 JSON 近似:广告、弹窗、推荐流、实时消息、服务端风控、延迟 spike、server-side policy change 不会自然出现,除非显式建模为 controllable state。第三,模拟 App 只覆盖主要日常场景,不是完整 feature surface,长尾功能需要未来扩展。

更重要的边界是 safety。High-Risk subset 有 14 个任务(7 个 payment + 7 个 Test256 high-risk),Gemini 3.1 Pro 在 Payment 为 64.3%、Test256-Risk 为 71.4%、All 为 67.9%;这只是“执行能力”测量,不表示应该让 agent 执行这些不可逆操作。MobileGym 的价值在于把这类高风险能力放进无真实后果的 sandbox 中,便于后续做拒答、谨慎执行和安全对齐研究。