当前位置: 首页 > news >正文

GR00T N1.7源码学习(一):工程入口、模型结构与动作生成流程解析

1、GR00T N1.7由视觉语言骨干和动作模型组成

GR00T N1.7是一套面向机器人控制的Vision-Language-Action模型。模型接收相机图像、语言指令和机器人当前状态,输出一段连续的机器人动作序列。

GR00T N1.7内部包含两条比较明显的处理链路。第一条链路负责处理图像和语言,得到视觉语言特征;第二条链路负责将机器人状态、带噪动作以及视觉语言特征组合起来,生成连续动作。

模型主类定义在,

gr00t/model/gr00t_n1d7/gr00t_n1d7.py

其中最主要的两个类是,

class Gr00tN1d7ActionHead(nn.Module) """Action head component for flow matching diffusion policy.""" class Gr00tN1d7(PreTrainedModel) """Gr00tN1d7: VLA model with Cosmos-Reason2-2B (Qwen3-VL) backbone."""

Gr00tN1d7负责组织完整模型,内部包含视觉语言Backbone和动作头;Gr00tN1d7ActionHead负责根据视觉语言特征、机器人状态和带噪动作预测动作速度。完整的数据流如下,

图像、语言 -> Cosmos-Reason2-2B(基于Qwen3-VL) -> 视觉语言特征

视觉语言特征 + 机器人状态 + 带噪动作 -> DiT -> 动作速度场 -> Euler积分 -> 连续动作序列

这里需要先区分两个经常混在一起的概念。GR00T N1.7的动作网络使用DiT,也就是Diffusion Transformer,但动作的训练目标采用Flow Matching。DiT描述的是网络结构,Flow Matching描述的是训练和采样方法,两者并不冲突。

2、N1.7微调入口完成模型与数据配置

N1.7微调入口位于,

gr00t/experiment/launch_finetune.py

命令行参数由tyro根据FinetuneConfig自动生成,

ft_config = tyro.cli(FinetuneConfig, description=__doc__)

FinetuneConfig定义在:

gr00t/configs/finetune_config.py

该配置类中包含模型路径、数据集路径、机器人类型、训练哪些模块、学习率、Batch Size、保存频率等参数。例如,

@dataclass class FinetuneConfig: base_model_path: str dataset_path: str embodiment_tag: str modality_config_path: str | None = None tune_llm: bool = False tune_visual: bool = False tune_projector: bool = True tune_diffusion_model: bool = True global_batch_size: int = 64 learning_rate: float = 1e-4 gradient_accumulation_steps: int = 1 output_dir: str = "./outputs"

从默认值可以看出,常规微调不会更新完整的语言模型和视觉编码器,而是训练动作头中的状态编码器、动作编码器、动作解码器以及DiT主体。需要注意的是,配置项虽然叫tune_projector,但从后面的源码可以看到,它控制的并不是单独一个传统意义上的多模态投影层,而是动作头前后的多组特征映射模块。

tune_llm = False tune_visual = False tune_projector = True tune_diffusion_model = True

这种设置比较符合机器人数据集的实际情况。机器人示范数据的规模通常远小于视觉语言模型的预训练数据,如果直接更新完整VLM,不仅显存开销很高,也容易破坏原有的视觉语言能力。

进入主函数后,代码先解析embodiment_tag,

from gr00t.data.embodiment_tags import EmbodimentTag ft_config.embodiment_tag = EmbodimentTag.resolve(ft_config.embodiment_tag) embodiment_tag = ft_config.embodiment_tag.value

embodiment_tag可以理解为机器人本体类型标识。不同机器人可能有完全不同的状态维度和动作维度,例如单臂机械臂、双臂机器人、人形机器人以及仿真环境中的机械臂,其关节数量和动作定义都不同。GR00T通过该标识选择对应的数据配置以及类别相关的动作编码器。数据集路径支持传入多个目录,

dataset_paths = [ path for path in ft_config.dataset_path.split(os.pathsep) if path ]

在Linux系统中,os.pathsep通常是冒号,因此可以通过下面的方式传入多个数据集,

--dataset-path /data/task_a:/data/task_b

随后代码加载默认配置,并将用户传入的数据集注册到配置中,

config = get_default_config().load_dict( { "data": { "download_cache": False, "datasets": [ { "dataset_paths": dataset_paths, "mix_ratio": 1.0, "embodiment_tag": embodiment_tag, } ], } } )

接下来会覆盖N1.7使用的模型参数:

