3D-VLA 方法原理详解 模型直接输出动作。机器人按动作执行。这种方式有一个核心缺陷模型容易只在二维图像空间里“看起来懂了任务”但并没有显式建立三维物理世界的状态变化。例如“把薯片袋拿出来”“关上抽屉”“把钱放进保险柜”这些任务不只是识别物体还需要理解物体在三维空间中的位置。当前状态和目标状态有什么差别。哪些物体会移动哪些环境结构要保持。动作执行后世界应该变成什么样。3D-VLA 的核心思想是不要让模型直接从当前观察跳到动作而是先让模型具备“想象目标状态”的能力。也就是先回答如果这个指令完成了世界应该长什么样这个“目标世界”可以是目标 RGB 图像、目标 RGB-D 图像也可以是目标点云。然后再基于当前状态和目标状态生成机器人动作。因此 3D-VLA 的方法可以概括为用 3D 场景表示作为语言模型的输入让语言模型在文本 token、3D token、目标生成 token 和动作 token 之间推理当它需要预测未来状态时调用 embodied diffusion model 生成目标图像或目标点云最终将当前状态、目标状态和任务意图转化为机器人动作 token。整体框架3D-VLA 由三类能力组成3D Vision-Language-Action Model这是中枢模型。它基于 3D-LLM / BLIP2 / Flan-T5 的思想把三维场景特征转成语言模型能读的 token embedding并输出自然语言、目标生成请求或动作 token。Embodied Diffusion Models这是“目标想象器”。输入当前状态和语言指令生成任务完成后的目标状态。仓库中包含两种Goal Image LDM生成目标 RGB 或 RGB-D 图像。Goal Point Cloud Diffusion Model生成目标点云。Robot Control Tokenization这是“动作接口”。机器人动作被离散成语言模型能输出的特殊 token例如位置 token、旋转 token、夹爪开合 token 和动作分隔 token。语言模型通过生成 token 序列来表示一串机器人控制命令。整体数据流如下当前多视角/RGB-D观察|v3D特征提取与点云/体素表示|vQ-Former压缩为少量3D scene tokens|v3D Vision-Language-Action LLM|±- 输出目标生成请求 token| || v| Goal Image / Point Cloud Diffusion Model| || v| 生成目标图像或目标点云|±- 输出动作 token|v解码成机器人动作为了避免这个流程过于抽象下面用一个具体任务走一遍。示例任务用户指令: close bottom drawer当前观察: 机器人看到一个柜子底层抽屉是打开的系统已有当前场景点云 start_pc目标结果: 底层抽屉应该被关上生成对应目标点云 goal_pc并进一步产生关抽屉动作对应的数据流可以展开成传感器/数据集给出当前状态RGB-D / 多视角图像 / 点云start_pc [N, 6]每个点包含 [x, y, z, r, g, b]| v构建 3D 场景表示从当前状态中采样固定数量的点例如 2048 或 8192 个点。坐标做归一化颜色/语义特征保留。如果走 3D-VLA LLM则还会有 pc_feat [N, 1408] 的点级特征。| vQ-Former 把大量 3D 点压缩成 scene tokens输入:pc_feat: [1, T, N, 1408]pc: [1, T, N, 3]输出:scene_tokens: [1, T, 32, hidden_dim]这些 scene tokens 会插入到文本里的 位置。| v3D-VLA LLM 理解任务并产生中间意图输入文本可以理解为:“The initial scene is . close bottom drawer”插入 scene tokens 后LLM 看到的不是纯文本而是:文本 token 当前 3D 场景 embedding方法级输出可能是:“I should close bottom drawer ”这表示模型决定先生成一个“抽屉关闭后”的目标点云。| vGoal Point Cloud Diffusion 生成目标状态输入:start_pc: 当前点云 [1, 6, N]instruction: “close bottom drawer”x_T: 随机噪声目标点云 [1, 6, N]扩散模型每一步预测噪声:model_input concat(x_t, start_pc) # [1, 12, N]noise_pred transformer(model_input, timestep, text“close bottom drawer”)x_{t-1} scheduler.step(noise_pred, x_t)输出:goal_pc: 预测的关闭抽屉后点云 [1, 6, N]| v当前状态和目标状态一起形成控制上下文这里不能理解成“只把 goal_pc 直接丢给 action head”。action 预测至少需要三类信息:当前执行场景: 机器人现在面对的 3D 状态也就是 current_scene / start_pc。目标或意图上下文: goal_pc、goal_image或由它们重新编码出的 goal_scene_tokens。执行提示: 例如 “Execute now.”告诉 LLM 当前要从理解/想象阶段切换到动作输出阶段。在方法图里Robot Control 阶段画的是:User: [embed] Execute now.这个 [embed] 可以理解为用于控制的 3D 上下文 embedding。它通常应表达“现在在哪里、要达到什么状态”这类信息而不是裸目标状态本身。一种更清楚的控制输入写法是:“Current scene: . Goal scene: . Execute now.”输出动作 token:aloc…aloc…aloc…arot…arot…arot…gripper…ACT_SEP…| v动作 token 解码成机器人控制量aloc* - 末端执行器位置arot* - 末端执行器姿态gripper* - 夹爪开合ACT_SEP - 动作步之间的分隔最终得到一段关上底层抽屉的控制轨迹。如果走 goal image 分支步骤 5 换成输入:当前图像 image指令 “close bottom drawer”随机图像 latent x_T扩散过程:model_input concat(noisy_goal_image_latent, current_image_latent)noise_pred UNet(model_input, timestep, text“close bottom drawer”)输出:goal_image 关闭底层抽屉后的目标图像所以“整体数据流”真正表达的是3D-VLA 先把当前世界编码成 LLM 能读的 scene tokensLLM 决定任务需要什么目标状态扩散模型生成这个目标状态最后把当前执行场景、目标/意图上下文和 Execute now 这类执行提示一起作为控制输入生成动作 token。它不是一条单纯的图像到动作流水线而是一条带有“目标想象”中间环节的世界模型流水线。输入表示如何把 3D 世界喂给语言模型这一节要回答一个最基本的问题机器人看到的是图像、深度和点云为什么最后能变成 LLM 可以读的 先抓住一句话3D-VLA 不把原始图片直接塞给 LLM而是先把环境整理成一批“带位置的 3D 点”再把这些点压缩成少量 scene tokens。可以把它想成给 LLM 准备一张三维版的场景清单。清单里的每一行大概是第 i 个 3D 点:它在哪里: x, y, z它看起来像什么: 视觉/语义特征 pc_feat[i]例如“关上底层抽屉”这个任务里场景中可能有很多点点A: 位置在柜子底层抽屉把手附近语义上像金属把手点B: 位置在抽屉面板上语义上像木质抽屉点C: 位置在桌面边缘语义上像桌面点D: 位置在背景墙上语义上像无关背景LLM 最终要知道的不是一张照片而是“哪些东西在三维空间的哪里”。这就是第 3 节的核心。3.1 第一步从观察中得到一批 3D 点原始观察可以来自单视角或多视角 RGB 图像。深度图或 RGB-D 图像。已经预处理好的点云。如果有 RGB-D 或多视角信息就可以把像素反投影到 3D 空间得到点云。每个点至少有空间坐标pc[i] [x, y, z]这里的 pc 可以理解为 point coordinates也就是“点在哪里”。在代码的数据集中pc 最终会被整理成pc: [T, N, 3]含义是T这个样本里有几个 3D 状态。常见最大值是 2可以表示当前状态和目标状态。N每个状态采样多少个点。3每个点的三维坐标 [x, y, z]。3.2 第二步给每个 3D 点配一个视觉语义特征只有坐标还不够。坐标只能说明“这里有一个点”但不能说明“这个点属于抽屉、杯子、把手还是背景”。所以每个点还需要一个视觉特征pc_feat[i] 这个点看起来像什么在当前仓库的数据格式里每个点的视觉特征是 1408 维pc_feat: [T, N, 1408]可以不用纠结 1408 维每一维具体代表什么。直观理解就够了pc[i] 告诉模型: 这个点在哪里pc_feat[i] 告诉模型: 这个点是什么/像什么README 中提到的数据处理依赖 SAM、ConceptFusion、3D-CLR 等工具可以理解为这些工具提前帮模型把图像和 3D 点变成了“带语义的点特征”。也就是说3D-VLA LLM 训练时通常不是从原始图片端到端重新提取全部 3D 特征而是读取已经预处理好的 3D features 和 point coordinates。3.3 第三步把不同大小的点云整理成固定长度真实场景的点数不固定。有的场景可能有几十万个点有的场景点更少。但神经网络训练时需要每个 batch 形状一致所以要采样成固定数量的点。源码确认ThreeDVQADataset 会把每个 3D 状态采样到固定 sample_num。如果点太多就随机采样如果点太少就复制已有点补齐。整理后的一个样本大概是sample {pc_feat: [T, N, 1408], # N个点分别是什么/像什么pc: [T, N, 3], # N个点分别在哪里text_input: instruction_or_question,answer: target_text_or_action_tokens}举一个更具体的例子text_input:“The initial scene is . close bottom drawer”pc:当前场景里 N 个点的 3D 坐标pc_feat:这 N 个点对应的视觉语义特征answer:模型应该输出的文本、目标生成 token 或动作 token3.4 第四步把坐标信息加进点特征里如果只看 pc_feat模型可能知道某个点“像抽屉把手”但不知道它在底层还是顶层、在左边还是右边。因此 3D-VLA 会把坐标也编码进去。源码确认BLIP2/T5 模型中会把 x/y/z 三个坐标 clamp 到 0 到 255 的范围分别查 positional embedding再拼接成接近 1408 维的坐标编码加到点特征上。可以把它理解为每个点送入 Q-Former 前的表示 这个点的视觉语义特征这个点的三维位置编码这样模型看到的就不是孤立的“抽屉特征”而是“位于底层某个空间位置的抽屉特征”。3.5 这一节的最简总结第 3 节其实只讲一件事把三维世界整理成 LLM 前端可用的输入。原始 RGB-D / 点云|v一批 3D 点坐标 pc [T, N, 3]|v每个点配一个视觉语义特征 pc_feat [T, N, 1408]|v采样/补齐到固定 N 个点|v把坐标位置编码加到视觉特征上|v交给 Q-Former 压缩成 对应的 scene tokens所以pc 和 pc_feat 的区别可以一句话记住pc 点在哪里pc_feat 点是什么4. Q-Former把大量 3D 点压缩成少量 scene tokens点云或体素特征数量很多不能直接全部塞进大语言模型。3D-VLA 使用 BLIP2 风格的 Q-Former 做压缩。Q-Former 的输入是pc_feat_with_position: [B, T, N, 1408]Q-Former 内部有固定数量的 learnable query tokens。仓库中默认是 32 个 query token。每个 query token 通过 cross-attention 从所有 3D 点特征中抽取信息最终每个 3D 状态被压缩成 32 个语言模型尺度的 embedding。也就是说N个3D点/体素特征 -- 32个scene embeddings这些 scene embeddings 再经过线性投影变成 Flan-T5 可以接收的 hidden size。伪代码如下def encode_3d_scene(pc_feat, pc_coord):# pc_feat: 每个3D点的视觉语义特征形状为 [B, T, N, 1408]# pc_coord: 每个3D点的空间坐标形状为 [B, T, N, 3]# 先把坐标离散到0-255范围再查位置编码 pos_emb positional_encoding(clamp(pc_coord, 0, 255)) # 把“点是什么”和“点在哪里”加在一起形成带空间信息的点特征 point_tokens pc_feat 0.1 * pos_emb scene_tokens [] for each_state in range(T): # 每个3D状态用32个可学习query从N个点里提取关键信息 query learnable_query_tokens(num32) compressed q_former_cross_attention( queryquery, key_valuepoint_tokens[:, each_state] ) # 把Q-Former输出投影到T5/LLM能接收的hidden维度 scene_tokens.append(linear_project_to_t5(compressed)) # 返回每个3D状态对应的scene tokens形状为 [B, T, 32, t5_hidden_dim] return scene_tokens关键点是Q-Former 不是把点云转成文字而是把 3D 场景转成一组软 token embedding。这些 token 对语言模型来说就像“插入到句子里的视觉上下文”。token把 3D embedding 插进语言上下文3D-VLA 不是简单地把 scene embeddings 接在文本前面而是使用特殊 token 作为插入位置。输入文本中会出现类似The initial scene is . Find some snacks for me.模型在 tokenizer 层面加入了,当输入文本 token 中遇到 时模型会在这个位置后面插入对应的 32 个 3D scene embeddings。如果一个样本有多个 3D 状态就可以有多个 占位符每个占位符对应一个 3D 特征块。伪代码如下def insert_scene_embeddings(text_tokens, scene_embeddings):# text_tokens: 已经分词的文本其中包含 占位符# scene_embeddings: 每个 对应的一组3D embedding形状为 [num_scenes, 32, hidden_dim]output_embeddings [] scene_index 0 for token in text_tokens: # 普通文本token直接查语言模型词表embedding output_embeddings.append(embed_text_token(token)) if token scene: # 遇到 scene 时把对应的32个3D scene tokens插入到文本后面 output_embeddings.extend(scene_embeddings[scene_index]) scene_index 1 # 返回“文本embedding 3D embedding”混合后的LLM输入 return output_embeddings这种设计的好处是语言可以显式指代 3D 场景。模型不需要猜“这批视觉 embedding 是什么”因为输入句子已经告诉它这里是当前场景、这里是目标场景、这里是执行环境。Interaction tokens把语言、目标生成和动作统一成 token 接口3D-VLA 的一个重要设计是把不同模态的交互都包装成特殊 token。仓库中加入的特殊 token 包括位置:…3D场景:物体:目标图像:目标点云:动作:……ACT_SEP这使得语言模型可以输出一种“混合程序”Sure, I should pick up the chip bag loc…含义不是普通自然语言而是表示接下来要生成目标点云。… 标记目标物体。loc… 标记目标相关空间位置。表示目标点云请求结束。对于机器人控制输出可能类似ACT_SEP…这表示一串离散化后的动作参数。模型不是直接输出浮点控制量而是像生成文本一样生成动作 token再由后处理把 token 解码回连续动作空间。为什么要用 token 统一接口这样做有三个好处统一学习目标无论是回答问题、请求目标生成、描述物体位置还是输出动作本质上都变成 sequence-to-sequence 的 token 预测。可组合模型可以先生成“我需要一个目标点云”再由扩散模型生成目标状态之后再基于当前状态和目标/意图上下文生成动作。可解释中间输出不是黑盒向量而是带有结构的 token 序列。人可以看出模型正在想象图像、点云还是直接控制机器人。3D-VLA LLM 的训练目标3D-VLA 的 LLM 部分本质上是条件文本生成输入: 3D场景 用户指令/问题输出: 答案、目标生成请求、目标描述或动作token序列训练时使用标准 teacher forcingdef train_3d_vla_llm(sample):# 1. 把样本里的3D点特征和坐标编码成scene tokensscene_tokens encode_3d_scene(sample.pc_feat, sample.pc)# 2. 把scene tokens插入到文本里的 scene 位置 input_embeddings insert_scene_embeddings( tokenize(sample.text_input), scene_tokens ) # 3. answer是监督信号可能是普通答案、目标生成token或动作token target_tokens tokenize(sample.answer) # 4. 用teacher forcing训练T5生成目标token序列 logits t5( encoder_inputsinput_embeddings, decoder_inputstarget_tokens[:-1] ) # 5. 计算下一个token预测的交叉熵损失 loss cross_entropy(logits, target_tokens[1:]) update_trainable_parameters(loss)源码确认的训练细节语言模型基于 Flan-T5。T5 主体参数被冻结。T5 的输入/输出 embedding 会训练因为新增了大量特殊 token。Q-Former 和从 Q-Former 到 T5 hidden size 的投影层承担 3D 对齐。训练样本的答案字段可以有多个 answer训练时会展开为多个目标序列。这种设计不是从零训练一个大模型而是把预训练语言模型作为推理核心然后学习如何把 3D 场景嵌入到它能理解的 token 空间里。Goal Image / RGB-D Latent Diffusion ModelGoal Image LDM 用来回答给定当前图像和语言指令任务完成后的图像应该是什么样训练样本形式是当前图像 input_image语言指令 edit_prompt目标图像 edited_image可选当前深度 input_depth可选目标深度 edited_depth例如input_image: 桌上有瓶子和海绵edit_prompt: move water bottle near spongeedited_image: 瓶子移动到海绵附近后的图像8.1 它为什么基于 InstructPix2PixInstructPix2Pix 的任务是输入原图 编辑指令 - 输出编辑后的图机器人目标图像生成也可以看成一种编辑当前状态图像 任务指令 - 完成任务后的目标状态图像所以 3D-VLA 直接使用 Stable Diffusion / InstructPix2Pix 风格的 latent diffusion training。8.2 RGB 版本的训练过程RGB 版本使用 VAE 把图像压到 latent 空间目标图像 edited_image - target_latent当前图像 input_image - condition_latent语言指令 edit_prompt - text_embedding训练时对目标 latent 加噪noisy_target_latent add_noise(target_latent, noise, timestep)然后把 noisy target latent 和 condition latent 在通道维拼接送入 UNetUNet输入 concat(noisy_target_latent, condition_latent)UNet条件 text_embedding timestepUNet输出 predicted_noiseloss MSE(predicted_noise, true_noise)RGB 图像的 latent 通常是 4 通道因此noisy目标latent: 4 channels当前图像condition latent: 4 channelsUNet输入: 8 channelsUNet输出: 4 channels伪代码如下def train_goal_image_ldm(batch):# 当前图像是条件目标图像是扩散模型要学习生成的结果input_image batch[“original_pixel_values”]goal_image batch[“edited_pixel_values”]instruction batch[“input_ids”]# 把目标图像和当前图像都编码到Stable Diffusion的latent空间 goal_latent vae.encode(goal_image).sample() * vae_scaling input_latent vae.encode(input_image).mode() # 随机选择扩散步并给目标latent加噪声 t sample_random_timestep() noise randn_like(goal_latent) noisy_goal scheduler.add_noise(goal_latent, noise, t) # 文本指令作为条件当前图像latent也作为条件 text_cond clip_text_encoder(instruction) model_input concat_channels(noisy_goal, input_latent) # UNet学习预测加到目标latent上的噪声 pred_noise unet(model_input, t, text_cond) loss mse(pred_noise, noise) # 只更新扩散UNet相关参数 update_unet(loss)8.3 RGB-D 版本如何扩展RGB-D 版本把 RGB 和 depth 分别经过 VAE 编码然后在 latent 通道上拼接RGB目标latent: 4 channelsDepth目标latent: 4 channels目标latent合计: 8 channelsRGB当前latent: 4 channelsDepth当前latent: 4 channels当前condition: 8 channelsUNet输入: 16 channelsUNet输出: 8 channels源码确认训练脚本会修改 UNet 的输入卷积和输出卷积使其支持 RGB-D 的 16 通道输入和 8 通道输出。新增通道部分从预训练权重中尽量复制已有通道其余初始化为零或新权重。8.4 推理过程推理时没有目标图像。模型从随机噪声 latent 开始逐步去噪def infer_goal_image(input_image, instruction):# 当前图像提供“从哪里开始变化”的条件input_latent vae.encode(input_image).mode()# 文本指令提供“要变成什么样”的条件 text_cond clip_text_encoder(instruction) # 推理时没有目标图像所以从随机latent噪声开始 x random_latent() for t in scheduler.timesteps: # 每一步都把当前去噪状态和原始图像条件拼在一起 model_input concat_channels(x, input_latent) pred_noise unet(model_input, t, text_cond) # 根据预测噪声从x_t更新到x_{t-1} x scheduler.step(pred_noise, t, x) # 最终latent解码成目标图像 goal_image vae.decode(x) return goal_image仓库推理默认使用50 个 denoising steps。text guidance scale。image guidance scale。如果是 RGB-D则分别 decode RGB latent 和 depth latent。9. Goal Point Cloud Diffusion ModelGoal Point Cloud Diffusion Model 用来回答给定当前点云和语言指令任务完成后的目标点云应该是什么样训练样本来自 RLBench每个样本包含start_pc: 初始点云end_pc: 目标点云instruction: 任务文字每个点通常包含 6 个通道[x, y, z, r, g, b]9.1 点云预处理训练时会做几个关键预处理采样固定数量的点。仓库训练默认是 2048 点推理配置中可以用 8192 点。对起始点云和目标点云使用同一套采样索引。这样第 i 个起始点和第 i 个目标点在训练中形成对应关系扩散模型学习的是整组点的状态变化。归一化坐标。把起始点云和目标点云合在一起计算中心和半径再把坐标归一化到单位球附近。这样不同场景尺度不会让模型训练不稳定。伪代码def preprocess_pointcloud_pair(start_pc, end_pc, sample_size):# 1. 对起始点云采样固定数量的点# 这里对start_pc和end_pc使用同一组索引保持点的对应关系idx random_sample_indices(len(start_pc), sample_size)start_pc start_pc[idx]end_pc end_pc[idx]# 2. 用起始点云和目标点云共同计算归一化中心和尺度 xyz_all concat(start_pc[:, :3], end_pc[:, :3]) center mean(xyz_all) radius max_norm(xyz_all - center) # 3. 只归一化xyz坐标颜色/其他通道保持原来的语义 start_pc[:, :3] (start_pc[:, :3] - center) / radius end_pc[:, :3] (end_pc[:, :3] - center) / radius # 返回处理后的“当前点云”和“目标点云” return start_pc, end_pc9.2 为什么改造 Point-EPoint-E 原本是文本/图像条件点云生成模型。3D-VLA 需要的不是“从零生成一个物体点云”而是当前点云 语言指令 - 目标点云所以它把模型输入改成concat(noisy_goal_pc, start_pc)如果每个点云是 6 通道那么拼接后是 12 通道noisy_goal_pc: 6 channelsstart_pc: 6 channelsmodel_input: 12 channelsmodel_output: 6 channels predicted noise源码确认GoalPointDiffusionTransformer.modify_layer() 会把输入线性层改为 12 通道把输出线性层改为 6 通道并尽量继承 Point-E 预训练权重。9.3 训练过程训练目标和图像扩散类似只是对象从 image latent 换成 point cloud tensor。def train_goal_pointcloud_diffusion(batch):# start_pc是当前状态点云goal_pc是任务完成后的目标点云start_pc batch[“start_pc”] # [B, N, 6]每个点是[x,y,z,r,g,b]goal_pc batch[“end_pc”] # [B, N, 6]text batch[“instruction”]# 扩散模型按通道优先格式处理点云所以从[B,N,6]转成[B,6,N] start_pc transpose_to_channels_first(start_pc) # [B, 6, N] goal_pc transpose_to_channels_first(goal_pc) # [B, 6, N] # 随机选择扩散时间步并给目标点云加噪声 t sample_random_timestep() noise randn_like(goal_pc) noisy_goal scheduler.add_noise(goal_pc, noise, t) # 把“带噪目标点云”和“当前点云”拼接作为模型输入 model_input concat_channels(noisy_goal, start_pc) # [B, 12, N] # Transformer根据当前点云、时间步和文本指令预测目标点云上的噪声 pred_noise point_transformer( xmodel_input, timestept, input_pointcloudstart_pc, textstext ) # 训练目标是让预测噪声接近真实噪声 loss mse(pred_noise, noise) update_model(loss)模型内部使用timestep embedding 表示当前扩散步。CLIP text embedding 表示语言指令。transformer backbone 建模点之间的全局关系。conditioning dropout 支持 classifier-free guidance。9.4 推理过程推理时从随机点云噪声开始在每一步只更新目标点云那 6 个通道起始点云条件保持不变。def infer_goal_pointcloud(start_pc, instruction):# 先把输入点云采样、归一化成模型需要的格式start_pc normalize_and_sample(start_pc)# 推理时没有目标点云所以从随机噪声点云开始 x_goal randn([1, 6, N]) # 前6个通道是正在去噪的目标点云后6个通道是固定的当前点云条件 x concat_channels(x_goal, start_pc) # [1, 12, N] for t in scheduler.timesteps: # 条件分支带文本指令预测“按指令完成任务”的去噪方向 pred_noise_cond model(x, t, start_pc, texts[instruction]) if use_classifier_free_guidance: # 无条件分支不输入文本用于classifier-free guidance pred_noise_uncond model(x, t, start_pc, texts[None]) # 放大文本指令带来的方向差异让生成结果更听指令 pred_noise pred_noise_uncond scale * ( pred_noise_cond - pred_noise_uncond ) else: pred_noise pred_noise_cond # 只更新前6个通道也就是目标点云当前点云条件保持不变 x[:, :6] scheduler.step(pred_noise, t, x[:, :6]) # 返回最终生成的目标点云 return x[:, :6]仓库推理默认使用 64 个 reverse diffusion stepsclassifier-free guidance scale 为 2.0。目标想象如何服务机器人控制3D-VLA 最重要的思想不是“额外训练一个生成模型”而是把目标生成变成动作规划前的中间世界模型。完整方法可以分成两阶段阶段一Goal Imagination输入当前场景和用户指令User: The initial scene is [embed]. Find some snacks for me.3D-VLA LLM 生成带结构的响应Robot: Sure, I should pick up the chip bag [loc tokens]这表示模型判断为了完成任务应该在目标点云空间中想象“薯片袋被拿起”后的状态。随后系统把 … 中的语义、目标物体和位置 token 交给 point cloud diffusion model生成目标点云。如果输出的是 …则调用 goal image diffusion model生成目标图像或 RGB-D 图像。阶段二Robot Control得到目标状态后系统进入 Robot Control 阶段。这里的 action 预测输入不是“只有目标状态”。更准确地说输入是一个控制上下文至少包括当前执行场景机器人此刻所在的 3D 场景包含当前物体位置、障碍物、抽屉/柜子等结构状态。目标状态或目标意图Goal Imagination 生成的目标图像/目标点云或由目标状态重新编码得到的 scene tokens。执行提示文本例如 Execute now.告诉 LLM 这一步要输出动作 token而不是继续描述或继续想象。方法图中简化写成User: [current_or_goal_embed] Execute now.这句话容易造成误解。它不是说 action 预测只看一个目标点云而是说控制阶段仍然通过 … 这个接口把 3D 控制上下文插入 LLM。这个 scene embedding 可以来自当前状态、目标状态或者当前状态和目标状态共同构成的表示。对于真实控制任务只知道目标状态通常不够因为动作还依赖机器人当前末端位置、当前物体位置、可碰撞结构和执行起点。一种更明确的展开形式是User: Current scene is [current_embed].Goal scene is [goal_embed].Execute now.模型输出动作 tokenRobot: Actions are: [action tokens]这些 action tokens 再被解码为机器人轨迹或离散控制序列。源码边界也要说清楚当前公开仓库定义了 action special tokens也实现了 3D scene embedding 插入 LLM 的机制但没有完整释放“当前状态 目标状态 - 动作控制器”的端到端控制脚本。因此这里讲的是 3D-VLA 方法层面的控制输入结构不是公开代码里已经完整落地的控制数据格式。高层伪代码def run_3dvla_policy(observation, user_instruction):# 1. 从机器人当前观察中构建3D场景current_scene build_3d_scene(observation)# 2. 把当前3D场景编码成LLM可以接收的scene tokens current_scene_tokens encode_3d_scene( current_scene.features, current_scene.coordinates ) # 3. 先让LLM根据当前场景和用户指令判断是否需要目标想象 reasoning_text llm_generate( textfThe initial scene is scene/scene. {user_instruction}, scene_tokens[current_scene_tokens] ) if contains_goal_image_request(reasoning_text): # 4a. 如果LLM输出了image请求就调用目标图像扩散模型 goal_prompt parse_between(reasoning_text, image, /image) goal_state goal_image_ldm.generate( input_imageobservation.rgb_or_rgbd, instructiongoal_prompt ) # 把生成的目标图像重新编码成控制阶段可用的3D/scene上下文 goal_scene_tokens encode_goal_state(goal_state) elif contains_goal_pointcloud_request(reasoning_text): # 4b. 如果LLM输出了pcd请求就调用目标点云扩散模型 goal_prompt parse_between(reasoning_text, pcd, /pcd) goal_state goal_pcd_diffusion.generate( input_pointcloudcurrent_scene.pointcloud, instructiongoal_prompt ) # 把生成的目标点云编码成控制阶段可用的scene tokens goal_scene_tokens encode_goal_state(goal_state) else: # 4c. 如果LLM没有要求目标想象就直接基于当前场景执行 goal_scene_tokens None # 5. 动作预测不能只看目标状态有目标时同时输入当前场景和目标场景 if goal_scene_tokens is not None: action_text llm_generate( textCurrent scene is scene/scene. Goal scene is scene/scene. Execute now., scene_tokens[current_scene_tokens, goal_scene_tokens] ) else: # 没有显式目标状态时只输入当前场景和执行提示 action_text llm_generate( textCurrent scene is scene/scene. Execute now., scene_tokens[current_scene_tokens] ) # 6. 从LLM输出文本中解析动作token并解码成机器人动作 action_tokens parse_action_tokens(action_text) robot_actions decode_action_tokens(action_tokens) return robot_actions这里的核心是LLM 不只是“看图说动作”而是可以把任务拆成“当前状态理解 - 目标状态想象 - 动作生成”。动作 token 如何表达机器人控制仓库中加入了三类动作 token……ACT_SEP它们可以表达一个 7D end-effector action位置: x, y, z旋转: roll, pitch, yaw 或其他旋转参数化夹爪: open / close一种典型解码方式是def decode_action_step(tokens):# 前3个位置token表示末端执行器目标位置的离散binx_bin parse(“aloc*”, tokens[0])y_bin parse(“aloc*”, tokens[1])z_bin parse(“aloc*”, tokens[2])# 后3个旋转token表示末端执行器目标姿态的离散bin r1_bin parse(arot*, tokens[3]) r2_bin parse(arot*, tokens[4]) r3_bin parse(arot*, tokens[5]) # 夹爪token表示开合状态 gripper 1 if tokens[6] gripper1 else 0 # 把离散bin还原成连续空间中的位置和旋转 xyz dequantize_position(x_bin, y_bin, z_bin) rot dequantize_rotation(r1_bin, r2_bin, r3_bin) # 返回一个单步7D动作 return Action(xyzxyz, rotationrot, grippergripper)多步动作之间用 ACT_SEP 分隔def decode_action_sequence(token_sequence):# ACT_SEP 用来分隔多个动作步chunks split_by_token(token_sequence, “ACT_SEP”)# 每一段token解码成一个机器人动作 return [decode_action_step(chunk) for chunk in chunks]源码确认当前公开代码中定义了这些 action special tokens但没有完整释放 action token 到真实机器人控制器的后处理实现。因此本文对 7D 动作解码细节属于方法层解释而不是公开仓库中已完整实现的控制栈。三类训练任务之间的关系3D-VLA 不是只训练一个模型而是多种任务共同形成能力。12.1 3D-VLA LLM 对齐训练目标让语言模型读懂 3D 场景并输出正确文本、目标请求或动作 token。输入3D场景特征 语言问题/指令输出答案 / 目标生成token / 动作token损失token cross entropy12.2 Goal Image LDM 训练目标学习当前图像到目标图像的任务条件变化。输入当前RGB/RGB-D 指令输出目标RGB/RGB-D损失diffusion noise prediction MSE12.3 Goal Point Cloud Diffusion 训练目标学习当前点云到目标点云的任务条件变化。输入当前点云 指令输出目标点云损失diffusion noise prediction MSE三者的关系可以理解为LLM负责决定“应该想象什么/应该执行什么”Diffusion负责生成“完成任务后的世界状态”Action tokens负责把目标状态落实为控制命令13. 关键设计为什么有效13.1 显式引入目标状态如果直接输出动作模型必须隐式学会当前状态 - 目标状态 - 动作3D-VLA 把中间的目标状态显式化当前状态 - 目标想象 - 动作这让模型更容易处理长时程任务因为目标状态是一个可观察、可训练、可检查的中间变量。13.2 使用 3D 表示而不是只用 2D 图像二维图像容易受到视角、遮挡和尺度影响。3D 点云/体素表示能更直接表达物体的真实空间位置。操作目标和障碍物之间的距离。抽屉、柜子、桌面等结构的三维关系。机器人末端执行器应该到达的位置。13.3 用 token 统一多模态接口把位置、物体、图像请求、点云请求和动作全部做成 token使得系统可以复用大语言模型的序列生成能力而不需要为每种输出设计完全不同的 head。13.4 用扩散模型建模多解目标同一个任务可能有多个合理目标状态。例如“把杯子移到海绵附近”杯子可以放在海绵左边、右边或前面。扩散模型天然适合生成多模态分布而不是只回归一个平均结果。13.5 冻结大语言模型主体训练对齐层和特殊 token冻结 Flan-T5 主体可以保留语言模型已有的推理和泛化能力同时降低训练成本。训练 Q-Former、投影层和新增 token embedding让模型学会把 3D 世界接入语言空间。最简洁的完整伪代码下面是一份把 3D-VLA 方法串起来的端到端伪代码。它表达的是论文方法的完整运行逻辑当前公开仓库没有把这段闭环控制流程封装成同一个可直接运行的入口。class ThreeDVLA:definit(self):# 3D场景编码器把点云/3D特征压缩成scene tokensself.scene_encoder QFormer3DEncoder()# 带特殊token的语言模型负责推理、请求目标生成、输出动作token self.llm FlanT5WithSpecialTokens() # 目标图像生成模型当前图像 指令 - 目标图像 self.goal_image_model GoalImageLatentDiffusion() # 目标点云生成模型当前点云 指令 - 目标点云 self.goal_pcd_model GoalPointCloudDiffusion() def encode_scene(self, observation): # 从观察中构建点级视觉特征和3D坐标 pc_feat, pc_coord build_3d_features(observation) # 编码成LLM可以通过scene读取的scene tokens return self.scene_encoder(pc_feat, pc_coord) def imagine_goal(self, observation, instruction): # 先把当前观察编码成3D上下文 scene_tokens self.encode_scene(observation) # 让LLM根据当前场景和用户指令判断是否需要生成目标状态 response self.llm.generate( textThe initial scene is scene/scene. instruction, scene_tokens[scene_tokens], ) if has_tag(response, image): # LLM请求目标图像时调用goal image diffusion goal_instruction extract_tag(response, image) return self.goal_image_model.generate( input_imageobservation.rgb_or_rgbd, instructiongoal_instruction, ) if has_tag(response, pcd): # LLM请求目标点云时调用goal point cloud diffusion goal_instruction extract_tag(response, pcd) return self.goal_pcd_model.generate( input_pointcloudobservation.pointcloud, instructiongoal_instruction, ) # 如果LLM没有请求目标生成则返回空后续直接基于当前场景执行 return None def act(self, observation, instruction): # 动作阶段始终需要当前执行场景 current_scene_tokens self.encode_scene(observation) # 先尝试想象目标状态 goal_state self.imagine_goal(observation, instruction) if goal_state is not None: # 有目标状态时把目标状态也编码成scene tokens goal_scene_tokens self.encode_scene(goal_state) # 动作预测同时输入当前场景和目标场景 action_text self.llm.generate( textCurrent scene is scene/scene. Goal scene is scene/scene. Execute now., scene_tokens[current_scene_tokens, goal_scene_tokens], ) else: # 没有显式目标状态时只基于当前场景和执行提示输出动作 action_text self.llm.generate( textCurrent scene is scene/scene. Execute now., scene_tokens[current_scene_tokens], ) # 将LLM生成的动作token解析并解码成机器人控制量 action_tokens parse_action_tokens(action_text) return decode_robot_actions(action_tokens)