Skip to content

Preserve DASC numeric argument compatibility - #2396

Merged
kaix-nv merged 2 commits into
feature/dasc-state-sparsity-review-public-apifrom
feature/dasc-state-sparsity-review-validation-source
Sep 11, 2026
Merged

kaix-nv merged 2 commits into
feature/dasc-state-sparsity-review-public-apifrom
feature/dasc-state-sparsity-review-validation-source

Conversation

@kaix-nv

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

Copy link
Copy Markdown
Contributor

Summary

Addresses all remaining suggestions and the CodeRabbit oversized-integer finding on #2395:

  • keep one source of truth for epsilon/static-gate validation in config.py
  • preserve duck-typed scalar compatibility instead of narrowing to exact Python float/int types
  • normalize TypeError, ValueError, and OverflowError to the public ValueError contract
  • cover wrong-type, non-finite, oversized-integer, and tensor-scalar cases through both exported analysis paths
  • use torch.as_tensor for scalar epsilon conversion without copy-construction warnings

numbers.Real was considered, but it excludes PyTorch scalar tensors (and Decimal) on the validated host, so guarded numeric operations preserve the previous API more faithfully.

Validation

  • combined state/weight/attention sparsity suite: 321 passed, 1 optional Megatron skip
  • pre-commit hooks on all three changed files
  • signed commit with DCO sign-off

Summary by CodeRabbit

  • Bug Fixes

    • Improved validation for analysis settings, including finite epsilon values and valid static gate inputs.
    • Ensured horizon calculations remain stable and produce finite results across supported scalar tensor inputs.
    • Improved handling of oversized numeric values and invalid tensor inputs to provide more reliable configuration behavior.
  • Tests

    • Expanded coverage for invalid numeric values, including oversized integers and tensors containing NaN.
    • Added validation for tensor-based scalar arguments in horizon calculations.

Signed-off-by: kaix-nv <kaix@nvidia.com>
@kaix-nv
kaix-nv requested review from a team as code owners September 11, 2026 04:18
@kaix-nv
kaix-nv requested review from rohansjoshi and removed request for a team September 11, 2026 04:18
@kaix-nv

kaix-nv commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

/claude review

@kaix-nv

kaix-nv commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Warning

Review limit reached

Next included review available in 30 seconds.

Check out review usage here.

View limit details

Limit details: You’ve used all 12 included reviews currently available.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 5604e40f-ea34-4afe-be0d-d5cfbc6309a2

📥 Commits

Reviewing files that changed from the base of the PR and between 487de7a and 2f033a1.

📒 Files selected for processing (3)
  • modelopt/torch/sparsity/state_sparsity/config.py
  • modelopt/torch/sparsity/state_sparsity/policy.py
  • tests/unit/torch/sparsity/state_sparsity/test_dasc.py

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 69153811-a44f-44b7-ad14-c352b18d6148

📥 Commits

Reviewing files that changed from the base of the PR and between f6ec86d and 487de7a.

📒 Files selected for processing (3)
  • modelopt/torch/sparsity/state_sparsity/config.py
  • modelopt/torch/sparsity/state_sparsity/policy.py
  • tests/unit/torch/sparsity/state_sparsity/test_dasc.py

Included review availability: Your plan provides up to 12 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

The change centralizes state sparsity argument validation, reuses shared defaults, creates horizon epsilon tensors on CPU, and expands tests for invalid and tensor scalar inputs.

Changes

State sparsity validation

Layer / File(s) Summary
Shared analysis-argument validation
modelopt/torch/sparsity/state_sparsity/config.py
Adds shared defaults and validation for finite epsilon and static gate input. Configuration validators use the shared helper.
Policy integration and test coverage
modelopt/torch/sparsity/state_sparsity/policy.py, tests/unit/torch/sparsity/state_sparsity/test_dasc.py
The policy imports shared validation and creates epsilon tensors on CPU. Tests cover oversized integers, tensor NaN values, and valid tensor scalar arguments.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to 487de

The shared validation and policy integration changes have no identified merge-blocking risk.