config.model.tune_llm = ft_config.tune_llm config.model.tune_visual = ft_config.tune_visual config.model.tune_projector = ft_config.tune_projector config.model.tune_diffusion_model = ft_config.tune_diffusion_model config.model.load_bf16 = False config.model.reproject_vision = False config.model.model_name = "nvidia/Cosmos-Reason2-2B" config.model.backbone_trainable_params_fp32 = True config.model.use_relative_action = True

这里有几个配置值得单独说明。model_name被固定为nvidia/Cosmos-Reason2-2B,说明N1.7默认使用Cosmos-Reason2-2B作为视觉语言骨干;use_relative_action=True表示训练时默认使用相对动作;backbone_trainable_params_fp32=True表示VLM中可训练的参数会保留FP32精度,避免低精度训练导致数值不稳定。最后将训练参数写入总配置并调用:

run(config)

因此,launch_finetune.py本身并不包含训练循环,它的主要工作是读取命令行参数、构造配置,然后将真正的训练交给gr00t/experiment/experiment.py。

3、Gr00tN1d7主类组织完整的VLA模型

Gr00tN1d7继承自Hugging Face的PreTrainedModel,

class Gr00tN1d7(PreTrainedModel): config_class = Gr00tN1d7Config supports_gradient_checkpointing = True

继承PreTrainedModel以后,模型可以使用Hugging Face提供的权重保存和加载接口,例如,

AutoModel.from_pretrained(...) model.save_pretrained(...)

初始化函数中首先构造Backbone,

backbone_cls = get_backbone_cls(config) self.backbone = backbone_cls( model_name=config.model_name, tune_llm=config.tune_llm, tune_visual=config.tune_visual, select_layer=config.select_layer, reproject_vision=config.reproject_vision, use_flash_attention=config.use_flash_attention, load_bf16=config.load_bf16, tune_top_llm_layers=config.tune_top_llm_layers, trainable_params_fp32=config.backbone_trainable_params_fp32, transformers_loading_kwargs=transformers_loading_kwargs, )

get_backbone_cls(config)会根据配置返回对应的视觉语言模型封装类。N1.7配置中使用的是Cosmos-Reason2-2B,其底层结构属于Qwen3-VL系列,因此后续动作模型拿到的不是原始图片,而是Qwen3-VL编码后的序列特征。动作头的初始化比较直接,

self.action_head = Gr00tN1d7ActionHead(config)

此外还会创建数据Collator,

from .processing_gr00t_n1d7 import Gr00tN1d7DataCollator self.collator = Gr00tN1d7DataCollator( model_name=config.model_name, model_type=config.backbone_model_type, transformers_loading_kwargs=transformers_loading_kwargs, )

Collator负责把一个Batch中的图像和语言整理成VLM能够接受的输入格式,包括input_ids、attention_mask、图像Tensor以及视觉相关的网格信息等。模型的forward函数没有额外的复杂逻辑,

def forward(self, inputs: dict) -> BatchFeature: backbone_inputs, action_inputs = self.prepare_input(inputs) backbone_outputs = self.backbone(backbone_inputs) action_outputs = self.action_head(backbone_outputs, action_inputs) return action_outputs

这段代码将整个训练过程分成三步,prepare_input -> backbone -> action_head

prepare_input会将同一个输入字典拆成两部分。图像、文本等数据交给Backbone,状态、动作、动作Mask和embodiment_id交给动作头,

backbone_inputs = self.backbone.prepare_input(inputs) action_inputs = self.action_head.prepare_input(inputs)

随后使用统一函数把Tensor移动到模型所在设备:

def to_device_with_dtype(x): if torch.is_floating_point(x): return x.to(self.device, dtype=self.dtype) else: return x.to(self.device)

浮点Tensor不仅会移动到GPU,还会转换为模型当前使用的数据类型;整数类型的Token ID和Mask只移动设备,不进行浮点转换。

4、动作头由状态编码器、动作编码器和DiT组成

动作头类名为Gr00tN1d7ActionHead,

class Gr00tN1d7ActionHead(nn.Module): """Action head component for flow matching diffusion policy."""

注释中已经直接写出了flow matching diffusion policy。这个表述容易让人误以为代码同时实现了两套方法,实际上这里只实现一套动作生成流程:使用DiT预测Flow Matching速度场。 动作头首先根据配置创建DiT,

if config.use_alternate_vl_dit: self.model = AlternateVLDiT( **config.diffusion_model_cfg, cross_attention_dim=config.backbone_embedding_dim, attend_text_every_n_blocks=config.attend_text_every_n_blocks, ) else: self.model = DiT( **config.diffusion_model_cfg, cross_attention_dim=config.backbone_embedding_dim, )

