Index
2026-05-06 — Engineering

ApiMem 모듈 구현 완성 & 인프라 Gotchas

LetSur / 2-step detection+reasoning / Vulkan restart / SLURM 패턴 — memer × robomme_policy_learning

TL;DR

~330
api.py 라인 수
2-step
Call 구조
54
API call/ep (pro×2-step)
$0.10
비용/sweep (flash-lite)
$1–2
비용/sweep (pro)

1 배경 / 목적

260505 분석 카드에서 BinFill 8-run 결과와 bottleneck 진단을 다뤘다. 본 카드는 그 과정에서 구현된 코드 아키텍처8-run을 거치며 발견한 인프라 트랩을 기록한다. 다음 세션(PatternLock 실험 등)에서 동일 인프라를 재사용할 때 같은 함정에 빠지지 않도록 engineering reference로 남긴다.

구현 원칙: gemini/, qwenvl/ 무수정. 모든 변경은 api_mem/ 신설 모듈 또는 eval.py/subgoal_predictor.py 최소 수정으로 한정.

2 작업 내용

2-1. 신설 모듈 구조

examples/robomme/subgoal_prediction/api_mem/ ├── __init__.py (빈 파일) ├── api.py (~330 lines) — ApiMemModel 클래스, LetSur OpenAI-compat client └── prompts/ ├── __init__.py — TASK_NAMES, prompt_dict_with_memory, DETECT_HINTS, │ build_detection_system_prompt └── base.py — SYSTEM_PROMPT_WITH_MEMORY, MEMORY_RULES, OUTPUT_FORMAT_WITH_MEMORY, GROUNDED_COORD_INFO, DETECTION_SYSTEM_PROMPT, DETECTION_USER_PROMPT, USER_PROMPT_FIRST_CALL, USER_PROMPT_STEP, DEMO_VIDEO_MARKER

2-2. ApiMemModel 외부 API

m = ApiMemModel(provider="letsur", model_name="gemini-2.5-pro", image_upscale=0) m.start_new_episode(save_dir, demo_video, task_goal, task_id) # 매 K=48 sim step 마다: subgoal = m.get_subgoal(current_image) # → 256-space 좌표 string (자동 rescale) m.end_episode()

2-3. 내부 2-step 동작

Call메서드InputOutput비고
Call A (detection) _call_detection(image) image [{label, box_2d}] (1000-norm) detection-only system prompt. 1-step 모드에서는 skip
Call B (reasoning) get_subgoal(image) image + detected_objects + prev_mt + prev_subgoal (+ ep0 demo video) {scratchpad, memory_summary, subgoal} 5-rule MEM system prompt. subgoal은 1000→256 rescale 후 반환

2-4. mt (text memory) 5-rule system prompt

  1. 완료된 subtask만 기록 (진행 중이면 mt 그대로 copy)
  2. 이전 subtask 아직 진행 중 → mt 동결 + 동일 subgoal 재발행 (PI Mem distribution-shift 방지)
  3. enumeration → count 압축 ("I have picked 3 cubes")
  4. image에서 verify 불가한 사건 mt에 넣지 말 것
  5. 마지막 subtask 완료 → mt = "task complete"

8 runs 전부에서 mt 메커니즘 자체 정상 작동 확인 (rule 2 idempotency, monotonic 진화, count compression, "task complete" emit).

2-5. 기존 파일 수정 범위

파일변경 내용
examples/robomme/eval.pyArgs에 5개 필드 추가: use_api_mem, api_mem_provider, api_mem_model_name, api_mem_call_period, api_mem_image_upscale. setup_save_directory"api_mem" 브랜치 추가
examples/robomme/subgoal_predictor.pyApiMemSubgoalPredictor 클래스 추가 + builder switch
scripts/run_api_mem_one_episode.sh신규 — server(uv .venv pi0.5) + client(robomme conda + ApiMem) + Vulkan restart loop 10x. 4 args: <task> [model] [tag] [upscale]

2-6. Log 위치 & 형식

runs/api_mem_smoke/<TASK>_<tag>/symbolic-grounded-subgoal/ckpt79999/seed7/api_mem/<TASK>/ep<N>_ApiMem_log.jsonl # JSONL event 종류: # start_new_episode # detection (Call A): call_idx, latency_s, response_text, detected_objects # call (Call B): call_idx, latency_s, user_text, response_text, parsed{scratchpad,memory_summary,subgoal}, prev_mt, prev_subgoal # parse_fail / empty_response / detection_empty

2-7. 통화 횟수 & 비용

구성API call/ep50 ep sweep 비용
1-step (flash-lite)~27$0.10
2-step (flash-lite)~54$0.20
2-step (gemini-2.5-pro)~54$1–2

BinFill 평균 ~1300 sim steps, K=48 → 27 high-level tick/ep 기준.

3 결과 — 8-run에서 발견한 인프라 트랩

LetSur (Gemini API) 모델 선택

