← Back
2026-05-01 · experiment

ApiMem zero-shot πHL — BinFill flash-lite baseline (50 ep)

TL;DR. [260430/research-pi_mem_text_summary_adaptation.md](../260430/research-pi_mem_text_summary_adaptation.md)

배경/목적 (왜)

260430/research-pi_mem_text_summary_adaptation.md + 260430/plan-api_mem_implementation.md Phase 2.

PI Mem 의 long-term language summary mtzero-shot LetSur (Gemini) API 로 구현해서 pi0.5 closed-loop 에 꽂은 첫 BinFill 50 ep. 비교 baseline 은 MemER LoRA (exp-closed_loop_full_sweep.md) 의 BinFill 64%.

작업 내용 (어떻게)

사용한 setting (260501 시점)

전 단계 (260501) 의 두 차례 시행착오

  1. --args.only-ep0 잘못된 위치 → wrapper level flag 인 --only-ep0 로 수정. wrapper 의 prepopulate-progress 는 여전히 잘못된 dir(oracle/) 에 씀 → 결국 only-ep0 가 무력화. 이번엔 50 ep full sweep 으로 결정 (B 옵션).
  2. gemini-2.5-flash thinking 으로 truncation — 응답이 평균 39 token 만에 끊김 (정상 응답 평균 62 token). extra_body.thinking_config.thinking_budget=0 무시됨. → flash-lite 로 교체.
  3. SAPIEN Vulkan ~27 ep leak (직전 sweep 도 동일 발견) → script 에 client restart loop (max 10) 추가. flash-lite 50 ep 1 회 안에 다 끝나서 trigger 안 됨.

핵심 코드 변경

결과 (수치)

Top-line

Token / parse 진단 (직전 flash 와 비교)

측정 flash (thinking) flash-lite (non-thinking)
Parse fails 41/389 calls (10.5%) 0/~1100 calls (0%)
event=error (parse abort 후) 16/27 = 59% 0/50 = 0%
Truncation 평균 길이 39 token (max_tokens 1024 의 4%) n/a
정상 응답 평균 62 token 비슷
Latency (warm) 2.5–4.7 s 비슷

→ Truncation 원인은 LetSur 가 extra_body.thinking_config 을 drop, Gemini flash 가 hidden thinking 으로 1024 token 을 거의 다 써버리고 visible content 39 token 만 출력. flash-lite 은 default non-thinking 이라 이 class 의 failure 가 사라짐.

Success 3 ep 의 좌표 패턴

example 의 좌표 (BinFill.py): <356,499>, <472,700>, <156,483>, <180,376>.

ep task_goal 좌표 source 패턴
1 put one green cube into bin, press button call 0~10: parrot (<356,499>, <472,700>). call 15~22: fresh <400,175>.
40 put two green cubes into bin, press button call 0~20: fresh (<375,156>, <375,475>, <156,156>, <372,172>).
41 put one green cube into bin, press button call 0: fresh <375,175>. call 5: parrot <180,376> (button — 실제로 안 움직이는 위치).

→ 모델이 ground 가능은 함. 다만 default 는 example 좌표 parroting. 47 fail 의 대다수도 같은 메커니즘일 가능성 높음 — example 좌표가 cube 위치와 우연히 일치하는 ep 만 성공 (ep1, 41). ep40 만 진짜 grounding success.