正常配置下使用DiT,开启use_alternate_vl_dit后使用AlternateVLDiT。两种结构都会将状态和动作特征作为主序列,并通过交叉注意力读取视觉语言特征。下面几个成员负责状态和动作的维度映射,

self.state_encoder = CategorySpecificMLP( num_categories=config.max_num_embodiments, input_dim=config.max_state_dim * config.state_history_length, hidden_dim=self.hidden_size, output_dim=self.input_embedding_dim, ) self.action_encoder = MultiEmbodimentActionEncoder( action_dim=self.action_dim, hidden_size=self.input_embedding_dim, num_embodiments=config.max_num_embodiments, ) self.action_decoder = CategorySpecificMLP( num_categories=config.max_num_embodiments, input_dim=self.hidden_size, hidden_dim=self.hidden_size, output_dim=self.action_dim, )

state_encoder将机器人状态映射到DiT的输入维度;action_encoder将连续动作和时间步编码为动作Token;action_decoder将DiT输出还原为连续动作速度。

这里使用了CategorySpecificMLP和MultiEmbodimentActionEncoder,原因是不同机器人本体的数据分布不同。即使两台机器人都使用7维动作,其中每一维的物理含义也可能不同。通过embodiment_id选择类别相关参数,可以在共享DiT主体的同时,为不同机器人保留各自的输入输出映射。

动作头还会记录以下参数,

self.action_dim = config.max_action_dim self.action_horizon = config.action_horizon self.num_inference_timesteps = config.num_inference_timesteps

action_dim是补齐后的最大动作维度,action_horizon表示模型一次预测多少步动作,num_inference_timesteps表示推理时执行多少次速度积分。

5、动作头同时控制参数梯度和模块运行模式

动作头通过set_trainable_parameters控制不同模块是否参与训练,

def set_trainable_parameters( self, tune_projector: bool, tune_diffusion_model: bool, tune_vlln: bool, ): for p in self.parameters(): p.requires_grad = True if not tune_projector: self.state_encoder.requires_grad_(False) self.action_encoder.requires_grad_(False) self.action_decoder.requires_grad_(False) if self.config.add_pos_embed: self.position_embedding.requires_grad_(False) if not tune_diffusion_model: self.model.requires_grad_(False) if not tune_vlln: self.vlln.requires_grad_(False) self.vl_self_attention.requires_grad_(False)

tune_projector控制状态编码器、动作编码器、动作解码器以及位置编码;tune_diffusion_model控制DiT主体;tune_vlln控制视觉语言特征进入动作头之前的LayerNorm和自注意力层。代码中还实现了set_frozen_modules_to_eval_mode,

def set_frozen_modules_to_eval_mode(self): if self.training: if not self.tune_projector: self.state_encoder.eval() self.action_encoder.eval() self.action_decoder.eval() if not self.tune_diffusion_model: self.model.eval() if not self.tune_vlln: self.vlln.eval() self.vl_self_attention.eval()

仅设置requires_grad=False并不会自动将模块切换到评估模式。Hugging Face Trainer在训练期间会调用model.train(),被冻结模块中的Dropout等训练态行为仍可能生效,因此源码又在forward开头调用set_frozen_modules_to_eval_mode(),将这些模块重新切换到eval()状态。

6、Flow Matching在噪声和真实动作之间构造训练轨迹

动作头forward函数的输入包括视觉语言特征和动作相关数据,

def forward( self, backbone_output: BatchFeature, action_input: BatchFeature, ) -> BatchFeature:

Backbone输出中主要包含,

backbone_features: [B, seq_len, backbone_embedding_dim] backbone_attention_mask:[B, seq_len]

动作输入中主要包含,

state: [B, state_history, state_dim] action: [B, action_horizon, action_dim] embodiment_id: [B] action_mask: [B, action_horizon, action_dim]

首先对Backbone特征做额外处理,

backbone_output = self.process_backbone_output(backbone_output) vl_embeds = backbone_output.backbone_features

process_backbone_output中包含LayerNorm和可选的视觉语言自注意力层,

def process_backbone_output(self, backbone_output): backbone_features = backbone_output["backbone_features"] backbone_features = self.vlln(backbone_features) backbone_features = self.vl_self_attention(backbone_features) backbone_output["backbone_features"] = backbone_features return backbone_output

接着处理机器人状态。状态历史会先展平,

