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.
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 updated every step as , where is the memory and 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 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 tokens and pay 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 to step 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 tokens and computes scaled dot-product attention at every step:
Here (queries) is what the current step is looking for, (keys) is what each past token offers, (values) is the content each past token contributes, and 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 tokens costs work per step. Recurrence costs 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 is a lower-triangular matrix of ones and zeros that blanks every future position: token may read token only when . In code the masked scores become before the softmax wherever , 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 (the reward still to collect this episode) alongside the state , and predicts the action that fits both. After the environment pays reward , the target ticks down as . 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 . Three past tokens offer keys: (picked up the key two rooms back), (empty corridor), (the turn just taken). Key dimension , so the scale is .
Step 1, raw scores as dot products:
Step 2, scale by :
Step 3, exponentiate:
The sum is . Divide through:
The policy blends past contents as , where each is the value content of token . 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 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 from the episode. Set the opening target once, then tick it down yourself with .
Evaluating past the training window
The trap is training on a context of steps and deploying on 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 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, , 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 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.