RB-Y1 skill planner (Qwen3.5-4B + LoRA) β μ΄λν°
μ΄ λ¦¬ν¬λ LoRA μ΄λν°λ§ λ΄κ³ μμ΅λλ€(100 MB). vLLMμΌλ‘ λ°λ‘ μλΉνλ €λ©΄ λ³ν©λ³Έ napalna/rby1-skill-planner-qwen3.5-4bλ₯Ό μ°κ³ , PEFTλ‘ μ§μ μΉμΌλ €λ©΄ λ² μ΄μ€ Qwen/Qwen3.5-4B μμ μ΄ μ΄λν°λ₯Ό λ‘λνμΈμ.
머리 μΉ΄λ©λΌ μ΄λ―Έμ§ ν μ₯κ³Ό μμ°μ΄ μ§μ(μμ΄/νκ΅μ΄)λ₯Ό λ°μ, μν λ‘λ΄ RB-Y1μ΄ μ€νν μ€ν¬ μνμ€λ₯Ό JSONμΌλ‘ μΆλ ₯ν©λλ€.
μ
λ ₯: "λ°λλ μ£ΌμΈμ" + μ΄λ―Έμ§
μΆλ ₯: approach(banana, right) β pick(banana, right) β lift(right) β handover(right)
1. νμΈνλ λ°©λ²
| νλͺ© | λ΄μ© |
|---|---|
| λ² μ΄μ€ | Qwen/Qwen3.5-4B (Apache-2.0) |
| λ°©μ | LoRA r=16, μΈμ΄ λͺ¨λΈμ q/k/v/o/gate/up/downμλ§ μ μ©, λΉμ νμ λκ²° |
| λ°μ΄ν° | μ€μ νλ μ 7μ₯ β λ°μ ·물체 λΆμ¬λ£κΈ°Β·νμ§μ νλ‘ 63μ₯λ©΄ β μ§μλ¬Έ 5,503κ° μλ μμ± β 1,600κ°λ‘ 1 epoch |
| νμ΅ μκ° | A100 1μ₯, μ½ 60λΆ |
| μΆλ ₯ κ°μ | vLLM ꡬ쑰ν μΆλ ₯(json_schema). μ€ν¬ μ΄λ¦κ³Ό νμ μ€ν€λ§ enumμ΄λΌ μλͺ»λ κ°μ΄ λμ¬ μ μμ |
μ§μλ¬Έμ handover / pick-up / put-on / μ μ§μ / κ±°μ (μλ 물체, λ―Έμ§μ λμ, λͺ¨νΈ, μν) / μν 볡ν©μ 6κ°μ§ μ ν, μμ΄Β·νκ΅μ΄ λ°λ°μ λλ€.
2. νμΈνλ μ ν λΉκ΅
νμ΅μ μ°μ§ μμ νλ μ 3μ₯, 170κ° μ§μλ¬Έ(μ 85 / ν 85). κ°μ λ°©Β·μΉ΄λ©λΌΒ·λ¬Όμ²΄ μΈνΈμ΄λ―λ‘ "μ΄ μμ° νκ²½" κΈ°μ€ μ μμ λλ€.
| λͺ¨λΈ | μμ μΌμΉ | arm μ νλ | μ§μ° (p50) |
|---|---|---|---|
| Qwen3.5-4B, ν둬ννΈλ§ | 140 / 170 (82%) | 0.87 | 1.3 s |
| Qwen3.5-9B, ν둬ννΈλ§ | 153 / 170 (90%) | 0.95 | 2.0 s |
| Gemma 4 12B, ν둬ννΈλ§ | 152 / 170 (89%) | 0.89 | 2.8 s |
| Qwen3.5-4B + LoRA SFT (μ΄ λͺ¨λΈ) | 169 / 170 (99%) | 0.99 | 1.3 s |
| + GRPO | 170 / 170 | 1.00 | 1.2 s (SFTμ μ°¨μ΄λ λ Έμ΄μ¦ μμ€) |
ν둬ννΈλ§μΌλ‘λ handoverΒ·pick-upΒ·μ μ§μ μ κ±°μ λ§μ μ λλ€. νμΈνλμ΄ κ³ μΉ κ²μ κ±°μ νμ, μ μ± μ 물체 무μ, "λμ Έ" κ°μ λ―Έμ§μ λμ, λͺ¨νΈμ± νλ¨, μν λ³΅ν© μ§μμ λλ€.
μ£Όμ: μμ°μ λμ¬ λ¬Όμ²΄λ λ°λμ μμ λ μμ λμΈ μ¬μ§μΌλ‘ νμ΅ λ°μ΄ν°μ λ£μ΄μΌ ν©λλ€. μ μ± μ λ°©ν΄λ¬Όλ‘λ§ λ±μ₯ν 물체λ "νμ μλ€"κ³ νμ΅λ©λλ€(μ¬κ³Όμμ μ€μ λ‘ λ°μ, ν©μ± λ°μ΄ν°λ‘ μμ ).
3. μΆλ ₯ νμ
{
"visible_objects": [{"name": "banana", "nearest_arm": "right"}],
"status": "ok",
"reason": null,
"skills": [
{"skill": "approach", "target": "banana", "arm": "right"},
{"skill": "pick", "target": "banana", "arm": "right"},
{"skill": "lift", "target": null, "arm": "right"},
{"skill": "handover", "target": null, "arm": "right"}
]
}
skill:approach | pick | lift | handover | put_on | ready_posetarget: approach/pickμ 물체 μ΄λ¦(SAM3 ν둬ννΈλ‘ μ¬μ©), put_onμ λͺ©μ μ§, λλ¨Έμ§λ nullarm:left | right. κ°μ νμ λ°°μ΄ μμλλ‘, λ νμ λ³λ ¬ μ€νstatusκ°okκ° μλλ©΄(object_not_visible | unsupported_action | ambiguous | unsafe)skillsλ λΉ λ°°μ΄μ΄λ©° μ€ννμ§ μμ΅λλ€
4. μ€ν λ§€λ΄μΌ
μλ² (GPU λ¨Έμ )
pip install "vllm>=0.27"
vllm serve napalna/rby1-skill-planner-qwen3.5-4b --served-model-name planner-sft --port 8002 \
--dtype bfloat16 --max-model-len 8192 --limit-mm-per-prompt '{"image": 1}' \
--structured-outputs-config '{"backend": "xgrammar"}'
curl http://localhost:8002/health # 200μ΄λ©΄ μ€λΉ μλ£ (κΈ°λ μ½ 3λΆ, VRAM μ½ 20 GB)
μμ λͺ¨λ β νλλ νΈμΆ (λ‘λ΄ λ¦¬ν¬ plan_actions.py)
from plan_actions import plan_actions
plan = plan_actions("λ°λλ μ£ΌμΈμ",
endpoint="http://<server>:8002/v1/chat/completions",
image=head_frame_bgr, # numpy(BGR) / νμΌ κ²½λ‘ / Noneμ΄λ©΄ ZED SHMμμ μλ μΊ‘μ²
strict=True) # μ΄μ μλ΅ μ μμΈ (κ·μΉ plannerλ‘ μ‘°μ©ν λμ΄κ°μ§ μμ)
plan.meta # {'status': 'ok', 'reason': None, 'visible_objects': [...], 'problems': []}
plan.to_json() # νμ λͺ¨λ(TaskQueueManager.load_plan)μ λκΈ°λ νμ
νλλ β νμ λͺ¨λ: plan.to_json()μ skills λ°°μ΄μ TaskQueueManager.load_plan()μ λκΈ°λ©΄ λ©λλ€. νμ λͺ¨λμ΄ μ½λ νλλ skill, target, arm μ
λΏμ
λλ€. plan.meta["status"] != "ok"μ΄κ±°λ problemsκ° μμΌλ©΄ μ€ννμ§ λ§μΈμ.
νμ΄μ¬ μμ΄ HTTPλ‘ μ§μ νΈμΆ
import base64, json, requests
from plan_actions import SYSTEM_PROMPT, PLAN_JSON_SCHEMA
img = base64.b64encode(open("frame.jpg", "rb").read()).decode()
r = requests.post("http://<server>:8002/v1/chat/completions", json={
"model": "planner-sft", "temperature": 0, "max_tokens": 512,
"chat_template_kwargs": {"enable_thinking": False},
"response_format": {"type": "json_schema", "json_schema": {"name": "plan", "schema": PLAN_JSON_SCHEMA, "strict": True}},
"messages": [{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": [{"type": "image_url", "image_url": {"url": "data:image/jpeg;base64," + img}},
{"type": "text", "text": "Instruction: λ°λλ μ£ΌμΈμ"}]}]})
print(json.loads(r.json()["choices"][0]["message"]["content"]))
μμ€ν
ν둬ννΈμ μ€ν€λ§λ λ‘λ΄ λ¦¬ν¬μ plan_actions.pyμ μμΌλ©°, νμ΅ λμ κ°μ ν둬ννΈλ₯Ό μ¨μΌ ν©λλ€.
5. λ¦¬ν¬ κ΅¬μ±
napalna/rby1-skill-planner-qwen3.5-4bβ λ³ν© κ°μ€μΉ (vLLMμΌλ‘ λ°λ‘ μλΉ)napalna/rby1-skill-planner-qwen3.5-4b-loraβ LoRA μ΄λν°λ§ (100 MB, PEFTλ‘ λ² μ΄μ€ μμ λ‘λ)- λ²€μΉλ§ν¬Β·νμ΅ μ½λ: λ‘λ΄ λ¦¬ν¬
arpa_h_demo_robot_side(plan_actions.py,PLANNER.md)