Index
2026-05-12 — Engineering

BUS Task-Specific Pipeline — 코드 오케스트레이션 Stages 1-8 구현

memer · robomme_policy_learning · ButtonUnmaskSwap shell-game

TL;DR

0%
Tool-calling SR (BUS)
12
Commits (Stages)
4
Bug Fix Commits
≥30%
Target SR (10ep)

1 배경 / 목적

apimem-tools-v1 (P2-P4.1) — LLM이 annotate_objects / track_objects / save_keyframe tool을 직접 호출하는 multi-turn agent — 이 VideoUnmaskSwap(VUS) 50ep sweep을 위해 구현·제출됐다. 동시에 ButtonUnmaskSwap(BUS)에도 적용해봤으나 결과는 SR=0.

실패 진단 (6 fix patch 후에도 SR=0):
  • (a) Tool hallucination: LLM이 존재하지 않는 tool을 호출하거나 잘못된 인자 형식 사용
  • (b) Coord instability: LLM이 매 tick마다 다른 좌표를 emit → VLA가 일관된 target 못 받음
  • (c) Subgoal sequence 추적 불가: vision만으로 "버튼 1 눌렀는가 → 2 눌렀는가 → swap 완료됐는가"를 신뢰성 있게 판단 못 함

BUS의 핵심 insight: 버튼 누르기는 위상 전환을 gate하지 않는다. 컨테이너 drop + swap은 시뮬레이터가 agent와 병렬로 자동 실행한다. 즉 6-step 시퀀스는 결정론적이고, 유일하게 LLM이 필요한 시점은 task_goal text에서 타깃 색상을 추출하는 steps 3/5뿐이다. LLM-driven agent 구조는 과설계였다.

2 작업 내용

Pivot 결정: Code-Orchestrated, Task-Specific Pipeline

Branch를 6c93bcb (P0/P1 SAM2+GroundingDINO 안정 상태)로 hard-reset. 새 접근의 핵심 3가지:

핵심 설계 결정 ──────────────────────────────────────────────────────── 1. Code drives state machine / LLM picks ONE discrete action → LLM은 좌표 emit 없음. code가 anchor lookup → subgoal text 생성 → tool hallucination / coord instability failure class 제거 2. Memory = single post-swap keyframe (code가 SAM2로 생성) → SAM2 obj_id로 swap 전 container 위치 ↔ cube 색상 mapping 보존 → LLM은 keyframe 보고 색상만 pick (단순 VQA) 3. Action menu = 6-step fixed sequence Steps 1-2: press_button_1/2 (고정, LLM 호출 없음) Steps 3/5: pick_container_ (regex parse task_goal, LLM 필요 시만) Steps 4/6: put_down (고정) ────────────────────────────────────────────────────────

모듈 구조 (신규 파일, bus_pipeline/)

Stage파일역할Commit
Stage 1anchor_registry.pyAnchorRegistry 데이터 클래스, coord 저장/조회420ccab
Stage 2phase_tracker.py6-step 시퀀스 state machine + PhaseTrackerd1cc710
Stage 3phase_tracker.pyPhase A detection — GroundingDINO로 button + cube anchor 추출a643516
Stage 4swap_tracker.pySwapTracker — SAM2로 swap window ~96 frames container 추적b1429ae
Stage 5action_selector.pyActionSelector — task_goal regex parse, fallback LLM calld92ecbe
Stage 6orchestrator.pyBUSPipeline 메인 클래스 — phase 전환 + subgoal text 생성58e564b
Stage 7subgoal_predictor.pyBUSPipelinePredictor + eval.py Args 연결 (use_bus_pipeline flag)7fe4fa1
Stage 8-Step gating + seed validation + per-tick viz dump5f39884

Bug Fix Commits (Stage 8 이후)

502dfd5 BUS fix: rescale 1000-scale subgoal → image-pixel for VLA (GroundingDINO 출력이 1000-scale, pi0.5는 256px space → rescale 누락) 077bbe9 BUS fix: relax seed score threshold 0.35 → 0.20 + log raw detections (Phase A에서 GroundingDINO가 container 못 잡는 경우 → threshold 낮춤) 101354e BUS rearchitect: SAM2 tracks CUBES from t=0, not containers post-drop (컨테이너가 cube 위에 떨어진 후 추적하면 occlusion 때문에 실패 → t=0부터 cube를 추적해서 drop 후에도 container 위치 파악) 10d85af BUS fix: dense per-env-step frame buffer for SAM2 (SAM2에 넘기는 frame이 sparse하면 swap motion 중 tracking drift → env step마다 frame append, dense buffer 유지)

