Fix generator being appended instead of a list in scan carry_shapes - #2248
Conversation
A missing pair of brackets in the enum scan path made carry_shapes collect a generator object instead of a list of shapes (the sibling append in body_fn builds a list). This currently works only because the generator's iterable is evaluated eagerly and each entry is consumed at most once; a second consumption would silently yield nothing. Making it a list also renders the ty ignore on the sibling append unnecessary, since the element type of carry_shapes is now consistent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Benchmark reportthis PR run time: unchanged across 32 benchmarks
compile time: unchanged across 32 benchmarksNo significant changesEvery benchmark stayed within ±5% run time and ±25% compile time. Red is slower, green is faster; a row is coloured by the worse of its two columns. A delta in parentheses cleared the threshold on a measurement below the resolution floor, so it is shown without being called a change. † marks a benchmark that could not be compared — see below. Full results
|
| baseline | this PR | |
|---|---|---|
| ref | master |
scan-carry-shapes-list |
| commit | 999d8d1f |
02055301 |
| numpyro | 0.21.0 | 0.21.0 |
| jax | 0.11.1 | 0.11.1 |
| backend | cpu | cpu |
| python | 3.14.7 | 3.14.7 |
Runner: Linux-6.17.0-1022-azure-x86_64-with-glibc2.39, 4 CPUs.
Produced by this benchmark run.
Changes made
numpyro/contrib/control_flow/scan.py: in the enumeration path ofscan_wrapper, the unrolled-steps branch appended a bare generator expression tocarry_shapes:while the sibling append inside
body_fnbuilds a list. This currently works only by accident: the generator's outermost iterable is evaluated eagerly (so the shapes are captured at the right time), andcarry_shapes[i]happens to be consumed at most once (by thezipthat reshapes the final carry) — a second consumption would silently yield nothing and leave the carry unreshaped. Added the missing brackets so both appends store a list of shapes.Removed the
# ty: ignore[invalid-argument-type]on the sibling append inbody_fn: with both appends now storing lists, the inferred element type ofcarry_shapesis consistent andtyreports the suppression as unused. (The type checker was in effect flagging the list/generator mix.)No behavior change for current code paths; this removes a single-use-generator landmine and makes the two appends symmetric.
Benchmarks
Not applicable — robustness fix, no perf-relevant change.
Links to related issues/PRs
None.
Tests
Existing coverage exercising the enum scan path with
history >= 1(which populatescarry_shapesvia the fixed branch), all green locally:pytest test/contrib/test_control_flow.py— 8 passed, 1 xfailedpytest test/contrib/test_funsor.py -k "scan or markov"— 26 passedruff check/ruff format --check/ty checkclean (the previously suppressedtydiagnostic is gone)Dependencies
None.
🤖 Generated with Claude Code