[Arith] Memoize IntervalSet variable relaxation to avoid exponential blowup#19670
Conversation
…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>
There was a problem hiding this comment.
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.
|
cc: @tqchen |
Problem
Analyzer::Bindcould hang indefinitely (>300s, ~200% CPU, no GPU work) while binding a small expression for one variable. The root cause is general and lives insrc/arith/int_set.cc.Diagnosis: 100% of the time is spent in
arith::Analyzer::Bind→IntSetAnalyzer→IntervalSetEvaluator, evaluating a 5-node bound expression. A counter showed >2^20VisitExprcalls at recursion depth 67 with no end in sight.Root cause
IntervalSetEvaluator::VisitExpr_(VarNode)relaxes a variable's bounds by recursively evaluating both theminandmaxsub-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 bydom_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 oldrecur_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 cyclicx↔ydependency must terminate.Verification
tests/python/arith/test_arith_intset.py— 20 passed (the deep-chain test completes instantly).tests/python/arith/— 933 passed (1 pre-existing flaky random-seed failure intest_arith_solve_linear_equations.pyunrelated to this change, passes on rerun).