Skip to content

Add GLM-5.2 adapted-target scope and path-qualified LoRA targets - #63

Draft
qywu wants to merge 4 commits into
mainfrom
feat/glm52-lora-scope
Draft

Add GLM-5.2 adapted-target scope and path-qualified LoRA targets#63
qywu wants to merge 4 commits into
mainfrom
feat/glm52-lora-scope

Conversation

@qywu

@qywu qywu commented Aug 19, 2026

Copy link
Copy Markdown
Member

What

GLM-5.2's block-FP8 QLoRA lane builds a complete deterministic inventory (1,700
factor tensors) and rejects lora_target_modules, so there was no way to isolate
where MoE learning happens. This adds glm52_lora_scope
(all | moe | shared_experts | routed_experts).

Scope selects which factors train, not which modules are adapted. The
complete exact family is always constructed, because NativeBlockFP8Linear
implements no activation backward ("phase-one forward is scoring-only; activation
backward requires a validated kernel") — a region left unadapted blocks gradients
from reaching adapted regions downstream of it. Out-of-scope factors are frozen
with lora_B == 0, so they contribute nothing to the forward: step one still
reproduces the frozen-base loss exactly and the forward program is identical for
every scope.

glm52_lora_scope: routed_experts   # 450 of 1,700 factors train
ep_dispatch: alltoall              # the exact family is still built

Result

Measured on 16× H100, rank 64 / alpha 64, 64 steps, lr 1e-4, ~500 label
tokens/step. The two runs differ only in which factors were trainable.

Trainable factors Final loss
1,700 (all) 0.0328
450 (routed_experts) 0.0645

Routed experts alone reach about half the improvement on 26% of the factors, so
the Qwen MoE-only recipe transfers to GLM-5.2 only partially. Adapters:
togethercomputer/GLM-5.2-Password-LoRA-xorl.

Assertions relaxed

Three checks assumed adapted ⇒ trainable, which held while the complete family
was the only supported configuration:

  • exact LM head required both factor masters trainable. The real invariant is
    that the pair shares trainability — a half-frozen pair would take a gradient
    on one side of a product whose other side is fixed.
  • adapter gradient ownership required a gradient, a staged gradient, and a
    compiled-producer numerator for every inventory factor (3 sites). A frozen
    factor produces none by design; absence is still a fault for a factor that is
    supposed to be learning.

Also: path-qualified LoRA targets (all architectures)

Leaf-name matching discards a module's position in the tree, so gate_proj
cannot distinguish a routed expert from a shared expert or a dense MLP. A target
containing a path separator or glob is now matched with fnmatch against the
full module path:

lora_target_modules: ["*.mlp.experts.*.gate_proj"]   # routed only
lora_target_modules: ["*.shared_expert.*_proj"]      # shared only
lora_target_modules: ["gate_proj", "up_proj"]        # unchanged

Bare names keep their existing leaf/indirect semantics; patterns join the same
fail-closed coverage check.

Diagnostics

The worker entrypoint is decorated with torch elastic's record and enables
faulthandler, so a rank dying mid-forward reports a traceback instead of
error_file: <N/A> and a bare exit code. Finding the three bugs above took
several 16-GPU reload cycles precisely because no traceback was emitted.

Tests

  • tests/models/test_glm52_lora_scope.py — scope partitioning, freeze is the
    complement of the scope, all freezes nothing
  • tests/models/test_lora_path_pattern_targets.py — bare-name regression,
    routed/shared isolation, unmatched patterns fail closed, matches never nest
  • 139 passing across the exact-lane, audit, and arguments suites; ruff clean

Known gaps (why this is a draft)

  • Recall is unverified by generation. Loss only; that needs a GLM-5.2 SGLang
    endpoint and a weight sync.
  • Narrowed scopes still export the full inventory (16 GB at rank 64), with
    untrained factors stored as zeros. Filtering them at export would shrink it.
  • No CPU-level construction test. All three bugs above were found only by a
    ~7-minute load on 16 GPUs; a miniature GLM-5.2 config exercising
    _validate_constructed_model would catch that class in seconds.
  • Narrowed scopes run the same forward program but train a different parameter
    set, so they carry no bit-exactness claim; only all at rank 1 / alpha 1
    remains qualified.

GLM-5.2's block-FP8 QLoRA lane builds a complete deterministic inventory and
rejects `lora_target_modules`, so there was no way to isolate where MoE learning
happens. Add `glm52_lora_scope` (all | moe | shared_experts | routed_experts).

Scope selects which factors TRAIN, not which modules are adapted. The complete
exact family is always constructed because NativeBlockFP8Linear implements no
activation backward ("phase-one forward is scoring-only"), so a region left
unadapted blocks gradients from reaching adapted regions downstream of it.
Out-of-scope factors are frozen with lora_B == 0 and therefore contribute
nothing to the forward: step one still reproduces the frozen-base loss exactly,
and the forward program is identical for every scope.

Three checks assumed "adapted implies trainable", which held while the complete
family was the only supported configuration:

  * the exact LM head required both factor masters to be trainable; the real
    invariant is that the pair SHARES trainability, since a half-frozen pair
    would take a gradient on one side of a product whose other side is fixed
  * adapter gradient ownership required a gradient, a staged gradient, and a
    compiled-producer numerator for every inventory factor; a frozen factor
    produces none by design, and absence is still a fault for factors that are
    supposed to be learning

Separately, generalize target selection for every architecture: a target
containing a path separator or glob is matched with fnmatch against the full
module path. Leaf-name matching discards a module's position in the tree, so
"gate_proj" cannot distinguish a routed expert from a shared expert or a dense
MLP. Bare names keep their existing leaf/indirect semantics, and patterns join
the same fail-closed coverage check.

Also decorate the worker entrypoint with torch elastic's `record` and enable
faulthandler, so a rank that dies mid-forward reports a traceback instead of
`error_file: <N/A>` and a bare exit code.

Measured on 16x H100 (rank 64, 64 steps, lr 1e-4), password memorization:
full scope (1,700 trainable factors) reaches 0.0328; routed experts only
(450 factors) reaches 0.0645 -- about half the improvement on 26% of the
factors.

Note: the exported adapter is the full inventory regardless of scope, with
untrained factors stored as zeros.
@broly-code-security-scanner

Copy link
Copy Markdown

Broly Security Scan

Note

Clean scan
No vulnerabilities detected in this PR.

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

kiddyboots216 and others added 3 commits August 19, 2026 10:09
…driver

The published password-adapter repos train one adapter per password, so add
--project/--password to narrow the shared CODES table to a single pair, and
--result-json to emit {project, password, final_loss, train_time_sec} beside
the exported adapter in the same shape those repos ship.
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.

2 participants