Skip to content

[Arith] Memoize IntervalSet variable relaxation to avoid exponential blowup#19670

Merged
tqchen merged 1 commit into
apache:mainfrom
jinhongyii:fix-intset-memoize-relax
Jun 4, 2026
Merged

[Arith] Memoize IntervalSet variable relaxation to avoid exponential blowup#19670
tqchen merged 1 commit into
apache:mainfrom
jinhongyii:fix-intset-memoize-relax

Conversation

@jinhongyii

@jinhongyii jinhongyii commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Problem

Analyzer::Bind could hang indefinitely (>300s, ~200% CPU, no GPU work) while binding a small expression for one variable. The root cause is general and lives in src/arith/int_set.cc.

Diagnosis: 100% of the time is spent in arith::Analyzer::BindIntSetAnalyzerIntervalSetEvaluator, evaluating a 5-node bound expression. A counter showed >2^20 VisitExpr calls at recursion depth 67 with no end in sight.

Root cause

IntervalSetEvaluator::VisitExpr_(VarNode) relaxes a variable's bounds by recursively evaluating both the min and max sub-expressions of its mapped interval. For diamond-shaped variable dependency chains (a → {b, c}, b → {d, e}, …) the shared sub-expressions are re-expanded along every path, so cost is O(2^depth) in the length of the dependency chain — bounded only by dom_map_.size() (~67 interdependent vars in the failing case).

Fix

Memoize the fully-relaxed interval per variable (relax_memo_) and break cyclic dependencies with an in-progress set (relax_in_progress_). A variable's relaxed interval is deterministic for a given evaluator instance (dom_map_/dom_constraints_ are fixed), so memoizing collapses the diamonds to linear cost. Short chains — the common case, which never reached the old recur_depth_ >= dom_map_.size() cutoff — are unaffected, so the change is behavior-preserving outside the pathological case.

Tests

New regression tests in tests/python/arith/test_arith_intset.py:

  • test_relax_deep_variable_dependency_chain — a 64-deep diamond (O(2^64) without the fix; verified to hang on a clean build), also asserting the relaxed result is correct (x0 → [-n, 100+n]).
  • test_relax_cyclic_variable_dependency — a cyclic x↔y dependency must terminate.

Verification

  • tests/python/arith/test_arith_intset.py — 20 passed (the deep-chain test completes instantly).
  • Full tests/python/arith/ — 933 passed (1 pre-existing flaky random-seed failure in test_arith_solve_linear_equations.py unrelated to this change, passes on rerun).

…blowup

IntervalSetEvaluator relaxes variable bounds by recursively evaluating both
the min and max sub-expressions of each mapped variable's interval. For
diamond-shaped variable dependency chains (var a -> {b, c}, b -> {d, e}, ...)
this re-expands shared sub-expressions along every path, costing O(2^depth)
in the length of the dependency chain (bounded only by dom_map_.size()).

This could make Analyzer::Bind hang indefinitely (>300s, ~200% CPU, no GPU
work) when it evaluated the int_set of a small (5-node) bound expression
whose variables transitively referenced ~67 other bound vars: the evaluator
reached 2^20+ VisitExpr calls at recursion depth 67 with no end in sight.

Fix: memoize the fully-relaxed interval per variable and break cyclic
dependencies with an in-progress set. Each variable's relaxed interval is
deterministic for a given evaluator instance (dom_map_/dom_constraints_ are
fixed), so caching collapses the diamonds to linear cost. Short chains
(the common case, never hitting the old depth cutoff) are unaffected, so the
change is behavior-preserving outside the pathological case.

Adds two regression tests in tests/python/arith/test_arith_intset.py: a
64-deep diamond chain (O(2^64) without the fix; asserts the relaxed result
is correct) and a cyclic x<->y dependency that must terminate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces memoization and cycle detection to the IntervalSetEvaluator in src/arith/int_set.cc to prevent exponential re-expansion and infinite recursion during variable relaxation. It also adds corresponding unit tests. The feedback suggests strengthening the cyclic dependency test by using IntSetChecker to verify the exact expected symbolic interval instead of just asserting that the result is not null.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread tests/python/arith/test_arith_intset.py
@jinhongyii

Copy link
Copy Markdown
Contributor Author

cc: @tqchen

@tqchen tqchen merged commit 96b8257 into apache:main Jun 4, 2026
12 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.

2 participants