← Index
2026-09-09 — scene_bench · 온라인 실행 트랙 엔지니어링

온라인 memory writer 전략별 분리 + VLA policy 섀도잉 치명 버그 수정

"온라인 노트 규칙이 context 단계 규칙과 같아야 한다"는 지적에서 시작해, 공통 writer 결함과 그 수정 과정에서 나온 런타임 크래시까지 봉쇄

TL;DR

4
전멸한 샤드 (섀도잉)
$6.37
검증 런 100행 비용
160턴/4회
vis-pure 실제 KEEP 빈도
0.155
SR 개선 p (Bonferroni 후)

1배경 / 목적

사용자 지적(260909): "온라인 노트 규칙은 context 때의 본인 노트 규칙이랑 똑같아야지 — 시작만 그런 게 아니라." 코드를 확인해보니 실제로 그렇지 않았다:

기존 결함: run_online_plan이 전략과 무관하게 memory_mode="append"를 넘김(online.py:390) — 전 전략이 매 턴 텍스트 노트 한 줄을 추가. OnlineBenchPlanner.memory_images는 생성 시 한 번 세팅된 뒤 한 번도 갱신되지 않음 — vis-pure의 본체인 KEEP/DROP이 온라인에 아예 없었다.

즉 지금까지의 온라인 비교는 "각 방법의 메모리 시스템을 온라인에서 돌린 것"이 아니라 "서로 다른 시드 + 공통 writer"였다. 특히 vis-pure는 자기 방법에 없는 텍스트 노트를 쓰고, 자기 방법의 본체인 프레임 선택은 못 하는 상태로 측정되고 있었다.

2작업 내용

2.1 전략별 writer 정책 배선 (커밋 fc51c6f)

읽기 단계 write 규칙(sbench_eval)과 1:1 대응하는 online_memory.WRITER_POLICY 표를 만들고(모르는 전략은 즉시 ValueError) 배선했다.

전략읽기 단계 write 규칙온라인 writer (신규)
blind메모리 없음notes none / images none — 진짜 무기억
textlogGT 이벤트 로그(모델 writer 없음)notes none / images none — 시드 고정
frames-uniform균등 샘플러(모델 없음)notes none / images uniform — 통합 타임라인 매 턴 재샘플
rollforward-v3청크마다 요약 전체 재작성(MEM식)notes rewrite / images none
rollforward-vis-pureKEEP/DROP 프레임만, 텍스트 없음notes none / images keep
KeepDropImageMemory: 읽기의 _parse_keep_drop을 그대로 재사용 ("KEEP: F2 DROP: m5" 형식), cap 초과 시 가장 오래된 것 축출(읽기 최근성 폴백과 동일) UniformImageMemory: linspace로 (ep.n_steps + 온라인 관측) 타임라인에서 cap장 재샘플 컨텍스트 프레임 디코드 캐시(64) — 턴당 8회 디코드를 대부분 캐시 히트로 UnifiedTurn.keep 필드 추가 — KEEP/DROP 한 줄이 같은 JSON 콜에 실림(추가 VLM 콜 없음) 시스템 프롬프트가 정책별 메모리 계약을 명시(종전엔 전 전략에 노트 규칙을 그대로 지시)

2.2 textlog "오라클 로그" 시도 → 철회

