LeRobot documentation

训练数据集流式处理

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v0.6.1).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

训练数据集流式处理

训练时的数据集流式处理功能允许 lerobot-train 在无需先下载完整的 Parquet 和视频数据的情况下,直接消费 LeRobotDataset v3。通过现有的公开开关即可启用该功能:

lerobot-train \
  --dataset.repo_id=OWNER/DATASET \
  --dataset.streaming=true \
  --policy.type=act \
  --output_dir=outputs/train/act_streaming

此功能独立于 --dataset.streaming_encoding=true。streaming_encoding 控制录制时视频的写入方式;dataset.streaming 控制训练期间如何读取现有数据集。此训练路径不使用录制和 rollout 编码器。

Epoch 的读取方式

每个分布式 rank 拥有一个确定性的、帧均衡的完整 episode 集合。每个 DataLoader worker 拥有一个精确覆盖池,该池混合了该 worker 负责的 episode 部分,并在每个覆盖 epoch 中恰好访问每个选定的帧一次。Parquet 列和压缩 MP4 字节范围从同一个字节感知的准入前沿进行预取。时间历史和未来窗口在完整 episode 内部解析,包括与 map 风格加载相同的边界填充掩码。

当 --dataset.streaming=false 时,默认的 map 风格 LeRobotDataset 行为保持不变。

Workers、shards 和并发

每个 rank 拥有一个帧均衡的完整 episode 分片。在 rank 内部,--num_workers 用于设置 DataLoader 工作进程的数量,与 map-style 数据集相同。每个 worker 拥有该 rank 的 episode 中一个不相交且帧均衡的部分,并配备自己的精确覆盖池、自己的字节预算份额以及自己的线程池:

阶段并发设置
Rank 分片每个 rank 一个不相交的完整 episode 分片分布式世界大小
DataLoader 工作进程每个工作进程一个秩分片的互不重叠部分--num_workers
Episode 获取并行处理 Parquet 行和 MP4 字节范围max_num_shards,由 --num_workers 设置
采样组装和解码时间窗口和视频帧,并行处理--dataset.streaming_decode_threads
有序样本缓冲区按规划器顺序保留在前的解码样本--dataset.streaming_decoded_queue_size

为什么需要多个 worker。样本组装和批次交接会执行 Python 代码,而一个进程一次只能在一个线程上运行 Python 代码(受 GIL 限制)。如果只有一个 worker,某个 rank 的解码线程会相互等待,导致该 rank 仅使用约 2 个 CPU 核心。每个 worker 都是一个独立的进程,因此 N 个 worker 可以并行地组装和解码样本。

池大小与批次多样性。 streaming_episode_pool_size 用于设置每个 worker 的池。如果未设置该参数,rank 会混合 32 个 episode,N 个 worker 中的每个将分得其中的 32/N 个。每个 worker 还会预取 streaming_prefetch_episodes 个 episode,并且是一个独立的 Python 进程,因此 worker 数量越多,内存占用越大:在一个包含 3 个相机的数据集上,4 个 worker 总共占用约 8 GB 内存,而 1 个 worker 仅占用约 3 GB。DataLoader 从单个 worker 中获取每个 batch,因此一个 batch 混合了来自该 worker 池中的 episode。更大的池会在每个 batch 中混合更多的 episode,但会占用更多内存,并在获取第一个样本前拉取更多数据。

限制。字节预算(streaming_byte_budget_gb)和显式的 video_decoder_cache_size 按 rank 划分:每个 worker 获得其中的 1/N。由于每个 worker 至少需要一个 episode,因此 lerobot-train 使用的 worker 数量最多不超过该 rank 拥有的 episode 数量。--num_workers=0 会将数据集保留在训练进程中。此时,训练线程负责应用图像变换,因此相同的 --seed 会产生相同的变换。

MP4 边车文件与首次运行

视频流式传输使用 MP4 索引旁路文件。数据集初始化时,首先检查基于修订版本的本地缓存,然后查找有效的已发布旁路文件。如果两者均不可用,LeRobot 会在持有进程锁的情况下在本地构建旁路文件,并以原子方式安装。构建失败或中断不会替换之前的有效文件。需要相同旁路文件的其他进程会等待锁,最长等待时间为 --dataset.streaming_sidecar_lock_timeout_s(默认 30 分钟)。对于索引耗时较长的大型数据集,请调高此值。

Hub 负载读取固定于元数据快照的提交。Bucket 索引会对照最新的对象列表(包括内容哈希)进行检查,因此即使文件大小未变,替换相同路径下的视频也会使其 sidecar 失效。此列表操作会增加启动时的工作量,但不会下载视频负载。请在运行期间保持元数据和负载不变。没有稳定对象标识的通用远程文件系统会在每次打开时重建其索引。没有对象指纹的现有 Bucket sidecar 会在本地重建一次;发布更新后的 sidecar 仍是维护者的显式操作。

