Skip to content

Adapter-sync LoRA weight sync (supersedes #90) + reload correctness fixes - #94

Draft
qywu wants to merge 6 commits into
devfrom
qywu/adapter-sync-reload-fixes
Draft

Adapter-sync LoRA weight sync (supersedes #90) + reload correctness fixes#94
qywu wants to merge 6 commits into
devfrom
qywu/adapter-sync-reload-fixes

Conversation

@qywu

@qywu qywu commented Aug 27, 2026

Copy link
Copy Markdown
Member

Summary

Retargets #90 at dev and adds two correctness fixes found by exercising the path live on a Qwen3.5-35B-A3B EP=2 trainer + TP2 LoRA SGLang sampler (the configuration merged-weight sync structurally cannot serve — it OOMs in the LoRA fold).

Base change (from #90): LoRA sessions publish PEFT adapters via /load_lora_adapter instead of folding into the base and broadcasting merged full weights; registration rejects endpoints that don't report enable_lora=true or whose max_lora_rank is below the substrate rank; weight-sync docs scope NCCL transport to dense full-weight only.

Fix 1 — result whitelist (request_processor.py): execute_sync_inference_weights.build_output whitelists result keys and silently dropped adapter_sync/adapter_path/model_id; the API layer then saw a generic success and never drove the endpoint-side loads. Observed live: engine exported the adapter, client got success, sampler had dict_keys([]) loaded.

Fix 2 — stale adapter on re-sync (inference_endpoints.py): the load helper treats SGLang's "already loaded" as success (correct for its original one-shot use). Under per-step syncs that re-publish updated weights under the same name, every sync after the first silently no-ops and the sampler serves the step-1 policy forever — invisibly off-policy training with plausible-looking metrics. Both sync paths now unload the name before re-loading (the first sync's unload is a tolerated no-op).

Live validation

End-to-end on 35B-A3B: adapter export (~100MB PEFT) → whitelist passthrough → API load → Loaded LoRA adapter on http://...:30018 in the log; a 64-step RL run is in progress on this stack.

Endpoint compatibility contract learned in the shakedown (candidate doc additions)

  • --lora-target-modules must be the explicit projection list; all wraps lm_head, which the exact-contract fp32 decode head rejects (ValueError: ... does not support a LoRA-wrapped lm_head).
  • Trainer LoRA targets must be a subset of SGLang's supported module choices — GDN's g_proj is not accepted, so qwen3_5 trainers targeting it produce unloadable adapters.
  • GDN-hybrid samplers need a high --mem-fraction-static with --max-total-tokens capped: the mamba state cache (~61MB/req) is carved from inside the static budget.
  • Client counterpart (xorl-client, branch qywu/wordle-upgrades): syncs carry the session model_id; LoRA runs publish the zero adapter before step-1 rollouts; generation selects the adapter by name.

Open questions

Unchanged from #90: K₃ numerics of runtime-LoRA vs exact merged-forward; QLoRA and DSV4/GLM factor banks excluded (guards intact); pools-scoped adapter loads.

qywu added 4 commits August 27, 2026 05:22
LoRA weight sync no longer folds the adapter into the base model and
broadcasts merged full weights. Instead the engine exports the session's
adapter in PEFT format and the API server loads it on every registered
endpoint via SGLang /load_lora_adapter. Inference endpoints for a LoRA
training server are now required to support LoRA: registration rejects
endpoints that do not report enable_lora=true or whose max_lora_rank is
below the substrate rank.

Why: the fold materializes W + (alpha/r) B A on a trainer whose base is
already resident, which OOMs at large-model scale (observed live: a
Qwen3.5-35B-A3B EP=2 trainer at 70GB+/GPU dies in canonical_lora_fold
during sync), and it ships full-model bytes over NCCL for a ~100MB delta.
Adapter publication is what the internal large-MoE RL runs already do
(merged_weight_sync: false).

Notes for review:
- QLoRA composites and the DSV4/GLM exact active-LoRA banks are NOT covered
  by the PEFT export; their guards still fail loudly.
- Numerics: endpoint-side LoRA applies base + B(Ax) through SGLang's LoRA
  kernels rather than the trainer's exact merged-forward fold, which widens
  train/inference K3 relative to merged sync; ratio-clipped objectives
  (CISPO/PPO) absorb this, and the Triton LoRA backend is required for the
  on-policy LM-head contract.
- Client counterpart: SamplingClients must target the adapter name; the
  xorl-client examples currently sample the base model under merged sync.
LoRA sessions do not participate in weight sync at all; adapters publish via
/load_lora_adapter and are documented with the LoRA adapter docs. The
weight-sync overview now only documents the dense full-weight transport.
execute_sync_inference_weights.build_output whitelists result keys, which
silently dropped adapter_sync/adapter_path/model_id — the API layer then saw
a generic success and never drove the endpoint-side /load_lora_adapter
calls.
The load helper treats 'already loaded' as success, so per-step adapter
re-publication under the same name silently no-ops and the sampler serves
the first synced adapter forever (off-policy with no error). Unload the name
first on both sync paths; the first sync's unload is a tolerated no-op.
@broly-code-security-scanner

broly-code-security-scanner Bot commented Aug 27, 2026

Copy link
Copy Markdown

Broly Security Scan

Note

Summary

1 actionable finding(s) in this PR

  • 🟡 1 medium

All actionable items are in the table below.

No finding is at or above high, so this check is not blocking. The findings above are still tracked and reported.

Severity Scanner Issue Location Dismiss Verdict
🟡 MEDIUM SAST Path traversal via unsanitized model_id in
adapter export directory construction. The
`model_id...
src/xorl/server/weight_sync/handler.py:503 d3 🔺 TRUE_POSITIVE · Confidence: HIGH

Dismiss false positives

Tick a box to dismiss the finding; untick it to bring the finding back. That is the same as replying /broly dismiss d1 and /broly undismiss d1. To record why it is a false positive, reply with /broly dismiss d1: your reason instead — Broly reuses those reasons to triage similar findings across the org.

  • d3 · 🟡 MEDIUM   · src/xorl/server/weight_sync/handler.py:503 · Path traversal via unsanitized model_id in adapter export directory constru...

Note

Re-scan this PR anytime with /broly scan — useful after /broly undismiss, or to refresh findings without a new push.

Broly — SAST (zai-org/GLM-5.2) · Secrets · SCA · IaC · GH Actions · Base Images · Supply Chain Threats · Exploit Chains · Adversarial Verification

We're continuously improving Broly's accuracy and finding quality — your feedback is valuable. False positives, missed findings, bugs, and feature requests all welcome.

Ask in #security-engineering   Powered by Together AI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant