Index
2026-09-21 — Engineering

실험 3 하네스 해설 — 입력에서 출력까지

SceneMem | Oracle-plan Online Control Eval · 이 문서의 모든 문자열은 실제 하네스가 생성한 것

먼저 — 파일럿은 아직 안 돌렸습니다

$0
지출
4/7
Task 완료
1,490
측정 예정 칸
8
잡은 계획 결함

1 무엇을 재려는 실험인가

이 벤치마크는 메모리를 두 종류로 나눠 잽니다 — retrospective(과거에 본 것을 회상)와 execute-state(실행 중 바뀐 상태를 기록하고 꺼내 씀). 실험을 셋으로 쪼갠 이유가 그것입니다.

축실험 1 (읽기)실험 2 (계획형 온라인)실험 3 (이 문서)
retrospective memory측정 대상계획 출처로 사용제거
계획 품질—전략이 만든 계획 (틀릴 수 있음)GT 고정 (항상 정답)
execute-state memory없음6 arm 중 4개 채널 꺼짐 → 측정 불가유일한 변수
실험 3의 핵심 동기: 260920 감사에서 execute-state 채널이 6 arm 중 4개가 꺼져 있었습니다(textlog은 note:null 강제, frames-uniform은 현재 뷰 중복이라 순증 0, vis-pure는 89% 칸에서 KEEP 0장, rf-v3는 계획 줄 100% 삭제). 그 상태로 얻은 "전략 차 없음"은 있는 arm vs 없는 arm의 비교가 아닙니다. 시드를 빼면 8 이미지 슬롯이 비어서 시작해 KEEP의 축출 비용이 0이 되고, 채널이 전부 켜집니다.

2 입력 ① — GT 계획이 만들어지는 과정

실험 2는 모델이 기억에서 답한 것을 계획으로 넘겼습니다. 실험 3은 정답을 넘깁니다. 중요한 설계 결정: GT를 손으로 조립하지 않고 읽기 트랙의 정본 빌더를 그대로 호출합니다.

데이터셋 answers[] └─ facts.gt_selections(ep, q, room_map) ← 읽기 트랙이 쓰는 바로 그 함수 또는 facts.gt_selections_restore(...) └─ online_plan.gt_plan_answer(sels) ← obj(uid) 제거, abstain=False └─ online_plan.plan_texts(family, ...) ← 실험 2와 동일한 문장 생성기 └─ initial_plan() → render_plan()
왜 손으로 안 만드는가: 저 두 빌더가 260826 표면 결손 3분류 계약을 이미 인코딩하고 있습니다 — 바닥 → surface="floor", hidden → candidates[].fixture가 정답 표면, 미라벨 → 평가 제외. 직접 조립하면 읽기 트랙과 GT가 조용히 갈라집니다.
보관 merged.json 전 행으로 검증: find 4000/4000 · restore 4000/4000 행에서 오류 0.

실제 출력 (오늘 하네스가 생성한 것)

가족질의 & GT생성된 계획
find
single
Navigate to the cup I moved earlier
gt: category=cup, room=null, surface=floor
go to the cup — on the floor
find
multi
Pick up all the potatos
gt: irish potato ×2 (dining table / countertop, kitchen)
pick up the irish potato — on the dining table in the kitchen
pick up the irish potato — on the countertop in the kitchen
restore
single
Pick up the butter knife and place it in or on where it was just before put the butter knife back — it is now on the dining table in the kitchen; return it to the armchair in the living room
restore
multi
… cell phone … then … remote control … put the cell phone back — … ; return it to the dining table in the bedroom
put the remote control back — … ; return it to the tv stand in the living room
바닥 답의 방 이름은 채우지 않습니다. 위 find single 예시의 room이 null인 게 보이실 겁니다. 처음엔 채우려 했지만 데이터셋에 그 값이 없습니다(바닥 답 64/64가 null). 설령 좌표에서 역산해도 채우면 안 됩니다 — GT 계획이 어떤 읽기 답도 도달할 수 없는 수준이 되어 "종단 SR = 읽기 × 실행" 분해가 깨집니다. GT 계획은 완벽한 읽기 답과 정확히 같아야 합니다. 영향: find 722질의 중 25(3.5%), restore는 0.

3 입력 ② — 어떤 칸을 도는가

실험 2는 전략마다 다른 칸을 돌았습니다(그 전략의 읽기 답이 맞은 칸만). 그래서 난이도가 섞여 leave-one-out 보정이 필요했습니다. 실험 3은 전 arm이 같은 칸을 돌아 McNemar 짝비교가 됩니다.

