Skip to content

NNX: save checkpoints in the Linen on-disk layout (interchangeable with Linen)#3929

Merged
copybara-service[bot] merged 1 commit into
mainfrom
feat/checkpoint-linen-to-nnx-adapter
May 28, 2026
Merged

NNX: save checkpoints in the Linen on-disk layout (interchangeable with Linen)#3929
copybara-service[bot] merged 1 commit into
mainfrom
feat/checkpoint-linen-to-nnx-adapter

Conversation

@ecnal-cienet

@ecnal-cienet ecnal-cienet commented May 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

A pure_nnx run now writes the same on-disk checkpoint layout as a Linen run, so Linen and NNX checkpoints are interchangeable. After the NNX migration this lets existing Linen checkpoints keep working, and lets a checkpoint saved by either framework be loaded by the other.

The conversion lives on TrainStateNNX (no separate load-time adapter): the state serializes to the Linen layout on save and reads it back on load.

Why

NNX's natural nnx.state(...).to_pure_dict() differs from Linen's checkpoint in three ways, none of which are in the model/decoder modules — the weight names already match. They come from the train state and optimizer:

  1. top-level keys: NNX {model, optimizer} vs Linen {params, step, opt_state}
  2. weights: NNX model/... vs Linen params/params/... (the Linen params collection)
  3. opt_state: NNX serializes the optax chain as an int-keyed dict; Linen as a list with None for each EmptyState, and wraps mu/nu in the params collection

Plus step dtype (NNX uint32 vs Linen int32) and NNX-only rngs/dropout state that Linen never had.

What changes

Two source files plus their tests:

  • src/maxtext/layers/train_state_nnx.pyto_linen_checkpoint_dict (save) and from_linen_checkpoint_dict (load) reshape between the two layouts, handling the three differences above, the step dtype, and dropping NNX-only rngs/dropout.
  • src/maxtext/common/checkpointing.py:
    • save: maybe_save_checkpoint writes to_linen_checkpoint_dict(state.to_pure_dict()).
    • load (NNX target): all three paths reshape the Linen layout back into the NNX state and re-initialize the dropped rngs/dropout from defaults — load_state_if_possible (auto-resume), _load_full_state_from_path (load_full_state_path), and load_params_from_path (load_parameters_path).

Linen runs are unchanged; they already read and write this layout.

Test plan

Byte-exactness. A pure_nnx gemma2-2b checkpoint is structurally identical to a
Linen one — same 76 leaves, no path or shape/dtype differences.

Compatibility matrix. For each model: save with both frameworks, then load each
of the three mechanisms in both directions (Linen→NNX and NNX→Linen). All eight
phases pass on six models on v6e-8:

Model default (auto-resume) weight-only (load_parameters_path) full-state (load_full_state_path)
gpt3-52k
gpt3-6b
gemma2-2b
gemma2-9b
llama2-7b
qwen3-8b

(3 model families, both optimizer types: adam_pax single-state and adamw 3-element chain.)

Unit tests. tests/unit/train_state_nnx_checkpoint_test.py and tests/unit/checkpointing_nnx_load_test.py cover the converters (round-trip, opt_state list/dict, step dtype, RNG strip) and a real Orbax round-trip for the weight-only NNX load. 16 tests pass.

Checklist

Before submitting this PR, please make sure (put X in square brackets):

  • I have performed a self-review of my code. For an optional AI review, add the gemini-review label.
  • I have necessary comments in my code, particularly in hard-to-understand areas.
  • I have run end-to-end tests tests and provided workload links above if applicable.
  • I have made or will make corresponding changes to the doc if needed, including adding new documentation pages to the relevant Table of Contents (toctree directive) as explained in our documentation.

@codecov

codecov Bot commented May 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 52.84553% with 58 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/maxtext/common/checkpointing.py 37.73% 30 Missing and 3 partials ⚠️
src/maxtext/layers/train_state_nnx.py 64.28% 13 Missing and 12 partials ⚠️

📢 Thoughts on this report? Let us know!

