Skip to content
AI360Xpert
Beta

Transformers in RL

A transformer policy treats its own past as a memory it can reread. At each step it looks back over recent states, actions and goals, and puts the most weight on the moments that matter now.

A transformer policy reads three past trajectory tokens with causal attention weights 0.35, 0.24 and 0.41, then blends them into the next action.
A transformer policy reads three past trajectory tokens with causal attention weights 0.35, 0.24 and 0.41, then blends them into the next action.

Why Does This Exist?

Some tasks punish forgetfulness. You pick up a key at step 10, wander corridors for two hundred steps, then face a locked door. The right action now depends on one observation from minutes ago.

Recurrent policies handle this with a single hidden vector hth_t updated every step as ht=f(ht−1,st)h_t = f(h_{t-1}, s_t), where hth_t is the memory and sts_t is the current state. That vector has a fixed size, so it must squeeze the whole past into a few hundred numbers. Gradients also shrink as they travel back through hundreds of updates, so the key pickup stops moving the weights. In practice the agent remembers the last room and forgets the key.

Transformers replace that bottleneck with direct lookup. The policy keeps the last KK tokens around (past states, actions and goal signals) and attends to all of them at once. Step 210 can pull step 10 straight in with one learned weight instead of waiting for a signal to survive two hundred squashes. That is the whole idea: attention over trajectories used as episodic memory.

This page teaches that memory mechanism: how the lookup works, why it beats recurrence on long horizons, how causal masking keeps it honest, and how goal conditioning steers it. The full return-conditioned sequence model gets its own treatment in Decision Transformers and Trajectory Transformer, which is the natural sequel. For the neural policy background, see Deep Reinforcement Learning; for the bootstrapping alternative this sidesteps, see Temporal Difference Learning.

Think of It Like This

The Stenographer and the Note Scribbler

Two clerks sit in on a long trial. The scribbler compresses each day into one paragraph of notes. The stenographer keeps every word on record.

On day 40 the judge asks what the witness said on day 2. The scribbler flips through forty paragraphs and guesses, because the detail was summarized away weeks ago. The stenographer searches the transcript, rereads the exact lines, and quotes them. A query (today's question) is matched against keys (past lines), and the best-matching lines get the most weight.

A transformer policy is the stenographer and a recurrent policy is the scribbler. Attention is the search step: reread everything, weight what matters, answer from the exact record.

Where the analogy stops: a transcript grows forever, and rereading all of it costs work that grows with the square of its length. The stenographer charges by the page. Real agents cap the window at KK tokens and pay O(K2)O(K^2) attention cost, so memory length is a budget choice, not free omniscience.

How It Actually Works

From one hidden vector to direct lookup

A recurrent policy compresses: new observation in, old memory overwritten, one vector out. The path from step t−200t - 200 to step tt runs through two hundred applications of the same update, and each one can dilute or overwrite the trace.

A transformer policy keeps the receipts. It stores embeddings of the last KK tokens and computes scaled dot-product attention at every step:

Attention(Q,K,V)=softmax(QKTdk)V\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) V

Here QQ (queries) is what the current step is looking for, KK (keys) is what each past token offers, VV (values) is the content each past token contributes, and dkd_k is the key dimension used to keep the scores at unit scale. The output is a weighted blend of past contents, and the weights are learned. The path from any past token to the present is one direct connection, which is why a two-hundred-step dependency costs the model nothing special to reach.

The online variant you will meet by name is GTrXL. It wraps the transformer with gating on the residual connections (borrowed from recurrent gates) plus segment-level recurrence and relative positions, so the memory reaches past the current window and training stays steady where plain transformers in RL loops blew up. Think of it as the version built to survive acting and learning at the same time.

The price is compute. Attention compares every token with every other token, so a window of KK tokens costs O(K2)O(K^2) work per step. Recurrence costs O(1)O(1) per step but forgets. Long memory or cheap steps: pick one.

Causal masking and goal conditioning in one sketch

Tokens are ordered in time, and the model must not peek. A causal mask MM is a lower-triangular matrix of ones and zeros that blanks every future position: token ii may read token jj only when j≤ij \le i. In code the masked scores become −∞-\infty before the softmax wherever Mij=0M_{ij} = 0, so future tokens get exactly zero weight.

Goal conditioning steers the lookup. Instead of asking "what action is good here," the policy is handed a desired remaining return R^t\hat{R}_t (the reward still to collect this episode) alongside the state sts_t, and predicts the action that fits both. After the environment pays reward rtr_t, the target ticks down as R^t+1=R^t−rt\hat{R}_{t+1} = \hat{R}_t - r_t. The full version of this trick, with trajectory token layouts and training losses, lives on the Decision Transformers and Trajectory Transformer page, so this page stops at the sketch.

Worked example: three attention weights

