NavGPT-3:把语言模型的长程推理接到机器人的实时动作上
0. 简介
NavGPT-3 面向连续环境中的视觉语言导航,处理的是一个很具体的工程矛盾:语言模型能够理解长指令、维护路线假设并在多个步骤后修正计划,但一次推理通常要等待数秒;导航策略能够以更高频率输出动作,却容易在长路径中漂移,也很难向推理模型解释自己到底走过了哪里。论文提出的方案由三部分组成:用 Harness(导航适配层) 组织上下文、工具、路线状态与执行证据,用 Runtime(类操作系统运行时) 管理推理、执行和监控线程的权限,再由 NavGPT VLA 负责每一步的局部航点预测。仓库则公开了 VLA 推理、工具服务器、实验配置和仿真评估代码。下面把这套系统拆成输入、状态、调用、控制和验证五个环节,让没有读过论文的人也能沿着一次导航回合看懂它是怎样工作的。
1. 研究问题与方法缺口
1.1 物理世界不会等待模型完成思考
进一步看,视觉语言导航的输入通常是一条自然语言指令和一串相机观测,例如“沿着走廊经过餐桌,在楼梯前停下”。在离散导航图上,模型只需要从候选节点中选择下一个节点;到了连续环境,机器人还要处理相机视野有限、转弯角度、障碍物、动作执行误差和停止位置。语言模型每次输出一个动作前,需要重新读图、回顾历史、解释指令并选择工具,论文的消融结果显示这一循环的平均间隔可能是 3 到 19 秒。机器人在这段时间里不能停在原地等答案,否则吞吐量太低;如果继续运动,又可能在模型尚未看到新障碍时越过关键位置。
1.2 单一模型很难同时覆盖长程和高频
换句话说,把所有能力压进一个端到端策略看起来直接,但训练数据中的动作监督通常只覆盖专家轨迹,模型一旦走偏,便缺少可靠的恢复机制。反过来,让大语言模型逐步调用“前进、左转、右转”工具,虽然有利于解释和修正,却把每个低层动作都变成一次昂贵的语言模型回合。已有研究已经探索图记忆、回溯、验证和恢复等能力;NavGPT-3 的侧重点,是把执行证据怎样回到规划上下文、已经走过的地方怎样被引用、哪个线程在危险时刻拥有运动权限统一放进系统接口中。核心问题在于接口缺失,而不只是模型参数不足:如果状态、证据和权限没有被明确设计,两个能力不同的模型只能串行排队。
难点提示:可以把语言模型想成负责看地图和安排路线的教练,把 VLA 想成熟悉油门和方向盘的司机。教练擅长判断“下一段应该经过哪扇门”,司机擅长在每个瞬间保持车身稳定;真正困难的是两者如何交接方向盘,以及司机发现路被挡住时怎样让教练看到证据,而不是只收到一句“我停了”。
1.3 NavGPT-3 的设计判断
这里的关键是,NavGPT-3 把问题拆成两个边界清晰的层。Harness 规定模型能够看到什么、能够调用什么、调用后留下什么状态;论文提出的 Runtime 规定多个逻辑线程怎样并行、谁能够让机器人移动、哪些事件可以立即撤销权限。Planner 可以自己调用空间工具,也可以把一段指令交给 NavGPT VLA;VLA 返回路线关键帧、终点观测和执行状态后,Planner 再决定继续、回到某个已访问位置、做局部修正,或者显式结束任务。这个循环让“执行了一次动作”与“任务已经完成”成为两个不同事件,正是长程推理接入物理控制所需要的分界。
2. 从一条指令到一次完整导航回合
2.1 输入、状态与输出
一次回合的输入包括原始导航指令、当前或历史相机图像、机器人的位姿、已观察区域和路线记录。Harness 将这些来源拼成 Planner 的上下文,同时根据实验配置注册可用工具。输出不是一个抽象的“成功”标签,而是一组可追踪的结果:工具调用参数、实际执行的位移、路线关键帧、观察地图、访问节点、模型是否要求复查以及最终停止原因。仓库的 configs/experiments/vln/ 目录把数据集、模型、工具集合、回合上限和视觉 token 预算写在同一个配置文件里,因此一次实验可以复核“使用了哪些能力”,而不必从运行日志中猜测。
2.2 三个角色的分工
这意味着系统中有三个直接参与导航的角色,而不是三个模型:Planner 负责解释任务、选择工具和判断终点;NavGPT VLA 负责根据指令与视觉历史输出八个局部航点;Environment 负责渲染观测、执行位移并报告实际状态。Harness 位于三者之间,维护路线和节点,检查工具参数,格式化返回信息。论文提出的 Runtime 位于 Harness 之上,管理 Planner、VLA 和监控器的线程状态。这是一种职责分离,而不是简单的模型串联:Planner 不直接修改机器人位姿,VLA 不能自行宣布任务结束,环境也不能把未经授权的迟到命令重新执行。