D1D2D3S1S2표본× 5 arm
find v1.2 (722질의)30/22030/22930/5030/12330/100150750칸
restore v1.1 (575질의)30/21530/19628/2830/7930/57148740칸

restore D3는 모집단이 28개가 전부라 전량 사용합니다. 합계 298질의 × 5 arm = 1,490칸.

실제 셀 파일 (오늘 생성된 것)

tmp/oracle_plan/find_cells_gt.json (750칸) ["v1_train_6818__2", "Pick up the key chain I moved earlier", "blind"] ["v1_train_6818__2", "Pick up the key chain I moved earlier", "frames-uniform"] ["v1_train_6818__2", "Pick up the key chain I moved earlier", "rollforward-v3"] ["v1_train_6818__2", "Pick up the key chain I moved earlier", "rollforward-vis-pure"] ["v1_train_6818__2", "Pick up the key chain I moved earlier", "tagmem-r3"] ↑ 같은 (편, 질의)에 5 arm 이 전부 붙는다 = 짝비교의 근거 tmp/oracle_plan/find_sample_manifest.json seed 20260921 · k 30 · 하위유형별 모집단/표본 · 질의 id 목록 ← 재현 기록
데이터셋 루트를 섞으면 안 됩니다. find는 fd856246(v1.2), restore는 6511da78(v1.1)입니다. v1.2 루트에도 restore 질의 575개가 들어 있지만 가리키는 편이 다른 판입니다(같은 이름, 다른 traj.h5 — 12편 표본 중 7편 상이). restore GT는 v1.1 편 위에서 계산됐으므로 v1.2로 돌리면 조용히 틀린 목표가 됩니다.

4 입력 ③ — 모델이 실제로 받는 프롬프트

아래는 오늘 build_online_system_prompt()가 실험 3 조건(--plan gt --plan-slot instruction --seed none)으로 생성한 blind arm의 시스템 프롬프트 전문입니다. 2,865자.

You are the high-level controller of a household robot, and you are also its memory. You are given ONE goal. Each turn you decide the single next subgoal for the low-level policy to execute. Each turn you receive: the frames captured while your previous subgoal was executing, your current view (… exterior camera on the left and the wrist camera on the right …), the goal, and the object and room names used in this house. Each turn the low-level policy executes your subgoal for a fixed budget of 128 steps (about 4 action chunks); a pick usually needs several turns … Skills: - navigate(room, surface) … The room is REQUIRED and taken literally — if you name the wrong room you end up in the wrong room, nothing corrects it. - pick(category) / pick_and_place(category, surface) / open / close / done / abort Rules: - Refer to objects and rooms by the names in the lists below the images — copy the spelling exactly. - The low-level policy fails often. Judge from the frames whether your previous subgoal actually worked … - Holding: after you have lifted an object, the robot sets it back down where it is before your next subgoal … Memory — you do not write anything down. Any notes shown above were recorded during the context phase and will not change; apart from those, the frames of your previous subgoal and your current view are all you have. Always return "note": null. Plan — the task has been broken into these steps for you: 1. [ ] pick up the mug — on the desk in the bedroom This plan is part of the task. It is accurate and does not change: you cannot edit it or tick items off. Work through the items in order. Output a single JSON object, nothing else: {"note": "...", "reasoning": "...", "next": {"skill": "...", "category": "...", "surface": "...", "room": "..."}}

보라 부분이 실험 3에서 새로 생긴 지시 슬롯 계획입니다. 실험 2에서는 이 계획이 노트 안에 [plan] 1. [ ] … 줄로 있었고, 문구도 "It can be wrong or incomplete: if what you see contradicts it, trust what you see" 였습니다.

왜 옮겼나: ① GT 계획에 "틀릴 수 있다"고 말하면 거짓입니다 ② 실험 2에서 rollforward-v3가 계획 줄을 100% 삭제했는데, 틀릴 수 있는 계획에서는 그게 측정 대상이었지만 정답 계획에서는 그 arm만 정답을 잃는 핸디캡이 됩니다. 노트 밖으로 빼면 어느 arm도 못 지웁니다.

arm 별로 달라지는 것은 메모리 블록 하나뿐