의미 (Takeaway)

  1. PI Mem 의 mt 메커니즘 자체는 zero-shot API 로 정상 동작 검증됨: - Rule 2 idempotency (변경 없으면 mt copy + same subgoal): 동작 - mt monotonic 진화 ("not started" → "picked up" → "put in bin"): 동작 - Per-task notes (BinFill 의 "count yourself"): 인식 - Output JSON 안정성: 0 parse fail / 1100+ call (flash-lite 이후)
  2. flash-lite 가 flash 보다 명백히 좋음 (이 task 에서는). thinking 이 grounding 에 도움 줄 거란 가설 기각. Phase 3 ablation 에서 다른 task 도 검증해야.
  3. 6 % vs 64 % 의 격차는 거의 전적으로 좌표 grounding 문제 — example 의 hardcoded coord 가 anchor 로 작용. 이건 prompt 만 고치면 해결될 가능성 큼.
  4. MemER LoRA 의 in-domain training 이 grounding 에서 차지하는 비중이 압도적. "zero-shot 으로도 충분" 가설 (research 문서) 의 일부 부정 — text mt 만으론 grounding gap 메우기 부족. grounding 별도 fix 필요.

보완점 / 다음 (Next)

즉시 (다음 1 회 launch)

중기

분석 / 정리

참고 파일


업데이트 — Anti-parroting prompt fix (job 3317)

변경

결과 (3317, BinFill 50 ep, gemini-2.5-flash-lite)

지표 3279 baseline 3317 anti-parrot Δ
SR 3/50 = 6.0 % 3/50 = 6.0 % 0 pp
Errors 0 0
Parrot-dominant eps (>50% parrot calls) 29/50 = 58 % 13/50 = 26 % -32 pp
Per-call parrot rate ~50 % 29.7 % -20 pp
Successful eps 1, 40, 41 4, 25, 40 다른 ep set
Wall-clock 1 h 5 min 1 h 18 min +13 min

핵심 해석

  1. prompt fix 는 모델 행동을 의도대로 바꿈: parrot rate 가 절반 가까이 줄었음 (50 % → 30 %), parrot-dominant eps 비율도 58 % → 26 % 로 감소.
  2. 그런데 SR 은 변동 없음 (6.0 % → 6.0 %) — 두 run 의 success eps 가 다른 것 (1/40/41 → 4/25/40) 은 stochastic noise 수준. 즉 parroting 은 fail 의 원인 이 아니라 증상 이었음. 진짜 fail 원인은 fresh grounding 도 부정확하다는 것.
  3. Flash-lite 의 visual localization 능력 자체가 BinFill scene 의 cube/bin/button 을 256×256 image 에서 정확히 짚지 못함. 모델이 "그쯤이다" 정도의 좌표를 내는데 pi0.5 가 그 좌표로 가도 grasp 실패 → episode fail.
  4. PI Mem 의 mt 메커니즘은 여전히 정상 동작 (2-run 합쳐 ~700 call 모두 0 parse fail, rule 2 idempotency, mt monotonic 진화 다 OK). 언어 메모리 부분은 검증됨.
  5. 다음 실험의 가설: 같은 prompt + 같은 mt 메커니즘 + 더 강한 vision 모델 (gemini-2.5-pro) → SR 가 30 % + 까지 올라가면 grounding 가 정말 bottleneck 이었음 확정. 만약 그래도 안 오르면 prompt 자체 또는 image resolution 등 다른 요인 의심.

다음 (즉시)

보존 데이터

(다음 run 시작 전에 위 두 디렉토리를 보존해 두면 ablation 비교 데이터 재사용 가능)


업데이트 — gemini-2.5-pro vs flash-lite + 512 upscale 병렬 (jobs 4263/4264, 2026-05-02)

배경

직전 baseline 6 % + anti-parrot 6 % 에서 grounding 정확도가 bottleneck 으로 결론. 두 fix 동시 시도: - A. gemini-2.5-pro (vision ↑, 비용 ↑): tag=pro, save_dir=runs/api_mem_smoke/BinFill_pro/ - B. flash-lite + 256→512px LANCZOS upscale (model 그대로, image ↑): tag=upscale512, save_dir=runs/api_mem_smoke/BinFill_upscale512/

인프라 변경 (이번에 추가)

결과 — pro (4263 → preempt → 4284 FAILED)

지표 pro 5/50 ep
done 5/50 (preempt 후 sbmr 5 exhausted)
SR 0/5 = 0 %
Errors 0
Parse fails 0/115 calls
Parrot rate 0.9 % (vs flash-lite anti-parrot 30 %)
Latency 9 ~ 11 s/call