2.3 工具调用是系统的可观测接口
仓库中的工具集合按能力分成观察、委派、空间修订和终止四类。observe_forward 给 Planner 一张细节更完整的前视图,observe_panorama 同时返回前、右、后、左四个方向,observe_map 只绘制已经观察过的区域,避免把未知空间误当作可通行区域。navigate_by_instruction 把完整指令或剩余子任务交给 VLA,返回沿途关键帧和执行状态;navigate_to_node、annotate_node 让 Planner 能够回到已经走过的节点并给它加上语义标签;terminate_episode 负责最终停止。工具的名字并不是装饰,它们定义了模型能够执行的动作边界。
2.4 用一条走廊指令串起整个闭环
仍以“沿走廊经过餐桌,在楼梯前停下”为例,Planner 首先要把指令理解成有先后关系的空间条件:经过餐桌是中途约束,楼梯前才是停止条件。它可以将路线委派给 VLA,但委派后的每一步都由当前观测重新驱动。如果 VLA 在餐桌旁提前停止,Harness 返回的终点画面和路线关键帧就会暴露问题:餐桌出现了,楼梯却还没有被确认。Planner 据此继续委派剩余路段;若已经走进错误支路,则先查看地图,回到有证据支持的节点,再选择新的局部路线。完成一次委派,只能证明一个操作结束,不能证明原始指令已经满足。
下面这组配图设计把抽象模块落实到同一个导航场景。走廊、餐桌、楼梯和节点编号均用于解释流程,不对应论文中的某个实际测试回合,也不暗示系统能提前读取整栋建筑的地图。阅读时可以顺着两条方向相反的箭头:从 Planner 到 VLA 的箭头承载指令,从环境回到 Planner 的箭头承载执行证据;Runtime 则对所有能够产生运动的操作检查权限。这样,即便暂时不读公式,也能看出路线修正依赖的是执行后的可检查反馈。

