You import pandas, scipy, or some other numpy-adjacent library and get a wall of warnings like this, sometimes followed by a hard crash later when the mismatched dtype actually gets used:
RuntimeWarning: numpy.dtype size changed, may indicate binary incompatibility. Expected 96, got 88
from pandas._libs.interval import Interval
Or as a hard error instead of a warning, depending on the exact version combination:
ValueError: numpy.dtype size changed, may indicate binary incompatibility. Expected 96 from C header, got 88 from PyObject
The numbers (96, 88, sometimes 80 or 104) are irrelevant to memorize. What matters is what they represent: two different compiled expectations for how many bytes a numpy dtype struct takes up, and something in your environment was built against one size while numpy itself is now reporting the other.
numpy's C API isn't fully binary-stable across releases. When pandas, scipy, scikit-learn, or any other package with compiled C/Cython extensions gets built, that build process bakes in assumptions about numpy's internal struct layout at that specific numpy version. If you later install a different numpy version, that difference doesn't get resolved automatically. Python packages don't re-check binary layout compatibility at import time the way you'd hope, they just read data at hardcoded offsets, and the warning is numpy's runtime noticing the offsets don't line up.
This is not a numpy bug and it's not a broken pip install in the usual sense. It's an ABI (application binary interface) mismatch between two compiled artifacts that were never built against the same numpy version in the first place. The most common ways it actually happens in practice:
pip install -U numpy) without also upgrading the packages that depend on its C API, and one of those packages ships a compiled wheel built against the older ABI.RUN pip install layer bumped numpy on its own.site-packages from a run before a requirements.txt numpy version bump, so the cache has the new numpy but the old compiled dependents.The traceback tells you which module was importing when the mismatch fired, that's your starting point, not necessarily the final culprit if multiple packages are involved:
import numpy as np
print("numpy:", np.__version__)
import pandas as pd
print("pandas:", pd.__version__)
import scipy
print("scipy:", scipy.__version__)
import sklearn
print("scikit-learn:", sklearn.__version__)
Cross-reference each package's numpy build requirement against what's actually installed:
pip show pandas | grep -i requires
pip show scipy | grep -i requires
pip show scikit-learn | grep -i requires
# What numpy version is actually present
pip show numpy | grep -i version
If a package's declared numpy requirement is satisfied on paper but the warning still fires, the compiled wheel you have locally predates that requirement being tightened, which happens when pip resolves versions independently across separate pip install calls instead of one pass over a full requirements file.
If numpy is the newer, correct version and one specific package (say, pandas) is the one with the stale compiled extension, reinstall that package so it rebuilds or re-fetches a wheel against the current numpy:
pip install --force-reinstall --no-cache-dir pandas
--no-cache-dir matters here specifically because pip's wheel cache is exactly the kind of stale-artifact source that caused this in the first place. Skipping it forces a fresh wheel fetch or build against your current numpy.
pip install --upgrade numpy
This is the right call when everything else (pandas, scipy, scikit-learn) already requires a newer numpy than what's installed, i.e. numpy is the straggler, not the other way around. Check the requirement strings from Step 1 to confirm before running this, upgrading numpy blindly can trade one mismatch for a different one if some other package has an upper pin on numpy.
# In requirements.txt, pin numpy explicitly alongside its dependents
# rather than leaving numpy unpinned and letting pip resolve it independently
numpy==1.26.4
pandas==2.2.1
scipy==1.13.0
scikit-learn==1.4.2
# Then rebuild the environment from scratch instead of upgrading in place
python -m venv .venv --clear
source .venv/bin/activate
pip install --no-cache-dir -r requirements.txt
This is the version worth doing for a real project rather than a one-off script: pinning numpy explicitly alongside everything that depends on its C API, instead of letting numpy float and hoping every dependent package's wheel happens to match. A clean venv rebuild also sidesteps any locally cached wheel that a plain --upgrade might still reuse.
If this only shows up inside a container and not locally, it's almost always layer caching. The fix is to make sure numpy and its dependents install in the same RUN layer from the same requirements file, rather than numpy getting installed early (and cached) while a dependent package gets added or bumped in a later layer:
# Avoid: numpy pinned in one layer, pandas added later in a separate layer
# RUN pip install numpy==1.26.4
# COPY requirements-extra.txt .
# RUN pip install -r requirements-extra.txt # pandas here might reuse a stale cached wheel
# Prefer: everything numpy-adjacent installed together, one layer, no cache reuse across bumps
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
If you're stuck debugging an existing image rather than rewriting the Dockerfile, docker build --no-cache for one run will confirm whether caching is actually the cause before you restructure anything.
Mixing conda-installed numpy with pip-installed packages that have their own compiled numpy dependency is a common source of this specific error, because conda-forge and PyPI don't always ship numpy builds with identical ABI even at the same version string. If you're in a conda environment, prefer installing the numpy-adjacent stack entirely through conda rather than mixing:
conda install numpy pandas scipy scikit-learn -c conda-forge
rather than conda install numpy followed by pip install pandas, which is the combination most likely to reintroduce this exact mismatch even right after you thought you'd fixed it.
import warnings
# Turn the RuntimeWarning into a hard error temporarily so a clean import
# actually proves the fix, rather than the warning silently reappearing
# for a code path you didn't happen to exercise
with warnings.catch_warnings():
warnings.simplefilter("error", RuntimeWarning)
import numpy as np
import pandas as pd
import scipy
print("Clean import, no ABI mismatch")
Running the import under warnings.simplefilter("error", RuntimeWarning) is worth doing once after any fix here, because the warning-level version of this error is easy to miss in normal output and only becomes a hard failure later when the mismatched dtype actually gets read at the wrong offset, sometimes producing silently wrong values rather than a crash, which is a worse outcome than an import-time error.