LeRobot documentation
训练数据集流式处理
训练数据集流式处理
训练时的数据集流式处理功能允许 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_size | 32 / N 每个 | 每个 worker 混合的最大完整 episode 数 |
streaming_sampling_strategy | remaining | 剩余帧加权或打乱的round_robin |
streaming_prefetch_episodes | 8 | 每个工作线程池预先获取的剧集数量 |
streaming_byte_budget_gb | 8 | 每个 rank 的最大合成 MP4 字节数 |
streaming_decode_threads | 2 | 并行样本组装和视频解码工作进程 |
streaming_decoded_queue_size | 8 | 按规划器顺序预先缓冲的解码样本 |
video_decoder_cache_size | pool × 摄像头 | 每个工作进程的开放视频解码器 LRU 上限 |
streaming_native_http_connections | unset | 每个 rank 的原生 HTTP 连接上限 |
streaming_native_http_subranges | 1 | 每个原生 HTTP 范围读取的并发子范围 |
streaming_sidecar_lock_timeout_s | 1800 | 等待另一个 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_streamingHub 数据集默认使用 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 是否与修订版本匹配。 |