Skip to content

fix(billing): cost-model accuracy follow-ups from the #150 review - #151

Merged
spMohanty merged 12 commits into
mainfrom
fix/cost-model-billing-accuracy
Jul 23, 2026
Merged

fix(billing): cost-model accuracy follow-ups from the #150 review#151
spMohanty merged 12 commits into
mainfrom
fix/cost-model-billing-accuracy

Conversation

@spMohanty

Copy link
Copy Markdown
Collaborator

Summary

Four cost-model accuracy corrections plus a documentation clarification, following the forum review by @AndreiBulzan on #150. Each was reproduced against production weights and fixed with regression pins; changes are server-side cost formulas only (no weight/dtype-rate table edits, no client-surface changes — sync_client --check stays green).

What changed

  1. Weighted / dense-q quantiles priced per the work they do. quantile/percentile/nanquantile/nanpercentile used a flat axis_dim + 4·q.size per output. The weights= branch sorts internally, and a dense q with method="inverted_cdf" returns the sorted input — both were priced linearly. Now piecewise: unweighted bills the cheaper of k partition passes or one shared sort-parity pass; weighted is priced at sort parity plus a per-q lookup. The scalar path is unchanged (byte-identical), so common usage is unaffected.

  2. N-D FFTs billed per execution stage. fftn/ifftn/fft2/ifft2/rfftn/rfft2/irfftn/irfft2 were priced from the final transform shape, which collapses when s pads-then-truncates. They now replay numpy's actual 1-D cascade over the evolving intermediate shapes, so the fused bill equals the sum of the equivalent explicit 1-D transforms. The rfftn remaining-axis order is version-gated (numpy changed it at 2.1.0) to stay correct across the declared 2.0–2.4 range.

  3. asarray charges real copies. copy=True / order= materializations were billed 0. Now charges numel × heavier(src,dst) whenever a fresh buffer is produced (verified via may_share_memory), 0 for a genuine view — matching astype/copy, and aligning asarray([list]) with array([list]).

  4. select includes its default in dtype resolution. A wider default (e.g. float64 against float32 choices) promotes the output but was billed at the choice rate; it now bills at the honest output-dtype rate. Weak Python-scalar defaults stay weak per NEP 50.

  5. out= billing rationale documented (no behavior change). Clarifies in _dtype_billing.py and the cost-model page why pointwise out= bills the widest participating buffer (max(compute, store) width), which holds out=-casting at exact astype parity in both directions. Locked by parity pins.

Docs regenerated (ops.json) and the cost-model reference page refreshed for the quantile and FFT rows.

Verification

  • Full suite green (5431 passed), coverage 85.84% (≥85% gate), pyright src/flopscope tests clean, ruff/gitlint clean.
  • FFT correctness cross-checked against numpy's real execution order across c2c/r2c/c2r × 2/3/4 axes × no-resize/truncate/pad; behaviour parity confirmed against numpy's own test_pocketfft.
  • Quantile invariants verified: scalar path unchanged, weighted ≥ unweighted, dense-q clears sort parity, monotonic in k.

Follow-ups (not in this PR)

Thanks to @AndreiBulzan for the careful report.

spMohanty added 12 commits July 23, 2026 20:20
Wrap the new _quantile_dense_cost(...) call sites at the 88-col line
limit, sort tests/test_quantile_weighted_pins.py's import block, and
bind the per-op loop variables (op, qval) as lambda defaults in
test_weighted_ge_unweighted_and_all_four_ops to satisfy flake8-bugbear
B023, matching the existing convention in
tests/test_batch_underbill_pins.py::test_percentile_family_scales_with_q_count.
rfftn/rfft2/irfftn/irfft2 (2+ transform axes) and any c2c fftn/fft2/ifftn/
ifft2 call with a resized `s` now bill from numpy's real per-axis cascade
instead of the final transform shape, so the previously flat 5*N*sum(log2)/
5*(N//2)*sum(log2) pins no longer match. Updated the affected assertions in
tests/test_fft_transforms_extended.py and the OP_EXPECTATIONS conformance
table in tests/test_cost_convention.py to the new staged values, each with
the per-stage arithmetic in a comment.
Document why pointwise out= bills the widest participating buffer
(max of compute width and store width) rather than just the compute
width: a wider out= store is a real materialization, and pricing it
at the wider rate holds out=-casting at exact astype parity in both
directions -- a wider out= costs what the equivalent astype costs,
and a narrower out= never discounts the loop that actually runs.
Reductions fold out= into their accumulator for a separate, genuine
reason (it changes the loop numpy runs) and are unaffected.

Adds tests/test_out_billing_doctrine.py to pin both directions of
this behavior. No billing logic changes.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dba9dcc15c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

total += stage(ax, n, True)
current[ax] = n // 2 + 1
remaining = range(len(axes) - 1)
remaining = reversed(remaining) if _NUMPY_GE_2_1 else remaining

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use NumPy 2.3 as the rfftn reversal cutoff

For NumPy 2.1/2.2, which are still allowed by numpy>=2.0.0,<2.5.0, np.fft.rfftn still applies the remaining complex FFT axes in forward order; the reversal appears in 2.3+. With this cutoff, any 3+ axis rfftn case that resizes an earlier remaining axis (for example s=(4,16,4), axes=(0,1,2)) charges the reversed cascade instead of the installed NumPy's cascade, so FLOP counts are wrong for supported environments. Please gate the reverse order at the version where NumPy actually changed it.

Useful? React with 👍 / 👎.

@spMohanty
spMohanty merged commit b61e453 into main Jul 23, 2026
24 checks passed
@spMohanty
spMohanty deleted the fix/cost-model-billing-accuracy branch July 23, 2026 20:48
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