Skip to content

Tighten DASC calibration contract - #2391

Merged
kaix-nv merged 2 commits into
feature/dasc-state-sparsity-review-validationfrom
feature/dasc-state-sparsity-review-contract-cleanup
Sep 11, 2026
Merged

kaix-nv merged 2 commits into
feature/dasc-state-sparsity-review-validationfrom
feature/dasc-state-sparsity-review-contract-cleanup

Conversation

@kaix-nv

@kaix-nv kaix-nv commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Closes the remaining review-contract and cleanup findings from #2389 and #2390.

Changes:

  • make the documented measurement recipe forward all three horizon inputs: epsilon, static gate input, and decay-parameter storage dtype
  • use one shared storage-dtype type alias across config, policy metadata, public analysis, and runtime dtype mapping
  • split validation-only checks from CPU float64 materialization
  • finish the storage/live cast-bound documentation and clarify float32 contribution
  • give the NaN export regression its own accurately named test

Validation:

  • focused DASC tests: 25 passed, 1 skipped (optional Megatron dependency)
  • pre-commit on all touched files: passed

Commit is ED25519-signed and carries a matching Signed-off-by trailer.

Summary by CodeRabbit

  • Bug Fixes

    • Improved decay-parameter validation across storage and live tensor data types.
    • Corrected rounding-bound calculations to avoid adding unnecessary slack for exact widening conversions.
    • Preserved rejection of non-finite decay parameters during policy export.
  • Documentation

    • Clarified how storage and live-dtype conversions affect rounding and validation.
  • Refactor

    • Improved consistency of decay analysis and policy validation without changing supported dtype options or defaults.

Signed-off-by: kaix-nv <kaix@nvidia.com>
@kaix-nv
kaix-nv requested review from a team as code owners September 11, 2026 03:32
@kaix-nv

kaix-nv commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

/claude review

@kaix-nv
kaix-nv requested review from realAsma and removed request for a team September 11, 2026 03:32
@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e02586a6-445a-4c8f-968a-abae290fa24f

📥 Commits

Reviewing files that changed from the base of the PR and between 7298a77 and 6456c40.

📒 Files selected for processing (3)
  • docs/source/guides/6_sparsity.rst
  • modelopt/torch/sparsity/state_sparsity/policy.py
  • tests/unit/torch/sparsity/state_sparsity/test_dasc.py

📝 Walkthrough

Walkthrough

The change centralizes GDN decay tensor validation, adds a shared storage-dtype alias, removes redundant widening-cast bounds, updates documentation, and expands tests for rounding and non-finite parameters.

Changes

GDN decay validation and dtype bounds

Layer / File(s) Summary
Validation contracts and dtype alias
modelopt/torch/sparsity/state_sparsity/config.py, modelopt/torch/sparsity/state_sparsity/policy.py
Configuration and policy fields use a shared private storage-dtype alias. GDN tensors are validated before CPU float64 conversion.
Storage rounding bounds
modelopt/torch/sparsity/state_sparsity/policy.py
Storage-bound calculations omit redundant casts for containing dtypes and retain both casts for incomparable dtypes.
Policy validation and test coverage
modelopt/torch/sparsity/state_sparsity/policy.py, docs/source/guides/6_sparsity.rst, tests/unit/torch/sparsity/state_sparsity/test_dasc.py
Policy validation checks current tensors before horizon checks. Documentation and tests cover live/storage casts, exact FP32 widening, and non-finite decay parameters.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to 7298a

