본 문서는 PI Mem 논문(
https://www.pi.website/download/Mem.pdf, Torne et al., 2025)을 정독한 뒤, 현재 RoboMME 평가에 쓰고 있는 MemER LoRA 파이프라인 (exp-closed_loop_full_sweep.md, avg sr 44%) 을 어떻게 PI Mem 의 long-term text summary 메커니즘으로 대체/보강할 수 있는지 정리한 research 노트.핵심 요청: VLM 자체는 API (zero-shot) 이고 pi0.5 가 short-term을 비디오로 받지 않으므로, text summary 만으로 long-horizon memory 효과를 낼 수 있는 system prompt 설계 가 본 문서의 산출물.
mt,
(b) short-horizon = video encoder. 그리고 mt 는 high-level policy πHL 가 스스로 다음
token 으로 출력 한다. 즉 summary 갱신 자체가 모델의 행동.mt + 다음 subtask + grounded <y,x> 를
안정적으로 emit 시키는 system prompt skeleton 을 설계.논문 식:
π(at:t+H, lt+1, mt+1 | ot−T:t, mt, g)
≈ πLL(at:t+H | ot−K:t, lt+1, g)
πHL(lt+1, mt+1 | ot, mt, g)
g : task goal (자연어).mt : 현재 시점까지의 자연어 summary ("language memory").lt+1 : 다음 subtask 자연어.πHL : low-frequency 호출, lt+1 과 mt+1 을 동시에 생성. mt 는 자기 자신의 직전 출력을 다시 받는 autoregressive 구조.πLL : action chunk 생성. K ≪ T (e.g., 6 frames) 의 short obs window + 새 subtask 만 받음. mt 를 받지 않음.우리 한국어 풀이: 고수준 호출은 "지금까지 무슨 일이 있었는지를 한 줄로 다시 적고, 다음에 뭘 해야 하는지를 정한다" 의 한 step. 저수준은 그 한 줄 subtask 를 받아 행동을 만든다.
mt: I placed a plate in the cabinet and moved to the counter.
↓
mt+1: I placed a plate in the cabinet, moved to the counter, and picked up a bowl.
학습 데이터 생성: per-episode subtask 시퀀스 (with success/fail) 를 off-the-shelf LLM 에 주고 "요약하되 이후 의사결정에 더는 필요 없는 정보는 버려라" 로 prompt → 그 출력을 GT mt 시퀀스로 사용.
"instead of remembering the precise attributes of all objects … 'I put a light green bowl, a dark blue bowl and a bright yellow bowl into the top right cabinet', it is often sufficient to just remember 'I placed three bowls in the top right cabinet'"
핵심 ablation (Fig 6 'Naive Text + Video'): subtask history concat 만 하면 성능이 크게 떨어짐. 원인은 train-inference distribution shift — training data 는 사람 demo 라 subtask 가 한 번씩 밖에 나오지 않지만, inference 때는 같은 subtask 가 fail 로 반복 출력되어 "pick up bowl" × 3 같이 누적된다. compressed memory 는 성공 까지 mt 갱신을 미루므로 이 shift 가 사라진다.
→ 우리 zero-shot 설계의 1번 규칙: "실패 / 진행중인 시도는 mt 에 절대 쓰지 마라".
| Variant | Recipe Setup / Clean Kitchen 평균 |
|---|---|
| π0.6 No memory | 가장 낮음 |
| Only video memory | 중간 |
| Naive text + video (concat) | video-only 보다 떨어짐 (distribution shift) |
| Only text memory | video-only 와 비슷~조금 우위 |
| π0.6-MEM (compressed text + video) | 최고 |
→ Long-horizon task 에서는 compressed text 만으로도 video 단독과 동등 이상 의 효과. → 따라서 video 없이도 text 가 "잘" 만들어지면 long-horizon task 에서 의미있는 gain 가능 — 우리 가설.
examples/robomme/subgoal_predictor.py 에 4 개의 subgoal predictor 가 있음:
- NullSubgoalPredictor — 사용 X.
- GeminiSubgoalPredictor — 이미 zero-shot API VLM 경로. Gemini 2.5 flash-lite 기본.
per-task prompt_dict_grounded[<TASK>] 로 system prompt 구성. video clip 단위로 호출.
- QwenVLSubgoalPredictor — finetuned Qwen3-VL adapter (memer 가 아닌 baseline).
- MemERSubgoalPredictor — Qwen3VLModelMemER 인스턴스. keyframe FIFO 메모리 + LoRA.
api_memer.py)System prompt (전체):
You are a robot program that predicts actions. The current input images from
the front-view camera shows the most recent actions the robot has executed.
The past keyframes are selected frames of particular importance from all the
actions the robot has executed so far. Based on these, output the current
subtask the robot should execute and nothing else. Some tasks may have a video
input for initial setup, some may not.
Return a JSON with:
- current_subtask: the action that should be executed at the current timestep
- keyframe_positions: list of frame positions (1-indexed) from the current input
images where actions change
User prompt 매 호출:
[The task has a video input for initial setup: <video>] # task 별
The task goal is: {task_goal}
Here are the selected frames from the entirety of the full execution that are
of particular importance: [<image>, <image>, ...] ← key_frame_paths (FIFO)
Here is current input image list from the front-view camera:
[<image>, <image>, ..., <image>] ← 최근 8 프레임 (stride 2)
What subtask should the robot execute and what is the keyframe position?
메모리 동작:
- add_execution_frame(image) — 매 step PNG 저장.
- _get_current_execution_frame_paths() — 마지막 idx 부터 stride 2 로 8개 frame.
- update_history_subgoals(response) — VLM 이 직접 1-indexed keyframe_positions 출력 →
해당 step PNG 들이 key_frame_paths 에 들어감.
- merge_key_frame_paths(dist=8) — sim-step 거리 ≤ 8 이면 같은 group, 그 group 의 median 만 남김.
→ memory 는 image 의 FIFO. 자연어 summary 가 아예 없음. VLM 이 keyframe 이미지를 보고 다시 "이게 무슨 의미였지" 를 매번 재해석해야 함.
gemini/prompts/base.py)이쪽은 per-task system prompt + few-shot example 로 zero-shot 동작:
SYSTEM_PROMPT = """You are a helpful assistant ...
Possible subgoals:{subgoals}
Example:{example}
Output Format:
- 첫 frame: {"subgoal_sequence": "...", "subgoal": "..."}
- video clip 들어올 때: {"description": "...", "subgoal": "..."}
"""
{subgoals} : task별로 "- pick up the [first/second/third] cube at {example} : 모범 시퀀스 1 ~ 5 + notes (e.g. "bin sucks cubes, count yourself").GROUNDED_SUBGOAL_INFORMATION 추가 ("normalized to 0-1000").→ 이 구조에 memory_summary 필드를 추가 하기만 하면 PI Mem 의 mt 가 그대로 들어간다. per-task notes 는 그대로 살림 (e.g., "bin sucks cubes 라서 push 하지 마라").
[Episode start]
- mt = "" # empty memory
- πHL call 1 (with task_goal, demo_video?, image, mt="")
→ emits {memory_summary, subgoal, point}
- mt ← memory_summary # high-level autoregressive
- subtask + point → pi0.5 (πLL) → action chunk
- sim 수십 step 진행 …
[Periodic high-level call] (e.g., 매 K=48 step 마다 — Gemini path 와 동일)
- πHL call (task_goal, current image, mt) → new {memory_summary, subgoal, point}
- mt ← new.memory_summary
- if subgoal != prev_subgoal: switch
- else: keep last subtask going
핵심 차이 vs MemER: - 메모리 = 자연어 한 문단 (≈30~60 token), keyframe image 아님. - VLM 호출 빈도 ↓ (매 step → 매 K step) — pi0.5 가 한 subtask 를 K step 동안 끌고감. - VLM 자체는 cloud API → in-domain finetune 없음.
per-task subgoals / example / notes 는 기존 prompts/<TASK>.py 그대로 재사용.
그 위에 task-agnostic memory 섹션을 얹는다:
You are the HIGH-LEVEL controller for a robotic arm. You decide ONE subtask at a
time and maintain a compressed natural-language MEMORY of progress made so far.
A separate low-level controller will execute the subtask you choose.
# Possible subgoals (task-specific)
{subgoals}
# Example episode (task-specific)
{example}
# Per-task notes
{notes}
# Memory (mt) — what to write
The memory is a 1-2 sentence summary of EVENTS THAT HAVE COMPLETED so far in
the current episode and that still affect the next decision.
Hard rules for memory:
1. Only describe COMPLETED subtasks. Never describe attempts, failures, or
"is approaching X" states.
2. If the previous subtask in the input has not yet visibly completed in the
current image, COPY the previous memory string UNCHANGED and continue
issuing the same subtask.
3. Compress aggressively. Replace enumerations with counts:
"I picked up a red cube and a blue cube" → on completion of the third pick,
write "I have picked 3 cubes". Drop colors once they no longer affect routing.
4. Never invent events you cannot verify from the current image.
5. After the FINAL subtask of the example sequence is completed, set memory to
"task complete" and reissue the final subtask.
# Grounded coordinates
For subgoals that include a target object, always emit a 2-D point in the
PIXEL coordinate frame normalized to 0-1000, e.g. "pick up the red cube at
<450, 612>". Use the LAST input image to ground the point.
# Output format (STRICT JSON, no prose, no markdown fences)
{
"scratchpad": "<= 30 words of reasoning, NOT used downstream>",
"memory_summary": "<= 60 tokens, follows the rules above>",
"subgoal": "<one of the subgoals listed above, with grounded <y,x>>"
}
User message 매 호출:
Task goal: {task_goal}
Previous memory: {mt or "(empty)"}
Previous subgoal: {last_subgoal or "(none)"}
Current image: <image>
[Demo video for initial setup: <video>] # task 별, 첫 호출 한 번만
| 결정 | 근거 |
|---|---|
scratchpad 필드 (downstream 미사용) |
Claude/Gemini 모두 reasoning 을 JSON 안에 강제하면 quality ↑. system prompt 에 "ignored downstream" 명시 → leakage 걱정 없음. |
memory_summary 가 별도 필드 (subgoal 안에 안 섞음) |
parse 안정성. 우리가 mt 를 다음 호출에 그대로 다시 넣어야 하므로 exact 추출 필요. |
| Rule 2 (변경 없으면 그대로 복사) | PI Mem 의 distribution-shift 방지 핵심. 실패한 grasp 를 mt 에 누적하지 않게 하는 강제 장치. zero-shot 이라 더 명시적으로. |
| Rule 3 (count compression) | 논문 직접 인용 ("3 bowls in cabinet"). RoboMME PickXtimes/SwingXtimes/BinFill 같은 counting task 에서 직접 도움. |
| Per-task notes 보존 | BinFill 의 "bin sucks cubes" 같은 task-specific 물리 quirk 는 학습 데이터로도 안 들어가니, system prompt 외 다른 곳에 둘 데가 없음. |
| 매 호출마다 previous memory 를 user message 에 다시 입력 | API 모델은 conversation history 에 의존하지 말고 fresh context 로 매번 호출 (cost ↓, cache 친화). |
| K=48 step 마다 호출 (Gemini path 와 동일) | pi0.5 가 한 subtask 를 ~1~2초 chunking 으로 끌고가는 시간. 너무 자주 부르면 mt 가 작은 변화에도 over-update. |
PI 논문 ablation (Fig 6) 의 Only Text Memory 가 video-only 와 거의 동등 → text-only 로 충분한 task 의 조건은 (a) 진행 상태가 이산적 이고 (b) 실패 횟수 보다 완료 횟수 가 결정적인 경우.
현재 RoboMME 16 task 분류:
| Tier | Tasks | text-only 적합? | 이유 |
|---|---|---|---|
| Counting / sequencing | BinFill 64%, PickXtimes 76%, SwingXtimes 60%, ButtonUnmask 82%, ButtonUnmaskSwap 18%, PatternLock 16% | YES, 큰 gain 가능 | "n 번 했음" 카운트가 곧 진행상태. MemER FIFO 에서 image 로 카운팅하던 부담을 text 로 옮김. |
| Spatial-memory + visual demo | VideoUnmask 78%, VideoUnmaskSwap 36%, VideoPlaceButton 28%, VideoPlaceOrder 36%, VideoRepick 22%, PickHighlight 82% | 부분 YES | demo video 자체는 첫 호출에서 한 번 본다. summary 에 "demo 에서 본 빨강→파랑→초록 순서" 같이 박아두면 이후 호출에서 video 재투입 불필요. |
| Continuous / precision | InsertPeg 4%, StopCube 4%, MoveCube 88%, RouteStick 10% | NO 또는 미미 | 실패의 원인이 grounding/제어 정밀도. text mt 가 도와줄 게 많지 않음. (단 RouteStick 는 stick 의 통과 지점 이 spatial sequence 라서 약간 도움 가능.) |
→ counting/sequencing tier 에 가장 큰 즉시 gain. precision tier 는 grounding 개선이 별도 작업.
| 축 | MemER LoRA (현재) | PI Mem 원논문 | 본 제안 (zero-shot API) |
|---|---|---|---|
| πHL 모델 | Qwen3-VL-4B + LoRA | π0.6 (Gemma3-4B + SigLIP), trained | Claude/Gemini API (zero-shot) |
| πLL 모델 | pi0.5 (mme_vla_suite/symbolic-grounded-subgoal/79999) | π0.6 with video encoder | pi0.5 (그대로) |
| Long-horizon memory | Keyframe image FIFO | Compressed natural-language mt | Compressed natural-language mt |
| Short-horizon memory | 최근 8 frame stride 2 (image) | 5~17 frame video encoder | (text only — 최근 1 frame + 직전 subtask) |
| Memory 갱신자 | LoRA-finetuned VLM (training-data 의 keyframe label 모방) | πHL 자기 자신 (mt+1 token 출력) | API VLM (system prompt 규칙으로 출력) |
| Distribution-shift mitigation | (없음, 그래서 fail 시도가 keyframe FIFO 에 그대로 쌓임) | mt 갱신을 성공 시까지 미룸 (training time 에 그렇게 라벨) | system prompt rule 2 ("변경 없으면 mt 복사") 로 명시 |
| Grounded coords | LoRA 학습으로 얻음 (in-domain) | n/a (πHL → 다음 subtask string) | Gemini path 처럼 example 에 <y,x> 박고 system prompt 강제 |
| Cost / call | 로컬 GPU 1 frame ~150 ms | 로컬 GPU 50 ms 추정 | API 1~3 s + 토큰 비용 |
| In-domain finetune 필요 | YES (이미 받았음) | YES (큰 학습 진행) | NO |
| OOD 적응 | 약함 (학습 task 외에는 미검증) | ? | 강할 가능성 (큰 모델 reasoning) |
parse_markdown_json (코드 fence 까지 처리) 재사용 + retry-once on parse fail.이 단계에서 측정 한 수치는 없음. 대신:
~/env/api_keys.txt (read-only) 로 ANTHROPIC_API_KEY / GEMINI_API_KEY 존재 여부만 확인 — 직접 출력/전송 금지.subgoal_prediction/api_mem/ 신설. 기존 gemini/ 를 base 로 fork:prompts/base.py 에 MEMORY_RULES 블록 + OUTPUT_FORMAT_WITH_MEMORY 추가.api.py 에 ClaudeMemModel / GeminiMemModel 두 개 (provider switch). 첫 실험은 모델 1개로.subgoal_predictor.py 에 ApiMemSubgoalPredictor 클래스 추가. mt 를 episode state 에 보관, 매 call 마다 user prompt 에 주입.gemini/ qwenvl/ 는 무수정 (closed-loop sweep 재현성 보존).mt log 를 사람이 검수.render_with_memory.py 를 mt 표시용으로 확장 — keyframe FIFO strip 자리에 mt 의 marquee 표시.<video> 입력을 mt 1 줄로 압축할 때, 그 1 줄이 정확한가 를 검증할 method 가 필요. (사람 검수 외에는 없음 → Phase 2 에서 BinFill 외에 VideoUnmask ep0 도 같이 검수.)enum-like constraint).https://www.pi.website/download/Mem.pdf (./tmp/mem.pdf 로컬 캐시).https://www.pi.website/research/memory.examples/robomme/subgoal_prediction/qwenvl/api_memer.py — 현 MemER inference.examples/robomme/subgoal_prediction/gemini/api.py — 기존 zero-shot API 인프라 (재사용 base).examples/robomme/subgoal_prediction/gemini/prompts/base.py — system prompt template.examples/robomme/subgoal_prediction/gemini/prompts/BinFill.py — per-task prompt 예시.examples/robomme/subgoal_predictor.py — 4 종 predictor wrapper.260430/exp-closed_loop_full_sweep.md — MemER LoRA sweep 결과.260428/setup-robomme_b200_cluster.md.