Skip to content

feat(billing): dtype-aware cost accounting#144

Open
spMohanty wants to merge 36 commits into
mainfrom
fix/dtype-aware-billing
Open

feat(billing): dtype-aware cost accounting#144
spMohanty wants to merge 36 commits into
mainfrom
fix/dtype-aware-billing

Conversation

@spMohanty

@spMohanty spMohanty commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

We identified a complex-packing and a differential dtype bit-packing issue that
cause FLOP undercounting: complex operations billed the same as their real
counterparts (letting two real payloads ride one complex op), and all dtypes
billed at one width (letting two lower-precision multiplies ride one float64
multiply). This PR closes both by making billing dtype-aware.

The model

charged = int(flop_cost × dtype_rate × complex_factor × weight)
  • dtype_rate — policy, configurable like weights (dtype_rates in
    src/flopscope/data/default_weights.json). Anchor: 1 FLOP = one 32-bit-class
    real op. All ≤32-bit dtypes 1.0; int64/uint64/float64 2.0; extended
    precision priced by width class (float96 3.0, float128 4.0 — packing
    narrower payloads through their mantissas still loses at these rates);
    complex entries carry component width (complex64 1.0, complex128 2.0,
    complex192 3.0, complex256 4.0). Only genuinely unknown future numeric
    dtypes fail closed (UnsupportedDtypeError); non-numeric kinds
    (object/str/datetime/…) bill neutrally at 1.0 (no floating-point arithmetic
    is possible through them; keeps the numpy-compat guarantee intact). The
    evaluation wire codec still accepts only the 14 standard dtypes.
  • complex_factor — math, per-op registry field: the textbook real-FLOP
    count per billed unit on complex inputs (add 2, multiply 6 = 4M+2A, divide
    11, abs 4, sqrt 10, lexicographic compare 2, transcendentals from their
    closed forms, linalg 4). The contraction family (einsum/matmul/dot/…) bills
    exactly — 6·multiplies + 2·adds computed from the einsum accumulation
    decomposition per call — so einsum('i,i->i', …) cannot dodge multiply's
    factor. FFT is priced-in at 1.0 (its 5n·log₂n formula already counts the
    complex butterflies). Every charged op carries an explicit classification,
    enforced by a completeness guard.
  • Billing dtype per call = np.result_type over the declared operands plus
    any explicit dtype=/out= — declared via a now-required dtypes= keyword
    at every deduct site (263 sites; an AST guard keeps future ops honest). NEP
    50 weak-scalar promotion is preserved (f32_array * 2.0 bills float32).
    astype bills the heavier of source/destination rate. real/imag are
    reclassified to the free tier (component extraction is a view).

Reference tables

dtype rates — policy, the full dtype_rates table (18 entries), grouped by
rate. Complex dtypes sit at their component precision; the complex arithmetic
is billed separately by complex_factor.

rate dtypes
1.0 bool, int8, int16, int32, uint8, uint16, uint32, float16, float32, complex64
2.0 int64, uint64, float64, complex128
3.0 float96, complex192
4.0 float128, complex256

Only the 14 standard dtypes cross the eval wire; the four extended-precision
rows are priced for in-process use (defense-in-depth — the wire codec rejects
them anyway).

complex factors — math, per-op registry field (real dtypes are always 1.0).
Every charged op is classified (a completeness guard enforces it); the factor
is the real-arithmetic decomposition of one billed unit, with z = a + bi.