3. Harness:让推理拥有可回看的空间上下文
3.1 上下文构造不是拼接几张图片
如果 Planner 只看到当前一帧,它很难判断自己是否已经经过某个房间,也无法区分“已经完成的指令片段”和“还没有发生的片段”。Harness 会把当前观测、路线历史、节点表、工具返回值和任务指令放在同一份上下文中。路线记录保存连续执行的证据,节点表保存可以回到的空间引用,语义标签则记录“这里是餐桌旁”这类由模型确认的解释。这样,Planner 的下一次决策可以引用一个稳定的节点编号,而不是凭记忆描述一张已经离开视野的图片。
3.2 Map 与 Backtrack 把错误变成可修复状态
这里值得把地图和回溯看成一组互补操作:observe_map 只显示“看过的空间”,并用路线线段和节点编号标记已访问位置;这让模型能够区分未知区域与已经确认的空地。navigate_to_node 沿着已经走过的路径返回节点,避免为了寻找一个旧位置重新探索整栋房子。论文在 100 个 R2R-CE 回合上的消融显示,GPT-6 Astra 从基础工具的 72 SR 提升到加入地图与回溯后的 83 SR,同时平均 token 从 325.0k 降到 190.8k。空间记忆既提升准确率,也减少了语言模型重新猜测的成本,因为回溯动作把一个模糊的空间判断变成了可执行的地址。
C k = Context m a i n ( S k , H k , T k ) , a k ∼ π P l a n n e r ( ⋅ ∣ C k , T k ) C_k=\operatorname{Context}_{\mathrm{main}}(S_k,H_k,\mathcal{T}_k),\qquad a_k\sim\pi_{\mathrm{Planner}}(\cdot\mid C_k,\mathcal{T}_k) Ck=Contextmain(Sk,Hk,Tk),ak∼πPlanner(⋅∣Ck,Tk)
其中 S k S_k Sk 是当前共享状态, H k H_k Hk 是 Planner 自己的交互历史, T k \mathcal{T}_k Tk 是当前允许的工具集合。这个表达式的工程含义是:模型并不是直接读取世界,而是在 Harness 选择后的上下文上作决定;改变工具返回格式、节点状态或图像组织方式,就等于改变了模型的决策接口。
3.3 代码:工具集合如何成为能力边界
下面的片段摘自仓库的 navgpt/planner/tools/ablation.py。CAPABILITIES 把三种能力包映射到注册工具名,validate 则在启动前检查界面、能力包和复查步数是否合法。这个边界使消融实验可以复用同一套 Planner,只调整实际开放给它的能力;它也清楚地区分了“模型说要调用某个工具”和“工具服务器确实注册了该操作”。
# navgpt/planner/tools/ablation.py
CAPABILITIES = {
'core': ('navigate_by_instruction', 'observe_forward', 'observe_panorama', 'terminate_episode'),
'spatial_memory': ('navigate_by_instruction', 'observe_forward', 'observe_panorama', 'terminate_episode',
'observe_map', 'navigate_to_node', 'annotate_node'),
'full': ('navigate_by_instruction', 'observe_forward', 'observe_panorama', 'terminate_episode',
'observe_map', 'navigate_to_node', 'annotate_node', 'navigate_relative'),
}
def validate(interface, capabilities, review_every):
if interface not in ('standard',) + INTERFACES:
raise ValueError('Unknown interface: ' + interface)
if capabilities not in CAPABILITIES:
raise ValueError('Unknown capability bundle: ' + capabilities)
if isinstance(review_every, bool) or not isinstance(review_every, int) or review_every < 0:
raise ValueError('review_every must be a non-negative number of VLA steps')
这段代码没有实现地图算法,却决定了 Planner 能否调用地图。换句话说,Harness 的能力边界由注册表落地:工具没有注册时,模型即使在文本中“想象”出一个工具名,也不能改变环境状态。论文把这种边界与模型推理分开研究,正是为了说明成功率变化来自接口能力,而不是某个隐藏的提示词差异。运行器随后把这个集合传给 MCP 工具服务器,由服务器在注册环节过滤未启用的工具。
直觉理解:把工具注册表看成驾校车辆的控制面板。教练可以告诉学员“应该倒车入库”,但如果车辆没有倒车挡,这句话不会自动变成动作;增加一个工具,就是把一个经过检查的控制按钮交给模型,同时保留环境对参数和权限的最后检查。
4. Runtime:论文的异步控制设计与仓库实现边界
4.1 为什么顺序调用无法满足实时性
最简单的流程是 Planner 调用 VLA,等待 VLA 走完整段路线,再把结果交回 Planner。它实现容易,却让路线复查和危险监控都排在执行之后。若 Planner 需要 8 秒才能读完返回信息,机器人可能已经沿着旧路线走了数米。论文提出的 Runtime 让 Planner、VLA 和监控线程拥有各自的上下文与更新频率;VLA 可以继续输出局部航点,Planner 在后台阅读路线证据,监控器则持续检查安全条件。需要分清的是,公开仓库中当前的 ReviewScheduler 明确是同步、单写入者调度,只在已提交的推理边界暂停;论文中 Go2 使用的 ZEOS 线程运行时代码另行发布。下面的控制语义来自论文,代码示例则会标明公开仓库的实际实现。
4.2 Motion authority 是运行时的核心状态
在论文描述的运行时中,任意时刻只有一个前台操作拥有运动权限。路线复查事件到来时,Runtime 等待当前运动步完成,然后撤销 VLA 的权限,把控制交给修订工具;保护性停止则直接把权限置空,不等待 Planner 的推理返回。每次撤销或替换权限都会递增版本号,旧版本的航点即使晚到,也不能重新驱动机器人。论文用控制转移函数表示这一点;真正执行还要检查命令版本和环境前置条件,不能仅凭线程处于前台就放行动作。
f ℓ + 1 = σ ( S ℓ , f ℓ , e ℓ ) , eligible ℓ ( j , a ) = 1 [ j = f ℓ ∧ a ∈ P j ] f_{\ell+1}=\sigma(S_{\ell},f_{\ell},e_{\ell}),\qquad \operatorname{eligible}_{\ell}(j,a)=\mathbf{1}[j=f_{\ell}\land a\in\mathcal{P}_j] fℓ+1=σ(Sℓ,fℓ,eℓ),eligibleℓ(j,a)=1[j=fℓ∧a∈Pj]
这里的 f ℓ f_{\ell} fℓ 表示事件索引 ℓ \ell ℓ 时拥有运动权限的线程, e ℓ e_{\ell} eℓ 是新到达的事件, P j \mathcal{P}_j Pj 是线程 j j j 被允许提出的操作集合。公式强调了一个容易被忽略的事实:线程可以继续计算和接收观测,但只有获得运动权限的线程才能提交任务动作。部署实现还会携带权限版本并校验动作前置条件,因此迟到的模型响应不能靠“刚好轮到我”重新取得控制权。
4.3 运行时如何处理安全停止与恢复
这里要把论文的安全路径理解成运行时的保护层,而非 Planner 的又一项推理能力。传感器发现障碍或控制循环超时后,监控器设置保护标志、撤销当前权限,并让底层命令过滤器输出停止动作。解除警报并不自动恢复运动,系统必须重新获取观测、确认权限版本并授权新的操作。论文在 Unitree Go2 上报告,重叠推理加优先级监控将停止延迟的 95 分位从 1840 ms 降到 180 ms,30 次保护试验中漏停次数从 7 次降为 0 次。这个结果验证的是调度和权限机制,不等同于一个更强的障碍检测器;而 Go2 部署代码并不包含在当前公开仓库中。