pro 는 anti-parrot 거의 완벽 (vs flash-lite 의 30 %). subgoal coords 가 episode 마다 모두 다른 fresh 값. mt 도 정상 진화 (I have picked up the red cube.). 다만 5 ep 표본 너무 작아 SR 판단 보류 — 4314 (sbmr 10 으로 재제출) 결과 대기.

결과 — flash-lite + upscale 512 (4264 → 4283 COMPLETED)

지표 upscale512 1/50 ep
done 1/50 (model 가 망가져서 restart 10번 모두 fail → MAX_CLIENT_RESTARTS exit)
SR 0/1
Errors 1
Parse fails 40/240 calls (17 %)
Failure mode model 이 "I have picked 0 cubes." 무한 반복 (=degenerative loop)

중요 발견 — 256→512 LANCZOS upscale 은 flash-lite 를 망가뜨림. 모델이 같은 문장을 max_tokens 까지 반복하는 degenerative loop 에 빠짐. 가설: - LANCZOS resampling 이 256×256 native 이미지를 512×512 로 키우면서 texture 정보 없는 평탄한 픽셀 영역이 늘어남 → image token 의 정보 밀도 감소 → 모델이 텍스트 출력으로 "더 많이 생각하려 시도" → repetitive loop. - 또는 LetSur gateway 가 더 큰 base64 payload 에 다른 처리 적용. 이 variant 폐기, 다른 image preprocessing (e.g. center-crop instead of upscale, or send native 256 with explicit zoom-in coordinates description) 가 필요.

다음


업데이트 — Pro 50/50 최종 + 본질 진단 (job 4314, 2026-05-02)

결과 비교 표 (BinFill 50 ep)

Run Model parrot rate parse fail SR 비교 baseline
MemER LoRA Qwen3-VL-4B + LoRA (in-domain) n/a n/a 64.0 % (32/50) (외부 baseline)
3279 baseline flash-lite ~50 % 0 % 6.0 % (3/50) -58 pp
3317 anti-parrot flash-lite + prompt fix 30 % 0 % 6.0 % (3/50) -58 pp
4283 upscale512 flash-lite + 512 LANCZOS n/a 17 % (loop) 0/1 (broken) n/a
4314 pro gemini-2.5-pro 3.0 % 0.8 % 4.0 % (2/50) -60 pp

Pro 의 latency 11 s/call (vs flash-lite 2-3 s) — 5x 비싸고 5x 느린데 SR 더 낮음.

Cross-ref: 어떤 ep 들이 성공했는가

LoRA success eps (32 개): 0,1,3,4,5,6,9,12,16,17,20,21,22,23,24,25,27,28,29,31,32,34,36,37,40,41,42,43,44,45,...

Variant success eps LoRA 도 success?
flash-lite baseline 1, 40, 41 모두 ✅ (LoRA-easy 만 품)
flash-lite anti-parrot 4, 25, 40 모두 ✅ (LoRA-easy 만 품)
pro 16, 18 16=✅, 18=❌ (pro 가 LoRA 가 못 푼 ep 1개 품)

→ ApiMem 의 success 는 거의 모두 LoRA 도 푸는 쉬운 ep 의 부분집합. ApiMem 만의 강점 ep 는 50 ep 중 1 개 (ep18).

본질 진단 — bottleneck 은 pi0.5 와의 alignment

pro ep0 (LoRA success, pro fail) 의 trajectory:

call 0: pick first red cube at <453, 248>   mt=''
call 1: put it into the bin at <665, 525>   mt='I have picked up the red cube.'
[episode ends as fail with only 2 calls]

pro ep18 (pro success, LoRA fail) 의 trajectory:

15 calls, 11 distinct subgoals, mt 정상 진화 (picked one → put one → picked two →
put two → press button → task complete → reset → 재시도)

