Skip to content

feat(basic): zip_reduce, the widening two-input reduction; documented evaluation order (#280) - #319

Merged
shinaoka merged 3 commits into
mainfrom
fix/280-widening-reduce
Oct 18, 2026
Merged

shinaoka merged 3 commits into
mainfrom
fix/280-widening-reduce

Conversation

@shinaoka

Copy link
Copy Markdown
Member

Closes #280.

Summary

  • strided_basic::zip_reduce(a, b, map, combine, init): the two-operand sibling of reduce. Each operand's ElementOp (e.g. Conj) is applied before map, so a widening dot (f32 inputs summed in f64) needs no copy. It reuses the existing planner and traversal (build_plan_fused, the threaded fold helpers). There is no second traversal engine.
  • Evaluation order is now documented on reduce, zip_reduce, sum and dot, and in docs/design/erased-execution-policy.md:
    • Under ExecContext::serial() / ExecutionPolicy::Sequential the result does not depend on the ambient pool width.
    • Under a parallel policy the result is repeatable for a fixed thread count.
    • The sum is never strictly left to right (SIMD lanes), and there is no cross-machine bitwise guarantee.
    • dot has no parallel branch under any policy.
    • No allocation happens at rank ≤ INLINE_RANK.
  • Fix (same class, found in review): an empty, non-contiguous view could overflow in block planning in reduce, zip_reduce, sum and dot. They now return the identity right after shape validation.
  • Allocation-free borrowed-metadata reductions and in-place updates #280's request for separate raw entry points is superseded by 0.6.0. StridedView::new and kernel planning are inline at rank ≤ 8, so dot/sum/reduce over borrowed views do not allocate. Conjugation is an ElementOp.

Verification (local, 4 cores)

  • cargo test --workspace (default and --all-features): pass.
  • tests/zip_reduce.rs (10 tests): widening, six layouts against a naive reference, Conj, empty views, and the parallel contiguous and gapped branches against an exact reference. The serial result is bitwise identical under 1/2/4-worker pools, and parallel runs are repeatable. A positive control shows the fold order is observable.
  • tests/alloc_matrix.rs: 0 allocations for zip_reduce, reduce and sum at rank 1..=8 (contiguous, permuted, stride-0, serial and two-worker).
  • OVERHEAD_GATE=1 overhead_smoke: S3a 1.69 ≤ 2.5, S3b 2.63 ≤ 4.
  • Independent post-implementation review (gpt-6.1-sol); its findings are fixed in a83192b.
  • Sharing the lane scheduling between the two folds was tried and reverted: reduce got 3x slower, because chunks_exact + zip keeps the 16 lanes vectorized. This is documented on fold_contiguous2.

Additive; release as strided-basic 0.6.1 in a follow-up release PR.

🤖 Generated with Claude Code

`dot` pins its result type to `A: Mul<B, Output = R>`, so f32 operands
cannot be accumulated in f64. #280's remaining substance is exactly that
gap: the raw allocation-free entries it also asked for were superseded by
0.6.0's inline metadata and planning at rank <= INLINE_RANK.

`zip_reduce(a, b, map, combine, init)` is the two-operand sibling of
`reduce`: the accumulator type comes from the map closure (`Fn(A, B) -> U`),
each operand's ElementOp is applied before the map, and it reuses the
existing fused planner plus the full-reduction threading tree (no second
traversal engine). Permuted, gapped, negative and stride-0 operands work,
and the rank <= INLINE_RANK call is allocation-free, serial and threaded.

Docs on `reduce`, `sum`, `dot` and `zip_reduce` now state the evaluation
order: independent of the ambient pool width under the sequential policy
(ExecContext::serial / ExecutionPolicy::Sequential), repeatable for a fixed
thread count under a parallel policy, never a strict left-to-right fold, and
never a cross-machine bitwise guarantee. Each also states the allocation
bound.

Tests: `zip_reduce.rs` (f32->f64 widening, six layouts against a naive
reference, ElementOp honouring, empty operand, shape/rank rejection, the
parallel branch above the threshold, serial-policy bitwise identity inside
1/2/4-worker pools, parallel repeatability, and a positive control that the
determinism data observably depends on fold order) and new alloc-matrix rows
for `sum`, `reduce` and `zip_reduce` at rank 1..=8 with contiguous,
permuted and stride-0 operands, both sides of the small-tensor threshold and
above the threading threshold.
`erased-execution-policy.md` gains a "Reduction evaluation order and the
deterministic route" section: the sequential policy is the pool-width
independent route and is asserted for reduce/zip_reduce/sum/dot, a parallel
policy is repeatable only for a fixed thread count, and `zip_reduce` joins
the covered full-reduction family.

The worklog records what #280 asked for, why the raw entries stay
superseded, what landed, the gate results, and that no version is bumped
(the strided-basic 0.6.1 release is a separate PR).
…ning

Post-review of the zip_reduce change.

A zero-extent dimension makes any stride representable, so a valid empty
view such as dims [0, 2] with strides [0, isize::MAX] passed shape
validation and then reached the block planner, where isize::MAX * 8
overflowed (with overflow checks on). `zip_reduce`, `reduce` and `sum` all
panicked on that input; `dot` happened to survive only because its small
tensor plan builder did not multiply. All four entries now return their
identity for zero elements right after shape validation and checked element
counting, and `zip_reduce` reuses that count instead of recomputing it.
`reduce_axis` already returned the identity for an empty reduced axis.

Also from the same review:

- the parallel contiguous leaf now wraps `fold_contiguous2` in
  `simd::dispatch_if_large`, exactly as `reduce` dispatches each of its
  parallel contiguous leaves, so the two routes take the same runtime
  target-feature dispatch;
- the forwarding-only `zip_reduce_impl` boundary is gone; its body lives in
  `zip_reduce`;
- lib.rs and docs/design/erased-execution-policy.md now distinguish `dot`
  (no parallel branch under any policy) from the reductions that fan out,
  instead of implying the sequential policy is the only reproducible route
  for every entry.

Tests: the empty-layout reproducer for all four entries; the above-threshold
contiguous layout (which reaches the contiguous parallel leaf) is now part
of the naive-reference, serial-identity and repeatability checks, not only
of the allocation matrix, and the test module docs say so.

Deliberately not shared: `fold_contiguous2` still mirrors `fold_contiguous`'s
lane scheduling by hand. Routing `fold_contiguous` through a generic
index-accessor lane helper measured 0.34 ns/element against 0.11 ns/element
on a contiguous f64 `reduce` of 1 MiB (release, -j 4); the chunks_exact +
zip form is what keeps the 16 lanes bound-check free. The measurement is
recorded on the function and in the worklog.
@github-actions

Copy link
Copy Markdown

Repository rules review

Repository rules review (55c7bb4cc6d42c3d0db0fbbce822aa3b260005f1...a83192b4ae0c982672df50ed1375305d04499f4c)
Verdict: pass
Findings:
- [warn] llm-skipped (External LLM Review) <unknown>: External LLM review was skipped
  External LLM review is permanently disabled; deterministic checks only.

@shinaoka
shinaoka merged commit 13f8165 into main Oct 18, 2026
10 checks passed
@shinaoka
shinaoka deleted the fix/280-widening-reduce branch October 18, 2026 00:30
shinaoka added a commit that referenced this pull request Oct 18, 2026
Additive: zip_reduce (the widening two-input reduction), the documented
evaluation order, and the empty-view identity return (#319). Release note:
docs/worklogs/2026-10-18-issue-280-widening-reduce.md. Other crates stay at 0.6.0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
shinaoka added a commit that referenced this pull request Oct 18, 2026
Additive: zip_reduce (the widening two-input reduction), the documented
evaluation order, and the empty-view identity return (#319). Release note:
docs/worklogs/2026-10-18-issue-280-widening-reduce.md. Other crates stay at 0.6.0.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

Allocation-free borrowed-metadata reductions and in-place updates

1 participant