在 NVIDIA 开源的 Alpamayo 项目中,实现了将连续的自车历史轨迹坐标离散化为 Token,并无缝注入到大模型的 Prompt 中,让大模型不仅“看懂”图像和文本,还能“理解”车辆过去的行驶轨迹。
本文围绕推理过程中fuse_traj_tokens函数的调用,解析了自车历史轨迹从连续坐标到离散 token、再到替换 prompt 占位符的过程,并与 OpenDriveVLA 的多模态特征注入机制进行了简要对比。
一、input_ids
在推理代码 test_inference.py 中,模型首先通过 HuggingFace 的 processor 对输入消息进行 tokenize,得到 input_ids:
# dict_keys(['input_ids', 'attention_mask', 'pixel_values', 'image_grid_thw'])tokenized_data=data["tokenized_data"]input_ids=tokenized_data.pop("input_ids")- input_ids 是什么?
是一个形状为 [B, seq_len] 的 PyTorch LongTensor,包含了 VLM 分词后的完整文本 Prompt。 - 主要包括哪些字段?
为了支持多模态和轨迹预测,input_ids 中不仅包含常规文本,还包括许多特殊的占位符 Token:
| 内容模块 | 说明 |
|---|---|
| 系统/用户 Prompt | 常规的指令文字 |
| 图像占位符 | 对应 pixel_values 中的图片特征占位,如<|image_pad|> |
| 历史轨迹占位符 | 如<|traj_history_start|><|traj_history|>...<|traj_history_end|>,这是本文的核心,后续将被替换为真实的历史轨迹 Token |
| 推理/生成标记 | 如<|cot_start|>(思维链起始)、<|traj_future_start|>(未来轨迹生成起始) |
- input_ids 中的历史轨迹占位符
上述表格中,input_ids中包含了历史轨迹占位符 token。在构造消息时(helper.create_message),有这样一段设计:
num_traj_token=48hist_traj_placeholder=(f"<|traj_history_start|>{'<|traj_history|>'*num_traj_token}<|traj_history_end|>")这意味着 prompt 中预先放了48 个<|traj_history|>token,前后分别用<|traj_history_start|>和<|traj_history_end|>包裹。这些 token 在 tokenize 后变成对应的整数 ID,占据input_ids中的固定位置。
这些占位符的作用就是预留的slot——后续会用量化后的真实轨迹 token 来替换。
二、自车轨迹的离散化与量化
大语言模型(LLM)的自回归架构只能处理离散的 Token(整数),无法直接理解连续的 XYZ 坐标。因此,Alpamayo 引入了Delta Tokenizer(增量分词器),将连续的轨迹转化为离散的数字 ID。
量化过程主要分为以下 4 个关键步骤(核心代码位于delta_tokenizer.py的encode函数):
- 1. 计算增量(Delta)位置
模型不关心车辆在整个地图上的绝对坐标,只关心 “相对于上一步,车辆移动了多少”。
# shape: (1,16,3) --> (1,17,3), 对于3D tensor, 在第二维前面填充一个元素, 默认是0xyz=torch.nn.functional.pad(fut_xyz,[0,0,1,0,0,0])xyz=xyz[:,1:]-xyz[:,:-1]通过相邻帧求差,将绝对坐标转换为步进增量。第一个时间步的增量是相对于原点 (0, 0, 0) 的偏移。这降低了对绝对坐标的依赖,使量化范围更集中。
- 2. 归一化到 [0, 1] 区间
为了统一量纲,根据配置的物理范围(例如 x/y 轴 [-4m, 4m],z 轴 [-10m, 10m]),将增量值线性映射到 [0, 1] 之间。
ego_xyz_max=torch.tensor(self.ego_xyz_max,dtype=xyz.dtype,device=xyz.device)ego_xyz_min=torch.tensor(self.ego_xyz_min,dtype=xyz.dtype,device=xyz.device)xyz=(xyz-ego_xyz_min)/(ego_xyz_max-ego_xyz_min)- 3. 量化为整数 Token
将 [0, 1] 的浮点数映射到有限的词汇表(Bins)中。默认情况下,词汇表大小为 1000(num_bins=1000)。
# self.num_bins = 1000xyz=(xyz*(self.num_bins-1)).round().long()# 乘以 999 并四舍五入xyz=xyz.clamp(0,self.num_bins-1)# 安全裁剪到 [0, 999]本质:这是一个向量量化编码器。连续的物理位移被离散化为了0~999的整数 Token。
- 4. 展平与词表偏移
将形状从 (B, Th, 3) 展平为 (B, Th×3)。对于 16 个历史时间步,每步 3 个 token(x, y, z),共输出 48 个 token。
随后,在tokenize_history_trajectory中,还会加上一个start_idx(self.hist_token_start_idx = 154669),将历史轨迹 Token 偏移到独立的词表区间,与未来轨迹 Token 互不重叠,让模型能通过 ID 范围直接区分二者。
三、将量化 token 注入 Prompt
轨迹量化完成后,得到了一串离散的 Token 序列(形状为 [B, 48] 的 hist_idx),然后需要将其嵌入到 input_ids 中。
这一流程调用了 replace_pad_token 函数来实现:
defreplace_pad_token(input_ids:torch.Tensor,new_ids:torch.Tensor,pad_idx:int)->torch.Tensor:"""Replace pad tokens in input_ids with new token values."""mask=input_ids==pad_idxreturninput_ids.masked_scatter(mask,new_ids)找到 input_ids 中所有等于pad_idx(占位符ID = 155684)的位置,生成布尔掩码 mask;
使用 PyTorch 的masked_scatter操作,将量化好的new_ids按顺序填充到这些位置中。
四、OpenDriveVLA 与 Alpamayo 的 Prompt 构建差异
对比 Alpamayo 与 OpenDriveVLA,核心区别体现在视觉特征处理与历史轨迹注入两个维度:
1. 视觉特征的注入:BEV感知特征 vs 原始 2D 图像
- OpenDriveVLA:高度依赖BEV(鸟瞰图)3D 结构化感知结果。它将 3D 视觉特征(
Scene/Track/Map)通过投影层映射为连续的特征向量序列,拼接到 Prompt 中。
📌 参考笔者对OpenDriveVLA特征嵌入的文章:
(四)OpenDriveVLA的5类Prompt设计与跨模态特征注入机制
(五)揭秘 OpenDriveVLA:结构化感知如何“跨模态对齐”大模型?
- Alpamayo:放弃复杂的 BEV 特征提取,直接依赖视觉编码器提取的 2D 多视角图像特征,让大模型直接从原始 2D 图像中理解 3D 空间。
2. 历史轨迹的嵌入:文本化拼接 vs Token 量化
- OpenDriveVLA:将历史轨迹(如过去 2 秒的坐标点)直接格式化为人类可读的文本字符串(例如 “Historical trajectory (last 2 seconds): [(-0.30,-17.01),…]”),作为文本 Prompt 的一部分直接拼接进去。LLM 的 Tokenizer 会将其切分为普通的数字 Token,完全依赖大模型的文本阅读与推理能力来理解这些物理坐标。
📌 参考笔者OpenDriveVLA推理数据加载的文章:
(三)深度拆解 OpenDriveVLA 推理数据加载:基于__getitem__()方法获取单帧数据
- Alpamayo:历史轨迹作为独立的外部物理条件,将过去若干帧的真实历史轨迹序列(XYZ坐标+旋转)进行 Delta 量化,转为离散 Token,显式嵌入到 Prompt 序列中。
参考
- 项目地址:NVIDIA/alpamayo
- 核心文件:
src/alpamayo_r1/models/alpamayo_r1.py— 推理入口src/alpamayo_r1/models/base_model.py—fuse_traj_tokens、tokenize_history_trajectorysrc/alpamayo_r1/models/delta_tokenizer.py—DeltaTrajectoryTokenizer.encodesrc/alpamayo_r1/helper.py— prompt 模板构造
(本文为笔者阅读与跟踪ALpamayo开源代码后,撰写的CSDN原创文章,转载请注明出处。)