→ pro 의 mt + grounding 자체는 정확히 작동 (ep18 처럼 풀 수 있음). 그러나 ep0 같이 sim 의 success-predicate / fail-detector 가 매우 일찍 firing 해서 pi0.5 가 충분한 시도 기회를 못 갖고 종료. 같은 정보를 받았을 때 LoRA 는 따라가는데 pi0.5+API 조합은 못 따라감 → wording / coord normalization / timing 중 어딘가의 학습 분포 mismatch.

Phase 3 권장 — BinFill 떠나기

BinFill 은 LoRA 가 64 % 까지 풀 정도로 학습 분포에 잘 맞는 task — ApiMem 의 zero-shot strength (큰 모델 reasoning) 이 의미 있게 발휘될 영역이 아니다. 격차 60 pp 는 prompt tweak 으로 못 메움.

대신 LoRA 도 약한 task 로 옮겨서 ApiMem 이 의미 있는 영역 찾는다. 가설: - PatternLock (LoRA 16 %): 긴 sequence + memory-of-positions-pressed → mt 의 자연어 카운팅이 도움 가능. ApiMem ≥ 30 % 면 의미 있음. - ButtonUnmaskSwap (LoRA 18 %): swap 시점 추적 → mt rule 2 (변경 없으면 unchanged) + grounding flexibility 가 도움 가능. - InsertPeg / StopCube (LoRA 4 %): precision-critical, ApiMem 이 도움 줄 가능성 낮음.

즉시 (다음 launch)

추가 참고 — 대안 시나리오: pi0.5 alignment 직접 검증

가장 cheap 한 alignment test: API 가 emit 한 subgoal 을 oracle ground-truth 좌표로 overwrite 한 후 pi0.5 에 넣음. 만약 그래도 SR 낮으면 wording mismatch, 높으면 coord 정확도 문제로 분리 가능. 단 oracle 좌표 추출 자체가 일이라 우선순위 낮음.


🚨 업데이트 — 좌표 normalization bug 발견 + 재실험 (job 4381, 2026-05-02)

발견

사용자 피드백으로 4 runs 모두 ApiMem path 에 coord scaling 단계 누락 확인.

Path emit scale 256-space rescale?
MemER LoRA (api_memer.py:_parse_subgoal_for_vla) 1000-norm <x*256/1000, y*256/1000>
Gemini (gemini/api.py:normalize_point_in_response) 1000-norm _normalize_point
ApiMem (api_mem/api.py:get_subgoal) 1000-norm ❌ no rescale (raw 1000 returned)

→ pi0.5 가 받는 좌표가: - LoRA: pick up at <91, 127> (256-space, valid pixel) - ApiMem 직전까지: pick up at <356, 499> (1000-space — 256x256 image 에서는 out-of-range 4 배 어긋남)

위 4 runs 의 SR 6 %, 4 %, 0 % 는 LoRA-vs-ApiMem 격차가 아니라 ApiMem path 의 broken-pipe 결과. grounding/parroting 가설이 SR 변동 없었던 이유도 설명됨 — 어떤 좌표를 emit 하든 어차피 4x scale 어긋나서 모두 fail.

Fix

examples/robomme/subgoal_prediction/api_mem/api.py_rescale_coords 메소드 추가, get_subgoal 의 return 직전에 적용:

def _rescale_coords(self, subgoal: str) -> str:
    h, w = self.image_size  # (256, 256)
    def repl(m):
        y = int(int(m.group(1)) * h / 1000)
        x = int(int(m.group(2)) * w / 1000)
        return f"<{y}, {x}>"
    return re.sub(r"<\s*(\d+)\s*,\s*(\d+)\s*>", repl, subgoal)

Unit test 4/4 OK:

'pick up the first red cube at <356, 499>' -> '<91, 127>'
'put it into the bin at <500, 500>'         -> '<128, 128>'
'press the button at <0, 1000>'             -> '<0, 256>'
'two coords <100,200> and <800, 600>'       -> '<25, 51>' '<204, 153>'