중간에 "온라인엔 GT 행동이 없으니 textlog는 시드 고정"이라는 판단이 틀렸다는 걸 발견 — 읽기 단계 textlog도 모델이 쓴 게 아니라 데이터셋 생성기의 이벤트 로그다. 하네스가 서브골 실행 직후 로그를 이어 붙이는 oracle_log=True 경로를 구현(커밋 20156b3)했으나, 사용자 판단으로 철회하고 시드 고정으로 복귀했다: 온라인 시도는 대부분 실패·타임아웃(중간 런 pick 시도 대비 lift 15~26%)이라 "행동 기억"이 아니라 "헛수고 기록"이 되고, 판정 대상 GT 프로브와 VLA가 실제로 건드린 물체가 다를 수 있기 때문(restore v5 실측: bowl#2 대신 remote를 들었는데 held=bowl#2로 기록된 전례).

2.3 치명 버그: VLA policy 인자 섀도잉 (커밋 145c9b3)

증상: 검증 런 4샤드가 각각 9~11분 만에 COMPLETED로 끝나며 행이 1~2개씩만 나옴.
File "src/sbench_exec/online.py", line 403, in run_online_plan rec = run_episode( File "src/sbench_online/orchestrator.py", line 894, in run_episode task.register_policy(policy) File ".../molmo_spaces/tasks/task.py", line 180, in register_policy policy.task = self AttributeError: 'dict' object has no attribute 'task'

원인: run_online_plan(env, cfg, sampler, first_task, ep, plan, client, policy, ...)의 8번째 인자 policy는 VLA 정책 객체(MolmobotPolicy)인데, fc51c6f가 본문 안에서 policy = writer_policy(plan.strategy)로 그 인자를 덮어썼다. 첫 VLA 서브골(pick/open)이 시작되는 순간 task.register_policy(dict)에서 죽고, navigate만 하고 끝난 플랜은 살아남아(6행) 샤드가 조용히 "완료"로 보인 것.

조치: 변수를 wpolicy로 개명(online.py:390, 사용처 395·410·470·474·475). 실런 없이 잡을 수 있는 유일한 지점이라 AST 회귀 테스트 추가 — tests/exec/test_online_pure.py::test_run_online_plan_does_not_shadow_vla_policy가 run_online_plan 본문에서 policy 재대입을 금지한다. 결함본(당시 HEAD)에 돌리면 잡히는 것을 확인.

3결과 (수치)

3.1 섀도잉 사고 피해 범위

소모 비용 $0.1354 / 6행(승인분 $6 중, 취소 시점 기준). 살아남은 6행은 VLA 서브골이 없던 플랜만 남은 편향 표본이라 성능 비교에는 못 쓰지만, writer 동작 자체는 확인됐다:

전략writer_noteswriter_imagesseed→final imgsonline_in_mem
blindnonenone0→0—
textlognonenone0→0—
frames-uniformnoneuniform8→81
rollforward-v3rewritenone0→0—
vis-purenonekeep8→80 (KEEP 기회 없음, 2턴)

부수 확인: 예산 초과 행(blind 1887/1500, rf-v3 1532/1500)은 설계대로다 — drive는 경로 중간에 자르지 않고 초과분을 within_budget=False로 걸러 SR 0 처리(orchestrator.py:582-589, 260907 결정).

3.2 수정본 검증 런 (100행, 20컨텍스트 × 5전략, obs4/cap8, $6.37)

final: done 59 · budget_exhausted 23 · abort 10 · max_subgoals 8. within_budget False 14행.

전략writer(notes/img)메모리 이미지keep/drop/evict시드 잔존
blindnone / none0——
textlognone / none0——
frames-uniformnone / uniform8——
rollforward-v3rewrite / none0——
rollforward-vis-purenone / keep84/4/07.80/8
vis-pure cap 8 압력 걱정은 실측상 기우였다: 총 160턴 중 KEEP 4회(에피소드 4/20에서만), DROP도 4회라 축출 0회, 컨텍스트 프레임이 평균 7.8/8 남았다. 오프라인 20.2회 기회에서 8칸을 다투던 것과 달리 온라인에서는 모델이 거의 KEEP하지 않는다 — 예산 분리는 불필요하지만, vis-pure의 온라인 이미지 메모리는 사실상 비활성에 가깝다는 뜻이기도 하다.

frames-uniform: 메모리 8칸 중 온라인 프레임 평균 0.90장(20행 중 18행이 1장, 2행이 0장) — 온라인 구간이 컨텍스트 영상(1만+ 프레임)보다 훨씬 짧으니 균등 규칙의 산술적 귀결.

3.3 구 파이프라인(공통 append writer·obs 5·frames 16장) 대비 짝비교 — 같은 20컨텍스트

전략첫 행동 적중 구→신SR 구→신악화/개선McNemar p
blind0.25→0.200.20→0.151/01.000
textlog0.55→0.550.25→0.403/60.508
frames-uniform (n=9)0.44→0.400.00→0.220/20.500
rollforward-v30.40→0.400.30→0.301/11.000
rollforward-vis-pure0.55→0.500.20→0.500/60.031
vis-pure SR 0.20→0.50은 KEEP writer 때문이 아니다: p=0.031이지만 5전략 동시 비교라 Bonferroni 보정 시 0.155 — 유의하지 않다. 메커니즘도 안 맞는다: KEEP이 160턴 중 4회뿐이라 이미지 메모리가 바뀐 에피소드가 4개밖에 없는데 개선 6건 중 KEEP 있음 3/4·KEEP 없음 3/16. 진짜 원인은 구 파이프라인의 프로토콜 위반 — 구 vis-pure는 공통 append writer 탓에 온라인 텍스트 노트를 4~22줄 쌓고 있었다(이미지 전용 전략인데 텍스트 기억을 받음). 이 표는 "어느 쪽이 더 나은가"가 아니라 "결함 vs 정상 프로토콜" 비교다.

첫 행동 적중이 다섯 전략 모두 사실상 그대로인 것이 설계 확인이다 — t=0 결정은 시드 메모리만 보고 내리고 writer는 t≥1부터 작동하므로, writer 교체가 첫 행동을 바꾸면 안 된다. 실제로 안 바뀌었다. n=20이라 SR 차이 0.15는 노이즈와 구분 안 됨 — 프로토콜 변화가 없는 blind조차 0.20→0.15로 움직인 게 이 표본 크기의 노이즈 바닥이다.

4Takeaway

의미

이제 온라인 축이 재는 것은 "같은 시드 + 공통 writer"가 아니라 각 메모리 시스템을 온라인에서 돌린 결과다 — 논문에서 "we run each memory method online"이라고 쓸 수 있는 상태가 됐다. 기존 온라인 수치 중 t≥1 지표(SR@예산·도달·예산 소진)는 전부 무효고, t=0 지표(첫 행동 적중)는 대부분 유지된다. 이 수정이 없었다면 이후 670행 grid 런(260909-online_w2_grid_670.html)의 blind 대비 메모리 우위 결론이 "결함이 만든 우연"이었는지 검증 자체가 불가능했다 — 검증 결과 writer를 고친 뒤에도 결론이 같다(압축 원인은 writer가 아니었음).

또한 이 사고는 "순수 테스트 286개가 전부 통과해도 새는 지점이 있다"는 사례다 — run_online_plan은 env/policy/client가 필요해 단위 테스트 사각지대였고, AST 가드가 그 사각을 좁힌 구체적 방식이 됐다.

5Next Steps

미해결 — vis-pure 온라인 이미지 메모리의 실효성

vis-pure 온라인 KEEP이 160턴에 4회뿐인 것을 그대로 둘지 결정 필요 — 규칙은 읽기 단계와 동일하므로 "모델이 안 쓰는 것"이 사실이지만, 그렇다면 vis-pure의 온라인 축은 시드 이미지만으로 굴러간다는 뜻이라 논문 서술에 반영이 필요하다.

완료된 후속 — 참고

수정본으로 재제출한 본 런(670행, $42.84)이 이미 완료되어 260909-online_w2_grid_670.html로 별도 보고됨: writer 결함을 고친 뒤에도 "메모리끼리는 분리 안 됨" 결론은 그대로였고, blind 대비 메모리 우위는 네 전략 모두 처음으로 유의하게 나왔다.