Skip to content
AI360Xpert
Beta

Asynchronous Advantage Actor-Critic (A3C)

Many CPU workers play their own copies of the game at once and push gradients to one shared brain without locks. The mixed updates decorrelate the data, so no replay buffer is needed.

Two workers start at 1.00 2.00, push gradients in turn, and move the shared weights to 0.96 2.06 then 1.01 2.03 without locks
Two workers start at 1.00 2.00, push gradients in turn, and move the shared weights to 0.96 2.06 then 1.01 2.03 without locks

Why Does This Exist?

Deep Q methods broke correlation with a million step replay store. That works for off policy Q learning, but on policy methods cannot reuse old data without correction. Actor critic methods need fresh rollouts from the current policy, so the buffer does not fit.

A3C replaces memory with parallelism. Each of KK workers holds its own environment copy and its own local weights θ′\theta' for the policy and ϕ′\phi' for the value baseline. Workers roll out up to tmax=5t_{max} = 5 steps, compute nn-step advantages, and push gradients to the shared model with no locks. Because the workers sit in different states at different times, the push stream is mixed enough to train stably on CPU.

This page covers the async push and the nn-step update. The sync follow up lives in advantage actor critic, the shared architecture in actor critic architecture, and continuous control notes in continuous action spaces.

You need policy gradients first: log⁡π(a∣s)\log \pi(a \mid s) times advantage moves probability toward better actions.

Think of It Like This

Scouts phoning edits to one shared playbook

One playbook sits at HQ. Four scouts watch four different opponents with yesterday's copy. Each scout tests a short drill, phones in a small edit the moment it finishes, then pulls the latest playbook before the next drill.

No scout waits for the others, and HQ never locks the book. Because the calls come from different games, the edit stream stays balanced instead of overfitting to one opponent.

Where it stops: scouts write notes one at a time while A3C threads overwrite shared floats with Hogwild races. Stale edits are also real here: a slow scout phones in advice for a playbook that already moved on.

How It Actually Works

Each worker repeats pull, rollout, bootstrap, push. Local gradients start at zero, sync θ′←θ\theta' \leftarrow \theta and ϕ′←ϕ\phi' \leftarrow \phi, then step the private env for up to tmaxt_{max} steps.

Bootstrapping sets R=0R = 0 at a terminal state else R=V(st;ϕ′)R = V(s_t; \phi'). Walking backward with discount γ\gamma:

R←r+γRR \leftarrow r + \gamma R A=R−V(s;ϕ′)A = R - V(s; \phi')

Here rr is the one step reward, VV is the critic value, and AA is the advantage telling how much better this path ran than expected. Policy and value gradients accumulate with entropy bonus HH weighted by β\beta:

dθ+=∇log⁡π(a∣s)A+β∇Hd\theta += \nabla \log \pi(a \mid s) A + \beta \nabla H

Then the Hogwild push writes straight to shared memory with step α\alpha, no mutex:

θ←θ+αdθ\theta \leftarrow \theta + \alpha d\theta

The price is staleness: θ′\theta' lags θ\theta by the time the rollout took.

Worked example

Track shared weights ww in R2\mathbb{R}^2 with α=0.10\alpha = 0.10. Both workers pull:

w=[1.00,2.00]w = [1.00, 2.00]

Worker 1 finishes fast with g1=[0.40,−0.60]g_1 = [0.40, -0.60] and pushes:

w←[1.00,2.00]−0.10×[0.40,−0.60]=[0.96,2.06]w \leftarrow [1.00, 2.00] - 0.10 \times [0.40, -0.60] = [0.96, 2.06]

Worker 2 ran longer on the old copy [1.00,2.00][1.00, 2.00] and returns stale g2=[−0.50,0.30]g_2 = [-0.50, 0.30]. It still pushes:

w←[0.96,2.06]−0.10×[−0.50,0.30]=[1.01,2.03]w \leftarrow [0.96, 2.06] - 0.10 \times [-0.50, 0.30] = [1.01, 2.03]

Both edits land. The second used a one update old view, which is the staleness A3C accepts.

Code

Deterministic replay of the two pushes above.

import numpy as np

def push(params: np.ndarray, grad: np.ndarray, lr: float = 0.10) -> np.ndarray:    """One Hogwild style descent step, no locks in this model."""    return params - lr * grad

w = np.array([1.00, 2.00])g1 = np.array([0.40, -0.60])w = push(w, g1)print(np.round(w, 4))  # -> [0.96 2.06]
g2 = np.array([-0.50, 0.30])w = push(w, g2)print(np.round(w, 4))  # -> [1.01 2.03]

Outputs match the hand steps: [0.96,2.06][0.96, 2.06] then [1.01,2.03][1.01, 2.03].

Watch Out For

Too many workers make gradients too stale

A3C assumes delays stay short. With 32 threads or uneven episode lengths, a slow worker returns advice for a model that moved dozens of updates ago.

Symptom: sudden score collapse after adding cores, worse with longer episodes. Fix: cap workers near core count, use shared RMSprop statistics to damp odd pushes, or switch to advantage actor critic sync batches on GPU.

The Quick Version

  • A3C decorrelates with parallel env copies, not with a replay store, so it stays on policy.
  • Workers use nn-step bootstrapped advantages with tmax=5t_{max} = 5 plus an entropy bonus.
  • Hogwild pushes skip locks for CPU speed and accept mild staleness.
  • The traced run moves shared weights [1.00,2.00][1.00, 2.00] to [0.96,2.06][0.96, 2.06] to [1.01,2.03][1.01, 2.03].
  • Cap worker count or go sync A2C when staleness starts to hurt.