Skip to the lesson
FixTheRobot Home / VLA / Phase 1 / 1.4

VLA · Phase 1 · Introduction to VLA

1.4 — VLA Architecture and the Perception-to-Action Pipeline

The main components, from observation encoding through to the action output.

  • Roadmap phase 1
  • Introduction to VLA
  • About 16 minutes to read

Follow information through a VLA in order: image to patch tokens, tokens joined with the instruction, the backbone, and the action output. Each stage has a small NumPy version so you can see the shapes.

Claims about papers, models and textbooks carry numbered references, listed with what each one is cited for in the References. Code marked “Toy model” is our own illustration, not the cited system.

Learning objectives

After this lesson, you should be able to:

  • Name the main stages of a VLA pipeline.
  • Compute how many patch tokens a 224 by 224 image gives with 16 by 16 patches.
  • Explain what the projector does.
  • Decode action tokens to a bounded robot command.
  1. Camera images are cut into patches and encoded
  2. A projector maps features into the language model’s space
  3. The instruction is tokenised and joins the sequence
  4. The backbone processes the sequence
  5. The action output is decoded into a robot command
A typical VLA pipeline. Details differ between models.

The one-sentence idea

A VLA is a chain: encode the image, project it, join it with the instruction, run the backbone, decode an action.

Every stage has a cost in time and in data.

The pipeline in one picture

Different VLAs differ in detail, but many follow the same chain: an image encoder turns camera frames into feature vectors, a projector maps them into the input space of a language-model backbone, the instruction is tokenised and joins the sequence, and an action output turns the backbone’s result into robot commands. OpenVLA states its structure in these terms: a visual encoder that turns the image into patch embeddings, a projector that maps them into the language model’s input space, and the language-model backbone.[1] Its encoder fuses pretrained DINOv2 and SigLIP features, and its backbone is a Llama 2 language model.[1] π0 uses a different output stage: a pretrained vision-language model paired with a separate set of weights, the action expert, that generates action chunks with flow matching.[2]

Block diagrams. Read each diagram from top to bottom. The left column of the first one is the image path, the right column is the instruction path, and they meet in the language-model backbone.

OpenVLA block diagram A camera image goes through a visual encoder and then a projector. The instruction is turned into text tokens. Both join one sequence in a Llama 2 language-model backbone, which writes action tokens. These are decoded to continuous values and scaled to the robot’s units, giving the robot command. Camera imagewhat the robot sees Instruction“pick up the red block” Visual encoderDINOv2 + SigLIP featuresimage → patch embeddings Tokenizerwords → text tokens(vectors, one per token) Projectorinto the language model’s space Language-model backbone (Llama 2, 7 billion parameters)image tokens and text tokens are one sequenceit predicts the next token, one after another Action tokenseach action number is one of 256 bins Decode and de-normalisebin → continuous value → robot’s physical units robot command
OpenVLA. Visual encoder, projector, language-model backbone, then action tokens decoded to a command.[1] Blue marks the part that sees, orange the language model that decides, dashed grey the small conversion steps. Redrawn by us from the paper’s description.
π0 block diagram Camera images and an instruction go to a pretrained vision-language model. A separate action expert, with its own weights, works together with it and generates a chunk of actions by flow matching. Camera imageswhat the robot sees Instruction“pick up the red block” Pretrained vision-language modelunderstands the images and the instruction its information is used by… Action expert: a separate set of weightsgenerates the actions by flow matching(start from noise, refine in a few steps) Action chunka short sequence of actions at once
π0. A pretrained vision-language model paired with a separate action expert that generates action chunks with flow matching.[2] Compare with OpenVLA: here a separate component generates the actions. Redrawn by us from the paper’s description; the paper shows more detail.

The rest of the lesson takes the stages in order and runs a small version of each in NumPy. The sizes are smaller than in any real model and the weights are random, so the examples show how shapes and information flow, not what a trained model does.

Stage 1: images become patch tokens

Vision Transformers apply a standard Transformer directly to a sequence of image patches.[3] The title of that paper, “An image is worth 16x16 words”, names the patch size it uses. Cutting an image into patches is a reshape:

import numpy as np

# A fake colour image, 224 pixels tall and 224 wide, with 3 colour values (red, green, blue) per pixel.
# np.arange fills it with 0, 1, 2, ... so every number is different and easy to follow.
image = np.arange(224 * 224 * 3, dtype=np.float32).reshape(224, 224, 3)

patch_size = 16                  # each patch is a square of 16 x 16 pixels
grid = 224 // patch_size         # 224 / 16 = 14 patches along each side

# Step 1: split the rows into 14 groups of 16 and the columns into 14 groups of 16.
# The shape becomes (14, 16, 14, 16, 3) = (patch row, row inside patch, patch column, column inside patch, colour).
blocks = image.reshape(grid, patch_size, grid, patch_size, 3)

# Step 2: move the two "which patch" axes to the front.
# The shape becomes (14, 14, 16, 16, 3): the first two numbers now pick one patch.
blocks = blocks.transpose(0, 2, 1, 3, 4)

