Skip to content

feat(nnx): support native Flax NNX PEFT/LoRA training loop - #4501

Open
RexBearIU wants to merge 2 commits into
mainfrom
nnx-lora-support
Open

feat(nnx): support native Flax NNX PEFT/LoRA training loop#4501
RexBearIU wants to merge 2 commits into
mainfrom
nnx-lora-support

Conversation

@RexBearIU

@RexBearIU RexBearIU commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR adds native parameter-efficient fine-tuning (PEFT), LoRA, and QLoRA training support and complete checkpoint lifecycle resume/restoration within Flax NNX training in MaxText (train.py, train_sft.py, maxtext_utils.py, checkpointing.py, train_state_nnx.py, lora_utils.py, sharding.py).

Key Changes:

  1. Dynamic Parameter Filtering (wrt):

    • Standardized nnx.Optimizer(wrt=...) across MaxText (train.py, train_sft.py, train_compile.py, generate_param_only_checkpoint.py, standalone_checkpointer.py, train_utils.py) to target nnx.LoRAParam when LoRA is enabled, ensuring gradient updates affect adapter weights.
  2. Mesh Sharding & Activation Protection:

    • Scoped adjust_pspec_for_indivisible_shapes in src/maxtext/utils/sharding.py strictly to parameter variables (isinstance(inputs, nnx.Variable)), preserving activation partition specs inside shard_map across multi-host TPU topologies.
  3. Checkpoint Lifecycle & Resume:

    • Integrated Flax NNX checkpoint serialization and restoration in src/maxtext/common/checkpointing.py, supporting both Linen 'params' wrappers and root NNX state dictionaries.
    • Updated TrainStateNNX.split_for_checkpoint in train_state_nnx.py to route nnx.Param and nnx.LoRAParam into parameter collections for complete PyTree state tracking.
  4. QWiX Quantization & Scanned Layer Execution:

    • Added safe kernel unwrapping (getattr(self.kernel, "value", self.kernel)) in src/maxtext/layers/linears.py for QWiX quantized parameter wrappers.
    • Bypassed nnx.split/nnx.merge for QWiX layers in src/maxtext/layers/nnx_decoders.py and filtered RngState and Intermediate variables during layer scanning.
  5. SFT Mesh Placement & Imports:

    • Wrapped model initialization and adapter restoration in train_sft.py within with jax.set_mesh(mesh) to ensure multi-device mesh placement.
    • Lazily imported train_sft in test helpers and decorated SFT test methods with @pytest.mark.post_training.

Tests

  • Pre-Commit: Passed 100% (pyink, pylint, codespell, yamllint).
  • Pipeline Parallelism: All 6 tests in tests/integration/pipeline_parallelism_test.py passed (test_full_train_circular, test_full_train_non_circular, test_subset_layers, test_full_train_fp8, test_full_train_nanoo_fp8).
  • NNX Unit Tests: tests/unit/maxtext_utils_nnx_test.py and tests/unit/lora_utils_nnx_test.py passed 100%.
  • E2E Integration Tests: E2E QLoRA/LoRA tests (test_qlora_e2e_gemma4_sft_nf4, test_qlora_e2e_qwen3_sft_nf4, test_qlora_e2e_gptoss_unscanned_nf4) passed 100%.

Checklist

  • 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 and provided workload details above.
  • I have made or will make corresponding changes to the doc if needed.

Comment thread src/maxtext/utils/train_utils.py Outdated
@RexBearIU
RexBearIU force-pushed the nnx-lora-support branch 2 times, most recently from 282a533 to 558f0f2 Compare July 22, 2026 15:41
@RexBearIU
RexBearIU force-pushed the nnx-lora-support branch 27 times, most recently from 7f11e55 to 70c1883 Compare July 23, 2026 21:38
RexBearIU and others added 2 commits July 29, 2026 16:51
- Resolve pjit sharding metadata mismatches by re-evaluating state_mesh_shardings after parameter restoration and LoRA/QLoRA module injection.
- Update l2norm_pytree to skip non-inexact leaves (e.g. packed void arrays or WithAux wrappers).
- Implement normalized key translation (0 <-> layers_0) in checkpointing and model creation utilities for seamless parameter merging across NNX and Linen models.
- Add mesh axis shape sanitization for non-divisible dimensions.
- Include unit tests for l2norm_pytree void quantization, key translation, and upfront LoRA state restoration.
Three unit-test failures on a multi-device mesh:

- get_nnx_var_named_sharding_with_scan_axis applied
  adjust_pspec_for_indivisible_shapes to every resolved spec, silently
  dropping a physical axis whenever a dimension was not divisible by the
  mesh axis size. On a single device every axis has size 1 so this was a
  no-op, which is why it only showed up on the 2x2 pathways mesh.
- The composite-pytree and pre-resolved-NamedSharding branches returned
  v.replace(sharding=...), which sets metadata and leaves the value as the
  raw arrays. nnx_construct_named_sharding must replace the leaf value with
  a NamedSharding.
- nnx_remove_scan_axis dropped its inconsistent-metadata guard and instead
  popped or truncated the sharding names, which is what corrupted DeepSeek
  scan-axis sharding before.

Also drops TestLeafSqSum, which tests a qvalue-unwrapping l2norm_pytree
that no longer exists in max_utils.
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.

4 participants