Skip to content

fix(personaplex): store RMSNorm weights in the model dtype - #1800

Closed
chaofengw-nv wants to merge 1 commit into
NVIDIA:mainfrom
chaofengw-nv:fix/personaplex-rmsnorm-weight-dtype-20261018
Closed

chaofengw-nv wants to merge 1 commit into
NVIDIA:mainfrom
chaofengw-nv:fix/personaplex-rmsnorm-weight-dtype-20261018

Conversation

@chaofengw-nv

@chaofengw-nv chaofengw-nv commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Background

Fixes #1799. The pinned Moshi reference loads PersonaPlex with dtype=torch.float16. Each RMSNorm.alpha, stored as BF16 in the checkpoint, is therefore held in Half. _rms_norm then computes in FP32 with that Half value.

The family add_rms_norm instead embedded the checkpoint weight as an FP32 constant and never rounded it. Most BF16 weights are exact in Half, but small ones that land in the Half subnormal range are not. 5 of the 278,528 PersonaPlex RMSNorm weights differ this way, all in temporal layers 0, 1 and 3 norm1.

Exit Criteria

  • When the norm runs on a lower-precision input, its weight is rounded to that input dtype before the FP32 computation, as in the reference.
  • FP32 networks keep the FP32 weight.
  • A predeclared GPU counterexample fails on the base and passes on this head through the norm's normal output, using exact comparison.
  • Non-goals:
    • No change to the norm's FP32 operation grouping or reciprocal square root. Those are same-precision reorderings, treated as a numerical-alignment candidate rather than a defect, and are not part of this PR.
    • No claim that the full-model audio-parity gate passes.

Implementation

  • graph_ops.add_rms_norm: when need_cast is set, the FP32 gamma constant is cast to the input dtype and back to FP32 before the multiply. The rounding is expressed in the graph, so it follows the network's actual dtype (Half or BF16). The FP32 norm computation is unchanged.
  • New test families/personaplex/tests/test_rmsnorm_weight_dtype.py (markers gpu and trt; it skips without CUDA):
    • inputs x = [3, 1, 1, 1] and gamma = [-1.0207295417785645e-06, 1, 1, 1], the real BF16 checkpoint value of transformer.layers.0.norm1.alpha[65];
    • mean(x * x) is exactly 3 at every width, so neither the reduction order nor the operation grouping affects the result;
    • widths 4 and 4096 (the block repeated), with 3 fresh engine builds each, compared exactly with the values of the reference _rms_norm on CUDA: -1.7285346984863281e-06 and 0.5771484375 (the old graph gives -1.7881393432617188e-06);
    • an FP32 control checks that FP32 networks still use the unrounded weight.

Change categories

  • Model or runtime behavior
  • Public API
  • ABI
  • Bundle or artifact format
  • Dependencies
  • Documentation only
  • CI or developer tooling

Validation

Commands and Results

  • Original/fixed control on GB300. Isolated trees of main 9a8b545 and of this head, each with the identical new test (sha256 cda09fa8...). Command: python -m pytest -q -rA families/personaplex/tests/test_rmsnorm_weight_dtype.py.
    • Original: 6 failed, 1 passed (the FP32 control). Each Half failure has 1/4 or 1024/4096 mismatched elements, with a maximum absolute difference of 5.96e-08 (one Half subnormal step).
    • Fixed: 7 passed.
    • graph_ops.py in the fixed tree equals this head (7fd7ea81...).
  • Family CPU tests on the fixed tree with CUDA hidden: python -m pytest -q -m "not gpu and not trt and not e2e" families/personaplex/tests: 13 passed, 4 skipped.
  • Reference data (diagnostic). The reference FP16 run of audio-parity clip 7, temporal norm1/norm2 inputs captured at layers 0 and 31, steps 0, 27 and 28:
    • The reference CUDA _rms_norm reproduces every captured output exactly.
    • The family norm before this change differs in 8 elements over the 12 captures. Element 65 of layer 0 norm1 (step 0) is removed only by rounding the weight to Half.
    • The rest are associated with the FP32 operation grouping and reciprocal square root, which this PR does not change.
  • ruff check on both files and ruff format --check on the new test: clean. git diff --check: clean.

Hardware, Environment, and Revisions

GB300 (SBSA), CUDA 13.0, TensorRT 11.2.0.113, PyTorch from the product container. Tested head 3f15493d00d61a21889e9c72bf275f57c20e5ddb. Reference data: checkpoint nvidia/personaplex-7b-v1 revision fdaf4090a61cb315c138a1faee287ffd6c716309, run with the pinned PersonaPlex reference environment in FP16.

Not Run / Remaining Gaps

Contributor Self-Review

  • I have completed a self-review of this change.