# Step 3: flatten every patch into one list of numbers (16 x 16 x 3 = 768),
# and list all 14 x 14 = 196 patches one after another.
patches = blocks.reshape(grid * grid, patch_size * patch_size * 3)

print("image:", image.shape)
print("patches:", patches.shape, "=", grid, "x", grid, "patches, each", patch_size * patch_size * 3, "numbers")

# Check: the first patch should be exactly the top-left 16 x 16 corner of the image.
top_left_corner = image[:patch_size, :patch_size].reshape(-1)
print("first patch equals the top-left 16x16 block:", bool(np.array_equal(patches[0], top_left_corner)))

Output:

image: (224, 224, 3)
patches: (196, 768) = 14 x 14 patches, each 768 numbers
first patch equals the top-left 16x16 block: True

Each flattened patch is then mapped to a feature vector by learned weights, and the Transformer processes the sequence of those vectors. The Transformer itself is the architecture introduced in “Attention is all you need”, which is based entirely on attention, without recurrence or convolutions.[4]

Stage 2: join the image and the instruction

The language model expects vectors of its own width, so a projector maps the image features to that width. The instruction is tokenised and embedded to the same width, and the two are placed in one sequence. In the example below the weights are random, which is enough to show the shapes:

import numpy as np
rng = np.random.default_rng(0)       # fixed seed, so the "random" numbers are the same every run

# --- The image side ---------------------------------------------------------------
n_patches = 196                      # from the previous example: 14 x 14 patches
vision_dim = 64                      # width of each feature vector the image encoder produces
lm_dim = 128                         # width of the vectors the language model works with

# Pretend output of the image encoder: one feature vector per patch (random numbers here).
image_features = rng.normal(size=(n_patches, vision_dim))

# The projector is a matrix that converts vectors of width 64 into vectors of width 128.
# (Real projectors are trained; this one is random, so it only shows the shapes.)
projector = rng.normal(size=(vision_dim, lm_dim)) / np.sqrt(vision_dim)
image_tokens = image_features @ projector            # (196, 64) x (64, 128) -> (196, 128)

# --- The instruction side ---------------------------------------------------------
# A tiny vocabulary: each word gets a number, and each number gets a vector of width 128.
vocab = {"pick": 0, "up": 1, "the": 2, "red": 3, "block": 4}
embedding_table = rng.normal(size=(len(vocab), lm_dim))

words = "pick up the red block".split()               # ["pick", "up", "the", "red", "block"]
word_numbers = [vocab[word] for word in words]        # [0, 1, 2, 3, 4]
text_tokens = embedding_table[word_numbers]           # look up one vector per word -> (5, 128)

# --- Join them ----------------------------------------------------------------------
# Both now have width 128, so they can be stacked into one sequence: image tokens first, then text.
sequence = np.concatenate([image_tokens, text_tokens])

print("image tokens:", image_tokens.shape)
print("text tokens:", text_tokens.shape)
print("sequence into the backbone:", sequence.shape)

Output:

image tokens: (196, 128)
text tokens: (5, 128)
sequence into the backbone: (201, 128)

Toy model. A real projector, vocabulary and sizes are different (OpenVLA’s backbone is a 7-billion-parameter Llama 2). Random weights make this a shape demonstration only.

In this layout most of the sequence is image: 196 of 201 positions. In a layout like this, the number of image tokens largely sets the sequence length the backbone has to process.

Stage 3: the backbone’s result becomes an action

With the token approach, the backbone predicts action tokens one after another, as it would words. OpenVLA splits each action dimension into 256 bins and trains with next-token prediction on the action tokens, and at run time the predicted tokens are decoded to continuous values and de-normalised to the robot’s physical scale.[1][1] The example below takes a made-up set of logits for seven action dimensions, picks the most likely bin in each, and decodes it, using the same bin scheme as lesson 1.2:

import numpy as np
rng = np.random.default_rng(1)       # fixed seed, so the "random" numbers are the same every run

n_bins = 256                         # each action number is described by one of 256 possible bins

# The robot command has 7 numbers: x, y, z movement, 3 rotations, and the gripper (open to closed).
# These are the smallest and largest allowed values for each of the 7 (made-up limits).
low  = np.array([-0.05, -0.05, -0.05, -0.5, -0.5, -0.5, 0.0])
high = np.array([ 0.05,  0.05,  0.05,  0.5,  0.5,  0.5, 1.0])

# Pretend model output: for each of the 7 numbers, a score for each of the 256 bins (random here).
scores = rng.normal(size=(7, n_bins))

# Step 1: for each of the 7 numbers, pick the bin with the highest score.
# These bin numbers (0 to 255) are the "action tokens" the model writes.
tokens = scores.argmax(axis=1)

# Step 2: turn each bin number back into a real value.
# Bin k covers a small slice of [low, high]; we use the middle of that slice.
fraction = (tokens + 0.5) / n_bins           # a number between 0 and 1: how far along the range
action = low + fraction * (high - low)       # scale to the real range of each number