SAM2 Tracking 전략 변경 (rearchitect)

당초 설계는 "컨테이너가 cube 위에 drop된 후 3개 컨테이너를 SAM2로 추적"이었다. 문제는 컨테이너가 처음 등장할 때 occlusion이 심해서 SAM2 seed bbox가 불안정하다는 것. 해결책: t=0부터 cube 자체를 추적한다. Cube는 처음부터 fully visible, GroundingDINO가 정확히 잡는다. Drop 후 cube 위치 = 컨테이너 위치 → cube obj_id를 컨테이너 identity로 그대로 이어받는다.

3 결과

구현 완료 현황

항목상태비고
Stages 1-8 구현완료12 commits, bus_pipeline/ 6 파일
rescale bug fix완료1000→256px, pi0.5 coord 호환
SAM2 rearchitect (cube t=0)완료occlusion 문제 구조적 해결
dense frame buffer완료swap motion tracking drift 방지
10-ep BUS sweep대기Stage 9: sbm 제출 필요, 목표 SR ≥ 30%
apimem-tools-v1 VUS sweep진행 중VideoUnmaskSwap 50ep, 별도 트랙
구조적 장점: 코드가 state를 완전히 제어하므로 LLM cost ≈ 0 (regex parse 성공 시 LLM 호출 없음). GroundingDINO + SAM2는 로컬 GPU에서 무료. 50ep 예상 비용 < $0.10.
미검증: 실제 BUS episode에서 GroundingDINO seed score와 SAM2 tracking이 얼마나 안정적인지는 smoke run (Stage 8)에서만 확인. 10-ep sweep 결과가 없는 상태.

Action Menu → VLA Subgoal Text 매핑

press_button_1 → "press the first button at <y, x>" press_button_2 → "press the second button at <y, x>" pick_container_red → "pick up the container at <y, x> that hides the red cube" pick_container_blue → "pick up the container at <y, x> that hides the blue cube" pick_container_green → "pick up the container at <y, x> that hides the green cube" put_down → "put down the container" ※ subgoals_grounded in prompts/ButtonUnmaskSwap.py — 학습 분포 exact match

4 Takeaway

LLM agent의 한계와 task-specific 코드 오케스트레이션의 가치

BUS는 사실 LLM이 필요 없는 task다. 6-step 시퀀스는 결정론적, 좌표는 detection에서 바로 나오고, 타깃 색상은 task_goal text의 regex 파싱으로 충분하다. LLM-driven tool-calling agent는 이 단순한 문제를 "open-ended agentic loop"로 복잡하게 만들어 hallucination / instability가 발생했다.

이 pivot의 진짜 의미는: tool-calling agent가 적합한 task와 그렇지 않은 task를 구분해야 한다는 것. BUS는 코드 오케스트레이션, VideoUnmaskSwap은 다단계 VQA가 필요한 task로, 각자 다른 접근이 맞다. 두 track을 병행 운영하는 현재 구조가 올바른 방향.

SAM2 rearchitect (cube t=0 추적) insight: container가 아닌 cube를 처음부터 추적하는 것은 occlusion에 강건한 identity preservation 방법. swap 후에도 cube obj_id가 곧 "어느 컨테이너 아래에 어떤 색상의 cube가 있는가"를 직접 인코딩한다. 이 패턴은 다른 shell-game task (VideoUnmaskSwap 등)에도 일반화 가능.

5 Next Steps

Stage 9: BUS 10-ep Sweep 제출

sbmr 5 "bash scripts/run_bus_pipeline.sh ButtonUnmaskSwap 0 10" --gres=gpu:2 -c 16 --mem=400GB --qos=extra
성공 기준: SR ≥ 30% (MemER LoRA 18% 대비 개선). per-phase pass rate 로그 분석으로 실패 지점 파악.

미해결 리스크: Phase A detection 신뢰성

GroundingDINO seed score threshold 0.20으로 낮췄지만 여전히 "버튼 2개 + cube 3개"를 ep마다 안정적으로 잡는지 불명. 10-ep sweep 로그의 event=phase_transition과 raw_detections로 확인. 만약 phase A에서 stick이 많으면 Gemini-backed detection을 fallback으로 추가.

VideoUnmaskSwap tool-calling sweep 병행

apimem-tools-v1 VUS 50ep sweep은 별도 트랙. SR 결과에 따라 VUS에서도 code-orchestrated pivot이 필요한지 판단.