4.4 代码:VLA 客户端如何防止状态串线
仓库的 navgpt/planner/tools/vla_client.py 明确写出了 VLA 服务的状态约束:每个 /act 调用都会追加四路相机历史,服务端不是并发安全的,因此客户端用锁串行化请求;每个 episode 开始时必须调用 reset 清空历史,否则上一回合的图像会泄漏到下一回合。这个实现细节与论文的“持久状态”相呼应:状态可以跨调用保留,但必须在正确的生命周期边界上重置。
# navgpt/planner/tools/vla_client.py
class NavGPTVLAClient:
"""One NavGPT VLA service, one episode at a time."""
def __init__(self, base_url: str, timeout: float = 300.0):
self.base_url = base_url.rstrip("/")
self.timeout = timeout
self._lock = threading.Lock()
self._arrival_streak = 0
def reset(self, success: bool | None = None) -> dict[str, Any]:
"""MANDATORY between episodes — clear the frame history."""
with self._lock:
self._arrival_streak = 0
response = requests.post(
self.base_url + "/reset",
json={"success": success},
timeout=self.timeout,
)
response.raise_for_status()
return response.json()
def observe_history(self, views):
with self._lock:
response = requests.post(
self.base_url + "/observe",
json={"rgb_" + k: views[k]
for k in ("front", "right", "back", "left")},
timeout=self.timeout,
)
response.raise_for_status()
return response.json()
这里的锁不是为了提高吞吐,而是为了保证时间顺序。两个回合或两个线程同时写入相机历史,会让“第 5 帧”失去明确来源,后续 token 分配和航点预测都会变得不可解释。物理系统的并发控制首先要保证状态语义正确,再考虑并行加速。这也与论文 Runtime 的运动权限属于不同层次:客户端锁保护同一个 VLA 服务的请求历史,运动权限决定哪个操作可以驱动机器人。
工程价值:可以把权限版本和 episode reset 类比成仓库门禁与换班记录。旧班次留下的钥匙即使还在桌上,也不能打开新班次的门;新班次必须重新确认现场、拿到新钥匙,才能继续移动。
4.5 用一次迟到响应理解权限版本
假设 VLA 在权限版本 g = 7 g=7 g=7 下开始预测航点,推理尚未结束时,监控器发现障碍,立即撤销运动权限并将版本递增到 g = 8 g=8 g=8。随后到达的预测仍携带旧版本 7,即便数值合法,也必须被拒绝。障碍解除后,系统还要刷新观测、检查恢复条件,再授予新操作权限,例如 g = 9 g=9 g=9;不能因为 VLA 是原来的执行者,就让它自动续用中断前的输出。这里的 7、8、9 只是说明状态变化的示例编号,不是仓库配置值或实验统计。拒绝迟到动作,与停止正在执行的动作,是运行时必须分别落实的两件事。
∑ o G o ( t ) ≤ 1 , eligible ( u o , t ) = G o ( t ) 1 [ version ( u o ) = g ( t ) ] 1 [ preconditions ( u o , S ( t ) ) ] \sum_o G_o(t)\leq 1,\qquad \operatorname{eligible}(u_o,t)=G_o(t)\,\mathbf{1}[\operatorname{version}(u_o)=g(t)]\,\mathbf{1}[\operatorname{preconditions}(u_o,S(t))] o∑Go(t)≤1,eligible(uo,t)=Go(t)1[version(uo)=g(t)]1[preconditions(uo,S(t))]
其中 G o ( t ) G_o(t) Go(t) 表示操作 o o o 在时刻 t t t 是否持有运动权限, g ( t ) g(t) g(t) 是当前权限版本, u o u_o uo 是该操作提出的命令。三个条件共同成立,命令才有资格进入最终执行检查;保护性停止标志仍能覆盖这个资格并输出停止命令。因此,版本号不是给日志增加一个字段,而是在异步计算和物理执行之间切断旧因果关系。下图采用事件顺序轴,不给横轴标秒数,避免把机制示例误读成论文测得的延迟曲线,也避免将 VLA 更新周期与机器人停止延迟混为一谈。