armexecute-state 표현메모리 계약 (실물)
blind없음 — 대조군"you do not write anything down … Always return note: null" (245자)
frames-uniform자동 균등 표집 이미지"sampled automatically at even intervals over everything you have observed so far … You do not choose them" (256자)
rollforward-v3텍스트 상태 슬롯(재작성)Notes 규칙 1~5 — "Record what you attempted, what visibly changed, and what is still left to do" (614자)
rollforward-vis-pure이미지 KEEP/DROP(모델이 고름)Visual memory 규칙 1~3 — "KEEP: F2 DROP: m5" (658자)
tagmem-r3태그 누적 + 검색 + 이미지 KEEPNotes 1~5 + Visual memory 1~2 둘 다 (1,214자)
시드를 빼면 프롬프트 한 문장이 거짓이 됩니다. KEEP 계약의 3번 규칙이 그것입니다 — 실험 2와의 실제 diff:
- 3. You keep no written notes. Always return "note": null. ← 실험 3 + 3. Your memory starts full with 8 frames from the context phase. Dropping one of them to record what you just did is expected, not a loss … ← 실험 2 + 4. You keep no written notes. Always return "note": null.
메모리가 비어서 시작하는데 "꽉 찬 채 시작한다"고 말할 수는 없습니다. 그래서 --seed none이 이 문장을 함께 끕니다. 이 한 줄은 260920 진단에서 vis-pure의 KEEP 사용률을 0 → 0.237로 바꾼 바로 그 문장이라, 조건을 정확히 맞추는 게 중요합니다.

5 실행 — 어떻게 돌리는가

EXTRA_ARGS=' --v1-root ~/datasets/scene-mem-benchmark-v1_fd856246 # find (restore 는 …_6511da78) --v1-contexts $(cat tmp/oracle_plan/find_contexts_gt.txt) --v1-families find --plan gt # ← 실험 3: GT 계획 (실험 2 는 --plan read --plan-answers merged.json) --plan-slot instruction # ← 계획을 과제 지시로 고정 (어느 arm 도 못 지움) --seed none # ← 전 arm 빈 메모리로 시작 --plan-cells-file tmp/oracle_plan/find_cells_gt.json --obs-images 4 --image-cap 8 --max-episodes 3' STRATEGIES='blind frames-uniform rollforward-v3 rollforward-vis-pure tagmem-r3' OUT=./tmp/exec_online/oracle_find_s$S.jsonl OUT_ROOT=./tmp/exec_online_oracle_find sbm "$COMMON $EXTRA_ARGS bash scripts/run_online_worker.sh" \ -J oracle_find_s$S --gres=gpu:1 -c 8 --mem 128GB --time 48:00:00 --qos=own \ --container=docker.io/library/python:3.11

find 4샤드 / restore 8샤드, 편 단위 샤딩(씬 1회 빌드), 로그인 babysitter가 --max-episodes로 끊긴 잡을 재제출합니다. --plan gt는 읽기 병합본을 안 쓰므로 --plan-answers를 넘기지 않습니다.

배선 함정 하나: sbm은 PATH의 실행파일이 아니라 ~/slurm_commands.sh가 정의하는 bash 함수입니다. babysitter가 비대화형으로 bash submit_shard.sh를 부르므로 스크립트가 직접 source 하지 않으면 sbm: command not found로 죽습니다 — 그런데 대화형 셸에서 손으로 돌리면 멀쩡해 보입니다.

6 출력 — 무엇이 나오고 어떻게 읽는가

칸마다 한 줄의 JSON 행(99키)이 tmp/exec_online/oracle_{find,restore}_s*.jsonl에 쌓이고, 턴 단위 상세는 <out_root>/<plan_id>/plan_log.json에 남습니다.

사전등록 주지표 — SR 이 아닙니다

실험 2 실측에서 restore multi가 SR 0/47입니다. 그 칸들은 읽기 정답 칸이었으므로 GT 계획을 줘도 안 오릅니다 — 실행 문제입니다. 그런데 multi야말로 execute-state가 물리는 구간("1번은 끝냈다"를 기억해야 함)이라, 지표를 상태 추적으로 바꿔 측정합니다.

#지표정의실험 2 기준선재현 검증
M1held_repick_rate이미 든 물체에 또 pick을 낸 턴 / 총 턴 (성공 여부 불문)1055회 / 6237턴✅ 재현
M2false_done목표 미달인데 done 선언.25✅ 0.252
M3wasted_turn_share(종료턴 − goal_met_turn) / 총 턴.304✅ 0.314
M4gc (multi)충족된 답 / 전체 답 = 단계 전이미측정—