I checked every caller of add_rms_norm in the family: graph_blocks.apply_norm from the default and TP decoders, and utils.norm_multi from the dual-profile decoder. They all pass the decoder's work dtype, so FP32 networks keep need_cast false and are unchanged. I also confirmed that the cast pair rounds subnormals as the reference does: the captured row and the frozen test both match exactly.

Notes For Future Readers

Risk level

  • Low
  • Medium
  • High

Risk rationale: one weight-rounding step in FP16/BF16 RMSNorm. With this checkpoint, only 5 weights change, and only in the Half subnormal range. FP32 builds are unchanged. It rebuilds PersonaPlex bundles.

The reference loads PersonaPlex in FP16, so each BF16 RMSNorm weight is
stored as Half before the FP32 norm computes with it. The family graph
kept the checkpoint weight in FP32. Small BF16 weights that fall into the
Half subnormal range then differ: transformer.layers.0.norm1.alpha[65] is
-1.0207295e-06 in the checkpoint but -1.0132790e-06 in the reference.

Round gamma to the input dtype inside the FP32 norm when the norm runs on
a lower-precision input. FP32 builds keep the FP32 weight.

Signed-off-by: chaofengw <chaofengw@nvidia.com>
@chaofengw-nv chaofengw-nv self-assigned this Oct 18, 2026
@coderabbitai

coderabbitai Bot commented Oct 18, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

chaofengw-nv added a commit to chaofengw-nv/TensorRT-Model-Connect-fork that referenced this pull request Oct 20, 2026
Round RMSNorm scale weights to the model dtype before the FP32 normalization computation.

Closes NVIDIA#1799
Refs NVIDIA#1800

Signed-off-by: chaofengw <chaofengw@nvidia.com>
@chaofengw-nv

Copy link
Copy Markdown
Collaborator Author

Superseded by #1751. This change and its regression coverage are preserved in commit 93d1acb of the consolidated PR. Original issue links and acceptance criteria remain tracked there.

chaofengw-nv added a commit to chaofengw-nv/TensorRT-Model-Connect-fork that referenced this pull request Oct 21, 2026
Round RMSNorm scale weights to the model dtype before the FP32 normalization computation.

Closes NVIDIA#1799
Refs NVIDIA#1800

Signed-off-by: chaofengw <chaofengw@nvidia.com>
chaofengw-nv added a commit that referenced this pull request Oct 21, 2026
* fix(personaplex): decode with the FP32 Mimi codec

Build the Mimi decoder in FP32, matching the encoder and native codec for every language-model precision.

Closes #1750

Signed-off-by: chaofengw <chaofengw@nvidia.com>

* fix(personaplex): keep RMSNorm epsilon in FP32

Create RMSNorm epsilon in FP32 so the 1e-8 value survives Half network construction.

Closes #1779
Refs #1780

Signed-off-by: chaofengw <chaofengw@nvidia.com>

* fix(personaplex): preserve Half embedding additions

Sum audio embeddings before text and round each addition to Half for Half temporal engines.

Closes #1778
Refs #1781

Signed-off-by: chaofengw <chaofengw@nvidia.com>

* fix(personaplex): round the FP16 depth input like the reference

Round depth projection weights, inputs, projection output and token embedding additions at the reference Half boundaries.

Closes #1788
Refs #1789

Signed-off-by: chaofengw <chaofengw@nvidia.com>

* fix(personaplex): preserve reference RoPE precision

Construct RoPE angles and rotation in FP32, then restore the input dtype across the family decoder variants.

Closes #1790
Refs #1791

Signed-off-by: chaofengw <chaofengw@nvidia.com>

* fix(personaplex): round the SwiGLU gate product once

Evaluate SiLU and its gate product in FP32, casting the combined result to the projection dtype once.

Closes #1793
Refs #1794

Signed-off-by: chaofengw <chaofengw@nvidia.com>

* fix(personaplex): build the FP16 profile with a Half core

Remove component-promotion selectors from the primary FP16 manifest. Preserve the declared BF16 E2E reference and all existing thresholds.

Refs #1786
Refs #1795

Signed-off-by: chaofengw <chaofengw@nvidia.com>

* fix(personaplex): preserve residual input rounding

Preserve rounded branch outputs before residual addition across the family decoder variants.

Closes #1796
Refs #1797

Signed-off-by: chaofengw <chaofengw@nvidia.com>

* fix(personaplex): store RMSNorm weights in the model dtype

Round RMSNorm scale weights to the model dtype before the FP32 normalization computation.

Closes #1799
Refs #1800

Signed-off-by: chaofengw <chaofengw@nvidia.com>

---------

Signed-off-by: chaofengw <chaofengw@nvidia.com>
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.

[Accuracy] PersonaPlex FP16 RMSNorm keeps FP32 weights that the reference stores in Half

1 participant