재실험 (4381, RUNNING)

의미 (의미 갱신 — 위 분석 모두 잘못된 baseline 위에 있었음)

  1. PI Mem 의 mt 메커니즘 자체는 항상 정상 동작했음 (1100+ call 0 parse fail, rule 2 idempotency, mt monotonic 진화). 이건 변함없음.
  2. SR 6 % 는 ApiMem 의 상한이 아니라 broken pipe 의 noise floor. rescale 후 SR 가 의미 있게 오를 가능성 큼.
  3. 위에서 결론낸 "BinFill 떠나서 PatternLock 가자" 는 재검토 — rescale 후 BinFill SR 가 ≥ 30 % 면 BinFill 도 valid baseline 이고, 그 위에서 다른 task 도 다시 봐야 함.
  4. 직전의 anti-parrot 효과 측정도 부분적으로 무효화됨 — parrot 줄여도 broken pipe 라 SR 안 변한 거였음. rescale 후 다시 anti-parrot 효과를 측정해야 함.

결과 (4429, rescale 적용 final 50/50)

지표 4429 rescaled
done 50/50
SR 8.0 % (4/50)
success eps 12, 25, 28, 42 (모두 LoRA-success ep)
Errors 0
Parse fails 0/997
Parrot rate 41.3 %

5-run 비교 표 (BinFill 50 ep, 최종)

Run Model Coord Anti-parrot prompt SR success eps parrot rate
3279 baseline flash-lite broken pipe 6.0 % 1, 40, 41 50 %
3317 anti-parrot flash-lite broken pipe 6.0 % 4, 25, 40 30 %
4283 upscale512 flash-lite + 512px broken pipe 0/1 (loop fail) n/a
4314 pro gemini-2.5-pro broken pipe 4.0 % 16, 18 3 %
4429 rescaled flash-lite fixed (1000→256) 8.0 % 12, 25, 28, 42 41 %
MemER LoRA Qwen3-VL+LoRA (in-domain) n/a n/a 64.0 % 32/50 n/a

진단 (최종, 5 run 통합)

  1. Coord normalization 은 실제 contributor — broken pipe 조차 6 % 가 나왔던 이유는 pi0.5 가 좌표를 부분적으로 무시하고 언어/시각 단서로 reasonable action 을 만들 수 있었기 때문. fix 후 +2 pp (6 → 8 %) — 정상 범위 내 작은 개선.
  2. 그러나 grounding bottleneck 은 여전. 56 pp 격차 대부분이 zero-shot Gemini vs in-domain LoRA 의 grounding 정확도 차이. pro 도 4 % 에 그친 것이 같은 메시지 — 모델 reasoning 능력이 grounding 능력을 보완하지 못함.
  3. ApiMem 의 success 는 모두 LoRA-easy ep (success eps 4 개 모두 LoRA True). ApiMem 만의 강점 ep 는 5 run 합쳐서 1 개 (4314 pro 의 ep18) — 통계적으로 유의 하지 않음.
  4. BinFill 은 LoRA 가 학습 분포에 너무 잘 맞아서 zero-shot ApiMem 으로는 따라잡기 불가능. 같은 결론 재확정.

다음 (Phase 3 권장, 변동 없음)

BinFill 떠나서 LoRA 가 약한 task 에서 ApiMem 이 의미 있는 영역 찾기. 우선순위: - PatternLock (LoRA 16 %): 긴 sequence + memory-of-positions-pressed → mt 카운팅이 도움 가능 - ButtonUnmaskSwap (LoRA 18 %): swap 시점 추적 → mt rule 2 유리 - InsertPeg (LoRA 4 %): precision-critical, ApiMem 도 어려울 가능성 높음

각 task × pro 50 ep × sub partition. ≥ 30 % 나오면 Phase 3 ablation tier (mem / no-mem / LoRA) 비교 진행.