5. NavGPT VLA:高频输出局部航点
5.1 输出八个航点,并执行一个短目标
NavGPT VLA 从 Qwen3-VL-Instruct 初始化,并增加 MLP 动作头。输入是一条指令和最多 16 个保留观测,每个观测可以包含前、右、后、左四个视角;输出是八个按顺序排列的
(
x
,
y
,
θ
)
(x,y,\theta)
(x,y,θ) 航点,坐标位于机器人当前坐标系。论文方法部分描述每次提交第一个航点,剩余点作为短期轨迹预测;公开仓库的 Habitat 客户端则实际使用 navigation[1],也就是 Python 列表中的第二个元素,作为约 0.25 米的局部目标。两者共同遵循“预测一段、执行一小步、重新观测”的闭环,但具体航点索引不能混为一谈。
τ n = ( ( x n , r , y n , r , θ n , r ) ) r = 1 8 , u n = τ n , 1 \tau_n=\bigl((x_{n,r},y_{n,r},\theta_{n,r})\bigr)_{r=1}^{8},\qquad u_n=\tau_{n,1} τn=((xn,r,yn,r,θn,r))r=18,un=τn,1
其中
τ
n
\tau_n
τn 是第
n
n
n 次 VLA 推理给出的八个累计航点,
u
n
u_n
un 表示论文叙述中的第一个执行点。累计坐标意味着第
r
r
r 个点相对于本次输入位姿描述,而不是相对于第
r
−
1
r-1
r−1 个点;这一约定让执行器可以直接比较预测终点与真实位姿的偏差。仓库模型会先将归一化输出通过 recover_absolute_waypoints 还原到米和弧度,而客户端在离散网格上选 navigation[1] 组合成目标位姿;复现时应按所用实现核对索引、网格间距与坐标变换。
5.2 到达判断由调用方负责
VLA 服务不会直接返回一个不可逆的 STOP 动作。仓库客户端通过最后一个航点的位移和朝向是否同时接近零来判断“可能已经到达”,并要求连续两次命中才触发停止倾向;Planner 仍然拥有最终的 terminate_episode 权限。这种分工避免把一次模型输出误当成任务完成,也给 Harness 留出机会检查最后一条指令是否真的满足。例如模型可能在门口停下,但原始指令要求“经过门后在楼梯旁停下”,只有 Planner 结合关键帧和节点语义才能判断是否需要继续。
5.3 代码:观测历史如何变成八个航点
下面从 navgpt/vla/agent.py 摘出两个实际方法;为便于阅读,省略了外围类定义和中间的历史维护方法,代码块本身不是独立脚本。act 先把观测追加进历史,再运行一次模型推理,最后通过 embodiment 的尺度把归一化输出还原为米和弧度。独立的历史写入接口也让 Planner 在回溯或局部修订后补充机器人经历过的图像,确保 VLA 下一次判断仍能看到完整经历,而不是只看到自己亲自驱动的帧。
# navgpt/vla/agent.py
def _model_forward(self, data) -> list:
inputs = {key: data[key].to(self.model.device) for key in MODEL_INPUTS}
with torch.inference_mode():
pred = self.model.predict_actions(**inputs)[0]
return pred.to(torch.float32).cpu().numpy().reshape(-1, 3).tolist()
def act(self, images: list, text: str) -> list:
self.observe_history(images)
t0 = time.time()
navigation = self.predict_inference(text)
print(f"[NavGPT VLA] step {len(self.rgb_lists[0])} inference {time.time() - t0:.3f}s")
return recover_absolute_waypoints(navigation, self._scale)
这段实现体现了一个重要的时间顺序:观测先进入历史,模型才基于包含当前画面的上下文预测。若把 observe_history 放到推理之后,模型实际看到的就会落后一帧,短距离动作容易表现为反复修正或停在障碍前。仓库还支持预计算多种图像分辨率,把视觉预处理成本从关键路径中挪走。
6. Codec allocation:把有限视觉 token 给变化最大的画面
6.1 固定预算下,所有帧不应获得同样分辨率
VLA 的历史越长,输入图像越多,而视觉 token 预算是有限的。若平均分配,静止的后视图可能占用与刚转过拐角的前视图相同的预算;若只按时间新旧分配,快速变化的侧视图仍可能被低估。NavGPT-3 借鉴视频编码的 I-frame/P-frame 思路:每个视角的第一帧作为完整参考,后续帧根据与同一视角前一帧的 RGB 变化、时间新鲜度和相机权重共同决定 token 数。
δ h , v = ∥ thumb ( I h , v ) − thumb ( I h − 1 , v ) ∥ 1 255 ⋅ 3 ⋅ 64 2 , s h , v c h a n g e = { 1 , h = 0 clip ( 5 δ h , v , 0.05 , 1 ) , h > 0 \delta_{h,v}=\frac{\lVert\operatorname{thumb}(I_{h,v})-\operatorname{thumb}(I_{h-1,v})\rVert_1}{255\cdot3\cdot64^2},\qquad s^{\mathrm{change}}_{h,v}=\begin{cases}1,&h=0\\\operatorname{clip}(5\delta_{h,v},0.05,1),&h>0\end{cases} δh,v=255⋅3⋅642∥thumb(Ih,v)−thumb(Ih−1,v)∥1,sh,vchange={1,clip(5δh,v,0.05,1),h=0h>0
这里
I
h
,
v
I_{h,v}
Ih,v 表示第
h
h
h 个时间步的第
v
v
v 个视角,thumb 将图像缩放到 64×64,
s
c
h
a
n
g
e
s^{\mathrm{change}}
schange 是变化分数。它不是语义重要性模型,也不是光流估计器,而是一个便宜的像素级信号;它只回答“这一视角相对于上一次看起来变化了多少”。因此相机抖动和光照变化也会影响权重,不能把高变化分数直接解释成语义目标更重要。
6.2 分配器还要处理上下限与整数余数
变化分数只是权重,真正的分配还要满足每张图的最小和最大 token 限制。仓库实现先给所有图像分配下限,再按联合权重比例分配剩余预算;某张图达到上限后,超出的部分重新分给其他仍有容量的图像,最后把整数除法留下的余数按权重从高到低补齐。这里的关键是让分配总量受控、整数余数处理可复核;每张图最终仍要缩放并对齐编码器网格,因此不能从固定预算直接推导出固定输入形状。下面保留函数的完整参数检查,具体整数分配由末尾调用的辅助函数完成。
# navgpt/vla/runtime/models/image_processing_navgpt3.py
def allocate_vision_tokens(
total_tokens: int,
num_cameras: int,
num_timesteps: int,
temporal_decay: float = 1.0,
camera_weights: list[float] | None = None,
min_tokens_per_image: int = 1,
max_tokens_per_image: int | None = None,
change_scores: np.ndarray | None = None,
) -> np.ndarray:
T, C = num_timesteps, num_cameras
if max_tokens_per_image is None:
max_tokens_per_image = total_tokens
if min_tokens_per_image * T * C > total_tokens:
raise ValueError(
f"min_tokens_per_image={min_tokens_per_image} x {T} x {C} = "
f"{min_tokens_per_image * T * C} exceeds total_tokens={total_tokens}."
)
if max_tokens_per_image < min_tokens_per_image:
raise ValueError("max_tokens_per_image must be >= min_tokens_per_image.")
if T == 1:
t_weights = np.ones(T)
else:
t_weights = np.exp(temporal_decay * np.arange(T, dtype=float) / (T - 1))
if camera_weights is None:
c_weights = np.ones(C)
else:
if len(camera_weights) != C:
raise ValueError(f"Length of camera_weights ({len(camera_weights)}) != num_cameras ({C}).")
c_weights = np.array(camera_weights, dtype=float)
if np.any(c_weights <= 0):
raise ValueError("All camera_weights must be positive.")
weights = np.outer(t_weights, c_weights)
if change_scores is not None:
weights = weights * change_scores
return _allocate_with_constraints(
weights, total_tokens, min_tokens_per_image, max_tokens_per_image
)
代码中的 weights 将时间和相机因素相乘,再可选地乘上变化分数;当 change_scores 不传入时,系统退化为只考虑位置的基线。分配器只保证各图像预算落在上下限内,并尽可能按权重分配剩余量;如果总预算超过所有图像的上限总和,多出来的预算会闲置。实际图像还要对齐视觉编码器的 patch 网格,最终 token 数因此可能小于分配上限,所以这里的预算控制并不保证固定输入张量形状。论文的 codec 消融显示,8B 模型在 R2R-CE 和 RxR-CE 的多个训练范围中都受益。数据和视觉上下文的组织方式,往往比单纯增大模型参数更能改变动作质量。

直觉理解:这像视频会议的码率控制。主持人第一次进入房间时需要完整画面,之后如果墙和桌子没有变化,就只需保留较低细节;有人突然从侧面走进来,变化大的画面就应当获得更多带宽。NavGPT VLA 把同样的原则用于导航历史,但最终仍受固定 token 上限约束。
6.3 预算什么时候真正开始起作用
…详情请参照古月居
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)