factor why — the real-arithmetic decomposition representative ops
1.0 no complex arithmetic — a movement/creation op, or a formula that already counts complex real-FLOPs (conj negates b; fft.*'s 5N·log₂N is already a real-FLOP count) conj, angle, fft.fft, concatenate, take, shuffle
2.0 (a+bi) + (c+di) = (a+c) + (b+d)i2 real adds; ordering compares lexicographically (real, then imaginary) → also 2 add, subtract, sum, mean, cumsum, sort, less, equal
2.25–2.3 log(z) = ln(abs(z)) + i·atan2(b, a) — a modulus, one real log, one atan2 log, log2, log10, log1p
2.5 variance is square-and-sum about the mean (abs(z−μ)² accumulated) — mostly real adds, some multiplies var, std, nanvar, nanstd
3.0 dead metadatastats.* pdf/cdf/ppf force-cast their input to float64, so the complex path is unreachable; interp is a genuine 3 stats.norm.pdf, interp
3.1 exp(a+bi) = exp(a)·(cos b + i·sin b) — one real exp, one sin, one cos, two mults exp, exp2, expm1
3.4 / 3.6 sin(a+bi) = sin a·cosh b + i·cos a·sinh b — four real transcendentals + two mults (tan = 3.6 needs a little more) sin, cos, tan, sinh, cosh, tanh
3.5 inverse trig via the log/sqrt closed form, e.g. arcsin z = −i·log(iz + sqrt(1 − z²)) arcsin, arccos, arctan, arcsinh
4.0 abs(z) = sqrt(a² + b²) → 2 mul + 1 add + 1 sqrt = 4; dense linalg is complex-matmul-dominated (≈4× the real arithmetic) abs, linalg.inv, linalg.svd, linalg.eig, cov
4.7 2-D cross product — a pair of complex multiplies and a subtract per output cross
5.0 z² = (a² − b²) + (2ab)i reuses a,b → 3 mul + 1 sub + 1 add = 5 (cheaper than a general multiply) square
5.5 complex power z**w = exp(w·log z) — a complex log, a complex multiply, a complex exp (blended) power, float_power, geomspace, logspace
6.0 (a+bi)(c+di) = (ac − bd) + (ad + bc)i4 real mul + 2 real add = 6; reciprocal costs the same (a special-cased divide) multiply, prod, cumprod, outer, kron, reciprocal
7.0 complex sign = z / abs(z) — the modulus (4) plus the real divides and a zero-guard sign
10.0 complex square root — the modulus (abs = 4) plus further real roots and a sign/halving combination sqrt
11.0 z / w = z·conj(w) / abs(w)² — numerator 4 mul + 2 add, denominator 2 mul + 1 add, then 2 real divides → 11 divide, true_divide
14.5 sinc(z) = sin(pi·z) / (pi·z) — a complex sin (≈3.4) composed with a complex divide (11) sinc
exact not a flat factor: the einsum accumulation counts the actual complex multiplies (×6) and adds (×2) → 6·mults + 2·adds. A length-K dot is 8K − 2 real FLOPs (ratio 6 at K=1, →4 as K→∞), billed per call — which blocks alias-shopping: einsum('i,i->i', z, z) bills 6, same as multiply einsum, matmul, dot, inner, vdot
illegal numpy raises — the op is undefined on complex (rounding, bit/shift ops, atan2, real-only math/samplers) → fail closed bitwise_*, floor, ceil, trunc, mod, arctan2

The two axes compose: a complex128 multiply bills
flop_cost × 2.0 (component = float64) × 6.0 (complex multiply) × weight.

What participants see

  • float32 (and all ≤32-bit) work bills exactly as before.
  • float64/int64 (numpy defaults) bill 2×.
  • Complex ops bill their real-arithmetic decomposition; packing two real
    payloads into a complex op now loses (elementwise 3×, matmul ~2×), and
    packing two float32 multiplies into one float64 multiply is break-even
    before packing overhead — both locked by invariant tests
    (tests/test_dtype_cost.py).
  • Requesting a narrow output dtype does not discount the bill; casts bill the
    pricier side. Fixed-float64-output ops with no array operand (window
    functions, fft.fftfreq, the random samplers) bill the float64 rate.
  • astype and a value-changing asarray(x, dtype=...) are billed identically
    (numel at the heavier rate) — asarray is no longer a free way to downcast.

docs/reference/cost-model.md gained a Dtype and precision section — the
policy-vs-math split, the rate table, the per-op complex derivations (with the
FFT butterfly proof), the resolved-dtype rule with worked examples, and the
stance on packing (not banned; priced to break-even-or-losing at 32/64-bit
widths) — plus a full self-consistency sweep and an extended doc coverage
guard.

Verification

  • Full local CI pipeline green end-to-end (make ci exit 0): lint, lockfile,
    commit lint, typecheck, full suite (4808 tests, 0 failed), NumPy
    compatibility harness (numpy's own test suite under monkeypatch — 0 failed),
    client/server sync gates, docs build.
  • flopscope-client suite green (1353 tests). Registry data and ops.json
    regenerated (--check clean); the per-op complex_factor is publicly
    auditable in ops.json.
  • Exploit-kill invariants, packing-neutrality, NEP 50, fail-closed, and
    completeness guards all in tests/test_dtype_cost.py,
    tests/test_dtype_billing.py, tests/test_complex_factor_completeness.py,
    tests/test_deduct_declares_dtypes.py.

Rollout notes

  • Server-side billing change: effective on the graders only after release +
    eval redeploy. Existing submissions re-price on re-eval (float64-default
    submissions ≈2×; complex-packing submissions substantially more).
  • The dtype_rates table is intentionally tunable policy: revisiting values
    (e.g. sub-32-bit discounts) needs only a data change.

Follow-ups closed in this PR

The items tracked during the four-factor work are resolved here:

  • Random movement ops (shuffle/choice/permutation/permuted,
    module-level and Generator/RandomState) are reclassified from
    "illegal" to complex_factor 1.0. They keep billing dtype-neutral
    their shape[axis] cost counts the dtype-independent Fisher-Yates
    swap/selection work, so a complex or fp64 population costs the same as fp32
    (a genuine sampler, which synthesizes values, still bills its output width).
    The "illegal" classification was a footgun, not a live bug: numpy handles
    complex here, so it only mattered once a dtype was declared at these sites.
  • fnp.random.shuffle crash on FlopscopeArray inputs: fixed by stripping
    to a base ndarray before numpy's C path (its internal empty_like tripped
    the reentry guard on the in-place shuffle).
  • asarray with an explicit value-changing dtype= no longer converts for
    free — it now mirrors astype (numel at the heavier source/target rate);
    a no-op or lossless-widening asarray stays free.

Genuinely deferred (documented, no billing impact): the complex_factor on the
stats.* pdf/cdf/ppf family is dead metadata — those ops force-cast their
input to float64, so the complex path is unreachable and the value is never
consulted.

spMohanty added 30 commits July 13, 2026 18:52
Adds a dtype_rates table alongside the existing per-op weights table:
packaged defaults, custom-file override, unit-mode fallback, and a
fail-closed lookup for dtypes outside the loaded table. Also syncs the
new exception class into the generated client error module.
Thread optional dtypes/complex_factor_override through deduct(),
deduct_after(), and the shared _charge_op() choke point, changing the
formula from int(flop_cost * weight) to int(flop_cost * dtype_rate *
complex_factor * weight). Resolution happens before the budget check
so an unsupported dtype fails closed without charging. dtypes stays
optional (None => dtype-neutral, identical to prior billing) so all
existing call sites are unaffected until they migrate. Mirrors the
same formula in accounting._weight_cost for public cost estimates.
Threads dtypes=/billing_operand through every unary, binary, and
reduction deduct() site in _pointwise.py, plus the custom ops (clip,
around/round, sort_complex, isclose, average, count_nonzero, the
median/percentile/quantile family, ptp, outer, kron, cross, diff,
gradient, ediff1d, convolve, correlate, corrcoef, cov, trapezoid/trapz,
interp), so complex and fp64 inputs finally bill their registry
complex_factor and dtype_rate instead of the pre-migration flat rate.

Binary sites bill from the original (pre-coercion) operands via
billing_operand so NEP 50 weak scalar promotion is preserved. A few
ops needed numpy's actual (non-obvious) dtype-resolution behavior
verified before billing: diff/ediff1d's prepend/to_begin route through
concatenate (no NEP 50); cov/corrcoef always compute at a float64
floor; interp's search/blend is always float64 but the interpolated
value keeps fp's dtype.

Left unmigrated (dtypes=None, unchanged pre-migration billing):
- The five generic ufunc-method fallbacks (_counted_ufunc_outer/
  reduce_generic/accumulate_generic/reduceat/at) bill under a dynamic
  op_name (e.g. "subtract.reduce") that has no registry entry, so
  adding dtypes= would make any complex-dtype call raise
  UnsupportedDtypeError instead of billing correctly. Flagged
  separately for follow-up.
- _einsum_routed_binary, tensordot, and vdot route through the einsum
  accumulation model and carry an "exact" complex_factor requiring a
  computed override; out of scope here.

tests/test_cost_convention.py::test_conformance now has one expected
failure: sort_complex bills 600 instead of 300 (its complex_factor of
2.0 is now applied) — to be reconciled in a follow-up task.
The five generic ufunc-method fallbacks (_counted_ufunc_outer/
reduce_generic/accumulate_generic/reduceat/at) bill under a dynamic
op_name like "subtract.reduce" that has no direct registry entry.
Adding dtypes= there naively would make complex_factor_for fail
closed and raise UnsupportedDtypeError on every complex-dtype call
through these sites, rather than billing correctly.

Add a base-ufunc-name fallback to complex_factor_for: when the exact
op_name misses the registry, strip a known ufunc-method suffix
(.reduce/.accumulate/.reduceat/.outer/.at) and look up the base
ufunc's factor instead — the per-element arithmetic of these methods
is the base ufunc's (a reduce over multiply is a chain of complex
multiplies). The fallback only fires when the exact name isn't
already a registry key, so dotted keys like fft.fft, linalg.svd, and
linalg.outer always resolve on the direct lookup and are never
stripped. A base op classified "illegal" for complex (e.g.
logaddexp) still raises through the fallback, matching numpy's own
behavior.

