Fix "numpy.dtype size changed, may indicate binary incompatibility"

Fix "numpy.dtype size changed, may indicate binary incompatibility"

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.


What's actually happening

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:

  • You upgraded numpy directly (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.
  • A Docker image layer cached an old wheel for pandas/scipy while a later RUN pip install layer bumped numpy on its own.
  • You're mixing a conda environment's numpy with a pip-installed package that was built against a PyPI numpy build, or vice versa, and the two ABIs genuinely differ.
  • A CI cache restored site-packages from a run before a requirements.txt numpy version bump, so the cache has the new numpy but the old compiled dependents.

Step 1: find out which package actually has the stale build

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.


Step 2: the actual fix, in order of how invasive it is

Reinstall the mismatched package, not numpy

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.

If it's numpy that's actually behind

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.

Pin the whole numpy-adjacent stack together and reinstall from a clean environment

# 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.


Docker-specific fix

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.


Conda and pip together

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.


Confirming it's actually fixed

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.

Related articles