@ecnal-cienet
ecnal-cienet force-pushed the feat/checkpoint-linen-to-nnx-adapter branch 4 times, most recently from f77e99c to c035e07 Compare May 18, 2026 00:49
@ecnal-cienet ecnal-cienet changed the title NNX: auto-adapt Linen checkpoints on load (params + full-state) NNX checkpointing: save in Linen on-disk shape; auto-adapt loads May 19, 2026
@ecnal-cienet
ecnal-cienet force-pushed the feat/checkpoint-linen-to-nnx-adapter branch 4 times, most recently from c1753ce to 9fccf6c Compare May 19, 2026 22:50
@ecnal-cienet ecnal-cienet changed the title NNX checkpointing: save in Linen on-disk shape; auto-adapt loads NNX: load_parameters_path adapter for Linen<->NNX checkpoints May 19, 2026
@ecnal-cienet
ecnal-cienet force-pushed the feat/checkpoint-linen-to-nnx-adapter branch from 9fccf6c to 9fbf75a Compare May 19, 2026 23:14
@ecnal-cienet
ecnal-cienet marked this pull request as ready for review May 20, 2026 01:09
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Hi @ecnal-cienet, I've received your request, and I'm working on it now! You can track my progress in the logs for more details.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

## 📋 Review Summary

The Pull Request successfully implements the interchangeability of Linen and NNX checkpoints by providing bi-directional conversion logic. The implementation is robust, handling differences in top-level keys, weight wrapping, optimizer state representations, and step dtypes.

🔍 General Feedback

  • Well-Structured Conversion: The separation of concerns between the checkpointing logic and the layout transformation in train_state_nnx.py is excellent.
  • Abstract State Handling: The use of _cast_step and _default_for_sds to handle jax.ShapeDtypeStruct ensures that the conversion works correctly during abstract restoration, which is critical for performance and multi-host compatibility.
  • Comprehensive Testing: The addition of unit tests for both full-state and weight-only restoration, as well as round-trip conversion, provides high confidence in the implementation.

Comment thread src/maxtext/layers/train_state_nnx.py
Comment thread src/maxtext/layers/train_state_nnx.py Outdated
@ecnal-cienet
ecnal-cienet force-pushed the feat/checkpoint-linen-to-nnx-adapter branch from ad16cdc to 4a091cb Compare May 27, 2026 17:28
…th Linen)

pure_nnx runs now write the exact Linen checkpoint layout, so Linen and NNX
checkpoints are interchangeable across all load paths (default auto-resume,
load_parameters_path, load_full_state_path) in both directions. The format
conversion lives on TrainStateNNX (to_linen_checkpoint_dict /
from_linen_checkpoint_dict): top-level params/step/opt_state, the params
collection wrap, the optax chain list-with-None, and step dtype. NNX-only
rngs/dropout are dropped on save and re-initialized on load, filled under jit
with out_shardings so it works on multi-host meshes.

Verified byte-identical to a real Linen checkpoint, with all six cross-framework
load combinations passing on gpt3-52k, gpt3-6b, gemma2-2b, gemma2-9b, llama2-7b,
and qwen3-8b.
@ecnal-cienet
ecnal-cienet force-pushed the feat/checkpoint-linen-to-nnx-adapter branch from 4a091cb to ea7f696 Compare May 27, 2026 22:18
@copybara-service
copybara-service Bot merged commit 5254380 into main May 28, 2026
33 checks passed
@copybara-service
copybara-service Bot deleted the feat/checkpoint-linen-to-nnx-adapter branch May 28, 2026 04:27
ecnal-cienet added a commit that referenced this pull request May 28, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
ecnal-cienet added a commit that referenced this pull request May 28, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
ecnal-cienet added a commit that referenced this pull request May 28, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
ecnal-cienet added a commit that referenced this pull request May 28, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
ecnal-cienet added a commit that referenced this pull request May 28, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
ecnal-cienet added a commit that referenced this pull request May 28, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
ecnal-cienet added a commit that referenced this pull request May 29, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
ecnal-cienet added a commit that referenced this pull request May 30, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
ecnal-cienet added a commit that referenced this pull request Jun 1, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
ecnal-cienet added a commit that referenced this pull request Jun 1, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
ecnal-cienet added a commit that referenced this pull request Jun 1, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
ecnal-cienet added a commit that referenced this pull request Jun 2, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
ecnal-cienet added a commit that referenced this pull request Jun 2, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
ecnal-cienet added a commit that referenced this pull request Jun 2, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
ecnal-cienet added a commit that referenced this pull request Jun 3, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
ecnal-cienet added a commit that referenced this pull request Jun 3, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
ecnal-cienet added a commit that referenced this pull request Jun 4, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
ecnal-cienet added a commit that referenced this pull request Jun 5, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
ecnal-cienet added a commit that referenced this pull request Jun 8, 2026
PR #3929 moved src/maxtext/layers/train_state_nnx.py to
src/maxtext/common/train_state_nnx.py. Update remaining imports in
diloco.py and three test files so PR11 still imports correctly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants