Zhipu GLM-5.2 · SAO Asynchronous RL: Single-Rollout Surpasses GRPO, Agent RL New Paradigm Deep Dive
Zhipu GLM-5.2 · SAO Asynchronous RL: Single-Rollout Surpasses GRPO, Agent RL New Paradigm Deep Dive
1. Introduction
On July 15, 2026, Zhipu AI founder Jie Tang announced the release of SAO (Single-rollout Asynchronous Optimization), a new paradigm for large model Agentic Reinforcement Learning, jointly developed with Tsinghua University’s KEG Lab. The algorithm has been successfully deployed in the production training pipeline of GLM-5.2 (750B-A40B).
SAO’s core breakthrough: completely abandoning GRPO’s “group-wise sampling” in favor of single-rollout sampling, combined with innovative double-side token-level clipping and Critic network optimization, achieving stable training for over 1000 steps. Results: 97.3% on AIME 2025 (GRPO: 84.2%), 29.8% on SWE-Bench Verified (GRPO: 27.0%).
2. Why Asynchronous RL?
In Agent and coding tasks, rollout lengths vary dramatically. A simple code fix might need tens of tokens, while a complex multi-step Agent task requires thousands. Synchronous RL (PPO, GRPO) must wait for the slowest rollout in each group, causing severe GPU idling.
import numpy as np
class AsyncRLPipeline:
def simulate_rollout(self, difficulty):
times = {"simple": (0.5, 1.5), "medium": (1.5, 4.0),
"complex": (4.0, 10.0), "agent": (8.0, 30.0)}
low, high = times.get(difficulty, (0.5, 2.0))
return np.random.uniform(low, high)
def run_batch(self, tasks):
times = [self.simulate_rollout(t) for t in tasks]
max_time = max(times)
total_compute = sum(times)
sync_time = max_time * len(tasks)
return {"gpu_util": total_compute / sync_time * 100,
"speedup": sync_time / max_time}
# Agent tasks: 2 agent + 3 complex + 3 medium
tasks = ["agent"] * 2 + ["complex"] * 3 + ["medium"] * 3
async_p = AsyncRLPipeline()
result = async_p.run_batch(tasks)
print(f"Async RL: GPU util={result['gpu_util']:.0f}%, Speedup={result['speedup']:.1f}x")
3. SAO’s Three Core Innovations
3.1 Innovation 1: Single-Rollout + Direct Importance Sampling
SAO replaces GRPO’s group-wise sampling with single-rollout sampling. Each prompt generates one rollout, which is immediately used for training. To address the higher variance, SAO introduces Direct Importance Sampling (DIS):
import torch
import torch.nn as nn
class DirectImportanceSampling:
def __init__(self, clip_lower=0.2, clip_upper=5.0):
self.clip_lower = clip_lower
self.clip_upper = clip_upper
self.stats = {"total": 0, "masked_low": 0, "masked_high": 0}
def compute_ratio(self, current_log_probs, behavior_log_probs):
return torch.exp(current_log_probs - behavior_log_probs)
def apply_clipping(self, ratios, advantages):
mask_low = ratios < self.clip_lower
mask_high = ratios > self.clip_upper
self.stats["total"] += ratios.numel()
self.stats["masked_low"] += mask_low.sum().item()
self.stats["masked_high"] += mask_high.sum().item()
# Tokens outside trust region: masked out (zero gradient)
return torch.where(mask_low | mask_high,
torch.zeros_like(advantages),
ratios * advantages)
Key difference from PPO: PPO clips ratio to [1-ε, 1+ε] boundaries. SAO-DIS completely masks out tokens outside the trust region, preventing any gradient contribution from extreme deviations.
3.2 Innovation 2: Critic Network Optimization
SAO re-introduces the Critic (value) network that GRPO abandoned. Three key optimizations:
package main
import "fmt"
type SAOCriticConfig struct {
ActorUpdateSteps int
CriticUpdateSteps int // 2x: Critic updates twice per Actor update
FreezeAttention bool // Freeze attention layers
TrainMoEOnly bool // Only train MoE projection layers
SkipObservation bool // Skip observation tokens in GAE
}
func (c *SAOCriticConfig) CalculateTrainable(totalParams, attnParams, moeParams int64) map[string]int64 {
result := make(map[string]int64)
if c.FreezeAttention {
result["attention_frozen"] = attnParams
result["attention_trainable"] = 0
} else {
result["attention_trainable"] = attnParams
}
if c.TrainMoEOnly {
result["moe_trainable"] = moeParams
}
result["total_trainable"] = result["attention_trainable"] + result["moe_trainable"]
result["frozen"] = totalParams - result["total_trainable"]
result["freeze_ratio"] = result["frozen"] * 100 / totalParams
return result
}
func main() {
cfg := &SAOCriticConfig{
CriticUpdateSteps: 2, FreezeAttention: true, TrainMoEOnly: true,
}
// GLM-5.2: 750B total, ~5B attention, ~550B MoE
params := cfg.CalculateTrainable(750_000_000_000, 5_373_952_000, 549_755_813_888)
fmt.Printf("Trainable: %d (%.1f%%)\n", params["total_trainable"],
float64(params["total_trainable"])*100/750_000_000_000)
fmt.Printf("Frozen: %d (%.1f%%)\n", params["frozen"],
float64(params["freeze_ratio"]))
}
3.3 Innovation 3: Skip-Observation GAE
In multi-turn Agent interactions, model actions and environment observations alternate. If observation tokens participate in advantage computation, they introduce noise. SAO’s Skip-Observation GAE skips observation tokens when computing advantages:
import numpy as np
class SkipObservationGAE:
def __init__(self, gamma=0.99, lambda_=0.95):
self.gamma = gamma
self.lambda_ = lambda_
def compute(self, rewards, values, token_types):
seq_len = len(rewards)
advantages = np.zeros(seq_len)
last_gae = 0.0
for t in reversed(range(seq_len)):
next_value = values[t+1] if t+1 < seq_len else 0.0
if token_types[t] == 0: # Action token: normal GAE
delta = rewards[t] + self.gamma * next_value - values[t]
last_gae = delta + self.gamma * self.lambda_ * last_gae
advantages[t] = last_gae
else: # Observation token: skip (advantage = 0)
advantages[t] = 0.0
# last_gae unchanged, observation doesn't break GAE propagation
action_mask = token_types == 0
return advantages, {
"snr": np.mean(advantages[action_mask]) / max(np.std(advantages[action_mask]), 1e-10),
"skip_ratio": np.sum(~action_mask) / seq_len,
}
# Simulate: 5 turns of action-observation alternation
seq_len = 100
token_types = np.array([0]*10 + [1]*10 + [0]*15 + [1]*10 + [0]*10 + [1]*5 +
[0]*10 + [1]*10 + [0]*10 + [1]*10)
rewards = np.zeros(seq_len)
rewards[-1] = 1.0 # Final success reward
values = np.random.randn(seq_len) * 0.1 + 0.5
gae = SkipObservationGAE()
advantages, stats = gae.compute(rewards, values, token_types)
print(f"Skip-Obs GAE: SNR={stats['snr']:.2f}, Skip ratio={stats['skip_ratio']:.1%}")
4. Benchmark Results
| Benchmark | Base | GRPO | GRPO+DIS | SAO |
|---|---|---|---|---|
| AIME 2025 | 72.1% | 84.2% | 86.5% | 97.3% |
| SWE-Bench Verified | 23.0% | 25.1% | 27.0% | 29.8% |
| BeyondAIME | 58.3% | 65.7% | 68.2% | 74.8% |
| IMOAnswerBench | 45.2% | 52.8% | 54.1% | 61.5% |
5. Conclusion
SAO provides a new paradigm for Agentic RL in large language models:
- Single-rollout replaces group-wise sampling: Breaks free from GRPO’s group-waiting constraint
- DIS with strict clipping: Tokens outside trust region are completely masked
- Critic network triple optimization: 2x update frequency, frozen attention, skip-observation GAE
- 1000-step stable training: 97.3% on AIME 2025, 29.8% on SWE-Bench
SAO has been successfully deployed in GLM-5.2 (750B-A40B) training pipeline, marking the transition of LLM RL from “synchronous batch” to “asynchronous single-rollout” era.
Technical details based on arXiv:2607.07508 and Zhipu AI official announcements.