# Quickstart

This guide walks you through the core workflow: **create a model → predict actions → control a robot**.

## 1. Create a Model

```python
from neon.model.neon_vla import NeonConfig, NeonVLA
from neon.model.video_backbone import BackboneConfig

config = NeonConfig(
    backbone=BackboneConfig(
        model_id="Qwen/Qwen2.5-VL-3B-Instruct",  # 3B for fast iteration
        load_in_4bit=True,                          # Quantize for less VRAM
        freeze_backbone=True,                       # Only train action heads
    ),
    control_mode="arms_only",      # 14 DoF arms + grippers
    include_locomotion=True,       # + 3 DoF velocity output
    num_action_steps=16,           # Predict 16 future timesteps
    use_action_chunking=True,      # Smooth temporal predictions
    use_separate_heads=True,       # Separate MLP per joint group
)

model = NeonVLA(config)
print(model)
# NeonVLA(
#   backbone=Qwen/Qwen2.5-VL-3B-Instruct,
#   action_space=G1ActionSpace(mode=arms_only, joints=14, action_dim=17),
#   action_chunking=True (steps=16),
#   trainable=2,345,678 / 3,500,000,000 params
# )
```

!!! note "Backbone loads lazily"
    The 3B backbone isn't downloaded until you call `model.load_backbone()`. This keeps model creation fast.

## 2. Load the Backbone and Predict

```python
from PIL import Image
import numpy as np

# Load the video backbone (downloads from HuggingFace)
model.load_backbone()

# Simulate a camera frame and joint states
camera_frame = Image.new("RGB", (640, 480), color="gray")
joint_states = model.action_space.default_position()  # (17,) zeros + default pose

# Predict!
output = model.predict(
    image=camera_frame,
    instruction="Pick up the red cup",
    proprioception=joint_states,
)

print(output.actions.shape)        # (16, 17) — 16 future steps × 17 DoF
print(output.raw_actions.shape)    # (16, 17) — denormalized to real joint angles
print(output.locomotion)           # (16, 3) — vx, vy, omega per step
```

## 3. Understand the Output

```python
# Actions are in [-1, 1] (normalized)
print(f"Action range: [{output.actions.min():.2f}, {output.actions.max():.2f}]")

# raw_actions are in physical units (radians, m/s)
print(f"First step (radians): {output.raw_actions[0, :5]}")

# Split by joint group
split = model.action_space.split_action(output.raw_actions)
print(f"Left arm:    {split['left_arm'].shape}")    # (16, 7)
print(f"Right arm:   {split['right_arm'].shape}")   # (16, 7)
print(f"Locomotion:  {split['locomotion'].shape}")   # (16, 3)
```

## 4. Run on the Real Robot

```python
from neon.inference.g1_controller import G1Controller, G1Config

# Connect to the G1
controller = G1Controller(G1Config(
    robot_ip="192.168.123.10",
    mode="arms_only",
    max_joint_velocity=2.0,  # Safety limit: rad/s
))
controller.connect()

# Closed-loop control: observe → predict → act → repeat
controller.run_control_loop(
    model=model,
    instruction="Pick up the red cup",
    max_steps=200,     # ~4 seconds at 50 Hz
)

controller.disconnect()
```

## 5. Use as a Strands Agent Tool

```python
from strands import Agent
from neon.tools import neon_tool

agent = Agent(tools=[neon_tool])

# The agent handles everything: model loading, image capture, action execution
agent("Neon, pick up the red cup and place it on the table")
```

---

## What's Next?

- **[Why Video Models](../concepts/why-video.md)** — understand the core design decision
- **[Action Space](../concepts/action-space.md)** — deep dive into the G1's 29 DoF
- **[Training](../guide/training.md)** — train your own Neon model
- **[Audio In/Out](../concepts/audio.md)** — give the robot ears and a voice