Declare dtypes= at all five sites now that the fallback makes it
safe: .outer bills both operands' dtypes, the rest bill the primary
operand's dtype (plus out= where applicable); index/selector
operands (reduceat's indices, at's index/value args) are excluded.

No test_cost_convention.py shifts: the file has no np.<ufunc>.reduce/
.accumulate/.outer/.reduceat/.at assertions.
Wires complex_factor_override into every "exact"-classified contraction
deduct site (einsum, the shared dot/matmul/inner/vecdot/matvec/vecmat
router, both tensordot deduct sites, vdot) using complex_real_total's
mu/adds decomposition, so complex-dtype contractions bill their true
real-FLOP cost instead of silently falling back to dtype-neutral
billing (which previously let einsum('i,i->i', z, z) undercut an
equivalent multiply(z, z) call by ~12x). einsum_path stays declared
dtype-neutral (dtypes=()) since it does no value arithmetic.

tensordot's general fallback path has branches (oversized symmetry, or
rank>52) that compute a plain int with no accumulation decomposition;
those keep complex_factor_override=None and rely on the registry's
"exact" classification to fail closed via RuntimeError if a complex
dtype ever reaches them, rather than inventing an unverified ratio.

Real-dtype billing is unchanged: complex_factor_for short-circuits to
1.0 for real dtypes regardless of the override.
Threads dtypes= through every budget.deduct()/deduct_after() site in
_sorting_ops.py, _counting_ops.py, _polynomial.py, _window.py,
_unwrap.py, and _symmetric.py (53 sites), so these ops finally bill
their registry complex_factor and dtype_rate instead of the
pre-migration flat rate.