전 arm이 같은 칸을 돌므로 McNemar 짝비교를 씁니다(실험 2의 LOO 보정 불필요). 구현을 scipy binomtest와 대조해 모든 케이스에서 1e-12 이내 일치를 확인했고, 우연히 프로젝트 메모리에 기록된 값도 재현됐습니다 — b01=39, b10=32 → p=0.4767, 기록된 "find D2 tagmem 패배 분해 p=.477, 39 대 32"와 동일.

"재-pick 중 19%가 낙하"의 정의가 어디에도 없었습니다. 턴 레코드에 dropped라는 필드가 실제로 있어서 구현자가 자연스럽게 그걸 집을 텐데, 그걸로 재면 0.8%가 나와 주장이 무너집니다. 맞는 정의는 그 턴의 held_end is None(=끝에 빈손)이고 실측 190/1055 = 18.0%, 1108행으로 환산하면 ≈256 ≈ 인용값 257입니다. 계획과 spec 양쪽에 정의를 수치 옆에 박아뒀습니다.
반증선(사전등록): blind 대비 5 arm 중 어느 것도 M1~M3에서 |Δ| ≥ 0.05 & 2·SE 밖을 못 내면 → "execute-state 메모리는 이 과제에서 실행을 바꾸지 않는다"로 확정 보고합니다. 결과가 기대와 반대여도 그대로 씁니다.

7 지금까지의 구현 & 잡은 결함

Task상태수정 라운드리뷰가 잡은 것
1 --plan gt완료1테스트 중복 import (제 브리프가 원인)
2 --seed none완료2환경변수 재현 노브가 죽는 결함
3 --plan-slot instruction완료1가드를 지워도 통과하는 테스트뿐
4 frames-uniform 표집완료0— (Critical/Important 0건)
5 셀 생성기진행 중—산출물은 검증 통과, 위치 이동 중
6 지표·집계 / 7 제출대기—사전 검증 완료

리뷰·사전검증이 실제로 막은 것 (제 계획의 결함 8건)

조용히 틀렸을 것들만 추리면:

  • SBENCH_KEEP_EVICTION_NOTE 노브 사망 — eviction_note=seeded_memory로 넘기면 항상 bool이라 None 분기(환경변수 읽기)가 영영 안 걸립니다. 환경변수를 안 건드리면 증상이 안 보입니다. 260920 이전 프롬프트를 재현하려는 사람만 조용히 틀린 걸 받습니다.
  • M1이 항상 0이 될 뻔 — 제 계획이 쓴 턴 필드명(success/held_after)이 실제 생산자(ok/held_end)와 달랐습니다.
  • 낙하 정의 부재 — 위 §6의 dropped 함정.
  • _observe_plan 가드 미테스트 — 지워도 전체 스위트가 통과했습니다. 빠지면 plan_missing_count가 매 턴 올라가 계획 통계가 오염되는데, 실제 런의 행을 분석하기 전까지 안 보입니다.
  • gitignore된 파일을 테스트가 import — 제 디스크에서만 통과하는 테스트가 커밋될 뻔했습니다.
  • _observe_plan 시그니처가 제 계획과 달랐습니다(lines ≠ memory_text) — 그대로 썼으면 가드가 엉뚱한 자리에 붙어 안 걸렸습니다.

모든 Task에서 기본값(--plan read / --seed read / --plan-slot note)이 실험 2와 바이트 동일함을 회귀 테스트로 고정했고, Task 3·4는 이전 커밋의 구현을 꺼내 직접 실행해 출력이 같음을 증명했습니다.

8 남은 것

무료 ($0)

Task 5 마무리 → Task 6(지표·집계) → Task 7(사전점검 P2~P6 + 제출 스크립트). 전부 API를 안 씁니다.

승인 필요 (유료)

P1 스모크 $1.5 — v1.2의 cross_multi(이종 물체 2~3개 사슬, 턴 상한 53)는 온라인에서 한 번도 안 돌려봤습니다. 실패하면 find S1/S2 표본을 v1.0 기존 multi 54질의 중심으로 재구성해야 합니다.
본 런 ≈$210 · GPU ≈600시간 — 일일 캡 $100이라 3일 분할. 병목은 돈이 아니라 GPU 시간입니다(own 8 GPU 독점해도 3일, 현실적으로 5~6일).

선행 조건

게이트웨이 월 예산 여유 확인. 09-21 04:09 UTC에 cost:monthly 429가 5초 구간에 5개 잡을 동시에 때렸고 retry_after가 ≈9.5일이었습니다. 지금은 통과하지만 천장 근처라는 신호입니다. 리셋 09-30 16:47 UTC.