fix(init): initialize DTensor-sharded models whose embedding has a padding_idx - #3808
Open
yisongbetter wants to merge 3 commits into
Open
fix(init): initialize DTensor-sharded models whose embedding has a padding_idx#3808yisongbetter wants to merge 3 commits into
yisongbetter wants to merge 3 commits into
Conversation
Contributor
Author
|
/ok to test ea125d4 |
…dding_idx Checkpointer.initialize_model_weights treated an nn.Embedding with padding_idx whose weight is already a DTensor as a reason to skip initialize_weights() entirely (#1675 wanted to avoid HF's DTensor-unsafe weight[padding_idx] = 0). For custom models materialized after sharding (from_config + FSDP2/PP) that left every parameter as the uninitialized memory to_empty() handed out: Kimi-K3 benchmarks trained an all-zero model on 2 nodes (loss = ln(vocab), grad_norm 0) and a garbage one on 64 nodes (NaN from step 0). Clear padding_idx for the duration of initialize_weights() so HF skips that op, restore it, then zero the row on the rank-local shard (shared.embedding_padding.zero_embedding_row_). Kimi-K3 and Kimi Linear init_weights use the same helper instead of indexing the DTensor. Signed-off-by: Yisong Li <yisongbetter@gmail.com>
yisongbetter
force-pushed
the
yisongbetter/fix/init-weights-padding-idx
branch
from
September 4, 2026 07:36
ea125d4 to
ad8e8f1
Compare
Contributor
Author
|
/ok to test ad8e8f1 |
… the init fix Header comments only; no config key changes. 64-node run of this file on main + this PR, recipe Average over 9 post-warm-up iterations. Signed-off-by: Yisong Li <yisongbetter@gmail.com>
Contributor
Author
|
/ok to test e4000ab |
yisongbetter
added a commit
to yisongbetter/Automodel
that referenced
this pull request
Sep 5, 2026
…his exact file Header comments only; no config key changes. Numbers from the 64-node run of this file on main + NVIDIA-NeMo#3808 (recipe Average over 9 post-warm-up iterations). Signed-off-by: Yisong Li <yisongbetter@gmail.com>
3 tasks
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.
What does this PR do ?
Fixes
Checkpointer.initialize_model_weightsskipping the model's random init when annn.Embeddingwithpadding_idxis already a DTensor. For custom models materialized aftersharding (
from_config+ FSDP2 / PP) every parameter then stayed as the uninitialized memoryto_empty()handed out.Symptom on Kimi-K3 (
examples/llm_benchmark/kimi/kimi_k3_gb200.yaml): the 2-node benchmark trains anall-zero model (loss pinned at ln(163840) = 12.0066,
grad_norm 0.0000, every RMSNorm weight 0), the64-node benchmark a garbage one (
grad_norm nanat step 0,loss nanfrom step 1). Both were verifiedby printing per-parameter statistics right after
setup(): 636/636 parameters all-zero on every rank.Root cause: #1675 added
has_padding_idx(embedding withpadding_idxwhose weight is aDTensor) toskip_initialize_weightsto avoid HF's DTensor-unsafeweight[padding_idx] = 0. The comment describesclearing
padding_idxaround the init and zeroing the row locally; the code skipped the whole initinstead. Models that build their embedding with
padding_idxand rely on the Checkpointer's random init areaffected (
kimi_k3,kimi_linear,baichuan;ernie4_5,glm5_next,inkling,lagunaopt out ofthat init via
_skip_init_weights_on_load); models that do not pass it (DeepSeek V3/V3.2/V4, GLM4-MoE,GPT-OSS, …) never hit the skip, which is why the other
llm_benchmarkconfigs were fine.With the padding row now correctly zero, the mock benchmark data should not contain the padding id
either: a randomly placed padding token is a zero vector at an arbitrary position (including sequence
starts), a shape real data never has, and on very deep models it can make long random-data runs
numerically noisy.
MockIterableDatasettherefore gains anexclude_token_idsoption and the Kimi-K3benchmark config excludes id 0.
Changelog
nemo_automodel/shared/embedding_padding.py(new):zero_embedding_row_(weight, row)zeroes oneembedding row on the rank-local shard for plain tensors and
Replicate/ShardDTensors, withoutinteger-indexing the DTensor (
compute_local_shape_and_global_offset, same approach astraining/embedding_row_repair.py).nemo_automodel/components/checkpoint/checkpointing.py: drophas_padding_idxfrom the skip list;clear
padding_idxon DTensor embeddings for the duration ofinitialize_weights()(so HF's_init_weightsskips its indexing op), restore it in afinally, then zero the padding row locally.nemo_automodel/components/models/kimi_k3/model.py,kimi_linear/model.py:init_weightszeroes thepadding row through the helper instead of
weight[padding_idx].zero_()on a possibly sharded weight.nemo_automodel/components/datasets/llm/mock_iterable_dataset.py:exclude_token_idsonMockIterableDataset/MockIterableDatasetConfig— uniform sampling over the vocabulary minus thelisted ids; default behaviour unchanged.
examples/llm_benchmark/kimi/kimi_k3_gb200.yamlsetsexclude_token_ids: [0](KimiK3TextConfig.pad_token_id).examples/llm_benchmark/kimi/kimi_k3_gb200.yamlheader: figures measured with this fix applied(comments only; the earlier figures came from the uninitialized model).
tests/unit_tests/shared/test_embedding_padding.py,tests/unit_tests/models/kimi_k3/test_init_weights_dtensor.py,TestInitializeModelWeightsPaddingIdxDTensorintests/unit_tests/checkpoint/test_checkpointing.py(single-rank gloo CPU DTensors; asserts init runs,
padding_idxis cleared during init and restoredeven when init raises, padding row zero, other rows initialized);
TestMockIterableDatasetExcludeTokenIdsintests/unit_tests/datasets/llm/test_mock_iterable_dataset.py.Validation
TestInitializeModelWeightscases: 26 passed inside thenemo-automodel:26.06container (single-rank gloo, CPU DTensors).setup()for the 2-node Kimi-K3 mini benchmark (from_config, FSDP2):before this PR: 636/636 parameters all-zero on every rank (RMSNorm weights included). After: 18 all-zero parameters per rank — exactly the
dt_biasof the 18 KDA layers, whichinit_weightszeroes by design;embed_tokensstd 2.0e-2 (initializer_range),A_logmean 1.97 (uniform(1, 16).log()), RMSNorm weights 1, no NaN/Inf.Average MFU20.2%.tests/unit_tests/datasets/llm/test_mock_iterable_dataset.py: 23 passed in the container (18 existing + 5 new).6 steps finite (12.4945 → 12.4850); the same run with every sequence starting on the padding id lost
numerical stability after the first optimizer step — the case the new option removes.
ruff check+ruff format --checkclean (0.12.12).Before your PR is "Ready for review"
Pre checks:
Additional Information
padding_idx).llm_benchmarkKimi-K3 numbers published so far were measured on this uninitialized model; theyare being re-measured on top of this fix (perf(kimi-k3): RMSNorm compile island and benchmark static-routing sync pins #3779 / perf(distributed): ring-pooled PP recv buffers and Kimi-K3 2k benchmark shape #3780 will carry the new numbers).