MKL extension advisor
Purpose
Given NumPy or SciPy code, decides whether mkl_fft, mkl_random, or mkl_umath
apply to it, and presents verified changes with short reasons. A fit check, not a
profiler.
The judgement runs on two independent axes:
- Install — which numpy binary is present and what it links against.
- Runtime — whether call sites dispatch to oneMKL, which is set either by an
activation call or by build-level pre-wiring.
They are genuinely independent. An MKL-backed BLAS says nothing about FFT: numpy's
FFT is bundled pocketfft with no BLAS linkage, so threadpool_info() can report
mkl while np.fft.fft.__module__ is still numpy.fft.
The reverse inference is just as wrong. An mkl entry in the threadpool does not
prove numpy's BLAS is MKL — importing any mkl_* extension loads oneMKL into the
process, so a stock OpenBLAS numpy reports ['mkl', 'openblas', 'openmp'] after
import mkl_fft. For the BLAS question read np.__config__.show(), not the pool.
When to Use This Skill
Use this skill when:
- The user asks whether the MKL extensions apply to a snippet, file, or codebase.
- Code calls
np.fft.*, scipy.fft.*, scipy.fftpack.*, np.random.*, or
element-wise math ufuncs on arrays.
- An environment has to be checked for whether these extensions are already active.
- An install has to be repaired so the extensions or the SciPy FFT backend work.
- The numerical effect of patching matters, because the code has exact-output tests
or golden files.
Do not use this skill for GPU work, for non-Intel CPUs, for mkl-service
thread tuning, or as a source of speedup figures.
Quick Start
Read the environment before advising anything:
python scripts/probe_env.py
It prints one JSON object: whether numpy is MKL-backed, whether each surface is
patched, how FFT got wired, which packages are importable, and any blocking gaps.
It is read-only — it never patches, never times, never writes. If it cannot be run,
each reference carries an equivalent probe snippet.
Then read the state, not the installer: np.fft.fft.__module__ for whether FFT is
live, is_patched() for how it got that way, u.sin.types for umath coverage.
Implementation Guide
Take the input — a snippet, function, file, or codebase — and scan every
call site, categorizing each one:
| Call site |
Surface |
np.fft.*, scipy.fft.*, scipy.fftpack.* |
mkl_fft |
np.random.*, RandomState, default_rng/Generator |
mkl_random |
| element-wise math ufuncs on arrays |
mkl_umath |
Load only the references the code implicates. One per distinct surface
found, and none for a surface the code does not use:
references/mkl_fft.md for transforms, the SciPy
backend, activation and proof;
references/mkl_random.md for the covered API, the
reproducibility hazard and parallel streams;
references/mkl_umath.md for the coverage probe, the
eligibility gates and thread safety.
Establish the install axis once with scripts/probe_env.py, shared by all
surfaces. Do this before judging any call site — the answer for a build-wired
numpy is different from the answer for a stock one.
Judge each call site against the loaded reference. Does that extension cover
this call and this usage? Decide fit, including an honest no. When shape or
contiguity is not knowable from the code, do not guess and do not time code to
discover it: ask for typical shapes, read the config, or instrument the call
site. Failing that, answer conditionally on the gate — "worth patching only if
the inner dimension is well above the transcendental threshold; at or below it
the vector kernel never runs, so expect no speedup."
Collect the applicable sites: location, the call, the minimal change, and
the one-line reason it helps this code. Do not add a redundant patch to a
surface that is already wired.
Confirm the whole input was scanned before presenting. Do not stop at the
first hit.
Present the result. With suggestions: grouped by location, each with a diff
or code block and a short reason, plus the install commands if packages are
missing, the proof step per surface, and the mkl_random confirmation gate. If
the code has exact-output tests or golden files, flag the numerical effect —
none of the three is a bitwise drop-in. With no suggestions: say so plainly and
why (only uncovered ufuncs, only the Generator API, only sub-threshold arrays,
only strided views, only FFT helpers, or nothing MKL-relevant). Do not invent a
benefit.
Apply only on confirmation. Show suggestions first and edit after the user
agrees. mkl_random needs an explicit yes every time, because it changes
results.
Repair the install when a package is missing. conda, Intel channel first:
conda install -c https://software.repos.intel.com/python/conda \
-c conda-forge --override-channels \
"blas=*=*_intelmkl" numpy scipy mkl_fft mkl_random mkl_umath mkl-service
pip, with Intel's index as the primary index:
pip install --index-url https://software.repos.intel.com/python/pypi \
numpy scipy mkl_fft mkl_random mkl_umath mkl-service
pip install threadpoolctl # not mirrored on Intel's index
Use --index-url, not --extra-index-url: Intel's index is a partial mirror
and pip takes the highest version across indexes, so an extra index lets the
stock PyPI numpy win — and which numpy wins decides what is build-wired.
Performance
Static analysis decides eligibility; measurement decides benefit. Whether a
call site dispatches at all is answerable from the code plus the gates. How much
faster it runs is not — that depends on both builds, the thread count, and the CPU.
So this skill states no speedup figures of its own, and neither should an agent
using it.
Measurement is not illegitimate, it is the next step. Once eligibility is
established:
- Point the user at measurement on their own hardware; upstream ships benchmark
suites precisely because the answer varies per machine.
- Compare a warmed-up run of the same code with the surface patched and unpatched,
at the shapes the code actually uses.
- Never use a stopwatch to discover a fact that can be read — shape, dtype,
contiguity. Instrumenting a call site to log the inner-loop length is a good
answer, and so is asking.
- Do not claim a sub-threshold regression. It is unsupported and
build-dependent. If the user supplies measured numbers, reason about those.
Gotchas & Limitations
- Proof of "FFT active" is
np.fft.fft.__module__, not is_patched(). On a
build-wired numpy, FFT is live while the patch counter still reads zero. Read
together, the pair tells you how it was wired.
- An Intel-built numpy arrives build-wired, dispatching FFT and umath to MKL
before any activation call. That wiring lives in the numpy recipe, which rebinds
the
numpy/fft globals directly and never touches the patch counter. A
conda-forge or stock-PyPI numpy leaves the extensions dormant.
- The numpy build is not the only thing that wires a process.
mkl_fft's CLI
can install a persistent .pth patch that applies at every interpreter start,
invisible to pip list; a sitecustomize.py variant does the same; and an
earlier import in the same process may already have patched. Read runtime state
— never infer it from the installer, the channel, or a version string.
mkl-service is not optional for the SciPy FFT backend. It provides the
mkl module that the backend imports at module level, and the guard means the
backend disappears silently without it rather than raising. scipy is required
for that same backend even if the code only touches np.fft today. Outside that,
mkl-service is a thread-control API with no patch surface.
- A BLAS selector constrains BLAS only, never FFT wiring.
- Call activation functions on the top-level package (
import mkl_fft; mkl_fft.patch_numpy_fft()). No private submodules, no invented names.
- Reason only from the code the user gave you. Do not claim to have found
something in a document.
- Dispatch thresholds may be cited, but they are private
#defines, not API.
Withholding them produces wrong advice; quoting them from memory produces stale
advice. Read them from the installed build and say which build, or express the
answer as a gate the user can check.
- Every fact stated should be one the environment can confirm. These packages
move: APIs get added, thresholds change, coverage grows, docs lag source,
channels shift. Prefer "check
u.sin.types" over a dtype list. A fact with a
probe attached stays true; a fact with a version attached expires.
- If Intel's servers are unreachable, the extensions are on public PyPI and most
are on conda-forge. Coverage is not uniform across the family or stable over
time, so check per package rather than assuming parity — and any fallback install
pairs with a stock numpy, so nothing is build-wired.
References
| File |
Load it when |
scripts/probe_env.py |
you need the install and runtime axes for the current environment in one read-only call |
references/mkl_fft.md |
the code touches np.fft, scipy.fft, or scipy.fftpack |
references/mkl_random.md |
the code touches np.random, RandomState, or default_rng |
references/mkl_umath.md |
the code applies element-wise math ufuncs to arrays |
references/official-sources.md |
you need the current install channels, the documented activation API, or the coverage of an installed release |
Two things here must never be answered from memory: the coverage of the
installed build (read it from types, __module__, and is_patched()) and
the current install channels and package names, which have already changed.
1---2name: mkl-extension-advisor3description: Deciding whether Intel's MKL extension packages apply to NumPy or SciPy code on Intel CPUs. Use when a user asks whether mkl_fft, mkl_random, or mkl_umath help their code, or points at a snippet, function, file, or codebase using np.fft, scipy.fft, np.random, or element-wise math ufuncs. Also use to check whether these extensions are already active in an environment, to fix an install so they and the SciPy FFT backend actually work, or to judge whether patching would change results and break exact-output tests. DO NOT use for GPU work, non-Intel CPUs, or mkl-service thread tuning.4license: Apache-2.05---67# MKL extension advisor89## Purpose1011Given NumPy or SciPy code, decides whether `mkl_fft`, `mkl_random`, or `mkl_umath`12apply to it, and presents verified changes with short reasons. A fit check, not a13profiler.1415The judgement runs on two independent axes:1617- **Install** — which numpy binary is present and what it links against.18- **Runtime** — whether call sites dispatch to oneMKL, which is set either by an19 activation call or by build-level pre-wiring.2021They are genuinely independent. An MKL-backed BLAS says nothing about FFT: numpy's22FFT is bundled pocketfft with no BLAS linkage, so `threadpool_info()` can report23`mkl` while `np.fft.fft.__module__` is still `numpy.fft`.2425The reverse inference is just as wrong. An `mkl` entry in the threadpool does not26prove numpy's BLAS is MKL — importing any `mkl_*` extension loads oneMKL into the27process, so a stock OpenBLAS numpy reports `['mkl', 'openblas', 'openmp']` after28`import mkl_fft`. For the BLAS question read `np.__config__.show()`, not the pool.2930## When to Use This Skill3132Use this skill when:3334- The user asks whether the MKL extensions apply to a snippet, file, or codebase.35- Code calls `np.fft.*`, `scipy.fft.*`, `scipy.fftpack.*`, `np.random.*`, or36 element-wise math ufuncs on arrays.37- An environment has to be checked for whether these extensions are already active.38- An install has to be repaired so the extensions or the SciPy FFT backend work.39- The numerical effect of patching matters, because the code has exact-output tests40 or golden files.4142Do **not** use this skill for GPU work, for non-Intel CPUs, for `mkl-service`43thread tuning, or as a source of speedup figures.4445## Quick Start4647Read the environment before advising anything:4849```bash50python scripts/probe_env.py51```5253It prints one JSON object: whether numpy is MKL-backed, whether each surface is54patched, how FFT got wired, which packages are importable, and any blocking gaps.55It is read-only — it never patches, never times, never writes. If it cannot be run,56each reference carries an equivalent probe snippet.5758Then read the state, not the installer: `np.fft.fft.__module__` for whether FFT is59live, `is_patched()` for how it got that way, `u.sin.types` for umath coverage.6061## Implementation Guide62631. **Take the input** — a snippet, function, file, or codebase — and scan every64 call site, categorizing each one:6566 | Call site | Surface |67 |---|---|68 | `np.fft.*`, `scipy.fft.*`, `scipy.fftpack.*` | `mkl_fft` |69 | `np.random.*`, `RandomState`, `default_rng`/`Generator` | `mkl_random` |70 | element-wise math ufuncs on arrays | `mkl_umath` |71722. **Load only the references the code implicates.** One per distinct surface73 found, and none for a surface the code does not use:74 [`references/mkl_fft.md`](references/mkl_fft.md) for transforms, the SciPy75 backend, activation and proof;76 [`references/mkl_random.md`](references/mkl_random.md) for the covered API, the77 reproducibility hazard and parallel streams;78 [`references/mkl_umath.md`](references/mkl_umath.md) for the coverage probe, the79 eligibility gates and thread safety.80813. **Establish the install axis once** with `scripts/probe_env.py`, shared by all82 surfaces. Do this before judging any call site — the answer for a build-wired83 numpy is different from the answer for a stock one.84854. **Judge each call site against the loaded reference.** Does that extension cover86 this call and this usage? Decide fit, including an honest no. When shape or87 contiguity is not knowable from the code, do not guess and do not time code to88 discover it: ask for typical shapes, read the config, or instrument the call89 site. Failing that, answer conditionally on the gate — "worth patching only if90 the inner dimension is well above the transcendental threshold; at or below it91 the vector kernel never runs, so expect no speedup."92935. **Collect the applicable sites**: location, the call, the minimal change, and94 the one-line reason it helps *this* code. Do not add a redundant patch to a95 surface that is already wired.96976. **Confirm the whole input was scanned** before presenting. Do not stop at the98 first hit.991007. **Present the result.** With suggestions: grouped by location, each with a diff101 or code block and a short reason, plus the install commands if packages are102 missing, the proof step per surface, and the `mkl_random` confirmation gate. If103 the code has exact-output tests or golden files, flag the numerical effect —104 none of the three is a bitwise drop-in. With no suggestions: say so plainly and105 why (only uncovered ufuncs, only the `Generator` API, only sub-threshold arrays,106 only strided views, only FFT helpers, or nothing MKL-relevant). Do not invent a107 benefit.1081098. **Apply only on confirmation.** Show suggestions first and edit after the user110 agrees. `mkl_random` needs an explicit yes every time, because it changes111 results.1121139. **Repair the install when a package is missing.** conda, Intel channel first:114115 ```bash116 conda install -c https://software.repos.intel.com/python/conda \117 -c conda-forge --override-channels \118 "blas=*=*_intelmkl" numpy scipy mkl_fft mkl_random mkl_umath mkl-service119 ```120121 pip, with Intel's index as the *primary* index:122123 ```bash124 pip install --index-url https://software.repos.intel.com/python/pypi \125 numpy scipy mkl_fft mkl_random mkl_umath mkl-service126 pip install threadpoolctl # not mirrored on Intel's index127 ```128129 Use `--index-url`, not `--extra-index-url`: Intel's index is a partial mirror130 and pip takes the highest version across indexes, so an extra index lets the131 stock PyPI numpy win — and which numpy wins decides what is build-wired.132133## Performance134135**Static analysis decides eligibility; measurement decides benefit.** Whether a136call site dispatches at all is answerable from the code plus the gates. How much137faster it runs is not — that depends on both builds, the thread count, and the CPU.138So this skill states no speedup figures of its own, and neither should an agent139using it.140141Measurement is not illegitimate, it is the next step. Once eligibility is142established:143144- Point the user at measurement on their own hardware; upstream ships benchmark145 suites precisely because the answer varies per machine.146- Compare a warmed-up run of the same code with the surface patched and unpatched,147 at the shapes the code actually uses.148- Never use a stopwatch to discover a fact that can be read — shape, dtype,149 contiguity. Instrumenting a call site to log the inner-loop length is a good150 answer, and so is asking.151- Do **not** claim a sub-threshold regression. It is unsupported and152 build-dependent. If the user supplies measured numbers, reason about those.153154## Gotchas & Limitations155156- **Proof of "FFT active" is `np.fft.fft.__module__`, not `is_patched()`.** On a157 build-wired numpy, FFT is live while the patch counter still reads zero. Read158 together, the pair tells you *how* it was wired.159- **An Intel-built numpy arrives build-wired**, dispatching FFT and umath to MKL160 before any activation call. That wiring lives in the numpy recipe, which rebinds161 the `numpy/fft` globals directly and never touches the patch counter. A162 conda-forge or stock-PyPI numpy leaves the extensions dormant.163- **The numpy build is not the only thing that wires a process.** `mkl_fft`'s CLI164 can install a persistent `.pth` patch that applies at every interpreter start,165 invisible to `pip list`; a `sitecustomize.py` variant does the same; and an166 earlier import in the same process may already have patched. Read runtime state167 — never infer it from the installer, the channel, or a version string.168- **`mkl-service` is not optional for the SciPy FFT backend.** It provides the169 `mkl` module that the backend imports at module level, and the guard means the170 backend disappears *silently* without it rather than raising. `scipy` is required171 for that same backend even if the code only touches `np.fft` today. Outside that,172 `mkl-service` is a thread-control API with no patch surface.173- **A BLAS selector constrains BLAS only**, never FFT wiring.174- **Call activation functions on the top-level package** (`import mkl_fft;175 mkl_fft.patch_numpy_fft()`). No private submodules, no invented names.176- **Reason only from the code the user gave you.** Do not claim to have found177 something in a document.178- **Dispatch thresholds may be cited, but they are private `#define`s, not API.**179 Withholding them produces wrong advice; quoting them from memory produces stale180 advice. Read them from the installed build and say which build, or express the181 answer as a gate the user can check.182- **Every fact stated should be one the environment can confirm.** These packages183 move: APIs get added, thresholds change, coverage grows, docs lag source,184 channels shift. Prefer "check `u.sin.types`" over a dtype list. A fact with a185 probe attached stays true; a fact with a version attached expires.186- If Intel's servers are unreachable, the extensions are on public PyPI and most187 are on conda-forge. Coverage is not uniform across the family or stable over188 time, so check per package rather than assuming parity — and any fallback install189 pairs with a stock numpy, so nothing is build-wired.190191## References192193| File | Load it when |194|---|---|195| [`scripts/probe_env.py`](scripts/probe_env.py) | you need the install and runtime axes for the current environment in one read-only call |196| [`references/mkl_fft.md`](references/mkl_fft.md) | the code touches `np.fft`, `scipy.fft`, or `scipy.fftpack` |197| [`references/mkl_random.md`](references/mkl_random.md) | the code touches `np.random`, `RandomState`, or `default_rng` |198| [`references/mkl_umath.md`](references/mkl_umath.md) | the code applies element-wise math ufuncs to arrays |199| [`references/official-sources.md`](references/official-sources.md) | you need the current install channels, the documented activation API, or the coverage of an installed release |200201Two things here must never be answered from memory: **the coverage of the202installed build** (read it from `types`, `__module__`, and `is_patched()`) and203**the current install channels and package names**, which have already changed.