Windows-GPU-UNLOCK
RTX3090 UEFI 포트, 한국어 무리셋 윈도우 부팅 가이드, 검증된 소스와 벤치마크 제공
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Task description in, label-exact photorealistic dataset out. An embodied data engine for robot learning.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗git clone https://github.com/wellkilo/embodigen && cd embodigen
python3 -m venv .venv && . .venv/bin/activate
pip install -e .
# 一条
embodigen gen --task "把红色方块放进盒子" --out out --resolution 96
embodigen validate --episode out/episodes/franka-0000
# 一批(8 条,4 进程)
embodigen gen --task "把红色方块放进盒子" --out out --resolution 64 --episodes 8 --workers 4
embodigen index --out out
# 看一眼
embodigen preview --episode out/episodes/franka-0000
虚拟世界管线(GameScaling 数据后台,详见下一节):
embodigen rollout --world all --episodes 3 --seconds 30 --out data/raw # 在参考世界里录制
embodigen curate --raw data/raw --out data/curated # 对齐 · 规范化 · 闸门
embodigen build --curated data/curated --study configs/studies/reference.toml --out data/mixes
make samples # 真实公开数据:下载 VPT / D2E / Open-P2P 各 3 条 → ingest → curate → build
embodigen ledger data/mixes/reference # S1–S5 × 预算 的账本
embodigen export --curated data/curated --out data/embodied # 转成具身格式(make worlds 已包含)
不想记命令?embodigen ui 打开网页控制台,同样的步骤都能点按钮完成。
零第三方依赖,Python ≥ 3.11,不需要 GPU(ViZDoom 后端是可选的 [env] extra)。单条 96 px 约 3 秒,8 条 64 px 用 4 进程约 3.5 秒(Apple Silicon 实测)。
$ embodigen gen --task "把红色方块放进盒子" --out out --resolution 96
episode franka-0000
instruction 把红色方块放进盒子
robot arm / franka
frames 28 @ 8 fps, 96x96
attempts 2 (seeds [0, 1])
fidelity p95 2.27 mm max 2.28 mm threshold 12 mm -> PASS
written out/episodes/franka-0000
attempts 2:第一次渲染被默认注入 25 mm 几何漂移(--drift,用来演示闭环),验证器拒绝,第二次加严几何条件后通过。用 --drift 0 可关闭注入。
embodigen ui # 打印 http://127.0.0.1:8765/#token=…,用浏览器打开这个完整链接
把命令行的每一步做成表单:
make worlds / make samples,可勾选下载视频、填 Hugging Face 镜像、选理由标注器。embodigen … 命令,照抄到终端效果相同。data/、out/,点开就能看筛选账本(每条录像的对齐与拒绝理由)、训练集网格(S1–S5 × 预算、Hill D、锚点占比、缺口),原始录像可一键生成 GIF 预览。机器人数据(out/)列出 index.json 里的每个回合,点开后逐帧播放白模、写实、深度(.f32)、分割(.u16)四个通道,并能按目录浏览每个文件的原始内容:JSON / JSONL 原样显示,二进制帧显示十六进制。~/.config/embodigen/llm.toml(权限 600),不会回显到页面,也不进日志和命令行;没配好时带 llm 的任务直接拒绝,不会开跑。.part,下次断点续传。只监听 127.0.0.1,所有接口都要启动时打印的令牌,别的网页和本机其他用户都调不动它。程序跑在远程服务器上时,在自己电脑上执行 ssh -L 8765:127.0.0.1:8765 <服务器>,再在本地浏览器打开那个链接。控制台本身也只用标准库,零依赖。
服务于 GameScaling(arXiv 2610.28698)这类工作:把游戏当作具身智能体的仿真环境,把"玩家看到了什么、按了什么"做成可以直接做 scaling 研究的训练数据。它和机器人管线共用同一套做法:纯 dataclass 数据契约、按协议替换的后端、不达标进不了数据集的闸门、可审计的账本。
rollout / ingest → data/raw/<recording>/ 引擎录制,或把数据集转换成统一的原始布局
curate → data/curated/ 对齐校验 · 动作规范化 · 闸门,失败的进 rejected.jsonl
build → data/mixes/<study>/ 划分 · 窗口 · 配比 × 预算 · 稀疏推理 · 分片 + manifest
每个阶段只读上一个阶段的目录,可以分机器跑;每个阶段都写 ledger.json。
| 阶段 | 做什么 | 依据(论文) |
|---|---|---|
| 数据源 | vpt(Minecraft 承包商 jsonl)、d2e(OWAMcap:MCAP + mkv)、p2p(Open-P2P 的 protobuf 标注)三个适配器;raycast(标准库参考世界)与 vizdoom(真 Doom)两个可录制引擎 | 训练数据来自 VPT / D2E / OpenP2P;DOOM 是 in-corpus 世界 |
| 动作规范化 | 5 Hz 观测;每个 200 ms 动作包 = Δx、Δy ∈ [-1000, 1000],Δz ∈ [-5, 5],6 个 33 ms 按键槽,每槽 0–4 个键 | 统一键鼠动作格式 |
| 对齐校验 | 只读像素和事件日志,分段(开头 / 中间 / 结尾)测输入日志相对视频的时延;各段一致的常数时延校正后复检,时钟漂移或无法确认的直接拒绝 | 本项目新增(论文未描述) |
| 采集闸门 | 非游戏前台输入 ≥ 5%、≥ 50% 鼠标事件为光标回中、< 60 事件/分钟、< 3 次键/点击每分钟、空闲窗口 > 60% → 拒绝;OOD 标题需 ≥ 60% 录像通过 | OOD 录像池的拒绝规则 |
| 窗口 | 20 回合、步长 10;空闲为主的窗口按哈希降采样;每个源流按整条录像封顶 | 20-turn 窗口 / 10 步长 / idle 降采样 / per-stream cap |
| 划分 | 按录像划分,三级验证:anchor(论文里是 Minecraft)、in-corpus(每个游戏同样多的片段 + 同规模锚点子集)、OOD(按标题 / 别名 / 系列 / 模拟器去重) | 三级 validation loss |
| 配比 | S1–S5 锚点占比 100 / 51.4 / 27.1 / 2.8 / 0%;非锚点按池中比例交错;Hill 数 D、逆 Simpson、最大占比 | 混合配比与多样性定义 |
| 预算 | 同一优先级顺序的前缀,所以 50h ⊂ 100h ⊂ … ⊂ 1600h;数据不够时缩小规模、保持配比、记下缺口 | 6 档预算、控制变量 |
| 稀疏推理 | 每个回合按哈希决定是否带 rationale(默认 5.7%),所有配比密度一致;rationale 只解释、不改动作;按(标注器, prompt 版本, 录像, 回合)缓存 | 5.4–6.0% 的 assistant 回合带推理 |
| 序列化 | <think>…</think><|action_start|> Δx ; Δy ; Δz ; k1 ; … ; k6 <|action_end|>,messages + images 格式,只对 assistant 计 loss;按窗口哈希分片,manifest 记录每个分片的 sha256 | 训练模板 |
configs/studies/gamescaling.toml 按论文写好了真实网格(5 配比 × 6 档预算、160 片段/世界、30 个 in-corpus 游戏、15% VL anchor、seed 3407)。论文没公开的数值(空闲降采样率、源流上限)在文件里标了 unpublished,跑真数据前要定下来。
转视角时画面会整体水平平移。校验器从相邻两帧的列亮度剖面估计这个平移,再和事件日志里同一时段的鼠标 dx 做互相关,扫描 ±600 ms 的时延。两个序列都先差分(预白化)再取秩:鼠标运动是平滑的,不差分的话相关峰会很宽、能偏好几帧。
录像够长时,在开头、中间、结尾各测一段。各段时延一致,说明是采集时的常数偏移,可以平移整份日志修正;各段不一致,说明是时钟漂移,用一个常数修不好,直接拒绝。只有部分段测得出结果时,也不做修正。
两条规则来自真实数据:
真实运行结果(参考世界,30 Hz 录制):
| 条件 | 测得时延 | 判定 |
|---|---|---|
| 正常录制 | 0 ms | 通过 |
| 输入日志注入 +300 ms 时延 | +300 ms | 校正后复检对齐,通过,realigned_ms 记入账本 |
| 注入 −200 ms(迷宫布局,64×40) | −200 ms | 校正后通过 |
ViZDoom 注入 +250 ms,--no-realign | +229 ms(35 Hz 下的 8 帧) | 拒绝 |
| 输入日志按 600 ms/分钟 漂移(60 s 录像) | 三段分别 0 / 267 / 500 ms | clock_drift,拒绝 |
帧存在视频里的录像(VPT、D2E、P2P)通过 video 后端解码:pip install -e '.[video]' 安装 PyAV,校验器每段 seek 一次,向前解码,取灰度缩略图。没装解码器或视频不在本地时记为 skipped,不会因此被拒。
真实公开录像上的结果(make samples VIDEO=--video;"注入"指把真实录像的输入日志整体平移或按比例拉伸后重新校验):
| 录像 | 自然状态 | 注入 +200 / −150 / +400 ms | 注入 600 ms/分钟 漂移 |
|---|---|---|---|
| VPT(20 fps) | 三段 0 ms,相关 0.31–0.77,对齐 | 测得 +200 / −150 / +400,校正后对齐 | drift,拒绝 |
| Open-P2P(20 fps) | 三段 0 ms,对齐 | 测得 +200 / −150 / +400,校正后对齐 | misaligned,拒绝 |
| D2E Minecraft(60 fps) | 三段 −50 / −17 / −33 ms,一致的约 −33 ms 采集偏移,校正后三段 −17 / 0 / +17 | 测得 +167 / −183 / +366(即注入值加上原有的 −33),校正后对齐 | 追踪到 −17 → −600 ms,drift,拒绝 |
| D2E CS2(60 fps) | 开头在菜单里测不出,中段 0 ms,对齐 | 中段测得注入值,但开头测不出,不做校正,拒绝 | 三段都测不出,inconclusive,放行 |
| D2E Brotato(俯视 2D) | 鼠标只瞄准、不转镜头,inconclusive,放行 | — | — |
最后两行是这项校验的边界:一条录像如果没有任何一段能和鼠标对上(俯视游戏,或者漂移大到每一段内部都被拉伸),它就无法验证,结果记为 inconclusive,照常放行。curate 账本会单独统计这类录像(alignment: aligned 8, inconclusive 1),由使用者决定是否要求它们必须通过验证。
make worlds 的真实输出(7 个参考世界,32 条录像;其中一条故意挂机,一条注入 300 ms 时延,一条注入 600 ms/分钟 的时钟漂移):
raycast-lava-0070-player-lag300 realigned +300 ms PASS
raycast-maze-0080-player-drift600 drift (+267 ms) REJECT clock_drift: input/video lag moves across the recording ([0.0, 266.7, 500.0] ms)
raycast-rooms-0050-afk aligned REJECT idle_windows: 100% of windows idle (limit 60%)
certified 30/32 (94% yield), 15.0 min, 1 realigned -> data/curated
pool 238 windows, 8.5 min of footage (budgets count footage), D=5.37 over 6 worlds
val anchor 8 clips 1 worlds 1 recordings
val in_corpus 48 clips 6 worlds 5 recordings
val ood 8 clips 1 worlds 3 recordings
mix budget h footage samples worlds Hill D 1/Simp max anchor think short h
S1 0.08 0.050 84 1 1.00 1.00 100.0% 100.0% 5.6% 0.030
S2 0.08 0.081 91 6 4.32 3.17 51.6% 51.6% 6.2% 0.000
S3 0.08 0.081 84 6 5.71 5.42 27.4% 27.4% 6.1% 0.000
S4 0.08 0.080 101 6 5.38 5.15 25.7% 3.0% 5.9% 0.000
S5 0.08 0.080 108 5 4.93 4.86 26.9% 0.0% 5.9% 0.000
checks: budgets nested True, train/val disjoint True, rationale density spread 1.63%
note: pool too small for [email protected]: those runs are smaller than their budget (shares kept; see shortfall_hours)
(只截了最大一档预算;完整网格在 data/mixes/reference/ledger.json。)S1 在这一档锚点数据不够,账本如实写出缺口,而不是从别的世界补。
三个适配器都按公开数据的真实文件写成并验证过:字段名、编码和怪癖都取自下载下来的样本,没有靠猜。make samples 会下载这些样本(只取动作日志,约 88 MB;视频用 --video 另取),全程跑一遍。下载显示进度,中断后再跑会断点续传;Hugging Face 访问慢时加镜像:embodigen fetch --video --hf-endpoint https://hf-mirror.com(或设置 HF_ENDPOINT)。
| 数据源 | 原始格式 | 适配器处理了什么 |
|---|---|---|
| VPT(OpenAI 承包商数据 6.x) | 每帧一行 jsonl,20 fps | 首帧"卡住"的攻击键;yaw / pitch / xpos / ypos / zpos 存为位姿状态;按目录名识别 test/val 划分 |
| D2E-480p(OWAMcap) | MCAP(zstd 分块、JSON 消息)+ 60 fps mkv | 标准库解析 MCAP;鼠标位移取 mouse/raw 的 HID 原始计数,按 10 ms 合箱,正好整除 200 ms 网格;丢弃 Windows 按键自动重复(CS2 一条录像里 1864 次);前台时间最长的窗口标题视为游戏,切到别的窗口记为焦点变化,交给闸门的非游戏输入规则;_split_NN 分段共享同一个 session |
| Open-P2P | annotation.proto(protobuf),20 fps | 标准库按字段号解码;负的 int32 鼠标位移按补码解出;P2P 推理服务器代打的帧(system_action)和未知帧会把录像切开,只保留人类连续段,各段共享 session;帧级文字指令存进观测状态 |
session 是这次新加的划分单位:同一局切出来的几段永远落在划分的同一侧,assert_disjoint 会同时检查录像和 session。
make samples 的真实输出(9 条录像、6 个世界、72.5 分钟):
world source recs certified pass minutes clip rejections
brotato d2e 1 1 100% 4.8 0.0% -
call-of-duty-mobile p2p 1 1 100% 2.0 0.3% -
counter-strike-2 d2e 1 1 100% 10.4 0.4% -
grand-theft-auto-san-andreas p2p 1 1 100% 15.7 0.2% -
minecraft d2e,vpt 4 4 100% 21.3 0.2% -
roblox-a-dusty-trip p2p 1 1 100% 18.3 0.5% -
pool 1843 windows, 63.2 min of footage (budgets count footage), D=4.54 over 5 worlds
mix budget h footage samples worlds Hill D 1/Simp max anchor think short h
S1 0.2 0.201 237 1 1.00 1.00 100.0% 100.0% 5.3% 0.000
S2 0.2 0.201 193 5 3.69 2.98 51.3% 51.3% 5.2% 0.000
S3 0.2 0.200 192 5 4.54 4.28 27.6% 27.1% 5.1% 0.000
S4 0.2 0.200 196 5 3.87 3.46 36.7% 2.5% 5.6% 0.000
S5 0.2 0.200 197 4 3.56 3.30 38.1% 0.0% 5.6% 0.000
checks: budgets nested True, train/val disjoint True, rationale density spread 0.97%
D2E 的《我的世界》和 VPT 被识别为同一个世界(Minecraft_1.21.8 → minecraft),一起作为锚点;Call of Duty: Mobile 作为 OOD 留出。clip 列是鼠标位移撞到 ±1000 上限的动作包比例。CS2 在 mouse_scale = 1.0 时测得 3.2%,所以 configs/worlds/catalog-d2e.toml 给它设了 0.5(降到 0.4%)。其他游戏都在 0.5% 以内。
只取动作日志时,对齐校验记为 skipped;加上 VIDEO=--video 并装好 [video] 后,9 条里有 8 条测得对齐,1 条(Brotato)无法验证。D2E 的游戏目录(29 款)和 P2P 已知标题放在 configs/worlds/catalog-*.toml,一个文件写多个 [[world]]。没登记的标题会用 slug 作为标签照常进入数据池。
annotator = "llm" 通过 OpenAI 兼容的 Chat Completions 接口调用云端模型。客户端只用标准库 urllib 实现,不需要额外装 SDK,也不需要在本地部署模型。内置以下服务商的接口地址:
| provider | 接口地址 | 密钥环境变量 |
|---|---|---|
dashscope(通义千问) | https://dashscope.aliyuncs.com/compatible-mode/v1 | DASHSCOPE_API_KEY |
deepseek | https://api.deepseek.com/v1 | DEEPSEEK_API_KEY |
moonshot | https://api.moonshot.cn/v1 | MOONSHOT_API_KEY |
zhipu | https://open.bigmodel.cn/api/paas/v4 | ZHIPUAI_API_KEY |
ark(火山方舟) | https://ark.cn-beijing.volces.com/api/v3 | ARK_API_KEY |
openai | https://api.openai.com/v1 | OPENAI_API_KEY |
custom | 自己填 base_url | EMBODIGEN_LLM_API_KEY |
用之前必须先提供 API key,没有 key 什么都不会运行:
embodigen llm setup # 依次问服务商、模型名、API key(输入不回显),写入 ~/.config/embodigen/llm.toml,权限 600
embodigen llm check --ping # 显示最终生效的配置(key 只显示来源),发一个很小的请求确认能连通
embodigen build --curated data/samples-curated --study configs/studies/public-samples.toml --annotator llm
build --annotator llm 一开始就检查服务商、模型名和 key,缺了就停,并提示去跑 embodigen llm setup。这时还没切分、也没写任何文件。~/.config/embodigen/llm.toml > study 里的 [reasoning.llm]。[video] 解码,缩到 448 宽),加上这 200 ms 里实际的按键和鼠标输入,以及前 1 秒的输入概况。模型据此写一两句第一人称的推理,不超过 50 词。images = false,就只发文字。workers 个并发请求填满缓存,之后写分片时只读缓存。遇到 429、5xx 和超时会按退避策略重试;4xx 直接报错,不会反复浪费请求。中途失败时,已经拿到的结果都留在缓存里,重跑只补缺的部分;再次 build 不会发任何请求。annotator_config 记下服务商、模型、prompt 版本,以及请求数和 token 用量(不含 key)。缓存按"服务商 + 模型 + prompt 版本"分开,换模型不会和旧结果混在一起。目前还没有接真实服务商跑过:这台机器上没有任何服务商的 key。测试用的是进程内的假接口,覆盖了请求格式、图片、重试、失败后续跑、"无 key 不运行",以及 key 不外泄。拿到 key 之后,先跑 llm check --ping,再抽一批 rationale 人工检查质量。
data/
├── raw/<recording_id>/ # 录制或转换得到的统一原始布局
│ ├── recording.json # 世界 · 来源 · 源流 · 帧率 · 鼠标缩放 · 是否引擎真值
│ ├── events.jsonl # 按键/按钮按下抬起 · 鼠标计数 · 滚轮 · 前台应用切换
│ ├── obs.jsonl # 每帧:时间 · 帧路径(或 video.mp4#t=…)· 5 Hz 网格上的状态
│ ├── frames/ depth/ labels/ # PNG 画面;引擎导出的 float32 深度和 uint16 实例标签
├── curated/
│ ├── recordings.jsonl # 每条录像的指标、对齐结果、判定与理由
│ ├── rejected.jsonl # 被拒的录像,没有 turns 文件
│ ├── turns/<id>.jsonl # 规范化后的 5 Hz 回合
│ └── ledger.json # 按世界统计的通过率(OOD 标题的 60% 门槛用它)
└── mixes/<study>/
├── S1 … S5/<budget>h/ # train-*.jsonl 分片 · manifest.json · windows.txt
├── val/ # anchor / in_corpus / ood,所有配比共用
├── cache/ # rationale 缓存
└── ledger.json # 整个网格的账本与检查结果
一条训练样本(S3 / 0.08h 的真实产物,截断):
{
"id": "raycast-rooms-0101:000010",
"world": "raycast-rooms",
"recording": "raycast-rooms-0101",
"messages": [
{"role": "user", "content": "<image>"},
{"role": "assistant", "content": "<think>\nThere is a pickup to my left, about 1.0 m away, so I keep it in view: strafing right, swinging the view left.\n</think>\n<|action_start|> -66 ; 1 ; 0 ; d,2 ; d,2 ; d ; d ; d ; a <|action_end|>"},
{"role": "user", "content": "<image>"},
{"role": "assistant", "content": "<think>\n</think>\n<|action_start|> 125 ; 1 ; 0 ; a ; a ; a ; a ; a ; a,e <|action_end|>"}
],
"images": ["raycast-rooms-0101/frames/000168.png", "raycast-rooms-0101/frames/000174.png"]
}
(从一个 20 回合样本里截出两个回合。第一个回合恰好被选中带 rationale,第二个是空的 <think>。)
图片路径相对于 manifest 里的 media_root。
上面的训练样本是 GameScaling 的对话格式:模型看图,用文本说出动作。具身策略的代码(LeRobot 和多数 VLA 训练栈)读的是另一种格式:按回合组织,每一行是一个控制步,包含观测图像、数值状态和数值动作。export 把筛选通过的录像写成这种格式,一条录像一个回合,每 200 ms 动作包一行:
embodigen export --curated data/curated --out data/embodied # 参考世界:带位姿、深度、分割
embodigen export --curated data/samples-curated --out data/samples-embodied # 真实录像:从视频解码(需要 [video])
data/embodied/
├── meta/info.json # 特征、fps、划分、路径模板(LeRobot v2.1 的键)
├── meta/episodes.jsonl # 每个回合:长度、任务、来自哪条录像 / 世界 / 来源 / session
├── meta/tasks.jsonl # 任务文本(P2P 用它自带的逐帧指令,其余是 "Play <游戏名>")
├── meta/stats.json # 每个数值特征的 min / max / mean / std,用来归一化
├── data/chunk-000/episode_000000.jsonl # 装了 pyarrow 时另写 .parquet
└── images/observation.images.main/episode_000000/frame_000000.png
images/observation.depth/… .f32 # 仅引擎世界:精确深度
images/observation.segmentation/… .u16 # 仅引擎世界:实例标签
| 特征 | 内容 |
|---|---|
observation.images.main | 这一步开始时玩家看到的画面。整套数据集统一尺寸:引擎画面保持原尺寸,来自视频的帧按原始采集比例解码到 320×180;比例不同的画面加黑边,不拉伸 |
action(float32 × 61) | mouse_dx, mouse_dy, wheel(规范化后的计数)+ 58 个按键各自在 6 个 33 ms 槽里按住的比例 + key.other |
observation.state(float32 × 5) | x, y, z, yaw, pitch(z 朝上,单位和角度沿用各游戏自己的)。只有每条录像每一步都有位姿时才写(引擎世界、VPT) |
action.keys / action.text | 6 个槽的原样按键和 GameScaling 文本动作,向量丢掉的槽内顺序在这里都在 |
动作就是 curate 之后的回合(已按对齐结果校正),导出不改标签;被闸门拒掉的录像不会进来。实测:参考世界 30 个回合、4500 步,0.4 秒导完;9 条真实录像 21734 步、72.4 分钟,56 秒导完,峰值内存 280 MB。网页控制台的「分步操作」里也能导出,「数据与结果」里可以逐步查看画面、动作和每一行原文。
这只是"形状"对齐:行写成 JSONL(装了 pyarrow 时另写 parquet),画面是 PNG 而不是 mp4,所以要直接给 lerobot.LeRobotDataset 加载,还需要按它的版本补一次转换。
configs/worlds/ 放一个 <label>.toml(类别、视角、别名、系列;能跑的世界再写 engine 和 engine_args)。embodigen worlds 会列出来。discover(root) 和 convert(path, out_root, registry),写出统一的原始布局,然后在 BACKENDS["source"] 注册。可以参照 gameplay/sources/ 下的 vpt.py(逐帧 jsonl)、d2e.py(事件流)、p2p.py(逐帧 protobuf,一个文件切成多条录像)。reset / step / observe / done / close,observe 返回的状态里带 visible 列表,脚本玩家就能直接驱动它。rationale(turn, context, world, media),设置 name 和 prompt_version,注册到 BACKENDS["annotate"]。缓存和"每个回合只标一次"的规则自动生效;有 workers 属性的后端会先并发填缓存(参照 gameplay/llm.py)。embodigen world --task "把红色方块放进盒子" # 提示 → 场景 → 通过闸门的回合 → 4D 高斯世界
embodigen world --rollout raycast-arena --seconds 20 # 在游戏世界里录一段 → 用引擎深度和位姿抬升
embodigen world --recording data/raw/raycast-arena-0000 # 已有的引擎录像
embodigen ui # 「4D 世界」页:播放、环绕、编辑、导出
思路来自 3D4D(AAAI 2026 Demo):把一段生成的视频变成随时间变化的 3D 高斯(3DGS),在网页里交互地看和改。它的开源实现是 LucidDreamer 出单图 3DGS → 渲成全景 → Stable Video Diffusion 生成帧 → DPT 估深度 → 每帧点云 PLY → SuperSplat 网页查看,在 H100 上要跑半小时以上,帧之间的相机位移是写死的平移。这里的管线本来就有精确的几何,所以每一步的"估计"都换成了真值:
| 3D4D 开源实现 | 这里 | |
|---|---|---|
| 几何来源 | DPT 单目深度(相对深度,归一化) | 机器人:场景里每个盒子、球体的真实尺寸和位置;游戏:引擎深度 |
| 相机 | 帧间按固定步长平移 | 录制时的真实相机(机器人场景相机;玩家的位置、偏航、俯仰) |
| 动静分离 | 无,每帧整份点云 | 静态层只存一份;移动的物体、夹爪、敌人和道具按帧存 |
| 模型与算力 | LucidDreamer + SVD + DPT,本地 GPU | 不跑模型,纯标准库,CPU 上几秒到几十秒 |
| 网页 | SuperSplat(读 PLY 序列) | 控制台内置 WebGL2 高斯渲染器:时间轴播放、环绕 / 录制视角、球形选择删除与撤销、导出 PLY、录制 WebM;同时写标准 3DGS PLY,SuperSplat 也能打开 |
机器人场景的高斯是从物体表面直接采样的面元(薄圆盘,沿法线很薄),所以从任何角度看都是完整的,不是单视角深度图那样的一层壳;太大的面(5 m 的桌面)只保留工作区周围 0.35 m。游戏世界是逐帧抬升:墙和地面按 6 cm 体素合并为静态层,同一个体素保留离得最近时看到的颜色;敌人、道具只在看到它们的那一帧出现。天花板默认去掉,这样能从上方看清关卡(--ceiling 保留)。
data/worlds/<name>/
├── world.json # fps、帧数、每帧相机、包围盒、各文件、来源
├── static.splat # 每帧都有的高斯(32 字节一条:位置、尺度、RGBA、四元数)
├── dynamic/000000.splat # 只属于第 i 帧的高斯
└── ply/frame_000000.ply # 第 i 帧静态 + 动态,标准 3DGS 布局(默认总量 ≤ 64 MB 时每帧都写,否则只写第一帧)
实测(本机 CPU):任务 → 世界 28 帧,9462 个静态 + 每帧最多 795 个动态高斯,3.9 秒;30 秒的游戏录像 150 帧,40326 个静态 + 每帧最多 2100 个动态高斯,22 秒。测试把机器人世界的高斯中心用场景相机投回画面,物体像素被覆盖的比例 > 97%;游戏世界里 97% 以上的静态高斯落在网格墙面或 z = 0 的地面上。
没做的部分:3D4D 用视觉语言模型给每帧打重要性图来做注视点渲染;这里的高斯数量在几万级,浏览器直接全分辨率渲染,用不上。生成式的外观(用云端图生视频模型给白模换皮)可以作为写实化后端接进来,需要先选定服务商并提供 key。
机器人策略训练缺的不是视频,是带准确动作标签的视频。纯仿真的标签准,但画面是 CG;生成模型的画面真,但动作只能靠估计。
EmbodiGen 把这两件事拆开:
| 输入 | 一句任务,例如「把红色方块放进盒子」;外加机器人本体配置(URDF + 动作空间) |
| 输出 | 自包含的 episode 目录:照片级帧 + 动作序列 + 阶段标签 + 精确的深度 / 分割 / 位姿 + 忠实度报告 |
| 保证 | 标签由引擎从几何算出,不是从视频里估的;渲染器只改外观;验证器证明几何没有被带偏,不达标的样本进不了数据集 |
六个阶段都在 src/embodigen/modules/ 下,各自一个接口。当前的场景、引擎和渲染器是参考实现;换成 ManiSkill3 或 Wan 只需实现对应方法并注册。产物按 LeRobot 的形状组织,可以直接接进 OpenVLA、π0、Diffusion Policy 这类训练流程:视频帧喂视觉塔,action 喂动作头,phase 做子任务监督,fidelity 用来筛选或加权样本。
out/
├── index.json # 数据集账本:每条的 p95 / 重试次数 / 判定 + 良率汇总
├── episodes/ # 只放认证通过的样本
│ └── franka-0000/
│ ├── meta/
│ │ ├── info.json # 特征描述(LeRobot 形状)
│ │ ├── provenance.json # 引擎 · 渲染器 · 验证器 · 配置哈希 · 种子 · 重试
│ │ ├── fidelity.json # 逐帧误差与判定
│ │ └── scene.json # 相机 · 物体 · 成功判据
│ ├── data/chunk-000/
│ │ └── episode_000000.jsonl # 一帧一行,共 28 行
│ └── frames/
│ ├── photoreal/ 002.png / .rgb # 渲染后的真实感帧
│ ├── white/ 002.png / .rgb # 引擎白模
│ ├── depth/ 002.f32 # float32,平面 Z(米)
│ └── seg/ 002.u16 # 实例分割 id
└── rejected/ # 重试用尽仍未通过的样本,结构相同,留档审计
episode_000000.jsonl 里的一行(真实产物,未做修饰):
{
"frame_index": 2,
"timestamp": 0.375,
"language_instruction": "把红色方块放进盒子",
"phase": "pre_grasp",
"frames": {
"photoreal": "frames/photoreal/002.png",
"white_model": "frames/white/002.png",
"depth": "frames/depth/002.f32",
"segmentation": "frames/seg/002.u16"
},
"action": [-0.494248, 1.008909, -0.8, 0.0, 0.0, 0.0, 0.04],
"observation": {
"eef_position": [-0.041781, 0.126114, 0.14],
"gripper_width": 0.04,
"object_positions": { "cube": [-0.041781, 0.126114, 0.02] }
},
"fidelity": { "drift_px": 0.0 }
}
| 字段 | 含义 |
|---|---|
action | 7 维控制量:三轴平移速度(m/s)、三轴角速度(当前版本为 0)、夹爪开度(m) |
phase | 规划器给出的子任务标签:pre_grasp → grasp → close → lift → transport → place → release → retreat |
observation | 引擎真值:末端位置、夹爪开度、物体中心 |
frames | 同一帧的四个通道,路径相对于 episode 根目录 |
fidelity.drift_px | 该帧被注入的几何漂移;认证过的样本为 0 |
meta/fidelity.json 是这条样本的判定(另有 per_frame_mm,28 个逐帧误差):
| 字段 | 值 |
|---|---|
passed | true |
threshold_mm | 12.0 |
p95_error_mm | 2.265503286773332 |
max_error_mm | 2.278999569164691 |
mean_error_mm | 1.0253204858279945 |
validator | keypoint-appearance-v0 |
notes | object mean 3.3 mm … / eef mean 1.0 mm, gate on eef p95 |
读一条样本,不需要 import 本项目:
import json
from pathlib import Path
root = Path("out/episodes/franka-0000")
rows = [json.loads(l) for l in (root / "data/chunk-000/episode_000000.jsonl").open()]
row = rows[10]
action = row["action"] # 7 维,直接喂策略
phase = row["phase"] # 子任务标签
image = root / row["frames"]["photoreal"] # 照片级帧
depth = root / row["frames"]["depth"] # float32 深度,96×96
它只读渲染出来的像素和相机内参:把夹爪从画面里找回来,与引擎真值投到同一相机上比重投影误差,换算成毫米。
| 条件(96 px) | p95 | 判定 |
|---|---|---|
| 正常渲染 | 2.27 mm | 通过(阈值 12 mm) |
| 注入 25 mm 几何漂移,不重试 | 28.10 mm | 拒绝 → out/rejected/ |
重试不是再渲一次碰运气:每一轮都加严几何、深度、分割的条件权重,种子与权重写进 meta/provenance.json,事后可审计。
--episodes N 在种子 seed … seed+N-1 上生成 N 条,--workers K 用多进程并行;每条独立验证,认证通过的进 out/episodes/,没通过的进 out/rejected/。所有结果合并进 out/index.json:
embodigen gen --task "把红色方块放进盒子" --out out --resolution 64 --episodes 8 --workers 4
embodigen index --out out # 打印每条的 p95、重试次数、判定,以及良率
多次运行会按 episode id 合并进同一本账,不会互相覆盖。
| 命令 | 作用 |
|---|---|
embodigen gen --task "..." [--robot franka|mobile|humanoid] | 跑完整链路,生成一条 episode |
embodigen gen ... --episodes N --workers K | 在种子 seed … seed+N-1 上并行生成一批 |
embodigen validate --episode <dir> | 只凭目录重算忠实度 |
embodigen preview --episode <dir> [--channels white,photoreal,depth,seg] | 写出四通道 GIF |
embodigen index --out <dir> | 打印数据集账本与良率 |
embodigen backends | 列出已注册的后端与机器人本体 |
embodigen worlds | 列出已注册的虚拟世界 |
embodigen rollout --world <a,b|all> -n N [--profile player|afk|distracted] [--lag-ms X] [--drift-ms-per-min X] | 让脚本玩家在世界里录制 N 条(后几个参数故意制造坏样本,用来验证闸门) |
embodigen fetch [--video] [--hf-endpoint <镜像>] | 下载真实公开样例(带进度、可断点续传) |
embodigen ingest --source vpt|d2e|p2p --input <dir> [-j K] | 把数据集录像转换成统一原始布局 |
embodigen curate --raw <dir> --out <dir> [--no-realign] | 对齐校验、动作规范化、闸门 |
embodigen build --curated <dir> --study <toml> | 划分、配比 × 预算、稀疏推理、分片 |
embodigen export --curated <dir> --out <dir> [--worlds a,b] [--size WxH] | 把通过闸门的录像写成具身格式(LeRobot 形状:每步画面 + 数值动作 + 位姿/深度/分割) |
embodigen world --task "..." | --rollout <world> | --recording <dir> | --episode <dir> | 生成 4D 高斯世界(3DGS),在控制台「4D 世界」里看 |
embodigen ledger <dir> | 打印 curate 或 build 的账本 |
embodigen llm setup / llm check --ping | 填入并检查云端模型的服务商、模型名和 API key(annotator = "llm" 必须先做这一步) |
embodigen ui [--port 8765] | 本地网页控制台:上面这些步骤都能在浏览器里点按钮完成,带实时日志和停止按钮 |
embodigen preview --recording <dir> [--start 秒] [--max-frames N] | 把一条录像画成带键鼠条的 GIF;数据集录像从视频里解码(需要 [video]),按原始采集比例还原画面 |
常用参数:--resolution(默认 64)、--fps(8)、--threshold(12 mm)、--attempts(3)、--drift(首轮注入漂移,默认 25 mm,0 关闭)、--seed。
每个阶段只是一个满足 src/embodigen/modules/base.py 协议的对象(按形状匹配,不需要继承)。接一个新渲染器:
# my_pkg/wan.py
class WanPhotorealizer:
name = "wan2.2-fun-5b-control"
def __init__(self, drift_mm=0.0, drift_frames=(0, 0), grain=1.0): ...
def render(self, gt, scene, condition_weights, seed):
# 白模作首帧,深度 + 分割作控制,相机走 Plücker 嵌入
...
return PhotorealResult(model=self.name, seed=seed, frames=frames)
然后在 BACKENDS["photoreal"] 里注册 "wan": "my_pkg.wan:WanPhotorealizer",PipelineConfig(photoreal_backend="wan") 即可,其余阶段一行不动。新机器人本体则只需在 configs/robots/ 放一个 <name>.toml,embodigen backends 会自动列出。
把下面这段直接粘给 Codex 或 Claude Code:
克隆
https://github.com/wellkilo/embodigen到本地,用python3 -m venv .venv建环境并激活,执行pip install -e .。 然后运行embodigen gen --task "把红色方块放进盒子" --out out --resolution 96, 读取out/episodes/franka-0000/meta/fidelity.json,告诉我 p95 误差、阈值和是否通过, 并数一下frames/photoreal/里有多少帧。 任何一步失败,读完整报错再修,不要跳过。
仓库根目录的 AGENTS.md 写了安装、验收标准和常用命令,agent 会自动读到。
make test # 145 个单元测试(ViZDoom、zstd、PyAV 和真实样本相关的测试在缺依赖时跳过)
make demo # 单条生成 + 验证
make batch # 8 条批量 + 账本
make worlds # 虚拟世界:录制 → curate → build 参考网格(多核机器约 1 分钟)
make samples # 真实公开数据:下载 → ingest → curate → build(D2E 需要 Python 3.14+ 或 pip install -e '.[d2e]')
make world4d # 4D 高斯世界:一个来自任务描述,一个来自游戏录像
make ui # 网页控制台(同 embodigen ui)
make docs # 从真实运行重建展示页素材(约 75 秒;虚拟世界部分读 make worlds / make samples 的账本)
展示页在 docs/,纯静态、无构建步骤,直接用浏览器打开 docs/index.html 即可。深色主题(深青黑底、青色强调、衬线标题)。虚拟世界一节的数字来自 docs/assets/gameplay.js,由 scripts/build_gameplay_assets.py 从真实账本导出(--inject 会在真实录像上重跑注入时延实验)。三维回放(docs/assets/live.js)是一个约 600 行的 Canvas2D 渲染器,读取的是 build_docs_assets.py 从真实 episode 导出的数据。
mock-rationale-v0 用模板加引擎状态写 rationale,只用来验证密度、缓存和模板。云端模型后端 llm 已经实现,但还没在真实服务商上跑过,rationale 的质量要等提供 API key 后抽样检查。video.mkv#t=<秒> 的形式引用,训练样本里也是这个引用,没有抽帧落盘。只有对齐校验、预览和 llm 标注器会解码视频。D2E-480p 和 P2P 的 192×192 视频把 16:9 的画面压成了正方形;预览和标注器会按 recording.json 里的采集尺寸还原比例,但训练样本引用的仍是原视频,训练端需要自己处理宽高比。没有镜头运动可对照的录像(俯视游戏)无法验证,会放行。D2E 数据集按游戏而不是按玩家组织,所以源流上限按游戏计;P2P 的鼠标滚轮只记方向。mock-raycast-v0 证明的是数据通路和闸门,画面不是真游戏。真引擎目前只有 ViZDoom。budget_unit = "footage"),重叠窗口不重复计。如果论文是按窗口累计算的,改成 "window" 即可。token 数没有统计。pyproject.toml 里是 TBD,正式开源前需要补上 LICENSE。