Add scalar LoRA value-head (critic) support for SAO-style RL - #85
Open
qywu wants to merge 8 commits into
Open
Conversation
The critic is a second LoRA session on the shared base model. The value head is a LoraLinear(hidden, 1) with a zero frozen base weight, so its factors ride the existing multi-adapter machinery (per-session copies, optimizer state, layouts, rank slicing) unchanged. Two new server losses, value_loss and value_prediction, consume the head's folded weight like lm_head; per-token returns/old_values flow through the packer target-aligned. Sampler exports drop the head; training saves keep it. Includes a pure-Python skip-observation GAE reference implementation (SAO, arXiv:2607.07508, Eq. 4-5). Part of #84
Broly Security ScanNote ✅ Clean scan Note Re-scan this PR anytime with
|
…ules, docs GPU validation (2xH100 FSDP2 certification test + live Qwen3-8B E2E) surfaced and fixed two real integration bugs: - The value head now gets its OWN FSDP unit whose forward never runs, so its factors stay sharded DTensors at all times. The previous stay- gathered [norm, lm_head, value_head] grouping broke adapter layout validation after forward-only ops (a no-grad pass leaves the group materialized and FSDP2 reshard() cannot restore it). The loss consumes the folded delta via full_tensor() (direct DTensor lane). - Each rank can only differentiate its own factor shards, so the upstream weight gradient is now all-reduce-summed before reaching the factors (_SumGradAcrossRanks); staged numerators match analytic references exactly on 2 GPUs. Also: - Dedicated state_values field in LossFnOutput: value losses no longer reuse the logprobs channel on the wire. - Per-session frozen_module_patterns (SAO frozen-attention critic): matching factors are skipped at gradient staging, declared AUTHORIZED_ZERO, and provably stay at zero delta (verified on-disk in the E2E run and in the certification test). - Session normalization accepts the SDK's default dropout=0.0 as a no-op (previously every create_lora_training_client call was rejected). - xorl.rl.explained_variance (paper's critic diagnostic) derived from the value_loss moment metrics; value-model docs page; SAO example loop. Part of #84
Under expert parallelism with dp_shard=1, every rank runs the same batch: dense-module gradients are already complete per rank, and a WORLD sum would overcount by the EP factor. The correct group is exactly the mesh the head is sharded on (ranks holding shards AND seeing distinct data); for pure-DP setups that mesh IS the world, so behavior there is unchanged (re-certified on 2 GPUs). Part of #84
Contributor
|
Hm this doesn't really conform to the existing adapter gradient contract design that is currently present... |
Rank-64 LoRA session registration on a 35B-A3B MoE (per-expert factor banks, EP=2) legitimately exceeds the hardcoded 60s; align with the other heavyweight operations' timeouts.
The worker acknowledges new requests only between operations; a queued forward_backward behind a multi-minute MoE fb chunk exceeded the 300s ACK window and killed otherwise-healthy runs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #84 (design + full plan there). Adds value-model (critic) support to the tinker-compat API so SAO-style single-rollout RL (arXiv:2607.07508, the GLM-5.2/5.3 recipe) can run with a critic as a second LoRA session on the shared base model. GPU-validated: 2×H100 FSDP2 certification test with analytic gradient references, plus a live end-to-end Qwen3-8B server run. Client counterpart: togethercomputer/xorl-client#15.
Design in one paragraph
The value head is a
LoraLinear(hidden_size, 1)attached asmodel.value_headwith a zero, frozen base weight — a scalar head is rank-1, so the LoRA factorizationV(s) = B·A·h·(α/r)loses nothing. Because its parameters are namedvalue_head.lora_A/value_head.lora_B, the multi-adapter manager picks them up with essentially no manager changes: per-session copies, per-session optimizer, tensor layouts, rank slicing, deterministic init (B=0 ⇒ V≡0 at init), andsave_state/load_stateall compose. The head gets its own FSDP unit whose forward never runs, so its factors stay sharded DTensors at all times; the loss consumes the folded delta viafull_tensor()through the existing direct-output-projection ownership lane, with the upstream weight gradient all-reduce-summed across ranks (_SumGradAcrossRanks) so each rank's factor-shard gradients are complete. No new endpoints; the API surface grows by twoloss_fnnames, two per-token datum fields, one response field, and one per-session LoRA option.What's included
ops/loss/value_loss.py—value_loss(masked squared error vs per-tokenreturns, optional PPO-style clipping vsold_values, sum-composable moment metrics) andvalue_prediction(forward-only per-token V(s_t) for the no-gradforwardop → client-side GAE). Raw-sumTokenPartialreducer contract.LossFnOutput.state_values— dedicated wire field for per-token values (value losses no longer reuse thelogprobschannel).frozen_module_patterns— the paper's frozen-attention critic: matching adapter factors are skipped at gradient staging, declaredAUTHORIZED_ZERO, and provably stay at zero delta, without constraining other sessions. Legacy session specs stay byte-identical when the option is absent.enable_value_headonbuild_training_model; head created after LoRA injection in its own never-unsharded FSDP unit; base weight re-zeroed post-load._LOSS_EXCLUDE_KEYS, effective-weight helper (deltafull_tensor+ grad all-reduce),directclassification,AUTHORIZED_ZEROpresence for value-head and session-frozen factors, startup validation.returns/old_valuesjoinCAUSAL_TARGET_ALIGNED_FIELDS(a 0.0 return does not mask the token).value_head.*(verified on disk: 504-key adapter, zero value-head keys); training saves keep them so critics resume.dropout: 0.0as a no-op (previously everycreate_lora_training_clientcall with an explicit LoRA config was rejected — pre-existing tinker-compat bug).xorl.rl—compute_skip_observation_gae(paper Eq. 4–5) andexplained_variance(the paper's critic diagnostic, derived from theis_value_*/is_return_*moment metrics, which finalize to true global means).docs/server-training/value-modelguide;examples/server/sao_critic/runnable SAO loop.GPU validation (what the draft was waiting on)
Certification test (
tests/server/runner/test_value_head_adapter_gpu.py, 2×H100, real FSDP2 + NCCL, analytic references): ownership classifications (DIRECT_OUTPUT_PROJECTION + AUTHORIZED_ZERO), value-loss step with per-parameter staged-numerator equality for trunk and head, policy-style step on the same session (absent value-head grads accepted, exact-zero transport — no stale-numerator leakage), a forward-only op between training steps (regression for the layout-contract bug below), heterogeneous rank slicing, frozen-trunk session across two steps with factors provably unmoved, cross-session isolation.Live E2E (Qwen3-8B, 2 GPUs, real server + client SDK): fresh critic predicts exact zeros over the wire → 8 value-loss steps drop the loss 0.158→0.071 and move V(s) toward returns → client GAE over live
state_values→ frozen-attention critic leavesq_proj.lora_B ≡ 0on disk while MLP + head train → policy session (importance_sampling) coexists → sampler export excludes the head,save_statekeeps it.Validation surfaced and fixed two real integration bugs (details in the second commit): the stay-gathered FSDP grouping broke adapter layout validation after forward-only ops (FSDP2
reshard()cannot restore a group materialized by a no-grad pass — hence the own-unit design), and per-rank factor-shard gradients were incomplete without the upstream all-reduce (staged numerators were ~14% off analytic references before the fix; exact after).Not in this PR (tracked in #84)