Neon: Video Modeli Zaten Görüyor, Biz Sadece Kolu Bağladık 🧠🦾
Published: March 29, 2026
🎧 First in first, background music.
Üç Paper, Bir Fikir 📝
Bu yazı üç paper'ın hikayesi. Daha doğrusu, üç paper'ın aynı şeyi farklı açılardan söylediğinin hikayesi:
- GR00T N1 (NVIDIA) — Humanoid robotu kontrol etmek için vision-language-action modeli. Dual-system: yavaş düşünen LLM + hızlı hareket eden VLA.
- Cosmos-Transfer (NVIDIA) — Kameradan motor komutuna giderken aradaki köprü olarak video foundation model kullan.
- Strands Agents — "Robot bir tool'dur." Agent loop'u = robot loop'u.
Üçünü okuyunca kafamda bir şey tıkladı. Ve Neon doğdu.
Peki Neon Ne Yapıyor? 🤖
Bir çocuk masadan yuvarlanan topu görür ve elini uzatır. Fotoğrafa bakmıyor — hareketi görüyor. Yayı, ivmeyi, kenardan ayrılma anını. Geleceği zamanın akışından tahmin ediyor.
Bugünkü her robot buna kör. State-of-the-art VLA modelleri dünyaya donmuş anlık görüntülerden bakıyor. Nesnelerin nerede olduğunu görüyorlar ama nereye gittiğini görmüyorlar.
Neon'un fikri tek cümle:
Video foundation modelleri zaten hareketi anlıyor — biz sadece onları robot vücuduna bağlıyoruz.
Qwen2.5-Omni ve Cosmos-Reason2 gibi modeller milyonlarca saat video izlemiş. Bardağın itilince düştüğünü, kapının menteşede döndüğünü, elin kavramadan önce uzandığını öğrenmiş. Bu temporal anlayış — fizik, dinamik, sebep-sonuç — tam olarak bir robotun ihtiyacı olan şey. Pre-trained olarak orada duruyor.
Biz ne yapıyoruz? 7 milyar parametrelik video modelini alıyoruz, tamamen donduruyoruz, üstüne sadece 6 milyon parametrelik minik bir action decoder eğitiyoruz — toplam modelin %0.08'i — ve bu decoder, video modelinin zengin temporal anlayışını 29 eklem komutu × 16 timestep'e çeviriyor.
Video modeli görüyor. Decoder hareket ediyor.
graph LR
CAM["📹 Kamera"] --> VB["Video Backbone<br/><b>7B frozen</b><br/>Qwen2.5-Omni / Cosmos"]
MIC["🎤 Ses"] --> VB
PROP["🦾 Eklemler"] --> PE["Proprio Encoder"]
LIDAR["📡 LiDAR"] --> LE["PointCloud Encoder"]
EEF["🤲 Gripper"] --> EE["EEF Encoder"]
VB --> FUS["Feature Fusion"]
PE --> FUS
LE --> FUS
EE --> FUS
FUS --> AH["Action Heads<br/><b>~6M trainable</b>"]
AH --> ACT["🤖 29 DoF × 16 adım"]
style VB fill:#0097a7,color:#fff,stroke:#0097a7
style AH fill:#e65100,color:#fff,stroke:#e65100
style FUS fill:#333,color:#fff
Aradaki Sihir: Feature → Joint Açısı
Backbone bir video frame'ini alıyor ve bir feature vector döndürüyor. Bu vector "masada kırmızı bir küp var, el yaklaşıyor, küp hafif sağa kayık" gibi bir şeyi 3584 boyutlu bir sayı dizisine sıkıştırmış halde içeriyor.
Sonra action head bu feature'ı alıp motor komutuna çeviriyor. Yani:
Bu "feature → bu joint açısı" mapping'i. Tüm Neon bu.
Altı Duyu, Bir Robot 👁️👂🤚📡🗣️🦾
Neon tek kameralı değil. Altı modalite alıyor:
- Kamera — video frame'leri (backbone hallediyor)
- Ses — "dur" dediğinde duracak kadar (backbone hallediyor)
- Dil — "kırmızı küpü al" (backbone hallediyor)
- Proprioception — eklem açıları, hızlar (küçük MLP encoder, ~256 boyutlu çıkış)
- LiDAR — 3D nokta bulutu (PointNet-style encoder, ~256 boyutlu çıkış)
- End-effector — gripper pozisyonu ve durumu (küçük MLP, ~128 boyutlu çıkış)
İlk üçünü backbone zaten anlıyor — video modeli ya, ses ve text native. Son üçü için minik encoder'lar var: toplam ~650K parametre. Yani backbone'un bilmediği tek şeyler robotun kendi vücudu. Onu da öğretiyoruz.
Sonra hepsi bir fusion layer'da birleşiyor ve üç ayrı action head'e gidiyor:
- UpperBody: 14 serbestlik derecesi (kollar), 16 adımlık trajectory
- Locomotion: 3 DoF (ileri, yana, dönüş), 16 adım
- Head/Torso: 3 DoF, 16 adım
Her head kendi işine bakıyor. Kol kafayı bilmiyor, kafa bacağı bilmiyor. Ama hepsi aynı fused representation'dan besleniyor. Bir nevi aynı beyinden farklı sinir yolları.
Kod Gerçeği
neon/model/action_heads.py — G1ActionHead sınıfı her grup için ayrı ActionChunkingHead veya MLPDecoder kullanıyor. U-Net skip connection'lar ve learnable residual scale'ler var. soft_cap(x, cap) = cap * tanh(x/cap) ile hard Tanh yerine yumuşak sınırlama.
Eğitim: 4 GPU-Saat. Tek Kart. Ciddiyim. ⚡
Karşılaştırma:
| Model | Eğitilen Parametre | GPU-Saat |
|---|---|---|
| RT-2 | 55B (hepsi) | Binlerce |
| OpenVLA | 7B (hepsi) | Yüzlerce |
| GR00T N1.5 | ~3B (hepsi) | Yüzlerce |
| Neon | ~6M (%0.08) | ~4 |
Dört saat. Bir L40S kartı. Mixed precision FP16/BF16. Batch size 32.
Neon'u eğitmek, Netflix'te bir film izlemekten kısa sürüyor. (Tamam belki The Irishman değil ama normal bir film.)
Peki nasıl? Çünkü 7 milyar parametreye gradyan hesaplamıyorsun. Sadece 6 milyona hesaplıyorsun. Backprop'un %99.92'sini kestik. Geri kalan %0.08 ile iş görüyoruz.
Koddan Kanıt
neon/training/train.py:95-97 — trainable = sum(p.numel() for p in self.model.parameters() if p.requires_grad) ile kaç parametre eğitildiğini logluyoruz. Backbone freeze=True olduğunda requires_grad=False.
İki Katlı Kontrol: Beyin ve Refleks 🧠⚡
Tamam, Neon bir policy. Ama policy tek başına yetmiyor. Robota "masadaki üç küpü renge göre sırala" dediğinizde, policy bu kadar uzun vadeli planlama yapamaz. O sadece "şu ana bakarak sonraki 0.7 saniyenin hareketini üret" der.
Bu yüzden bir iki katlı kontrol hiyerarşisi var:
Kat 1 — Stratejik (LLM Agent, 0.5–5 Hz): Strands Agent. Doğal dili anlıyor, görevi alt görevlere bölüyor, hata olursa yeniden planlıyor. Saniyede yarım ile beş kez düşünüyor. İnsan hızında.
Kat 2 — Taktik (VLA/Neon, 10–50 Hz):
Kameraya bakıyor, 16 adımlık hareket üretiyor, motorlara gönderiyor. ActionQueue ile RTC-aware blending yapıyor. Refleks hızında.
Bu ayrım neden zorunlu? Çünkü aynı model ikisini de yapamaz.
LLM'i 50 Hz'de çalıştırmak için <20ms inference lazım — 7B+ modelde mümkün değil. VLA'yı 1 Hz'de çalıştırıp "şimdi ne yapmalı" dedirtmek için yeterli dünya modeli yok — policy sadece anlık sensör verisinden hareket üretir.
İkisinin birleşimi her şeyi kapsıyor:
Sen: "Kırmızı küpü al, mavi kutunun içine koy"
│
▼
┌─────────────┐
│ LLM Agent │ "Hmm, önce kırmızı küpü bul..."
│ (1 Hz) │ "...şimdi policy'e 'küpü al' de"
└──────┬───────┘
│ instruction
▼
┌──────────────┐
│ Neon Policy │ *16 frame'lik hareket üret*
│ (50 Hz ctrl) │ *ActionQueue ile blend et*
│ │ *motorlara gönder*
└──────┬───────┘
│ status: "aldım"
▼
┌─────────────┐
│ LLM Agent │ "Güzel, şimdi mavi kutuyu bul..."
└──────────────┘
Agent düşünüyor, Neon yapıyor. Agent yavaş ama akıllı, Neon hızlı ama dar. Birlikte çalışıyorlar.
Dört Satır, Bir Humanoid 🪄
Ve en güzeli: bu pattern Strands Agents SDK ile dört satır:
from strands_robots import Robot
from strands import Agent
robot = Robot("unitree_g1")
agent = Agent(tools=[robot])
agent("Kırmızı küpü al, mavi kutuya koy")
Robot burada bir tool. LLM'in gözünde, robotu çağırmak ile bir API çağırmak arasında fark yok. robot("al şunu") = weather_api("istanbul"). Aynı interface. Aynı loop. Aynı error handling.
Bu gözlem bizi üç paper'ın en felsefi olan kısmına getiriyor.
Loop Hepsini Yönetir 🔄
Bir chatbot'un döngüsünü yazın:
while not done:
response = model(context, tools)
for tool_call in response:
result = execute(tool_call)
context.append(result)
Bir robotun döngüsünü yazın:
while not done:
obs = sense()
action = policy(obs, instruction)
execute(action)
obs = sense() # tekrar
Dikkat ettiniz mi? Aynı döngü. Sense → Think → Act → Repeat.
Chatbot'ta sense() = kullanıcı mesajı. Robot'ta sense() = kamera + proprioception. Chatbot'ta execute() = tool çağır. Robot'ta execute() = motor komutu gönder.
Strands Agents bunu formalize ediyor: her şey bir tool. Shell komutu bir tool. GitHub API bir tool. Ve bir robot kolu da bir tool. Hepsi aynı agentic loop'un içinde.
Bu yüzden Neon'u Strands'e entegre etmek bu kadar doğal hissettirdi. Çünkü zaten aynı pattern.
graph TB
subgraph "Agent Loop (her ikisi)"
A[Observe] --> B[Think/Infer]
B --> C[Act/Execute]
C --> D[Feedback]
D --> A
end
subgraph "Chatbot"
E["User message"] -.-> A
F["Tool calls"] -.-> C
end
subgraph "Robot"
G["Camera + Joints"] -.-> A
H["Motor commands"] -.-> C
end
style A fill:#2ea44f,stroke:#1b7735,color:#fff
style B fill:#0969da,stroke:#044289,color:#fff
style C fill:#8250df,stroke:#5a32a3,color:#fff
style D fill:#bf8700,stroke:#875e00,color:#fff
Data Soup: Herkesin Verisi, Bir Tencere 🍲
Neon'un eğitim verisi tek bir kaynaktan gelmiyor. DataSoupDataset farklı embodiment'ları, farklı formatları, farklı robotları tek bir tencerede karıştırıyor:
| Kaynak | Robot | DoF | Format |
|---|---|---|---|
| LeRobot (HuggingFace) | SO-100, Koch, Franka | 7-8 | HF datasets |
| AgiBotWorld | GO-1 | 16 | H5 + video |
| GR00T teleop | Çeşitli | Çeşitli | HF datasets |
| G1 teleop | Unitree G1 | 43 | S3 parquet |
| Cosmos DreamGen | — | Relative EE | Video |
| Voice commands | — | — | Text-only |
| Stereo4D | — | — | Stereo pairs |
ActionMapper her kaynağın action space'ini G1'in 29 (veya 43) DoF'una map ediyor. SO-100'ün 7 DoF'u → G1'in kollarına. Franka'nın 8 DoF'u → G1'in kollarına. GO-1'in 16 DoF'u → tam eşleme.
Bu cross-embodiment transfer öğrenmenin temelidir. Farklı robotlardan gelen veri, aynı backbone feature space'inde buluşuyor.
Simülasyondan Gerçeğe: MuJoCo + Newton 🎮
Gerçek robota erişimin yoksa? NeonSimEnv tam burada devreye giriyor:
from neon.sim import NeonSimEnv, NeonSimConfig
config = NeonSimConfig(backend="mujoco", num_envs=1)
env = NeonSimEnv(config)
obs = env.reset(task="pick_and_place")
for step in range(1000):
action = policy.predict(obs)
obs, reward, done, info = env.step(action)
MuJoCo ve Newton backend'leri var. LiDAR raycast'ten kamera render'a, proprioception'dan EEF state'e kadar her şey simüle ediliyor. NeonLeRobotWriter ile simülasyonda toplanan veri doğrudan eğitime gidebiliyor.
Sim-to-Real Pipeline
- MuJoCo'da veri topla →
NeonLeRobotWriterile kaydet DataSoup'a ekle → gerçek veriyle karıştır- Eğit → 4 GPU-saat
- Gerçek robotta deploy et
Güvenlik: Robot Duvara Çarpmaz 🛡️
JointSafetyMonitor — GR00T-WBC'den port edilmiş:
- Velocity check: Her eklemin hız limiti aşılırsa → kill veya freeze
- Startup ramp: İlk 2 saniye lineer rampa — robot yavaşça başlıyor
- Joint limits: Her eklemin min/max açısı
G1ActionSpace'de tanımlı soft_cap: Action head'lerin çıkışındacap * tanh(x/cap)— sert sınır yerine yumuşak geçiş
# ❌ YANLIŞ — Robot duvara çarpar
if not model_loaded:
logger.warning("No model")
return []
# ✅ DOĞRU — Robot durur
if not model_loaded:
raise ValueError("No model loaded — cannot generate safe actions")
Strands Robots PR #56'dan öğrendiğimiz ders burada da geçerli: robotik kodda warn-and-continue yapma.
Benchmark Henüz Yok. Ama Mimari Sağlam. 📊
Dürüst olalım: Neon henüz RT-2 veya GR00T N1.5 ile karşılaştırılabilecek benchmark sonuçları yayınlamadı. Bunları yazmam lazım çünkü ben "önce yap, sonra konuş" adamıyım.
Ama mimari kararlar sağlam temellere dayanıyor:
- Frozen backbone → Cosmos-Transfer paper'ından. Pre-trained temporal features'ı bozma.
- Action chunking → ACT paper'ından. Tek bir action yerine 16 adımlık trajectory üret.
- Dual-system control → GR00T N1 paper'ından. LLM + VLA ayrımı.
- Cross-embodiment data → Open X-Embodiment'tan. Farklı robotların verisi birleşir.
- Parameter Golf → Tiny adapters büyük modellerin üstünde. LoRA'nın action space versiyonu.
Benchmark'lar gelecek. Ama gelene kadar claim'leri küçük tutacağız.
Neden Bu Önemli? 🌍
Üç paper'ın özeti aslında üç cümle:
- Agent loop = Robot loop. Aynı sense-think-act döngüsü. Strands bunu formalize etti.
- Video modelleri zaten fizik anlıyor. Onları sıfırdan eğitme, sadece kolu bağla. Neon bunu yapıyor.
- Küçük adapter + büyük frozen model = verimli transfer. 6M parametre, 4 GPU-saat. Demokratik robotik.
Bir yıl önce, bir humanoid robotu kontrol etmek için ya milyonlarca dolarlık compute'a ya da NVIDIA'nın internal cluster'ına ihtiyacın vardı. Bugün bir L40S kartıyla, açık kaynak bir modelle, 4 saatte kendi VLA'nı eğitebilirsin.
Bu demokratikleşme. Bu açık kaynak. Bu Neon.
Sırada Ne Var? 🔮
timeline
title Neon Roadmap
Mimari : Video backbone (Qwen2.5-Omni/Cosmos)
: 6 modalite fusion
: Action chunking heads
Veri : DataSoup cross-embodiment
: Sim-to-real pipeline
: AgiBotWorld + GR00T teleop
Eğitim : 4 GPU-saat training
: Mixed precision FP16/BF16
: Distillation (teacher→student)
Deploy : Strands Robots entegrasyonu
: Jetson Orin edge inference
: Real G1 hardware controller
İleri : Benchmark suite
: Whole-body locomotion
: Multi-robot coordination
Başla 🚀
from neon import NeonVLA, NeonConfig
# Modeli yükle
config = NeonConfig(backbone_model_id="Qwen/Qwen2.5-Omni-7B")
model = NeonVLA(config)
# Veya Strands ile
from strands_robots import Robot
from strands import Agent
robot = Robot("unitree_g1")
agent = Agent(tools=[robot])
agent("Kırmızı küpü al")
Bu yazıda çok teknik detaya girdik. Ama özü basit:
- ✅ Video modeli zaten görüyor — yeniden eğitme
- ✅ Küçük adapter yeter — 6M parametre, 4 saat
- ✅ Agent loop = Robot loop — aynı döngü, farklı domain
- ✅ Açık kaynak — MIT lisansı, herkes katkı verebilir
- ✅ Sim-to-real — MuJoCo'da dene, gerçekte çalıştır
"Sokak Sanatçısı" yazısında demiştim ki: sadece sevdiğin işi yapmaya devam et. 🤘
Robotlara görmesini, duymasını, dokunmasını öğretmek — bu da o sevdiğim iş.
Afiyet olsun! 🍽️
Çağatay.
Kaynaklar: