Skip to content

Native: keep the FFTW plans runs measure, by default (ADR-076) - #602

Open
jtravs wants to merge 10 commits into
mainfrom
perf/default-fftw-wisdom
Open

jtravs wants to merge 10 commits into
mainfrom
perf/default-fftw-wisdom

Conversation

@jtravs

@jtravs jtravs commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Follows #598 (merged; this PR was stacked on it and now targets main). Fixes #136.

What

The native backend now keeps the FFTW plans its runs measure, by default. Each transform shape is measured once per machine and build, not on every run. ADR-076 (proposed) records the decision.

  • Kept by transform. Each plan key (fft_plan_key: kind, sizes, strides, placement, alignment, threads, as a calibration keys its winners) holds the wisdom of that transform alone. That wisdom is measured in place by the first run that planned it, inside its own workspace. A seeded transform plans alone: the workspace's accumulated wisdom is set aside, only the key's stored wisdom is imported, and afterwards the workspace's wisdom comes back over the transform's (FftPlanning::stored, keep, measured, reused in core.hpp / fft_plan.hpp). A transform left at estimate takes its stored plan without being seeded, as FFTW does with wisdom.
  • Location and key. The plans live in wisdom.json in the calibration store's directory for the machine and build (<store>/machines/<fingerprint>/, the profile's fingerprint), so different machines and builds never share plans. SOLPROP_FFTW_WISDOM moves the file, to <name>.native-<fingerprint>, and no longer turns off a profile's FFT planning. SOLPROP_CALIBRATION=off keeps no default file. solprop calibrate --clear removes both files. A process keeps its own plans in memory for its later workspaces.
  • Precedence. A key the profile holds is planned from the profile's wisdom and is never read from or written to the store. SEP 46's transaction still forgets wisdom before and after a profiled workspace. Stored wisdom comes in key by key, imported beneath the workspace's wisdom.
  • Writes. Only a run's own planning (the policy's or the profile's) reads and writes the store. A calibration's given planning and its children (off) do neither. The atomic-rename and flock scheme is unchanged.
  • FFTW wisdom: an unusable SOLPROP_FFTW_WISDOM stops the run, and Apple silicon models share a key #136. An unusable name (., ~nouser/w, no home) warns and plans without stored plans, instead of raising. The key is the fingerprint, which tells Apple silicon models apart.
  • Metadata. Runs still record wisdom_loaded (the file held plans) and wisdom_digest (now a digest of the plans the workspace built from wisdom or measured).

Measurements

All runs use SOLPROP_FFTW_PLAN=measure and SOLPROP_CALIBRATION=off, length 0.05, with 5 interleaved rounds. "Stored" means the plans one earlier run kept.

AMD EPYC 7763, 16 cores (one NUMA domain), DMOG node19, both builds from source. Construction, measured cold in place, then warm from stored plans:

case cold, #598 / s cold, this PR / s warm / s
ctc 13.7 13.7 0.18
h2_raman_averaged 5.1 5.1 0.13
grin_nonlinear 3.6 3.6 0.36
rdw 1.8 1.8 0.16

Windows with stored plans against plans measured in place (median ratio, range over rounds):

case same build vs #598 rerun, fresh plans, same build rerun vs #598
h2_raman_averaged 0.993 (0.98–1.03) 1.016 (1.00–1.07) 1.002 (0.97–1.02) 1.005 (0.97–1.03)
ctc 1.017 (1.00–1.04) 1.014 (1.01–1.02) 1.003 (0.99–1.03) 1.013 (0.99–1.04)
rdw 1.001 0.999 – –
grin_nonlinear 0.920 (0.88–1.21) 1.046 (0.89–1.07) – –

So h2_raman_averaged is no slower with stored plans than with plans measured in place; the 1.17 seen on the AMD EPYC 9B45 with plans planned alone doesn't appear. ctc leans 0.3–1.7 % across these comparisons, and with a second stored measurement its ranges span 1. That is the spread between individual measurements of the same plan, which stored plans fix to one draw.

Intel Xeon Gold 6240, 18 and 36 threads, same build. Construction: ctc 23.2 → 0.12 s, rdw 6.7 → 0.10, h2_raman_averaged 4.9 → 0.07, grin_nonlinear 3.5 → 0.6. Windows: geometric mean 1.015, every range spans 1. The cold times here come from an earlier commit that measured each transform from nothing (see Codex's first finding). Later Xeon timings were disturbed by another session's load outside Slurm, so the comparison against #598 ran on DMOG instead.

The store holds about 20 kB per case at one width.

Not changed

The policy's threaded-rows effort stays at estimate. Plans measured once now cost nothing afterwards, so switching threaded rows to measure by default is a separate call for the maintainer.

Checks

Targeted tests pass on the Xeon: native/tests/test_wisdom.py (rewritten), test_store.py, test_profile.py, test_planning.py, test_backend.py, test_fftw_plan_refusal.py, tests/calibration/, tests/backends/test_execution_record_cost.py, test_native_forced_threads.py, tests/driver/test_scan_kept_backend.py. The full gates run in CI.

🤖 Generated with Claude Code

…DR-076)