Rule applied per site: declare the operand dtype(s) (both operands for
two-array ops like polyadd/searchsorted/allclose/digitize; the single
operand otherwise); include an explicit dtype=/out=/w= argument's
dtype where the op has one (trace, polyfit's weights). logspace/
geomspace follow the existing linspace/arange pattern (dtype= kwarg,
else result_type(start, stop)). Window ops (bartlett/blackman/
hamming/hanning/kaiser) take an int length with no array operand, so
they declare dtypes=() (dtype-neutral).

tests/test_fma2_cost_fixes.py::test_geomspace_logspace_cost_broadcast_output_times_transcendental
now fails: geomspace/logspace resolve to float64 and correctly bill
2x under production rates (1600 instead of 800, 160000 instead of
80000) instead of the pre-migration flat rate. Reconciliation needed
alongside the existing sort_complex shift from the pointwise
migration.
Threads dtype-aware billing through the remaining fft.*/linalg.*/
random.*/stats.* deduct sites (56 total). FFT declares the input
array dtype (registry complex_factor is priced-in at 1.0, no
override needed); linalg declares all array operands (registry
factor 4.0 applies uniformly); random declares the actual output
dtype, reading it off the already-computed result where the same
factory backs many distributions with different dtypes; stats
declares the forced-float64 coerced input.

random.permutation/shuffle/choice/symmetric (module-level) and the
Generator/RandomState choice/permutation/permuted/shuffle methods
intentionally keep dtypes=() -- these pass through arbitrary
(possibly complex) caller data that numpy handles fine, but the
registry's complex_factor="illegal" classification for them would
incorrectly reject legitimate complex input if declared.
deduct()/deduct_after() no longer default dtypes to None; both now
raise TypeError up front (before _charge_op) if dtypes is omitted or
explicitly None, with a message pointing callers at dtypes=() for
dtype-neutral ops. _charge_op keeps its own dtypes=None default since
its only two callers (both now guarded) always pass a tuple.

Adds a permanent AST guard (test_deduct_declares_dtypes.py) asserting
every deduct/deduct_after call site under src/flopscope names
dtypes=. Renames the Task 5 migration-era test to
test_dtype_neutral_and_none_rejected: dtypes=() still bills as
dtype-neutral, dtypes=None now raises.
np.real/np.imag return a view (or constant-fill) of a complex value's
component -- no floating-point arithmetic -- so charging numel FLOPs at
weight 1.0 was an overcount. Reclassify both to the free tier: flop_cost=0
hardcoded at the call site via a new _free_unary binding, registry category
counted_unary -> free, default weight 1.0 -> 0.0 (mirrors iscomplexobj/
isrealobj). Functional behavior (out=, symmetry, NaN/Inf checks) unchanged.
Pre-migration tests hardcoded the old (no dtype_rate/complex_factor)
billing model. Reconciles each shift against the new
charged = flop_cost * dtype_rate * complex_factor * weight formula,
with a derivation comment at every re-pinned assertion:

- float64-default inputs under production weights now bill 2x
  (dtype_rate) in test_cost_constant_unification.py,
  test_production_weight_billing.py, test_fma2_cost_fixes.py, and
  test_ufunc_alias_parity.py.
- genuinely-complex inputs pick up their op's registry complex_factor
  in test_cost_convention.py, test_cost_formula_vs_code.py, and
  test_pointwise_coverage.py (sort_complex, real_if_close).
- test_no_bare_numpy_in_counted_ops.py: allowlist the bare
  `_np.dtype(...)` calls the migration added to normalize billing
  dtypes -- same O(1) metadata class as the already-allowlisted
  result_type/promote_types/can_cast, not a compute call.
- drop the dead "imag" case from test_cost_formula_vs_code.py's
  _unary_input (imag was moved to the free tier and removed from the
  numel-parametrized list).
Document the four-factor billing formula charged = int(flop_cost x dtype_rate
x complex_factor x weight) as the public single source of truth.

- Rewrite the billing-model intro from two layers to four, with the extended
  separation invariant (width policy in dtype_rate; complex structure in
  complex_factor, computed per call for the contraction family).
- Add a Dtype and precision section: why width and structure are priced, the
  32-bit-class billing unit + 14-dtype rate table + fail-closed rule, complex
  arithmetic from first principles (atom table, per-op classification, exact
  contraction rule, FFT priced-in derivation), the resolved-dtype rule with
  worked cases, a worked billing table, and the packing stance.
- Sweep the rest of the doc: FMA=2 unit cross-ref; two new non-exploitability
  rows (complex/width packing); one complex-classification line per family
  section; move real/imag to the free tier.
- Extend tests/test_cost_model_coverage.py: assert the section heading, the
  verbatim four-factor formula, and one rate-table row per supported dtype.
Two dtype-billing follow-ups tracked during the four-factor work.

Random movement ops (shuffle/permutation/choice + Generator.permuted)
relocate caller-supplied data rather than sampling a distribution, so
they are movement ops, not value generators. Their registry
complex_factor was "illegal" -- valid only for the real-only samplers,
and a footgun here: numpy permutes/shuffles/selects complex arrays
fine, so any future dtype declaration at these sites would wrongly
raise on a legitimate complex operand. It is now 1.0. Billing stays
dtype-neutral (dtypes=()): the shape[axis] cost counts the
dtype-independent Fisher-Yates swap / selection work, so a complex or
fp64 population costs the same as fp32 -- unlike a genuine sampler,
which bills the width of the values it synthesizes. Stripping a
FlopscopeArray operand to a base ndarray before numpy's C path also
fixes a crash in the in-place shuffle (numpy's internal empty_like
tripped the fnp-reentry guard).

asarray with an explicit value-changing dtype= did the same work as
astype but billed 0 (weight 0.0), making it a free alternative to
astype for downcasting. asarray now mirrors astype: numel at the
heavier of source/target rate on a value-changing cast, 0 for a no-op
or lossless widening (weight 0.0 -> 1.0, relabel free -> counted_custom).

Regression tests pin dtype-neutral billing for the movement ops
(including the shuffle crash and registry hygiene) and astype-parity
for asarray casts.

@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: 732a401f32

ℹ️ 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".

Comment thread src/flopscope/_pointwise.py Outdated
Comment on lines +1027 to +1029
billing_dtypes: tuple = (a.dtype,)
if kwargs.get("dtype") is not None:
billing_dtypes += (_np.dtype(kwargs["dtype"]),)

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 Include NumPy's implicit reduction dtype

When dtype is omitted, several NumPy reductions do not actually compute in a.dtype: on 64-bit platforms sum/prod/cumsum of an int32 input promote to an int64 accumulator, and the mean/variance factories have the same issue for integer inputs promoting to float64. Declaring only a.dtype here means calls like fnp.sum(np.ones(1000, dtype=np.int32)) are charged at the 32-bit rate even though NumPy executes the reduction in a 64-bit dtype, leaving a width-discount path in the new dtype-aware accounting.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 4896aec — reductions now bill numpy's widened accumulator dtype (int64/uint64 for sum/prod/cumsum/cumprod, float64 for integer mean/var/std); extremum/index/boolean reductions intentionally keep the input dtype.

Comment on lines +73 to +74
else:
billing_dtypes = ()

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 Preserve dtype billing for scalar RNG draws

For Generator/RandomState sampler methods with size=None, NumPy returns a scalar (for example rng.standard_normal() returns a float64 scalar), so this else path declares the operation dtype-neutral. Under production dtype rates that undercharges scalar draws such as fnp.random.default_rng(0).standard_normal() or integers(dtype=np.int64) by skipping the float64/int64 rate, even though the module-level sampler wrapper already handles scalar result dtypes.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 4896aec — size=None sampler draws now bill their scalar output dtype (float64/int64) instead of dtype-neutral.

…lling

Two CI jobs outside the root `make ci` (client-server-integration runs the
flopscope-server suite; website-tests runs the node regression tests) exercised
billing paths the dtype-aware change touched:

- Three flopscope-server tests call BudgetContext.deduct() directly to stage a
  synthetic op for the namespace-breakdown assertions. deduct() now requires
  the dtypes= keyword, so they declare dtypes=() (dtype-neutral), matching the
  plumbing-site convention.
- The symmetry-guide regression test pins two einsum costs computed from
  fnp.random.randn (float64) data. Dtype-aware billing bills float64 at rate
  2.0, so the base 54/81 become 108/162; the expected values and the
  explaining comment are updated.
Two width-discount paths surfaced in review, both undercounting integer or
scalar work under the new dtype-aware accounting:

- sum/prod/cumsum/cumprod (and their nan* variants) widen a narrow-integer or
  boolean input to numpy's 64-bit accumulator (int64/uint64), and mean/var/std
  of an integer input compute in float64. Billing the input dtype charged these
  at the 32-bit rate while numpy runs at 64-bit. They now bill the accumulator
  / compute dtype. Extremum (max/min), index (argmax/argmin) and boolean
  (all/any) reductions are unchanged: they do not widen, and an int64 index
  output does not reflect the input-dtype comparison work.

- A size=None Generator/RandomState sampler draw returns a scalar, which the
  dispatcher billed dtype-neutral; it now bills the scalar's output dtype
  (float64/int64), matching the module-level sampler wrapper and the array
  draw's per-element rate.

Regression tests in tests/test_dtype_cost.py.
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