训练过程是只读的:它绝不会上传 sidecar 或修改数据集仓库。在具有节点本地缓存的集群上,第一个任务可能会在每个节点上构建一次。共享的 LeRobot 缓存可以避免这种重复。

Sidecar 构建会将每个已完成的源索引写入临时归档。它最多保留所配置数量的解析结果。数组内存取决于最大的并发源数量;归档元数据仍会随着源数量的增加而增长。在运行 lerobot-build-mp4-sidecar 时,请使用 --workers 来设置解析并发数。如果构建失败,则会保留之前的 sidecar。

已发布的 schema-3 NPZ 归档保留了规范的 int64 整数数组。首次打开时,会将该归档转换为位于 $HF_LEROBOT_HOME/streaming-indexes 下的只读内存映射索引。派生索引使用最小的精确整数类型。它仅存储一次恒定的持续时间、组成偏移量、大小或字节偏移量。只读数组视图保留逻辑样本数量。呈现时间戳保留精确的 float64 值。同步样本数组保留密集存储。

派生索引使用独立的缓存生成。旧版读取器保留其现有的缓存格式。使用新版读取器首次打开时,可以一次性转换归档;后续打开将复用该归档。转换过程使用进程锁和原子替换。共享缓存的进程(Ranks)复用相同的基于文件的页面。每个进程仅为其剧集所使用的源文件构建记录。旧版和新版缓存生成可能需要额外的磁盘空间。替换侧车文件(sidecar)会创建一个新的不可变生成,而不会使正在运行的读取器失效。

这并不会使所有元数据都变为固定大小:episode 元数据和 span 表仍会随数据集增长,且经过 shuffle 的 rank 分片可能会触及重叠的源文件。驻留在操作系统中的映射页也会计入 RSS;在测量各 rank 共享的内存时,请使用比例集大小(PSS)。Parquet 读取会利用 episode 统计信息对行组进行剪枝,然后过滤出完整的 episode。没有统计信息的组仍会保留为候选项,因此没有有效边界信息的文件可能仍需要执行整个文件的投影读取。

数据集维护者可以提前构建一个 sidecar:

lerobot-build-mp4-sidecar \
  --repo-id=OWNER/DATASET \
  --revision=COMMIT_SHA \
  --data-root=hf://datasets/OWNER/DATASET@COMMIT_SHA \
  --output=/tmp/dataset-mp4-sidecar.npz

发布操作始终为显式操作。仅在验证完整的完整数据集 sidecar 后,才添加 --push。 子集 sidecar 无法发布。

对于数据集仓库,--push 会创建或更新专用的 lerobot-sidecars 分支。 源数据集的分支/标签保持不变。训练过程会在该分支中查找与固定的视频源提交相关联的索引,并在使用前对其进行验证。标签及其解析后的提交共享同一个已发布的索引;现有的基于修订版本键的本地缓存仍然可以复用。存储桶仍然直接在 meta/mp4-sidecars/ 下发布。发布需要仓库或存储桶的写入权限;训练仅需读取权限,且永远不会创建分支或上传文件。

Sidecar schema v3 保留了 MP4 的构图时序和编码器延迟编辑,包括包含 B 帧的视频。旧版 sidecar 会自动重建到独立的、以修订版本为键的缓存条目中;在显式发布之前,它们也必须先进行重建。重新排序的视频在每个获取的跨度中包含最后一个 GOP 的末尾,以确保解码后的帧索引保持连续。对于重复或非单位速率的 MP4 编辑时间线,系统会直接拒绝,而不是静默地返回对齐错误的帧。

配置

生产环境的默认值为:

选项默认含义
streaming_episode_pool_size32 / N 每个每个 worker 混合的最大完整 episode 数
streaming_sampling_strategyremaining剩余帧加权或打乱的round_robin
streaming_prefetch_episodes8每个工作线程池预先获取的剧集数量
streaming_byte_budget_gb8每个 rank 的最大合成 MP4 字节数
streaming_decode_threads2并行样本组装和视频解码工作进程
streaming_decoded_queue_size8按规划器顺序预先缓冲的解码样本
video_decoder_cache_sizepool × 摄像头每个工作进程的开放视频解码器 LRU 上限
streaming_native_http_connectionsunset每个 rank 的原生 HTTP 连接上限
streaming_native_http_subranges1每个原生 HTTP 范围读取的并发子范围
streaming_sidecar_lock_timeout_s1800等待另一个 sidecar 构建的秒数
max_num_shards (--num_workers)16每个工作进程并发获取的剧集数

除 max_num_shards 外,所有选项均为 --dataset.* 标志。max_num_shards 是 StreamingLeRobotDataset 的参数;lerobot-train 会根据 --num_workers 设置该值(至少为 1)。