Say the agent stands in a hallway. Its query vector is q=[1.0,0.5]q = [1.0, 0.5]. Three past tokens offer keys: k1=[1.0,0.0]k_1 = [1.0, 0.0] (picked up the key two rooms back), k2=[0.0,1.0]k_2 = [0.0, 1.0] (empty corridor), k3=[1.0,0.5]k_3 = [1.0, 0.5] (the turn just taken). Key dimension dk=2d_k = 2, so the scale is 2≈1.4142\sqrt{2} \approx 1.4142.

Step 1, raw scores as dot products:

s1=1.0×1.0+0.5×0.0=1.0s_1 = 1.0 \times 1.0 + 0.5 \times 0.0 = 1.0 s2=1.0×0.0+0.5×1.0=0.5s_2 = 1.0 \times 0.0 + 0.5 \times 1.0 = 0.5 s3=1.0×1.0+0.5×0.5=1.25s_3 = 1.0 \times 1.0 + 0.5 \times 0.5 = 1.25

Step 2, scale by dk\sqrt{d_k}:

[0.7071, 0.3536, 0.8839][0.7071,\, 0.3536,\, 0.8839]

Step 3, exponentiate:

[e0.7071, e0.3536, e0.8839]=[2.028, 1.424, 2.420][e^{0.7071},\, e^{0.3536},\, e^{0.8839}] = [2.028,\, 1.424,\, 2.420]

The sum is 2.028+1.424+2.420=5.8732.028 + 1.424 + 2.420 = 5.873. Divide through:

w=[0.345, 0.243, 0.412]w = [0.345,\, 0.243,\, 0.412]

The policy blends past contents as 0.345 v1+0.243 v2+0.412 v30.345\,v_1 + 0.243\,v_2 + 0.412\,v_3, where each viv_i is the value content of token ii. The recent turn gets 41 percent, the key pickup from way back still gets 35 percent, and the corridor gets 24 percent. That middle number is the point: a two-step-old event outranks a one-step-old irrelevance because the content matches, not because it is near. These are the same three weights drawn in the diagram above.

Code

The snippet below recomputes the worked example. It is the same arithmetic as the section above, so you can check the diagram against it.

import mathfrom typing import List

def softmax(logits: List[float]) -> List[float]:    """Normalizes raw scores into weights that sum to 1."""    exps = [math.exp(s) for s in logits]    total = sum(exps)    return [e / total for e in exps]

def attention_weights(query: List[float], keys: List[List[float]]) -> List[float]:    """Scaled dot-product weights of one query over past tokens."""    dim = len(query)    scores = [        sum(q * k for q, k in zip(query, key)) / math.sqrt(dim) for key in keys    ]    return softmax(scores)

if __name__ == "__main__":    q = [1.0, 0.5]    keys = [[1.0, 0.0], [0.0, 1.0], [1.0, 0.5]]    w = attention_weights(q, keys)    print([round(v, 3) for v in w])    assert abs(sum(w) - 1.0) < 1e-9
# -> expected output:[0.345, 0.243, 0.412]

Watch Out For

Causal leakage: brilliant offline, lost live

The trap is letting the goal signal carry future information. If R^t\hat{R}_t is computed from the full episode during training but the mask or the data pipeline lets a token see it, the model learns to read the answer instead of the past. Offline scores look great because the future is sitting in the input.

The symptom is a sharp split: high accuracy on logged trajectories, confused actions the moment the agent must decrement its own target online. The fix is strict: keep the lower-triangular mask on every head, and at rollout time never recompute R^t\hat{R}_t from the episode. Set the opening target once, then tick it down yourself with R^t+1=R^t−rt\hat{R}_{t+1} = \hat{R}_t - r_t.

Evaluating past the training window

The trap is training on a context of K=20K = 20 steps and deploying on K=200K = 200 because the longer memory sounds better. Positional signals the model never saw at training time now dominate the scores, and performance falls off a cliff right past step 20.

The symptom is a policy that is sharp inside its training horizon and drifts outside it. The fix is to deploy at the trained KK and extend memory the honest way: relative positions plus segment recurrence in the GTrXL style, retrained and retested at the new length. Longer memory is a new experiment, not a free setting.

The Quick Version

  • A transformer policy keeps its recent trajectory tokens on hand and rereads them with attention instead of squeezing the past into one recurrent vector.
  • Attention output is a learned weighted blend, softmax(QKT/dk)V\text{softmax}(QK^T / \sqrt{d_k})V, so a step from hundreds of steps back can still win the most weight.
  • A causal mask gives every token access to the past and zero access to the future, which is what keeps the policy deployable.
  • Goal conditioning hands the model a remaining-return target R^t\hat{R}_t that ticks down as rewards arrive, steering which memories get used.
  • GTrXL is the online-RL variant with gated residuals and segment recurrence for memory past the current window.