print("tokens:", tokens.tolist())
print("action:", np.round(action, 3).tolist())

# Check: every decoded value must lie between its low and high limit.
inside = np.all((action >= low) & (action <= high))
print("every value inside its limits:", bool(inside))

Output:

tokens: [183, 17, 115, 92, 156, 128, 28]
action: [0.022, -0.043, -0.005, -0.139, 0.111, 0.002, 0.111]
every value inside its limits: True

Toy model. Random logits give a meaningless action. The example shows decoding, not prediction.

Notice the guarantee the decoding step gives for free: every bin lies inside the limits, so the decoded command is always in range. A model that generates continuous values instead, like π0’s action expert, has to produce valid values by training, and a safe system checks them anyway.

Where the time and the data go

Each stage costs time, and the loop in lesson 1.1 does not wait. The RT-1 system, an earlier robot Transformer, combined a FiLM-conditioned EfficientNet, a TokenLearner and a Transformer, and ran at 3 Hz with about 35 million parameters.[5][5] OpenVLA, at 7 billion parameters,[1] is far larger. Sizes this different show that “VLA” covers a family of designs, and model speed is one reason the execution method (lesson 1.5) matters.

Common misconceptions

Misconception 1 “All VLAs have the same architecture.”

Correction OpenVLA writes actions as tokens; π0 uses a separate action expert with flow matching; RT-1 used a different combination of components.

Misconception 2 “The language model sees the picture directly.”

Correction It sees feature vectors that an image encoder produced and a projector mapped into its input space.

Misconception 3 “The decoded action is always right if it is in range.”

Correction In range only means valid. Whether it is correct depends on training.

Engineering takeaways

  1. Patches, then projection, then a sequence with the instruction, then the backbone, then an action output.
  2. Image tokens dominate the sequence length.
  3. Token outputs are decoded to bounded values.
  4. Architectures differ between models.

Follow the shapes and you can follow the model.

Knowledge check

Try each question first, then reveal the explanation.

Question 1

How many 16 by 16 patches does a 224 by 224 image give?

Explanation

196. 224 / 16 = 14, and 14 × 14 = 196.

Question 2

What does the projector do?

Explanation

It maps image features to the width and space the language model expects.

Question 3

Why is decoding action bins always in range?

Explanation

Every bin lies between the limits, so its centre does too.

References

Numbered notes in the text, like [1], point here. Each entry says what it is cited for and how that was checked. Papers and books can be revised or misread, so follow the links before relying on a detail. Code marked “Toy model” is our own illustration of an idea, not the code of the cited system.

  1. Kim, M. J., et al. (2024). OpenVLA: An open-source vision-language-action model. Conference on Robot Learning (CoRL) 2024. arXiv:2406.09246

    Cited for:

    • The model has three parts: a visual encoder that turns the image into patch embeddings, a projector that maps them into the language model’s input space, and the language-model backbone. Checked against the paper, via summaries of its text.
    • A 7-billion-parameter open-source VLA trained on 970,000 real-world robot demonstrations, built on a Llama 2 language model with a visual encoder that fuses pretrained DINOv2 and SigLIP features. Checked against the paper’s abstract.
    • Each action dimension is split into 256 bins between the 1st and 99th quantile of the training actions; the 256 bins are written over the 256 least-used tokens of the Llama tokenizer, and the model is trained with next-token prediction on the action tokens. Checked against the paper, via summaries of its text.
    • At run time the predicted action tokens are decoded to continuous values and de-normalised to the robot’s physical scale. Checked against the paper, via summaries of its text.
  2. Black, K., et al. (2024). π0: A vision-language-action flow model for general robot control. arXiv:2410.24164

    Cited for:

    • Pairs the pretrained vision-language model with a separate set of weights for the action outputs (the “action expert”) that generates action chunks with flow matching. Checked against the paper, via summaries of its text.
  3. Dosovitskiy, A., et al. (2021). An image is worth 16x16 words: Transformers for image recognition at scale. International Conference on Learning Representations (ICLR) 2021. arXiv:2010.11929

    Cited for:

    • Applies a standard Transformer directly to a sequence of image patches. Checked against the paper’s abstract.
  4. Vaswani, A., et al. (2017). Attention is all you need. Advances in Neural Information Processing Systems 30 (NIPS 2017). arXiv:1706.03762

    Cited for:

    • Introduces the Transformer, an architecture based entirely on attention, without recurrence or convolutions. Checked against the paper’s abstract.
  5. Brohan, A., et al. (2022). RT-1: Robotics Transformer for real-world control at scale. arXiv:2212.06817

    Cited for:

    • Combines a FiLM-conditioned EfficientNet, a TokenLearner and a Transformer. Checked against the authors’ own blog post.
    • A model of about 35 million parameters that runs at 3 Hz. Checked against the authors’ own blog post.

What’s next

Next, how the robot executes what the model outputs: open loop or closed loop.

Next lesson

1.5 — Open-loop vs. closed-loop robot execution

Executing a plan blindly versus re-observing and correcting while the robot moves.

Back to the VLA roadmap