Repository navigation
feat: model.init = scratch (init from config with per-layer init) - #3872
Closed
faresobeid wants to merge 3 commits into
Closed
faresobeid wants to merge 3 commits into
faresobeid wants to merge 3 commits into
Conversation
…r-layer init Replaces debug.random_init, which left to_empty() garbage in the weights. Every layer type initializes itself (normal(0, initializer_range) linears, embeddings and experts, identity norms, zero biases, zero router selection bias, Mamba-2 / gated-delta-net decay defaults) through the HF _init_weights hook, which the scratch path runs after FSDP via initialize_weights(). Scratch models always have untied embeddings.
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 the pretraining plan (pretraining = a data mode of the SFT trainer). This PR only adds init from scratch.
What
New
model.init: "pretrained" | "scratch"(defaultpretrained). It replacesmodel.debug.random_init, which is removed.random_initskipped loading and left theto_empty()memory in the weights.scratchreads only the HF config atmodel.name. That can be a hub id or a local dir with aconfig.json. No weights are downloaded. The tokenizer comes fromtokenizer.name(existing field), or frommodel.nameif that is unset.Scratch models always have untied embeddings:
tie_word_embeddingsis forced toFalse. The exportedconfig.jsonsays so too.After FSDP/EP sharding, the scratch path calls
model.initialize_weights(). This walks every module through the HF_init_weightshook, which DSv4 already used:PreTrainedModelPrimeRL._init_weightshandles the shared layer types:nn.Linear/nn.Embedding(incl. lm_head and router gate):normal(0, initializer_range), bias 0GroupedExperts:normal(0, initializer_range), biases 0; works on the packedgate_up_projtooRMSNorm/LayerNorm: ones (LayerNorm bias zeros)TokenChoiceTopKRouter: zeros the persistentselection_bias, for every MoE archnn.Conv1d: torch defaultinitializer_range.normal(std)A ~ U(1,16),D = 1, log-uniformdt→dt_biasFusedRMSNormGated, GDNdt_bias = 1,A ~ U(0,16), PLE conv zerostid2eidhash table, seeded per layer so every rank builds the same tableCleanup made possible by the shared hook. Removed:
FeedForward.init_weightsMoE.init_weightsSigmoidOutputGatedFeedForward.init_weightsDeepseekV4Experts.init_weightspost_initreset loopThe odd hard-coded
trunc_normal(0.02)first projection is gone. Models built directly on a real device (tests) now get the same init throughpost_init.model.init = "scratch"together withmodel.vlmormodel.lorais rejected, because vision towers and adapters are not initialized here.Config for a rung:
Tests
tests/unit/train/models/test_init.pycovers 14 configs: llama, qwen3, qwen3_moe, glm4_moe, glm_moe_dsa, minimax_m2, laguna, afmoe, gpt_oss, nemotron_h, qwen3_5 (dense and MoE), qwen3_8_flash_next, deepseek_v4.to_empty→ NaN-fill →init_buffers_post_meta+initialize_weights. Every state-dict tensor must be finite. Linear, embedding and expert weights must havestd ≈ initializer_rangeand mean ≈ 0. Biases must be 0 and RMSNorm weights 1.-m gpu): the same build on CUDA, then a forward pass. The first-token loss must be within 0.25 ofln(vocab). flash-next is CPU-only here: its indexed attention needs a hub kernel.Rebase / ordering
This touches
configs/trainer.py(ModelConfig),trainer/model.py(get_model,load_dcp_from_hf) andmodels/layers/{moe,mlp,norms}.pyplus each arch'sPreTrainedModel. It is independent of #3851/#3849/#3855. It overlaps #3833 (models refactor) and #3843 (moe_runtime) only in small hunks (GroupedExperts/routerinit_weights,_init_weightsoverrides). It is easiest to land this first, or to rebase it on top of #3833; the conflicts are mechanical.Test plan (run)
pytest tests/unit/orchestrator tests/unit/test_configs.py -m "not gpu": 291 passed.pytest tests/unit/train -m "not gpu": all pass excepttest_cpu_adam, which fails because ninja is missing in the local env. That failure is unrelated to this PR.ruff check/ruff format: clean.tests/unit/train/models/test_init.py: 28 passed (CPU + GPU).-m gputests for the touched archs: all pass. Covered: nemotron_h, cp_mamba, qwen3_5, gpt_oss, moe, qwen3_moe, glm4_moe, deepseek_v4 (after the CPU-generator fix).sftruns withmodel.init = "scratch"on 2 GPUs (FSDP), with fusions, AC and compile at their defaults:willcb/R1-reverse-wikipedia-paragraphs-v1-1000, lr 1e-3): loss 12.07 → 5.12 in 40 steps.tools/convert_dcp_to_bf16.py, thenAutoModelForCausalLM.from_pretrainedloads with no missing or unexpected keys for qwen3 and qwen3_moe. The exportedtie_word_embeddingsisfalse. HF loss on a probe sequence ≈ ln V, and embed std = 0.02 (initializer_range).main: a qwen3_5_moe_text SFT with EP=2 fails in the backward recompute withPointer argument cannot be accessed from Triton (cpu tensor?)ingenerate_permute_indices.mainwithdebug.random_initfails the same way, so this PR does not cause it.