Each plan key holds the wisdom of that transform alone, measured in place
by the first run that planned it, in wisdom.json in the calibration store's
directory of the machine and build (the profile's fingerprint), or beside
the file SOLPROP_FFTW_WISDOM names. A workspace's own planning (the policy's
or the profile's) is offered the stored plans and keeps what it measures:
a seeded transform plans alone from its own stored wisdom, and the
workspace's wisdom comes back over it; a transform left at estimate takes
its stored plan unseeded. A profile's keys keep precedence and are never
stored. SOLPROP_CALIBRATION=off keeps no default file; calibrate --clear
removes the plans; a process remembers its own plans for later workspaces.

An unusable name or file now warns and plans without stored plans, and the
key tells Apple silicon models apart (#136). SOLPROP_FFTW_WISDOM no longer
turns off a profile's FFT planning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jtravs jtravs added area: native The native backend, threading and FFT planning performance Slower or larger than it should be, with no wrong result labels Nov 9, 2026
@jtravs
jtravs marked this pull request as ready for review November 9, 2026 17:07
Copilot AI balanced review requested due to automatic review settings November 9, 2026 17:07

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-09T17:10:22.271161Z 139b844 Draft marked ready
🔒 Security Review ✅ Completed 2026-11-09T17:12:39.098245Z 139b844 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: 139b844a3a

ℹ️ 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 on lines +167 to +169
fftw_forget_wisdom();
if (const auto found = planning.stored.find(key); found != planning.stored.end())
static_cast<void>(fftw_import_wisdom_from_string(found->second.c_str()));

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 Reuse plans already measured by the current workspace

On a cold cache, when a workspace seeds the same transform key more than once, this clears FFTW's accumulated wisdom and imports only planning.stored, which never receives the plans just recorded in planning.measured. Consequently, each occurrence measures the transform again instead of reusing the first measurement. This affects, for example, matching projected/coarse transforms across plasma streams in GeneralWorkspace::seed_stream_wisdom(), which previously shared process-local wisdom. Consult the current workspace's measured plans as well as the initial store before clearing wisdom, so construction does not repeat potentially expensive measurements for identical keys.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in bdf2498. A key measured earlier in the workspace is now reused (known_wisdom consults measured before stored). A transform being measured also starts from what the workspace has measured so far, so it shares their sub-plans as #598's single wisdom did. The Xeon timings showed the cost: cold construction of rdw took 6.7 s against #598's 1.4 s.

Comment thread src/solprop/calibration/__init__.py Outdated
Comment on lines +292 to +294
digest = found.fingerprint.digest
removed = _native.store.Store(store_directory(environ)).clear(digest)
named = (os.environ if environ is None else environ).get("SOLPROP_FFTW_WISDOM", "")

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 Clear explicitly named wisdom even when calibration is off

With SOLPROP_CALIBRATION=off and SOLPROP_FFTW_WISDOM set, runs explicitly continue to persist plans, but clear() cannot remove that file: _profile.choose() returns a choice without a fingerprint in off mode, so the early return above makes this new deletion path unreachable. Thus solprop calibrate --clear silently leaves the active named plan cache intact in a supported configuration. Compute the machine/build fingerprint independently of profile selection in this case, as _wisdom.machine_key() already does when writing the file.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in bdf2498 and c266076. When SOLPROP_CALIBRATION=off read no profile, clear() now computes the fingerprint itself (build identity and topology, as _wisdom.machine_key does) and removes the named file. tests/calibration/test_profiles.py covers it.

@read-the-docs-community

read-the-docs-community Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

jtravs and others added 6 commits November 9, 2026 17:59
…; clear a named file under off

A key measured earlier in the workspace is reused rather than measured
again, and a transform to be measured starts from the plans the workspace
measured so far, so that it shares their sub-plans as one wisdom does.
`solprop calibrate --clear` takes the fingerprint itself when
SOLPROP_CALIBRATION=off read no profile, so the file SOLPROP_FFTW_WISDOM
names goes too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g from the workspace's plans

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jtravs
jtravs changed the base branch from perf/native-fft-batching to main November 9, 2026 18:39
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jtravs
jtravs enabled auto-merge November 9, 2026 19:22

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: native The native backend, threading and FFT planning performance Slower or larger than it should be, with no wrong result

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FFTW wisdom: an unusable SOLPROP_FFTW_WISDOM stops the run, and Apple silicon models share a key

2 participants