모델Thinking주요 문제권장 용도
gemini-2.5-flash-lite❌ non-thinking좌표 정확도 낮음, bbox 불안정빠른 iteration, 비용 민감 sweep
gemini-2.5-flash✅ thinking ONthinking이 max_tokens 전부 소비 → response truncation 빈발사용 비추천
gemini-2.5-pro✅ thinking ONmax_tokens < 4096이면 visible response 0정확도 우선 실험. max_tokens=4096 필수
⚠️ LetSur는 extra_body.thinking_config.thinking_budget=0을 무시한다. flash와 pro 모두 thinking 비활성화 방법 없음. flash-lite가 유일한 non-thinking 옵션.
⚠️ gemini-2.5-pro + max_tokens=2048 → visible response = 0. thinking 토큰이 2048을 다 소비해버려 response 자체가 빈 문자열로 반환됨. run 8b(job 5834)에서 발견. 반드시 max_tokens=4096 이상 설정. detection call(A)도 마찬가지.

Vulkan Instance Leak

~27 ep마다 Vulkan renderer가 instance 누수로 crash. scripts/run_api_mem_one_episode.shMAX_CLIENT_RESTARTS=10 loop이 자동 재시작으로 회복. 단, tee 파이프가 exit code를 가리는 알려진 한계 있음 — 실제 abort 여부는 progress.json episode 수로 확인.

좌표 rescale

Gemini는 1000-norm 좌표를 emit, pi0.5는 256-space 좌표를 기대한다. api.py_rescale_coords()가 자동 처리하므로 외부에서 추가 변환 불필요. 이 fix가 run5(4429)에서 6%→8% (+2pp)의 유일한 실측 contributor였음.

save_dir 경합 회피

task마다 save_dir 분리 필수. runs/api_mem_smoke/<TASK>_<tag>/ 패턴. 동일 tag로 여러 task를 돌리면 progress.json race condition 발생. script의 4번째 인자 [tag]로 실험별 구분.

SLURM 제출 패턴 (검증된 설정)

conda activate robomme sbmr 10 "bash scripts/run_api_mem_one_episode.sh PatternLock gemini-2.5-pro pro 0" \ --gres=gpu:2 -c 28 --mem=400GB --partition=sub --qos=core-on-sub \ -J apimem-PatternLock-pro # 핵심 플래그 이유: # --gres=gpu:2 : server(uv .venv pi0.5) + client(robomme conda) 각 1 GPU # -c 28 : sub partition 노드 28 CPU / 2 GPU (14 CPU/GPU) # --partition=sub --qos=core-on-sub : own partition 자원과 경합 없이 sub 파티션 이용 # sbmr 10 : PREEMPTED 시 자동 requeue 최대 10회, CONDA_PREFIX 자동 wrap

API 키 위치 (gotcha)

키 파일 경로 주의: ~/envs/api_keys.txt (복수형 envs!). global CLAUDE.md의 ~/env/는 오기. api.py_load_api_key()는 env var 우선, fallback으로 파일 읽기. 또는 ~/.bashrcexport LETSUR_API_KEY=<key>.

4 Takeaway

인프라 완성도: 8-run이 코드를 hardened 했다

run 초기에는 loop abort, parse_fail, Vulkan crash 등 다양한 failure mode가 있었다. run 8c (job 6056, BinFill_2step_pro)에서 parse_fail 0 / loop 0 / empty_response 0 / errors 0 달성. 즉, 현재 api_mem/ + run_api_mem_one_episode.sh 조합은 production-grade robustness를 갖춘 상태.

동시에 분석 카드에서 결론냈듯이, robustness를 완성해도 BinFill SR은 4.3%에서 불변이었다. 이는 인프라 문제가 아닌 task-level alignment 문제임을 역설적으로 증명한다.

gemini/, qwenvl/ 무수정 원칙을 지켜 기존 closed-loop sweep 재현성에 영향 없이 ApiMem 전체 파이프라인을 추가했다. 다음 task(PatternLock)는 이 코드베이스를 그대로 사용하며 추가 구현 작업 없다.

5 Next Steps

✅ 즉시 사용 가능 — PatternLock sweep

코드 변경 없이 task 인자만 바꿔 실행. pro × 2-step이 LoRA 16%보다 ≥30% 달성하면 유의미한 finding.

conda activate robomme sbmr 10 "bash scripts/run_api_mem_one_episode.sh PatternLock gemini-2.5-pro pro 0" \ --gres=gpu:2 -c 28 --mem=400GB --partition=sub --qos=core-on-sub \ -J apimem-PatternLock-pro

만약 추가 모델 실험 시 체크리스트

  • pro 사용 시 detection call(A)과 reasoning call(B) 모두 max_tokens=4096 이상 설정 확인
  • 새 task tag 사용 — save_dir 충돌 방지
  • sweep 종료 후 progress.json episode 수로 abort 여부 확인 (tee exit code 신뢰 불가)
  • flash 계열 new model 시도 시 thinking_config 설정이 무시되는지 테스트 필요
⚠️ gemini-2.5-flash (non-lite) 사용 금지. thinking 비활성화 불가 + truncation 빈발. 선택지는 flash-lite(non-thinking, cheap) vs pro(thinking, 정확, max_tokens=4096) 둘뿐.