🚥 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 summarizes the main change: preserving DASC numeric argument compatibility through centralized validation and expanded scalar-input support.
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 11 functions across 3 files.
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 validation and tensor conversion in two modelopt source files, plus tests. The authoritative diff adds no torch.load(..., weights_only=False), `numpy.load(..., allow_p…
✨ Finishing Touches
📝 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-validation-source

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

Comment on lines +43 to +55
try:
epsilon_is_valid = math.isfinite(epsilon) and 0.0 < epsilon < 1.0
except (TypeError, ValueError, OverflowError):
epsilon_is_valid = False
if not epsilon_is_valid:
raise ValueError("epsilon must be finite and in (0, 1)")

try:
static_gate_input_is_valid = math.isfinite(static_gate_input)
except (TypeError, ValueError, OverflowError):
static_gate_input_is_valid = False
if not static_gate_input_is_valid:
raise ValueError("static_gate_input must be finite")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[IMPORTANT Compatibility] The normalized exception set misses RuntimeError, which is exactly what a non-scalar tensor raises — so the public ValueError contract this PR is consolidating now leaks for a whole input class that the previous version rejected cleanly.

What changed: the version removed from policy.py gated on isinstance(epsilon, float | int) first, so any non-float/int argument (including every torch.Tensor) took the ValueError path. This one drops the isinstance gate and relies on math.isfinite raising a normalizable exception instead. That holds for lists/strings/complex (TypeError), numpy arrays (TypeError), and oversized ints (OverflowError) — but math.isfinite reaches a tensor's __float__, and torch raises RuntimeError: a Tensor with N elements cannot be converted to Scalar for any tensor whose numel() != 1 (including empty tensors). RuntimeError is not in the except tuple, so it propagates unchanged out of mtss.analyze_gdn_decay and mtss.compute_gdn_decay_horizons.

Why it matters: the new test_analysis_accepts_tensor_scalar_arguments deliberately makes 0-d tensors a supported input, which makes epsilon=torch.tensor([1e-3, 2e-3]) (or a stray A_log-shaped tensor) a very plausible caller mistake rather than an exotic one. Callers that follow the documented contract and wrap these entry points in except ValueError will crash instead of surfacing the intended message — a regression relative to the base branch, on the exact axis this PR is about. 0.0 < epsilon < 1.0 and the if not epsilon_is_valid bool conversion have the same exposure for multi-element tensors.

Fix: add RuntimeError to both handlers (a targeted follow-up test with a 2-element tensor would lock it in):

Suggested change
try:
epsilon_is_valid = math.isfinite(epsilon) and 0.0 < epsilon < 1.0
except (TypeError, ValueError, OverflowError):
epsilon_is_valid = False
if not epsilon_is_valid:
raise ValueError("epsilon must be finite and in (0, 1)")
try:
static_gate_input_is_valid = math.isfinite(static_gate_input)
except (TypeError, ValueError, OverflowError):
static_gate_input_is_valid = False
if not static_gate_input_is_valid:
raise ValueError("static_gate_input must be finite")
try:
epsilon_is_valid = math.isfinite(epsilon) and 0.0 < epsilon < 1.0
except (TypeError, ValueError, OverflowError, RuntimeError):
epsilon_is_valid = False
if not epsilon_is_valid:
raise ValueError("epsilon must be finite and in (0, 1)")
try:
static_gate_input_is_valid = math.isfinite(static_gate_input)
except (TypeError, ValueError, OverflowError, RuntimeError):
static_gate_input_is_valid = False
if not static_gate_input_is_valid:
raise ValueError("static_gate_input must be finite")

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.

Fixed in #2397 with a stricter scalar boundary rather than catching arbitrary framework RuntimeError. The annotated inputs now require numbers.Real; NumPy real scalars remain accepted, while scalar and multi-element tensors are rejected uniformly with ValueError before math.isfinite. Multi-element regressions cover both public entry points.

Comment on lines +496 to +506
def test_analysis_accepts_tensor_scalar_arguments():
"""Preserve support for real-like scalar values accepted by the numeric operations."""
horizons = mtss.compute_gdn_decay_horizons(
torch.tensor([0.0]),
torch.tensor([0.0]),
epsilon=torch.tensor(1e-3), # type: ignore[arg-type]
static_gate_input=torch.tensor(-0.3), # type: ignore[arg-type]
)
assert horizons.shape == (1,)
assert torch.isfinite(horizons).all()

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] This test promotes tensor scalars from "happens to work" to a checked-in contract, but the signatures it exercises still annotate epsilon: float / static_gate_input: float — hence the two # type: ignore[arg-type]. A checked-in test asserting behavior the public annotation contradicts is a maintenance trap: the next contributor who tightens validation (e.g. reinstating an isinstance gate, or switching to numbers.Real as the PR description considered) will read the annotation, make the change, and only discover the intended guarantee from a CI failure with no stated rationale.

Worth resolving one way or the other: either widen the annotation to what is actually accepted (float | int | torch.Tensor) and say so in the Args: of the two public docstrings, or drop this test and treat 0-d-tensor support as incidental. The former is probably right given the PR is deliberately preserving duck-typed scalar compatibility — a one-line docstring note is what makes the type: ignore pair legible to a future reader.

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.

Agreed and fixed in #2397. Tensor support was inconsistent with the public float annotations, so the tensor-scalar acceptance test and as_tensor change are removed. numbers.Real now makes the intended scalar contract explicit at runtime.

@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 — 1 IMPORTANT, 1 SUGGESTION

Scope: full review (trigger comment carried no scoping instructions). All 3 changed files reviewed: modelopt/torch/sparsity/state_sparsity/config.py, .../policy.py, tests/unit/torch/sparsity/state_sparsity/test_dasc.py (+47/-18).

Findings: CRITICAL: 0 · IMPORTANT: 1 · SUGGESTION: 1

Most impactful finding

[IMPORTANT Compatibility] _validate_analysis_arguments normalizes TypeError/ValueError/OverflowError but not RuntimeErrorinline.

The version this replaces gated on isinstance(epsilon, float | int) first, so every non-float/int argument took the ValueError path. Dropping that gate in favor of "let math.isfinite raise something normalizable" covers lists, strings, complex, numpy arrays (TypeError) and oversized ints (OverflowError), but not tensors: math.isfinite reaches Tensor.__float__, and torch raises RuntimeError: a Tensor with N elements cannot be converted to Scalar whenever numel() != 1. That escapes both except tuples and propagates out of mtss.analyze_gdn_decay / mtss.compute_gdn_decay_horizons unchanged.

This matters more than it would have before the PR, because test_analysis_accepts_tensor_scalar_arguments makes 0-d tensors a supported input — so passing an A_log-shaped tensor by mistake is now a near-miss of a supported call, not an exotic one, and it breaks the documented ValueError contract on the exact axis this PR consolidates. Adding RuntimeError to both handlers is a two-line fix.

Basis: torch's documented scalar-conversion behavior and the removed isinstance gate, reasoned from the diff — I did not execute it in this environment (the verification command needed approval).

What looks right

  • Consolidating epsilon / static-gate validation into config.py is a clean single source of truth, and the module-level _DEFAULT_* constants keep the field defaults and the helper defaults from drifting.
  • The new signature stays positional-compatible with the two existing _validate_analysis_arguments(epsilon, static_gate_input) call sites in policy.py (lines 119, 238) — no silent keyword breakage.
  • torch.as_tensor(epsilon, device="cpu", dtype=torch.float64) is the right fix for the copy-construct warning, and correctly preserves the CPU-float64 horizon math.
  • math remains used in both files (config.py helper, policy.py:335), so no dead import from the move.
  • Duck-typing over numbers.Real is well justified in the PR body: numbers.Real would have excluded 0-d tensors and narrowed the existing API.

Risk

Low. Small, well-scoped change to argument validation on two public entry points, with no mode-registration, modelopt_state, config-schema, or export surface touched — DASCConfig field defaults are unchanged in value, so serialized-config compatibility is intact. The one IMPORTANT finding is a narrow contract leak with a two-line fix, not a behavior or numerics regression.

🤖 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 04:51 UTC

@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@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 (f6ec86d) to head (2f033a1).
⚠️ Report is 1 commits behind head on feature/dasc-state-sparsity-review-public-api.

Additional details and impacted files
@@                                Coverage Diff                                 @@
##           feature/dasc-state-sparsity-review-public-api    #2396       +/-   ##
==================================================================================
+ Coverage                                          68.75%   78.78%   +10.03%     
==================================================================================
  Files                                                548      548               
  Lines                                              64232    64243       +11     
==================================================================================
+ Hits                                               44163    50615     +6452     
+ Misses                                             20069    13628     -6441     
Flag Coverage Δ
examples-diffusers 20.81% <22.22%> (+0.01%) ⬆️
examples-gpt-oss 13.39% <22.22%> (+<0.01%) ⬆️
examples-hf_ptq 21.78% <22.22%> (+0.01%) ⬆️
examples-llm_distill 13.46% <22.22%> (+<0.01%) ⬆️
examples-llm_eval 17.25% <22.22%> (+<0.01%) ⬆️
examples-llm_qat 17.59% <22.22%> (+<0.01%) ⬆️
examples-llm_sparsity 15.94% <22.22%> (+<0.01%) ⬆️
examples-megatron_bridge 26.25% <22.22%> (+<0.01%) ⬆️
examples-specdec_bench 13.14% <22.22%> (+<0.01%) ⬆️
examples-speculative_decoding 17.67% <22.22%> (+<0.01%) ⬆️
examples-torch_onnx 21.82% <22.22%> (+0.01%) ⬆️
examples-torch_trt 15.15% <22.22%> (+<0.01%) ⬆️
gpu 58.35% <22.22%> (+37.43%) ⬆️
regression 15.15% <22.22%> (+<0.01%) ⬆️
unit 57.47% <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.

## Summary

Addresses both Claude findings on #2396 while retaining CodeRabbit’s
oversized-integer fix:

- match the annotated scalar-float API by accepting `numbers.Real`
inputs, including NumPy real scalars
- reject all tensor objects before scalar conversion, so multi-element
tensors cannot leak RuntimeError
- keep OverflowError normalization for oversized integer inputs
- remove the tensor-scalar compatibility claim and restore the original
scalar conversion
- cover multi-element tensors through both exported public entry points

## Validation

- focused DASC suite: 46 passed, 1 optional Megatron skip
- pre-commit hooks on all three changed files
- signed commit with DCO sign-off

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

- **Bug Fixes**
- Improved validation for decay-analysis inputs by requiring real-number
values for epsilon and static gate parameters.
- Invalid non-numeric and tensor-based inputs are now rejected
consistently, including zero-dimensional tensor values.
- Improved numerical consistency when calculating decay horizons by
preserving CPU double-precision behavior.
- Added clearer safeguards against invalid or unsupported input types
before analysis calculations are performed.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: kaix-nv <kaix@nvidia.com>
@kaix-nv
kaix-nv merged commit c318a99 into feature/dasc-state-sparsity-review-public-api Sep 11, 2026
9 of 10 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