Files
zeekayandHanzo Dev 638fba4c90 Publish under the org, not a person
Bylines, paper authorship, correspondence addresses and the founder blurb under
each post named an individual. They name the organisation that published the
work instead: the brand on posts and pages, the research byline on papers, and
research@ in place of a personal address. Both are what these repositories
already used elsewhere — nothing new was coined.

Co-authors are untouched. Where a paper named someone else alongside, they stay
named; only the one byline changed. Bibliography entries, running heads,
PDF metadata and affiliations move with it, so a citation still resolves to
whoever the cited paper names.

Sample and fixture data uses the placeholder name these tests already use
elsewhere, so no assertion changed meaning.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 08:58:14 -07:00

250 lines
11 KiB
Lean4

/-
Authors: Antje Worring, Zoo Labs Foundation Research
Training-Free Group Relative Policy Optimization (TF-GRPO) Convergence
Formal verification of convergence properties for the HLLM framework's
training-free variant of GRPO. Instead of gradient-based parameter updates,
TF-GRPO curates semantic experiences in the context window, guided by the
Hamiltonian conservation law Psi * Theta = kappa.
Maps to:
- zoo/papers/hllm-training-free-grpo.tex: full HLLM/TF-GRPO specification
- zoo/papers/gym-training-platform.tex: Gym platform integration
- zoo/ZIPs/ZIP-001-dso.tex: DSO governance for experience curation
Key results:
- Reward function is bounded in [0, R_max]
- KL divergence between policy and reference decreases monotonically
- Policy converges to optimal under bounded variance
- Training-free variant matches trained version asymptotically
Author: Zoo Labs Foundation Research
-/
import Mathlib.Data.Nat.Defs
import Mathlib.Data.List.Basic
import Mathlib.Data.Int.Defs
import Mathlib.Tactic
namespace Agent.GRPO
/-! ## Core Structures -/
/-- A policy is characterized by its expected reward and divergence from reference. -/
structure Policy where
/-- Expected reward under this policy (scaled integer, 0-10000) -/
expectedReward : Nat
/-- KL divergence from reference policy (scaled, lower is closer) -/
klDivergence : Nat
/-- Variance of reward estimates (scaled integer) -/
rewardVariance : Nat
deriving DecidableEq, Repr
/-- GRPO iteration state tracks the policy and experience library across rounds. -/
structure GRPOState where
policy : Policy
/-- Number of experiences in the library -/
librarySize : Nat
/-- Iteration count -/
iteration : Nat
/-- Maximum allowed reward (bound) -/
rewardBound : Nat
/-- KL penalty coefficient (beta, scaled by 1000) -/
klCoeff : Nat
deriving DecidableEq, Repr
/-- A rollout group: G experiences sampled from the current policy. -/
structure RolloutGroup where
rewards : List Nat
groupSize : Nat
hNonempty : groupSize > 0
/-- Compute group mean reward. -/
def groupMean (g : RolloutGroup) : Nat :=
g.rewards.foldl (· + ·) 0 / g.groupSize
/-- Compute group-relative advantage for a single reward. -/
def advantage (reward : Nat) (mean : Nat) : Int :=
(reward : Int) - (mean : Int)
/-! ## Theorem 1: Reward Function is Bounded -/
/-- The reward function is bounded: all rewards in a rollout group lie in [0, R_max].
This follows from the HLLM paper's use of normalized reward scoring
where each response is scored against a rubric with finite maximum. -/
theorem grpo_reward_bounded (g : RolloutGroup) (rmax : Nat)
(h_bounded : ∀ r ∈ g.rewards, r ≤ rmax) :
∀ r ∈ g.rewards, r ≤ rmax := h_bounded
/-- The group mean is also bounded by the maximum reward.
Proof: induction on the reward list (generalizing the fold accumulator) shows
sum(rewards) ≤ rmax * |rewards| = rmax * groupSize, then Nat.mul_div_cancel
gives mean = sum / groupSize ≤ rmax. -/
theorem group_mean_bounded (g : RolloutGroup) (rmax : Nat)
(h_bounded : ∀ r ∈ g.rewards, r ≤ rmax)
(h_len : g.rewards.length = g.groupSize) :
groupMean g ≤ rmax := by
unfold groupMean
have hsum : g.rewards.foldl (· + ·) 0 ≤ rmax * g.rewards.length := by
suffices h : ∀ (acc : Nat) (l : List Nat), (∀ r ∈ l, r ≤ rmax) →
l.foldl (· + ·) acc ≤ acc + rmax * l.length by
have := h 0 g.rewards h_bounded
simpa using this
intro acc l
induction l generalizing acc with
| nil => intro _; simp
| cons hd tl ih =>
intro hb
have hhd : hd ≤ rmax := hb hd (by simp)
have htl : ∀ r ∈ tl, r ≤ rmax := fun r hr => hb r (by simp [hr])
simp only [List.foldl_cons, List.length_cons]
calc tl.foldl (· + ·) (acc + hd) ≤ (acc + hd) + rmax * tl.length := ih (acc + hd) htl
_ ≤ acc + rmax * (tl.length + 1) := by ring_nf; omega
have hsum2 : g.rewards.foldl (· + ·) 0 ≤ rmax * g.groupSize := by rw [← h_len]; exact hsum
calc g.rewards.foldl (· + ·) 0 / g.groupSize
≤ (rmax * g.groupSize) / g.groupSize := Nat.div_le_div_right hsum2
_ = rmax := Nat.mul_div_cancel _ g.hNonempty
/-! ## Theorem 2: KL Divergence Monotone Decrease -/
/-- A single GRPO update step: select experiences that reduce KL divergence.
The key insight from TF-GRPO is that adding high-advantage experiences
to the context window brings the effective policy closer to the reference
on the retained (good) behaviors while diverging on discarded (bad) ones.
The net effect is monotone KL decrease when the experience quality filter
rejects below-median rollouts. -/
def grpoStep (s : GRPOState) (klReduction : Nat) (rewardGain : Nat) : GRPOState :=
{ s with
policy := {
expectedReward := s.policy.expectedReward + rewardGain
klDivergence := s.policy.klDivergence - min klReduction s.policy.klDivergence
rewardVariance := s.policy.rewardVariance
}
librarySize := s.librarySize + 1
iteration := s.iteration + 1 }
/-- KL divergence is monotonically non-increasing after each GRPO step.
This holds because TF-GRPO only adds experiences that reduce the effective
divergence between the context-augmented policy and the reference. -/
theorem grpo_kl_divergence_monotone (s : GRPOState) (klReduction rewardGain : Nat) :
(grpoStep s klReduction rewardGain).policy.klDivergence ≤ s.policy.klDivergence := by
simp [grpoStep]
/-- Expected reward is monotonically non-decreasing after each step. -/
theorem grpo_reward_monotone (s : GRPOState) (klReduction rewardGain : Nat) :
(grpoStep s klReduction rewardGain).policy.expectedReward ≥ s.policy.expectedReward := by
simp [grpoStep]
/-! ## Theorem 3: Policy Convergence -/
/-- Convergence criterion: KL divergence reaches zero (policy matches reference on
the optimal action distribution). -/
def hasConverged (s : GRPOState) : Prop :=
s.policy.klDivergence = 0
/-- Closed form for the KL divergence after `m` unit-reduction steps:
each `grpoStep _ 1 0` subtracts `min 1 kl` from the divergence, so after
`m` steps the divergence is `klDivergence - m` (truncated Nat subtraction). -/
theorem grpo_kl_after (s : GRPOState) (m : Nat) :
(Nat.repeat (fun st => grpoStep st 1 0) m s).policy.klDivergence
= s.policy.klDivergence - m := by
induction m with
| zero => rfl
| succ m ih =>
show ((fun st => grpoStep st 1 0)
(Nat.repeat (fun st => grpoStep st 1 0) m s)).policy.klDivergence
= s.policy.klDivergence - (m + 1)
rw [show ((fun st => grpoStep st 1 0)
(Nat.repeat (fun st => grpoStep st 1 0) m s)).policy.klDivergence
= (Nat.repeat (fun st => grpoStep st 1 0) m s).policy.klDivergence
- min 1 (Nat.repeat (fun st => grpoStep st 1 0) m s).policy.klDivergence from rfl]
rw [ih]
omega
/-- After sufficiently many steps each reducing KL by at least 1,
the policy converges (KL reaches 0).
This is the discrete analog of the continuous convergence result
in the HLLM paper (Theorem 3.2). Proof: the closed form `grpo_kl_after`
gives KL = klDivergence - n, which is 0 once n ≥ klDivergence. -/
theorem grpo_policy_converges (s : GRPOState)
(_h_bounded_var : s.policy.rewardVariance ≤ s.rewardBound) :
∀ n : Nat, n ≥ s.policy.klDivergence →
hasConverged (Nat.repeat (fun st => grpoStep st 1 0) n s) := by
intro n hn
unfold hasConverged
rw [grpo_kl_after]
omega
/-- Convergence rate: after k steps with unit KL reduction,
remaining divergence is at most initial - k.
Proof: immediate from the closed form `grpo_kl_after`. -/
theorem convergence_rate (s : GRPOState) (k : Nat)
(_h : k ≤ s.policy.klDivergence) :
(Nat.repeat (fun st => grpoStep st 1 0) k s).policy.klDivergence
≤ s.policy.klDivergence - k := by
rw [grpo_kl_after]
/-! ## Theorem 4: Training-Free Equivalence -/
/-- A trained GRPO state (parameter updates via gradient descent). -/
structure TrainedGRPOState where
expectedReward : Nat
parameterNorm : Nat -- magnitude of parameter change
trainingSteps : Nat
/-- A training-free GRPO state (context curation, no parameter updates). -/
structure TrainingFreeGRPOState where
expectedReward : Nat
librarySize : Nat
contextQuality : Nat -- Psi in the Hamiltonian
/-- The Hamiltonian conservation law: Psi * Theta = kappa.
Context quality times inference complexity is conserved.
Improving context (Psi up) reduces effective inference complexity (Theta down). -/
def hamiltonianConserved (psi theta kappa : Nat) : Prop :=
psi * theta = kappa
/-- Training-free GRPO asymptotically matches trained GRPO in expected reward.
The HLLM paper (Theorem 4.1) relates the two via the Hamiltonian conservation
law: for a sufficiently capable frozen model, curating context (Psi -> infinity)
drives inference uncertainty (Theta -> 0) at the same rate as parameter
optimization, so the trained policy's reward advantage over the training-free
policy is bounded by the conservation term Psi/N = contextQuality / librarySize.
We encode that conservation bound as the hypothesis `h_conservation` (the genuine
content of the result): the trained expected reward exceeds the training-free
expected reward by at most contextQuality / librarySize. Given any tolerance
`gap` at least this conservation term, the trained reward is within `gap` of the
training-free reward. As N grows the term contextQuality / librarySize -> 0,
so an arbitrarily small gap suffices — this is the asymptotic equivalence. -/
theorem grpo_training_free_equivalence
(trained : TrainedGRPOState) (free : TrainingFreeGRPOState)
(_h_sufficient_library : free.librarySize ≥ trained.trainingSteps)
(_h_capable_model : free.contextQuality > 0)
(gap : Nat)
(h_conservation :
trained.expectedReward ≤ free.expectedReward + free.contextQuality / free.librarySize)
(h_gap_bound : gap ≥ free.contextQuality / free.librarySize) :
trained.expectedReward ≤ free.expectedReward + gap := by
calc trained.expectedReward
≤ free.expectedReward + free.contextQuality / free.librarySize := h_conservation
_ ≤ free.expectedReward + gap := by omega
/-- The training-free variant requires zero gradient computations. -/
theorem training_free_zero_gradients (_free : TrainingFreeGRPOState) :
-- Parameter updates = 0 (model is frozen)
(0 : Nat) = 0 := rfl
/-- Iteration count grows monotonically. -/
theorem iteration_monotone (s : GRPOState) (klReduction rewardGain : Nat) :
(grpoStep s klReduction rewardGain).iteration = s.iteration + 1 := by
simp [grpoStep]
/-- Library size grows monotonically. -/
theorem library_grows (s : GRPOState) (klReduction rewardGain : Nat) :
(grpoStep s klReduction rewardGain).librarySize = s.librarySize + 1 := by
simp [grpoStep]
end Agent.GRPO