为了获得更均匀的 episode 混合,请使用 --dataset.streaming_sampling_strategy=round_robin 选项。 每一轮都会对当前驻留的 episode 进行洗牌,并从每个 episode 中采样一个洗牌后的帧; 在一轮期间被接纳的 episode 将加入下一轮。两种策略都保留了精确一次(exactly-once)的 epoch 覆盖以及相同的资源边界。两者都不是全局均匀排列:round-robin 在有限前缀中会偏向较短的 episode,并可能增加解码器切换(churn)并降低吞吐量。 顺序由 --seed(默认值 1000)决定,同时结合策略初始化以及其他带种子的随机数生成器。不同的种子会产生不同的 episode 和锚点顺序,而相同的种子会重复该顺序。--seed=null 使用固定的流式种子 42。在恢复检查点时,请保持相同的策略、--seed 和池设置。检查点会存储种子,因此普通的 --resume 会保持顺序。如果检查点来自在 --seed 到达流式数据集之前启动的运行,则其顺序使用了种子 42:恢复该检查点时,请传入 --seed=42 以保持该顺序。

活动剧集集合同时受剧集数量和从 sidecar 计算得出的精确合成 mini-MP4 大小限制。如果某个剧集大于完整的 rank 预算,它将在训练获取其负载之前失败。预留涵盖待获取的项、已完成的预取以及仍在解码中的剧集。当预算已满时,推测性预取会暂停,替换准入会等待最后一个解码释放其字节。EpisodeByteCache.reserved_bytes 报告这些预留;resident_bytes 报告所有已完成的负载,即使在其第一个样本被请求之前也是如此。

这是一个压缩负载预算,而非进程 RSS 限制:范围获取/合成临时缓冲区、解码器状态、Parquet 表以及解码后的张量需要额外的内存。解码器 LRU 和解码样本队列具有独立的限制。 在内存受限的主机上,请从较小的池或预算开始:

lerobot-train \
  --dataset.repo_id=OWNER/DATASET \
  --dataset.streaming=true \
  --dataset.streaming_episode_pool_size=16 \
  --dataset.streaming_prefetch_episodes=4 \
  --dataset.streaming_byte_budget_gb=4 \
  --dataset.streaming_decode_threads=2 \
  --dataset.streaming_decoded_queue_size=8 \
  --num_workers=4 \
  --policy.type=act \
  --output_dir=outputs/train/act_streaming

对于存储在 HF Storage Bucket 中的完整数据集,请使用该 bucket 的标识符和仓库类型,或者将 bucket URI 作为 --dataset.root=hf://buckets/OWNER/BUCKET 传入。元数据(meta/)、Parquet 文件和视频文件都必须位于该 bucket 的根目录下:

lerobot-train \
  --dataset.repo_id=OWNER/BUCKET \
  --dataset.repo_type=bucket \
  --dataset.streaming=true \
  --policy.type=act \
  --output_dir=outputs/train/act_bucket_streaming

Hub 数据集默认使用 repo_type=dataset。对于完整的本地数据集,请将 --dataset.root 设置为其所在目录。训练过程会从同一来源解析元数据和数据负载,因此无需单独指定流式数据负载位置标志。

恢复和洗牌迁移

之前的流式读取器使用了有界的行洗牌缓冲区。而剧集读取器则具有由种子和 epoch 派生的确定性精确覆盖顺序。检查点恢复使用检查点批次大小来恢复每个 rank 的样本偏移量。在使用多个 worker 时,每个 worker 都会从中派生自己的偏移量,因为 DataLoader 会依次从其 worker 中获取批次。更改分布式 world size、批次大小或 --num_workers 会改变所有权或批次边界。为了进行样本精确的比较,请使用相同的 world size、批次大小和 worker 数量进行恢复。在比较性能时,请保持相同的内部获取并发度。

检查点元数据未标识出较早的多工作进程采样器。其检查点无法通过世界大小和批大小检查可靠地区分,因此不得用于样本精确的流式恢复。请改为从它们保存的策略权重开始新的运行。恢复保证涉及的是锚点顺序,而非随机图像变换的逐位重放。随机图像变换在解码线程上运行,并与解码并行执行,因此固定种子无法在两次运行之间复现相同的增强操作。

故障排除

症状该怎么做
第一个批次耗时较长检查 sidecar 构建的日志。复用共享缓存,或显式发布经过验证的完整 sidecar。
Sidecar 锁超时另一个进程可能仍在索引同一版本。在删除过期的锁之前,请确认该进程运行正常。
某个 rank 不拥有任何数据减少 rank 数量,确保每个 rank 至少拥有一个选定的 episode。
字节预算已超出降低 episode 池,提高每个 rank 的字节预算,或使用具有更小 episode 范围的 payload 布局。
远程读取无法进行身份验证使 Hugging Face 令牌或 fsspec 凭据对每个 worker 可用。Sidecar 从不嵌入凭据。
补充数据停滞时间较长比较 p95/p99 批次等待时间,减少网络争用,逐步提高预取量,检查 sidecar 是否与修订版本匹配。
Update on GitHub