업데이트 — Option 1: bbox-first scratchpad (job 5092, 2026-05-03)

변경

prompts/base.py 에 bbox-first 3-step procedure 강제:

{
  "scratchpad": "...",
  "detected_objects": [
    {"label": "first red cube", "box_2d": [y1, x1, y2, x2]},
    {"label": "bin",            "box_2d": [...]},
    {"label": "button",         "box_2d": [...]}
  ],
  "memory_summary": "...",
  "subgoal": "pick up the first red cube at <y_center, x_center>"
}

결과 (5092, BinFill, flash-lite + bbox-first)

지표 5092 bbox 4429 rescaled (직전 baseline)
done 18/50 (script abort) 50/50
SR 16.7 % (3/18) 8.0 % (4/50)
success eps 1, 12, 25 (모두 LoRA-success) 12, 25, 28, 42
Errors 1 0
Parse fails 7.8 % (38/485 calls) 0 % (0/997)
bbox emit rate 99.8 % n/a

발견 — bbox 가 효과 있지만 flash-lite 가 robust 하지 못함

  1. Success eps 의 bbox quality 깨끗: 3-4 객체, center 계산 정확 - ep1 call 0: first green cube bbox [356, 100, 420, 156] → center <388, 128> → subgoal 'pick up the first green cube at <388, 128>' ✅ - ep12 call 0: first blue cube bbox [408, 475, 508, 549] → center <458, 512>

  2. 그러나 fail 시 catastrophic loop: model 이 100+ 가상 객체를 enumerate "...The ninety-eighth blue cube is at <372, 7140>. The ninety-ninth blue cube is at <372, 7212>. The one hundred and sixth red cube is at <472, 700>..." - bbox + reasoning + memory_summary 동시 생성이 flash-lite 의 cognitive capacity 초과. max_tokens 2048 까지 허위 객체 nested loop 으로 채워버림. - 38 parse_fails 중 대부분이 이 패턴. - 18 ep 만에 client process 가 10 restarts 다 소모하고 script 가 정상 종료.

  3. 그래도 SR 8% → 16.7% (제한된 18 ep sample, +8.7 pp). 통계적 유의성은 작은 sample 이라 약하지만 명확한 signal. bbox 가 grounding 정확도 올린 게 맞음.

결론


업데이트 — Option 2: 2-step detection + reasoning (job 5652, 2026-05-04)

변경

결과 (5652 47/50, BinFill flash-lite 2-step)

지표 5652 2-step 5092 1-call bbox 4429 rescaled
done 47/50 (RUNNING) 27/50 (loop abort) 50/50
SR 6.4 % (3/47) 16.7 % (3/18) 8.0 % (4/50)
success eps 1, 25, 44 (모두 LoRA-success) 1, 12, 25 12, 25, 28, 42
degenerative loops 0 8/27 ❌ 0
parse_fail rate 0.4 % 7.8 % ❌ 0 %
det_empty rate 4.1 % n/a n/a

핵심 진단

  1. Robustness fix 됐지만 SR 안 오름. 0 loop / 0.4% parse_fail vs 5092 의 30% loop — 분리가 cognitive overload 해결.
  2. Detection bbox 자체가 불안정. 같은 BinFill scene 의 같은 객체가 call 마다 매우 다른 bbox 로 detect 됨 (예: ep0 의 "first red cube" bbox y-range 가 call0=359-458 → call1=308-348 → call2=339-427). flash-lite 의 spatial precision 이 매 call 마다 흔들림.
  3. "first/second" 라벨링도 매번 다른 객체 가리킴 — 모델이 same scene 안의 객체에 일관된 ordinal 부여하지 못함.
  4. 결론: flash-lite-based detection 의 정확도 ceiling 이 SR 6-8 % 부근. 모델을 바꾸거나 (Option 4 → pro), 외부 detection tool 을 쓰지 않는 한 더 이상 못 넘김.

다음