assert action_input.state.shape[1] == self.config.state_history_length action_input.state = action_input.state.view( action_input.state.shape[0], 1, -1, )

假设状态历史长度为4,每一帧状态维度为32,展平后得到128维向量,再由state_encoder映射为一个状态Token,

state_features = self.state_encoder( action_input.state, embodiment_id, )

训练阶段还会按概率丢弃完整状态特征,

if self.training and self.state_dropout_prob > 0: do_dropout = ( torch.rand( state_features.shape[0], device=state_features.device, ) < self.state_dropout_prob ) do_dropout = do_dropout[:, None, None].to( dtype=state_features.dtype ) state_features = state_features * (1 - do_dropout)

这里不是对状态向量中的单个元素做Dropout,而是对一个样本的整个状态Token置零。这样训练出来的模型不会完全依赖机器人状态,在状态传感器存在噪声或缺失时仍能利用图像和语言生成动作。Flow Matching目标的构造集中在下面几行,

actions = action_input.action noise = torch.randn( actions.shape, device=actions.device, dtype=actions.dtype, ) t = self.sample_time( actions.shape[0], device=actions.device, dtype=actions.dtype, ) t = t[:, None, None] noisy_trajectory = (1 - t) * noise + t * actions velocity = actions - noise

模型输入noisy_trajectory、时间步以及条件信息,输出该位置上的速度。训练目标不是预测高斯噪声,也不是直接恢复干净动作,而是预测从噪声指向真实动作的速度方向。

时间t并不是均匀采样,而是由Beta分布产生,

self.beta_dist = Beta( config.noise_beta_alpha, config.noise_beta_beta, ) def sample_time(self, batch_size, device, dtype): sample = self.beta_dist.sample([batch_size]).to( device, dtype=dtype, ) sample = (1 - sample) * self.config.noise_s return sample

Beta分布可以控制训练样本更多地落在轨迹的哪个区域。不同于简单的均匀采样,这种方式可以调整模型对高噪声阶段和低噪声阶段的学习比例。

连续时间t随后被离散化,

t_discretized = ( t[:, 0, 0] * self.num_timestep_buckets ).long()

离散时间步会传给动作编码器和DiT,

action_features = self.action_encoder( noisy_trajectory, t_discretized, embodiment_id, )

如果启用了位置编码,还会给动作序列中的每一个时间位置添加独立Embedding,

if self.config.add_pos_embed: pos_ids = torch.arange( action_features.shape[1], dtype=torch.long, device=device, ) pos_embs = self.position_embedding(pos_ids).unsqueeze(0) action_features = action_features + pos_embs

这样DiT可以区分动作块中的第0步、第1步和后续时间步。

7、DiT根据多模态条件预测动作速度并计算损失

状态特征和动作特征会在序列维度拼接,

sa_embs = torch.cat( (state_features, action_features), dim=1, )

如果状态被编码为1个Token,动作块长度为16,那么拼接后的序列长度就是17,

[state_token, action_0, action_1, ..., action_15]

视觉语言特征不会直接拼接进这个序列,而是作为交叉注意力的encoder_hidden_states输入DiT,

model_output, _ = self.model( hidden_states=sa_embs, encoder_hidden_states=vl_embeds, encoder_attention_mask=vl_attn_mask, timestep=t_discretized, return_all_hidden_states=True, )

这种结构可以看成条件生成模型。主序列是机器人状态和动作,视觉语言特征提供环境和任务条件。每一层DiT都可以根据当前图像和语言指令更新动作Token。

DiT输出经过动作解码器,

pred = self.action_decoder( model_output, embodiment_id, )

由于输出中还包含状态Token对应的位置,因此只保留最后的动作部分,

pred_actions = pred[:, -actions.shape[1]:]

动作损失采用逐元素MSE,

action_loss = F.mse_loss( pred_actions, velocity, reduction="none", )

随后乘以action_mask。动作Mask的作用是忽略补零维度以及无效时间步。GR00T需要支持不同机器人,一些机器人可能只有7维动作,另一些机器人可能有20维动作,模型内部会统一补齐到max_action_dim,但补齐部分不应参与损失计算。

源码随后将逐元素MSE乘以action_mask,再根据有效动作元素的数量计算平均损失,

action_loss = action_loss * action_mask loss = action_loss.sum() / ( action_mask.sum() + 1e-6 )

分母使用有效元素数量,而不是直接对整个Tensor求平均,可以避免不同动作维度和不同Padding长度对损失尺度造成影响。

8、推理阶段从高斯噪声逐步生成动作序列

