UI-Voyager: A Self-Evolving GUI Agent Learning via Failed Experience
Paper: arXiv:2603.24533 Code: ui-voyager/UI-Voyager Code reference:
main@67b65e2b(2026-04-03)
1. Motivation(研究动机)
移动端 GUI agent 已经从“看图回答”推进到真实手机环境中的多轮操作:agent 需要理解屏幕、调用点击/滑动/输入等工具、在动态 UI 中完成跨 app 的任务。论文指出,AndroidWorld 这类任务有三个让训练变难的特征:界面布局和初始状态会随机变化,交互动作丰富且带异步状态转移,任务 horizon 可到约 30 步;因此,一个早期错误可能让整条轨迹失败,而最终 reward 只告诉模型“整条失败”。
现有方法的核心瓶颈不是没有失败样本,而是失败样本没有被有效利用。常规 SFT 往往只保留成功轨迹,失败轨迹被丢弃;GRPO/PPO 这类 RL 方法虽然能使用成功/失败信号,但在 GUI long-horizon sparse reward 下存在严重的 credit assignment(信用分配)问题:整条轨迹里的所有 token/action 共享同一个 trajectory-level advantage,无法定位到底是哪一步造成失败。论文用一个直观例子说明:第 5 步一个错误动作会让 30 步轨迹得 0 分,但其他 29 个本来正确的动作也被一起惩罚。
UI-Voyager 要解决的具体问题是:如何让移动 GUI agent 在没有人工逐步标注、没有外部 teacher policy 的情况下,从同一任务的成功/失败 rollouts 中抽取“关键错误步”的监督信号。这个问题值得研究,因为一旦可以自动把失败经验改造成高质量 step-level SFT 数据,GUI agent 就能形成数据—模型自进化闭环:模型越强,采样到的成功轨迹越多;成功轨迹越多,失败轨迹中可纠正的 fork point 越多;新的纠错样本又继续提升模型。
Figure 1 解读:这张图把 UI-Voyager 放在不同参数规模的 GUI agents / VLMs 中比较。关键点不是单纯“最高”,而是 4B 模型达到 81.0% Pass@1,超过 Human 的 80.0%,也高于 MAI-UI-235B-A22B 的 76.7%、UI-Tars-2 的 73.3% 和 Qwen3-VL-235B-A22B 的 63.7%。论文的主张因此集中在训练机制的数据效率,而不是扩大模型规模。
2. Idea(核心思想)
核心洞察:同一任务的多条 rollout 往往会在某些屏幕状态上“相遇”,但后续动作不同;如果其中一条最终成功、另一条最终失败,那么这些相同状态后的分叉点就是最有价值的 step-level supervision。UI-Voyager 不再把失败轨迹整体丢弃,也不只给整条轨迹一个 0/1 reward,而是把成功轨迹在 fork point 的正确动作蒸馏到失败轨迹的上下文里。
关键创新是一个两阶段自进化框架:第一阶段 Rejection Fine-Tuning(RFT)用自动生成任务、多轨迹采样和 rule-based verifier 筛出成功轨迹做 SFT;第二阶段 Group Relative Self-Distillation(GRSD)在同一任务的成功/失败组内用 SSIM 找到相同屏幕状态,构造“失败上下文 + 成功动作”的自纠错样本,再用标准 autoregressive SFT 训练。
和 EvoCUA 一类依赖外部 VLM 合成 correction trace 的方法相比,UI-Voyager 的差别在于它把“同组成功轨迹”当作 teacher,不需要额外模型来判断或重写失败步骤;和 GRPO/PPO 相比,GRSD 不直接优化稀疏 trajectory reward,而是先把稀疏成败信号转化为明确的 step-level imitation target。
3. Method(方法)
3.1 Overall framework:两阶段 self-evolving pipeline
Figure 2 解读:左半部分是 RFT:prior policy / base policy 在 AndroidWorld 环境中对每个 query 生成 条轨迹,rule-based verifier 只保留成功轨迹,得到 SFT 样本并更新 policy。右半部分是 GRSD:同一 query 的多条 rollout 被分成成功组和失败组,Fork Point Detection 用 SSIM Matching 找到相同 observation 后动作不同的位置,再把成功轨迹的 action/reasoning/tool call 替换到失败轨迹对应上下文中,最后用 mixed samples 继续 SFT。
从直觉上看,RFT 解决的是“先拿到一个足够会做任务的 warm-start agent”,GRSD 解决的是“如何利用 warm-start agent 仍然会产生的大量失败样本”。如果没有 RFT,后续 rollouts 的成功样本太少,成功 teacher 不稳定;如果没有 GRSD,失败轨迹中最有信息量的局部错误又会被丢掉或只以 sparse reward 的形式弱监督。
3.2 Task formulation 与 action space
论文把一个移动 GUI 任务表示为 query 下的多步交互轨迹,step 的核心输入是当前屏幕 observation 和历史 ,输出是动作 或 assistant response。AndroidWorld 的预定义动作空间包括:click(x,y)、long_press(x,y)、swipe(x,y,x',y')、open_app(app_name)、input_text(text)、keyboard_enter()、navigate_back()、navigate_home()、wait()、status(goal_status) 和 answer(text)。这个高层动作空间让 verifier 和轨迹采样可控,但也带来 §5 中提到的低层触控泛化限制。
3.3 Stage 1:Rejection Fine-Tuning(RFT)
RFT 包含三步。
- Trajectory generation:seed task generator 对原始任务模板中的时间约束、数量、文件实体等关键参数做扰动,生成新的任务实例;GUI agent 自动执行这些任务,产出多条轨迹。
- Rejection sampling:rule-based verifier 检查轨迹是否达到预定义目标或通过任务完成验证,只保留成功轨迹。这样得到的 SFT corpus 同时保证了 trajectory structure 和 action correctness。
- Iterative training:第一轮用 Qwen3-VL 系列生成轨迹,并以 Qwen3-VL-4B-Instruct 作为 SFT backbone;后续轮次用上一轮更新后的模型继续生成新轨迹,筛选成功样本,再训练下一轮模型。论文报告三轮后 Pass@1 从 37% 提升到 73%,并选择第三轮 RFT checkpoint(Pass@1 = 73.2%)作为 GRSD/GRPO/PPO 的起点。
RFT 的作用不是解决失败归因,而是把模型推到“能经常产生成功轨迹”的状态。只有当同一任务下同时存在成功和失败 rollouts 时,GRSD 才能抽取可靠的 teacher action;所以 RFT 是 GRSD 的数据前提。
3.4 Stage 2:为什么 GRPO/PPO 在这里低效
论文先写出 GRPO/PPO 的标准形式,用来说明 sparse reward 下的信用分配问题。GRPO 对每个任务 采样 ,目标为: 其中 PPO 目标为: 对 GUI agent 来说,基础 reward 只有成功为 1、失败为 0。于是同一条失败轨迹中所有 token/action 的 advantage 都一样,模型不知道哪个 step 是真正错误点,也不知道正确 action 应该是什么。GRSD 的设计就是把这类 trajectory-level signal 转换成局部的 action-level target。
3.5 Fork Point Detection:用 SSIM 找到“同屏不同动作”的关键点
Figure 3 解读:成功轨迹 和失败轨迹 来自同一个任务。Fork point 不是任意失败 step,而是满足两件事的位置:当前 observation 与成功轨迹某一步足够相似,即 ;但执行各自动作后,下一步 observation 发生 divergence,即 。这样的点说明失败轨迹“看到了和成功轨迹一样的屏幕,却做了不同决策”。
给定成功轨迹
和失败轨迹
论文定义 observation equivalence:
其中 是截图预处理 pipeline。released code 中这个 pipeline 更具体:cores/fork_utils.py 先裁掉顶部状态栏比例 ,再把截图 resize 到 并转成灰度;为了加速,先用 mean-hash 做预过滤,hash similarity 低于 0.80 的 pair 直接丢弃,再计算 SSIM。
对每个失败 step ,算法先做 transition alignment:如果存在成功 step ,使得当前屏幕和下一步屏幕都匹配,即 且 ,说明两条轨迹还在相同转移上,失败 step 不是错误点,算法跳过并推进最小成功索引 。
如果不是 aligned transition,则寻找候选 teacher step。论文定义 divergence: 候选集合为 。teacher step 选择为: 也就是优先选当前屏幕最相似的成功 step,并用较小 打破 tie。算法还保持 monotonicity:后续失败 step 只能匹配不早于当前 teacher index 的成功 step,避免后面的失败状态倒退匹配到更早成功状态。
3.6 Step-Level Self Distillation:把失败上下文改造成成功监督
对每个 fork point ,GRSD 保留失败轨迹在 step 的 prompt/history/screenshot,上下文不变,只把 response 替换为成功轨迹 step 的 assistant response: 训练目标是只在 response tokens 上计算 next-token prediction loss: 其中 是构造出的 self-corrective samples, 是 prompt tokens, 是 response tokens。论文实验中 GRSD 作为唯一训练目标替代 GRPO/PPO,核心假设是:对 GUI long-horizon 任务,来自成功 peer trajectory 的局部正确 action,比 trajectory-level advantage 更直接、更低方差。
Figure 7 解读:BrowserMaze 的失败轨迹在 Step 12 错误地继续点 “Right”,成功轨迹在相同状态下选择 “Down”。GRSD 构造样本时不是重新生成整条轨迹,而是把失败轨迹当前 step 的 user prompt、历史进度和 screenshot 保留下来,只替换 assistant response,包括 reasoning、action 和 tool call。这使失败经验变成监督样本:模型看到“同样的失败前上下文”时学会输出成功动作。
3.7 Pseudocode(基于论文与 released code)
released repo main@67b65e2b 主要公开了 AndroidWorld evaluation、fork point detection 和 fork SFT data generation;RFT/GRSD 的完整训练 launch/config 没有随 repo 发布。因此下面把已发布代码路径实现的部分与论文级训练流程分开标注。
def rft_iteration(policy, seed_task_generator, env, verifier, optimizer, k=8):
"""Paper-level RFT loop; released repo does not include the training launcher."""
sft_samples = []
for task_template in seed_task_generator.templates:
task = seed_task_generator.perturb(
task_template,
fields=["temporal_constraints", "quantities", "file_entities"],
)
trajectories = [policy.rollout(env, task) for _ in range(k)]
for traj in trajectories:
if verifier.is_success(task, traj):
sft_samples.extend(traj.to_supervised_steps())
loss = supervised_finetune(policy, sft_samples, optimizer)
return policy, loss, sft_samplesimport cv2
import numpy as np
from skimage.metrics import structural_similarity as ssim
SSIM_RESIZE_DIM = (480, 270)
CROP_TOP_RATIO = 25 / 878
HASH_SIZE = 16
HASH_FAST_REJECT_THRESHOLD = 0.80
def obs_similarity(image_path_a, image_path_b):
"""Reflects cores/fork_utils.py::obs_similarity."""
a = cv2.imread(image_path_a)
b = cv2.imread(image_path_b)
if a is None or b is None:
return -1.0
def preprocess(img):
h = img.shape[0]
img = img[int(h * CROP_TOP_RATIO):]
thumb = cv2.resize(img, SSIM_RESIZE_DIM, interpolation=cv2.INTER_AREA)
gray = cv2.cvtColor(thumb, cv2.COLOR_BGR2GRAY).astype(np.float64)
return gray
def mean_hash(img):
h = img.shape[0]
img = img[int(h * CROP_TOP_RATIO):]
small = cv2.resize(img, (HASH_SIZE, HASH_SIZE), interpolation=cv2.INTER_AREA)
gray = cv2.cvtColor(small, cv2.COLOR_BGR2GRAY)
return (gray >= gray.mean()).astype(np.uint8).flatten()
hash_sim = (mean_hash(a) == mean_hash(b)).mean()
if hash_sim < HASH_FAST_REJECT_THRESHOLD:
return float(hash_sim)
ga, gb = preprocess(a), preprocess(b)
if ga.shape != gb.shape:
return -1.0
if np.array_equal(ga, gb):
return 1.0
return float(ssim(ga, gb, data_range=255.0))def backward_state_match(success_steps, failed_steps, ssim_threshold=0.95):
"""Reflects cores/fork_algorithm.py::_backward_state_match."""
matches = []
min_success_idx = 0
for failed_idx, failed_step in enumerate(failed_steps):
failed_img = get_step_image_path(failed_step)
if failed_img is None:
continue
transition_aligned = False
if failed_idx + 1 < len(failed_steps):
failed_next = get_step_image_path(failed_steps[failed_idx + 1])
for success_idx in range(min_success_idx, len(success_steps) - 1):
succ_img = get_step_image_path(success_steps[success_idx])
succ_next = get_step_image_path(success_steps[success_idx + 1])
if (obs_similarity(succ_img, failed_img) >= ssim_threshold and
obs_similarity(succ_next, failed_next) >= ssim_threshold):
min_success_idx = max(min_success_idx, success_idx + 1)
transition_aligned = True
break
if transition_aligned:
continue
candidates = []
failed_tool = get_step_tool_call(failed_step)
for success_idx in range(min_success_idx, len(success_steps)):
succ_step = success_steps[success_idx]
succ_img = get_step_image_path(succ_step)
sim_current = obs_similarity(succ_img, failed_img)
if sim_current < ssim_threshold:
continue
if get_step_tool_call(succ_step) == failed_tool:
continue
diverged = True
if success_idx + 1 < len(success_steps) and failed_idx + 1 < len(failed_steps):
sim_next = obs_similarity(
get_step_image_path(success_steps[success_idx + 1]),
get_step_image_path(failed_steps[failed_idx + 1]),
)
diverged = sim_next < ssim_threshold
if diverged:
candidates.append((success_idx, sim_current))
if candidates:
best_success_idx, _ = max(candidates, key=lambda x: (x[1], -x[0]))
matches.append((failed_idx, best_success_idx))
min_success_idx = best_success_idx
return matchesdef build_fork_sft_entry(fail_step, success_step, task_name, succ_id, fail_id,
failed_step_idx, success_step_idx, sim_current, sim_next):
"""Reflects cores/fork_algorithm.py::_build_fork_sft_entry."""
entry = deepcopy(fail_step)
teacher_response = get_assistant_content(success_step)
if teacher_response is None:
return None
for message in entry.get("conversations", []):
if message.get("role") == "assistant":
message["content"] = teacher_response
break
entry["fork_info"] = {
"task_name": task_name,
"succ_id": succ_id,
"fail_id": fail_id,
"failed_step_idx": failed_step_idx,
"base_step_idx": success_step_idx,
"sim_current": round(sim_current, 4),
"sim_next": round(sim_next, 4),
}
return entryimport torch
import torch.nn.functional as F
def grsd_sft_train_step(model, tokenizer, batch, optimizer):
"""Training objective from Eq. (7); full GRSD launcher is not released."""
input_ids = batch["input_ids"]
attention_mask = batch["attention_mask"]
labels = batch["labels"]
logits = model(input_ids=input_ids, attention_mask=attention_mask).logits
shift_logits = logits[:, :-1].contiguous()
shift_labels = labels[:, 1:].contiguous()
loss = F.cross_entropy(
shift_logits.view(-1, shift_logits.size(-1)),
shift_labels.view(-1),
ignore_index=-100,
)
loss.backward()
optimizer.step()
optimizer.zero_grad()
return loss.item()3.8 Released code 与论文公式的对应和差异
论文公式与 released code 实现差异:released repo main@67b65e2b 只公开了 fork point detection / fork SFT 数据生成和 AndroidWorld evaluation;RFT 与 GRSD 的完整训练 launcher、optimizer 配置和混合数据训练脚本未在 repo 中出现。因此 RFT/GRSD training-loop 伪代码只能按论文描述与 arXiv source 的 hyperparameter table 还原,不能声称来自 released launch script。
论文公式与 released code 实现差异:论文把 概括为 crop-resize-grayscale,并把 SAME/DIVERGE 写成 SSIM threshold 判断;代码在 cores/fork_utils.py 中补充了工程细节:顶部裁剪比例 、resize 到 、 mean-hash 预过滤、hash 阈值 0.80、默认 SSIM threshold 0.95。代码还在 _backward_state_match 中显式过滤 same_tool_call 候选;这不是论文公式 (4) 的一部分,但与“相同动作不提供纠错信息”的意图一致。
Code reference:
main@67b65e2b(2026-04-03) — pseudocode and mapping based on this commit
| Paper Concept | Source File | Key Class/Function |
|---|---|---|
| 论文/模型/评测入口 | README.md | release note、model link、AndroidWorld quick start |
| AndroidWorld 并行评测 | run_android_world.sh | CONFIG_NAME、MODEL_NAME、NUM_WORKERS、AVD copy、result/log directory |
| GUI agent inference wrapper | androidworld/android_world/agents/mgui_agent.py | MGUIAgent、process_image、tool-call prompt |
| fork point analysis CLI | cores/run_fork_analysis.sh | DATA_DIR、SSIM_THRESHOLD=0.95、MODE=shortest_base、NUM_WORKERS=32 |
| fork analysis driver | cores/fork_main.py | argument parsing、parallel task analysis、fork_steps.jsonl / report output |
| teacher/failure pair selection | cores/fork_algorithm.py | _select_pairs_shortest_base、_select_pairs_all |
| SAME/DIVERGE matching | cores/fork_algorithm.py | _backward_state_match |
| self-corrective SFT sample | cores/fork_algorithm.py | _build_fork_sft_entry、analyze_single_task |
| screenshot preprocessing / SSIM | cores/fork_utils.py | crop_top、_get_gray_thumbnail、_get_image_hash、obs_similarity |
| trajectory data loading / logs | cores/fork_utils.py | load_task_repeats、save_fork_log |
4. Experimental Setup(实验设置)
Dataset / benchmark
- AndroidWorld:主评测 benchmark,包含 116 个真实移动应用任务,任务有随机初始化参数。训练时跟随 MobileRL 使用 AndroidWorld training sets,规模为 over 7000 tasks。
- BrowserMaze / SystemBluetoothTurnOff 等任务:用于 fork point detection 和 self-corrective sample case study;BrowserMaze 示例显示中间状态 fork point,SystemBluetoothTurnOff 显示初始状态 fork point。
Model / evaluation metrics
- Backbone:Qwen3-VL-4B-Instruct。
- Main metric:Pass@1 success rate / success rate,即 AndroidWorld 任务是否完成的平均成功率。
- RFT analysis metric:Pass@1 与 Pass@k,用来观察多轮 RFT 后采样成功率随 的变化。
- Reproducibility setting:UI-Voyager 的 81.0% 是 64 independent runs / 64 random seeds 下的平均成功率;baseline numbers 来自已有论文。
Baselines
论文比较了开源与闭源 GUI agents / VLMs:Qwen3-VL series、MAI-UI、ScaleCUA、Ferret-UI Lite、Step-GUI、UI-Tars / UI-Tars-1.5 / UI-Tars-2、UI-Venus / UI-Venus-1.5、GUI-Owl / GUI-Owl-1.5-8B-Thinking、Seed1.5-VL、Seed1.8、Gemini-2.5-Pro,以及 Human baseline。训练方法对比中还比较了从同一 RFT checkpoint 初始化的 PPO、GRPO 和 GRSD。
Training / implementation config
论文正文明确给出:RFT 第三轮 checkpoint 的 Pass@1 为 73.2%,后续 GRSD/GRPO/PPO 都从该 RFT model 出发;Qwen3-VL-4B-Instruct 是 backbone;AndroidWorld training sets 超过 7000 tasks;episode 最长可到约 30 interaction steps。
arXiv source chapters/7_appendix.tex 给出 GRPO/PPO 主要超参(注意该 appendix table 在源文件中存在;released GitHub 没有对应训练 launch script):
GRPO hyperparameters(source appendix)
- Data:Max Prompt Length 8192;Max Response Length 512;Train Batch Size 256。
- Actor / Policy:FSDP;PPO Micro Batch Size/GPU 8;LR ;Gradient Clipping 1.0;Clip Ratio low/high 0.2/0.28;PPO Epochs 1。
- Rollout & Sampling:Temperature 1.0;Max New Tokens 512;Number of Samples ;Max Turns 30;History Length 30。
PPO hyperparameters(source appendix)
- Data:Max Prompt Length 8192;Max Response Length 512;Train Batch Size 256。
- Actor / Policy:FSDP;PPO Micro Batch Size/GPU 4;LR ;Gradient Clipping 1.0;Clip Ratio 0.2;PPO Epochs 1。
- Rollout & Sampling:Temperature 1.0;Max New Tokens 512;Max Turns 30。
Released code execution config(不是训练超参)
run_android_world.sh默认CONFIG_NAME=Qwen3-VL-4B-Instruct、MODEL_NAME=Qwen3-VL-4B-Instruct、NUM_WORKERS=4、N_REPEATS=1,使用AndroidWorldAvd并为 worker 复制 AVD。cores/run_fork_analysis.sh默认SSIM_THRESHOLD=0.95、MODE=shortest_base、NUM_WORKERS=32、MAX_LOG_SEEDS=5,输入目录格式为eval_results/{MODEL_NAME}/results/{TIMESTAMP}/seed_{N}/{TaskName}/repeat_{N}_succ.jsonl/repeat_{N}_fail.jsonl,输出fork_analysis_report.json和fork_steps.jsonl。
论文未详细说明完整硬件配置;released repo 也没有训练 launch script,因此不应从默认配置推断 GPU 型号/数量。
5. Experimental Results(实验结果)
5.1 Main AndroidWorld results
UI-Voyager 在 AndroidWorld 上达到 81.0% Pass@1 success rate,超过 reported Human 80.0%。论文强调这是 4B 模型,与更大模型相比也更高效。
| Model | Params | Success Rate |
|---|---|---|
| Qwen3-VL-2B | 2B | 36.4 |
| MAI-UI-2B | 2B | 49.1 |
| ScaleCUA-3B | 3B | 23.7 |
| Ferret-UI Lite-3B | 3B | 28.0 |
| Qwen3-VL-4B | 4B | 45.3 |
| Step-GUI-4B | 4B | 63.9 |
| GUI-Owl-7B | 7B | 66.4 |
| Step-GUI-8B | 8B | 67.7 |
| MAI-UI-8B | 8B | 70.7 |
| GUI-Owl-1.5-8B-Thinking | 8B | 71.6 |
| UI-Venus-1.5-30B-A3B | 30B | 77.6 |
| MAI-UI-32B | 32B | 73.3 |
| UI-Tars-2 | 230B | 73.3 |
| Qwen3-VL-235B-A22B | 235B | 63.7 |
| Gemini-2.5-Pro | - | 69.7 |
| Seed1.8 | - | 70.7 |
| MAI-UI-235B-A22B | 235B | 76.7 |
| Human | - | 80.0 |
| UI-Voyager | 4B | 81.0 |
5.2 RFT self-evolving analysis
Figure 4 解读:左图显示 Qwen3-VL-4B-Instruct 经过四轮 RFT 后,Pass@1 和 Pass@k 持续提升;论文选择第三轮 RFT checkpoint(Pass@1=73.2%)作为后续训练基础。右图显示如果直接从 Qwen3-VL-4B-Instruct 做 GRPO/PPO,约 175 training steps 才达到单轮 RFT 的 64.0% 水平,说明直接 RLVR from scratch 在 long-horizon GUI 中样本效率很低。
5.3 Fork point case studies
Figure 5 解读:BrowserMaze 中失败和成功轨迹都到达 Step 12 的相同屏幕状态;失败轨迹点击 “Right”,但右侧有墙导致无效;成功轨迹点击 “Down” 完成关键转移。这个例子展示了中间状态 fork point:错误不是整条轨迹都错,而是在某个共享 observation 下做了错误 action。
Figure 6 解读:SystemBluetoothTurnOff 的 fork point 出现在 Step 0 初始状态。失败轨迹试图通过上滑打开 settings app,成功轨迹则从顶部下滑打开 notification shade 并进入 quick settings。这个例子说明 fork point detection 不只适用于中间状态,也能发现一开始就分叉的错误决策。
5.4 GRSD versus GRPO/PPO
Figure 8 解读:三种方法都从同一个 RFT model(73.2% success rate)开始。GRSD 提升到 81%,而 GRPO/PPO 进展慢并在约 76% 附近 plateau。论文把差距归因于两点:GRPO/PPO 没有定位关键错误 step 的机制;GRSD 能通过 fork point detection 把失败轨迹中的 decisive action 直接改写为监督样本。
Figure 9 解读:论文挑选 RFT baseline 成功率最低的十个代表任务,包括 RecipeDeleteDuplicateRecipes2、SystemCopyToClipboard、AudioRecorderRecordAudioWithFileName、TasksCompletedTasksForDate、VlcCreateTwoPlaylists、SportsTrackerActivitiesOnDate、SportsTrackerTotalDistanceForCategoryOverInterval、RetroCreatePlaylist、TasksDueOnDate、RecipeDeleteMultipleRecipesWithConstraint。GRSD 在这些低成功率任务上整体高于 PPO/GRPO,说明它特别适合“成功样本稀少但同组轨迹可对齐”的 sparse-reward 场景。
5.5 Limitations and practical caveats
论文讨论了两个主要限制。
- SSIM-based matching 在实时环境中不完美:移动 GUI 是异步执行的,截图会受动画、键盘弹出、loading、toast、cursor blinking、时钟变化等影响。相同逻辑状态可能 SSIM 偏低,不同状态也可能因局部相似而 SSIM 偏高。论文建议未来做 time-aware matching、短 temporal window 匹配、对高方差区域做 mask,并融合 OCR/layout/accessibility-tree signals。
- 高层 predefined action space 限制泛化:AndroidWorld 的 click/swipe/type/navigation 等高层动作让训练和验证更稳定,但忽略了 AndroidEnv 强调的低层触控动态,例如 gesture duration、trajectory shape、release timing、actuation noise。未来可以用 hierarchical action modeling:先用高层动作获得样本效率,再在 post-training 中引入低层手势扰动增强迁移。
总体结论:UI-Voyager 的贡献不是提出一个更复杂的 reward model,而是把同一任务的成功/失败 rollout 关系转化为可训练的 step-level self-distillation 数据。结果上,4B 模型在 AndroidWorld 的 116 个任务上达到 81.0% Pass@1;机制上,RFT 提供 warm-start,GRSD 负责从失败经验中抽取纠错监督,是两者共同支撑 self-evolving GUI agent 的性能提升。