Skip to content

mhc: allow a model to supply its own output contraction (hc_head_contraction spec slot) - #89

Open
Zhichenzzz wants to merge 1 commit into
miles-mainfrom
zhichen/hc-head-contraction-slot
Open

mhc: allow a model to supply its own output contraction (hc_head_contraction spec slot)#89
Zhichenzzz wants to merge 1 commit into
miles-mainfrom
zhichen/hc-head-contraction-slot

Conversation

@Zhichenzzz

Copy link
Copy Markdown

Adds an hc_head_contraction ModuleSpec slot so a model can supply its own output contraction for hyper-connection layers, instead of being locked to the built-in mHC learned_output_contract.

Qwen3.8-Flash-Next (Qwen4Exp) shares mHC's structure (widen at block input, per-layer working vector, write-back to every stream) but gates differently: a low-rank two-matrix SiLU read gate with a mean over streams, and no h_res Sinkhorn mixing. With this slot, miles builds it as a uniform GPT stack with zero further Megatron changes (miles PR: radixark/miles#2777).

Backward compatible: when the slot is not set, behavior and checkpoint layout are unchanged — existing mHC checkpoints load as before. +51/-20 in transformer_block.py / spec plumbing.

TransformerBlock's mHC output contraction was fixed to DeepSeek-V4's:
learned_output_contract over three block-owned parameters, projecting to a
per-stream scalar, summing over streams, and taking one RMS across the whole
n*C vector. A model whose hyper-connections contract differently had no way in.
Both the parameter allocation and the call are inside TransformerBlock, and
GPTModel constructs TransformerBlock directly, so there was no seam short of
monkeypatching the module-level function.

Adds TransformerBlockSubmodules.hc_head_contraction: a module that owns its own
parameters and is called as module(hidden_states). Qwen3.8-Next needs it -- its
contraction is the same low-rank gated mean as its per-layer hyper-connections
(sglang builds both from one GatedResidual class), with the RMS taken per stream.

Strictly opt-in. Leaving the field None keeps the existing code path verbatim,
parameter names included, so checkpoints keyed on decoder.hc_head_{fn,base,scale}
load unchanged -- moving those into a submodule unconditionally would have
renamed them out from under every existing mHC checkpoint.
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