训练阶段学习速度场,推理阶段则沿着该速度场进行积分。动作生成入口为,

Gr00tN1d7.get_action(...)

主模型先处理输入并计算视觉语言特征,

backbone_inputs, action_inputs = self.prepare_input(inputs) backbone_outputs = self.backbone(backbone_inputs) action_outputs = self.action_head.get_action( backbone_outputs, action_inputs, options, )

动作头首先创建一段高斯噪声,

actions = torch.randn( size=( batch_size, self.config.action_horizon, self.action_dim, ), dtype=vl_embeds.dtype, device=device, )

然后根据推理步数计算步长,

dt = 1.0 / self.num_inference_timesteps

如果num_inference_timesteps=4,每次积分步长就是0.25。

每轮迭代都会对当前动作轨迹重新编码,

for t in range(self.num_inference_timesteps): t_cont = t / float(self.num_inference_timesteps) t_discretized = int( t_cont * self.num_timestep_buckets ) timesteps_tensor = torch.full( size=(batch_size,), fill_value=t_discretized, device=device, ) action_features = self.action_encoder( actions, timesteps_tensor, embodiment_id, )

随后将状态Token和动作Token拼接,交给DiT,

sa_embs = torch.cat( (state_features, action_features), dim=1, ) model_output = self.model( hidden_states=sa_embs, encoder_hidden_states=vl_embeds, timestep=timesteps_tensor, )

动作解码器输出当前轨迹上的速度,

pred = self.action_decoder( model_output, embodiment_id, ) pred_velocity = pred[:, -self.action_horizon:]

最后使用Euler方法更新动作,

actions = actions + dt * pred_velocity * vel_strength
http://www.gsyq.cn/news/1503129.html

相关文章:

  • 亲测济南多家黄金回收门店,榜首添价收报价稳居本地前列 - 薛定谔的梨花猫
  • 2026年论文党必备:AI论文软件测评与推荐全攻略
  • 金发尼龙现货稳定供应 东莞屹立塑胶助力制造企业降本增效 - 资讯焦点
  • 工业大数据可信空间:制造业数字化的核心底座
  • 构建智能抖音内容下载解决方案:架构设计与工程实践
  • 朝阳法穆兰+宝玑手表专业回收,26年精选回收店铺排行榜推荐 - 莘州文化
  • 德宏江诗丹顿+万国手表专业回收,26年精选回收店铺排行榜推荐 - 莘州文化
  • 杰理之打开广播TCFG_BROADCAST_ENABLE后ble无法连接【篇】
  • 德阳法穆兰+宝玑手表专业回收,26年精选回收店铺排行榜推荐 - 莘州文化
  • 2026淡纹身体油选购指南!实测对比热门品牌,精准改善干纹细纹推荐 - 资讯焦点
  • 【MATLAB】工业控制网络时延补偿与优化
  • 潮州江诗丹顿+万国手表专业回收,26年精选回收店铺排行榜推荐 - 莘州文化
  • 高拟人度智能 外呼电话机器人排行推荐榜:覆盖多行业电销与客服场景 - 真知灼见33
  • CANN快速上手|sip会话管理库配置与实战指南
  • 杰理之增加AAC能量检测功能,修复1T2抢播需要等待时间偏长问题【篇】
  • 数据的加密与解密(09:24)
  • 保姆级教程:用Python的SciPy库搞定超效率SBM模型(含非期望产出处理)
  • B站视频下载终极指南:免费跨平台工具BilibiliDown完整使用教程
  • 3步创建你的AI模型:Teachable Machine零代码机器学习入门指南
  • FanControl完全指南:让Windows风扇控制变得简单又智能
  • 抖音内容高效管理:douyin-downloader 开源工具的完整解决方案
  • SEED情感脑电数据集避坑指南:标签解读、通道顺序与批量读取的常见错误
  • Qt可编辑下拉框实时搜索补全组件(含UI文件与完整编译配置)
  • 别再手动调参了!用C语言实现一个简易PID自整定库(附Arduino移植指南)
  • Windless核心组件探秘:AnimationFactory如何驱动流畅动画
  • 2026香格里拉民宿 TOP10 深度测评:锦瑟・在野院领衔的高原秘境住宿指南 - 玖叁鹿
  • 终极音乐解锁指南:如何免费解密和转换加密音频格式
  • 影刀RPA完全指南_从单个流程到自动化体系的设计思维
  • C# TcpClient连接状态检测:从Connected属性到实战心跳包方案
  • 汇川技术代理商选择:无锡炬能的驱控一体化优势解析 - 资讯焦点