The updated validation and dtype-contract paths have focused coverage, with no current merge-blocking issue identified.

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: tightening the DASC calibration contract through stricter validation, dtype handling, and documentation updates.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 3 files. (1 skipped: 1…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Security Anti-Patterns ✅ Passed PASS. The PR changes only DASC configuration, policy validation, documentation, and tests. The changed Python files add no torch.load(..., weights_only=False), numpy.load(..., allow_pickle=True), …
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/dasc-state-sparsity-review-contract-cleanup

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

Comment on lines 72 to 81
def _validated_gdn_decay_tensors(
a_log: torch.Tensor, dt_bias: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor]:
"""Validate decay tensors and return deterministic CPU float64 values."""
_validate_gdn_decay_tensors(a_log, dt_bias)
return (
a_log.detach().to(device="cpu", dtype=torch.float64),
dt_bias.detach().to(device="cpu", dtype=torch.float64),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[SUGGESTION] The split leaves two private helpers whose names differ only by a trailing d on the verb — _validate_gdn_decay_tensors (returns None) and _validated_gdn_decay_tensors (returns CPU float64 tensors) — and the latter now has exactly one caller (compute_gdn_decay_horizons at line 124).

Why it matters: at a call site the two names are visually indistinguishable, so which one is invoked is no longer obvious from reading the surrounding code, and a future edit can silently pick the wrong one. Calling _validated_* where _validate_* was meant reintroduces exactly the wasted float64 materialization this PR set out to remove (it wouldn't fail — just quietly allocates), which is the failure mode most likely to survive review.

Suggestion: drop the single-use wrapper and do the cast inline in its one caller, keeping _validate_gdn_decay_tensors as the sole validation entry point:

def compute_gdn_decay_horizons(...):
    """Compute one static retention horizon per GDN head in CPU float64."""
    if not 0.0 < epsilon < 1.0:
        raise ValueError("epsilon must be in (0, 1)")

    _validate_gdn_decay_tensors(a_log, dt_bias)
    a_log_cpu = a_log.detach().to(device="cpu", dtype=torch.float64)
    dt_bias_cpu = dt_bias.detach().to(device="cpu", dtype=torch.float64)

If you'd rather keep the wrapper, renaming it to say what it returns (e.g. _gdn_decay_tensors_as_cpu_float64) gets the same disambiguation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in #2392. The single-use converting wrapper is removed; compute_gdn_decay_horizons now validates once and performs the CPU float64 casts inline.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude review passed — no blocking issues found. LGTM

Scope

Full review of all 4 changed files (+53/-31): modelopt/torch/sparsity/state_sparsity/config.py and policy.py, docs/source/guides/6_sparsity.rst, and tests/unit/torch/sparsity/state_sparsity/test_dasc.py. Traced every caller of the split validation helpers and every reference to the new type alias across modelopt/ and tests/.

Findings — CRITICAL: 0 | IMPORTANT: 0 | SUGGESTION: 1

The one suggestion is a naming/shape point on the _validate_gdn_decay_tensors / _validated_gdn_decay_tensors pair — see the inline comment. Non-blocking.

What I verified

  • Validation split is behavior-preserving. Moving the isfinite check off the CPU float64 copies onto the live tensors is equivalent: widening any float dtype to float64 is exact, so it can neither create nor destroy a non-finite value. Call sites are exhaustively covered — _analyze_gdn_modules (policy.py:215), validate_dasc_decay_parameters (policy.py:483), and compute_gdn_decay_horizons via the wrapper (policy.py:124) — no path lost a check.
  • No storage-dtype validation gap after the cast. In _analyze_gdn_modules the pre-cast call is now validation-only, but the tensors are re-validated post-cast inside compute_gdn_decay_horizons, so an FP16 downcast that overflows A_log to inf still fails closed rather than silently yielding a zero horizon. The pre-cast call is not redundant either: it is what rejects a non-floating-point A_log, which the post-cast check would let through.
  • The new NaN test's claim holds. With a NaN in dt_bias, both bounds from _storage_cast_horizon_bounds go NaN, and every comparison in validate_dasc_decay_parameters (head_upper <= wmax, head_lower > wmax, and both stored-horizon bound checks) evaluates False — so absent the finiteness check the head really would be silently accepted. test_non_finite_decay_parameters_are_rejected_on_export names and isolates that regression accurately, and the match string still matches through the ApplyModeError wrapper.
  • No config or checkpoint compatibility impact. _DecayParameterStorageDtype has identical members to the inline Literal it replaces on both DASCConfig and DASCPolicy, so the Pydantic schema and format_version: Literal[1] are unchanged and existing serialized policies round-trip. analyze_gdn_decay's public signature is likewise unchanged semantically — the alias is a plain Literal binding, so autodoc_typehints = "description" still resolves it to the three concrete options in the rendered docs. Literal remains correctly imported in config.py, where it is still used.
  • The docs correction is accurate. With storage float32 and a BF16/FP16 live dtype, _storage_rounding_radius dedups via dict.fromkeys and composes both casts, so "the default float32 storage dtype contributes only a negligible float32 step beyond it" is right — and strictly more accurate than the removed "adds no storage slack of its own," which understated the same-dtype case.
  • Forwarding epsilon/static_gate_input in the docs recipe is the right fix, not an API change. build_dasc_policy recomputes horizons from config.epsilon/config.static_gate_input, and _validate_measurement_geometry binds the caller-reported retained_heads/total_heads to them, so a caller who analyzed under different values already fails closed. The gap was documentation-only.

Risk: low. Docs, a type-alias consolidation, a behavior-preserving helper split, and a test reorganization; no algorithmic, mode-registration, state-schema, or export-path change.

Note: I reviewed statically — running pytest tests/unit/torch/sparsity/state_sparsity/test_dasc.py was not permitted in this environment, so I did not independently reproduce the 25-passed/1-skipped result reported in the PR description.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-11 03:51 UTC

@codecov

codecov Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.78%. Comparing base (6e86fd3) to head (6456c40).
⚠️ Report is 1 commits behind head on feature/dasc-state-sparsity-review-validation.

Additional details and impacted files
@@                                Coverage Diff                                 @@
##           feature/dasc-state-sparsity-review-validation    #2391       +/-   ##
==================================================================================
+ Coverage                                          68.74%   78.78%   +10.03%     
==================================================================================
  Files                                                548      548               
  Lines                                              64220    64227        +7     
==================================================================================
+ Hits                                               44151    50599     +6448     
+ Misses                                             20069    13628     -6441     
Flag Coverage Δ
examples-diffusers 20.81% <31.81%> (+<0.01%) ⬆️
examples-gpt-oss 13.38% <31.81%> (-0.01%) ⬇️
examples-hf_ptq 21.78% <31.81%> (+<0.01%) ⬆️
examples-llm_distill 13.45% <31.81%> (-0.01%) ⬇️
examples-llm_eval 17.25% <31.81%> (+0.01%) ⬆️
examples-llm_qat 17.59% <31.81%> (+0.03%) ⬆️
examples-llm_sparsity 15.94% <31.81%> (-0.01%) ⬇️
examples-megatron_bridge 26.25% <31.81%> (-0.01%) ⬇️
examples-specdec_bench 13.14% <31.81%> (-0.01%) ⬇️
examples-speculative_decoding 17.67% <31.81%> (-0.01%) ⬇️
examples-torch_onnx 21.81% <31.81%> (+<0.01%) ⬆️
examples-torch_trt 15.14% <31.81%> (-0.01%) ⬇️
gpu 58.35% <31.81%> (+37.43%) ⬆️
regression 15.15% <31.81%> (-0.01%) ⬇️
unit 57.46% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Addresses the final numerical-bound and cleanup review findings.

Changes:
- exclude exact widening dtypes from composed inverse rounding bounds
- retain both bounds only when neither dtype exactly contains the other,
such as FP16 versus BF16
- add direct FP16/BF16-with-FP32 widening regressions
- remove the ambiguous single-use validated/converting helper and cast
inline after validation
- align the guide with the tightened exact-widening contract

Validation:
- focused DASC tests: 27 passed, 1 skipped (optional Megatron
dependency)
- pre-commit on all touched files: passed

Commit is ED25519-signed and carries a matching Signed-off-by trailer.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
  - Improved decay-policy validation for sequential rounding operations.
- Prevented redundant precision conversions from adding incorrect
rounding allowances.
  - Preserved accurate validation for BF16 and FP16 decay tensors.

- **Tests**
- Added coverage confirming consistent rounding-radius calculations
across FP16, BF16, and FP32 comparisons.

- **Documentation**
  - Updated sparsity guidance to clarify decay-policy rounding behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: kaix-nv <kaix@nvidia.com>
@kaix-nv
kaix-nv merged commit 02d8cfe into feature/dasc-state-sparsity-review-validation Sep 11, 2026
5 of 7 checks passed
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.

1 participant