mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
Both PyPI onnxruntime flavors unpack into the same site-packages directory, so a bundle carrying the CPU build (transcription, via faster-whisper) overwrote the GPU build's native libraries during install while the stale onnxruntime_gpu dist-info kept claiming otherwise. Every ONNX-backed tool then silently ran on CPU. The installer now reconciles the flavor before the venv merge and the GPU build always wins, in both install orders; reinstalling any GPU bundle repairs a previously clobbered venv. gpu.py's warning now says exactly that. Build-side, build-bundle.sh gains the same reconcile and verify-bundle-compatibility.sh layers bundles through the real installer merge and asserts a single flavor. Verified live on an RTX 4070 against the published bundles: reproduced the clobber with the stock installer, then confirmed both the prevention and repair paths with the patched one. Fixes #490
177 lines
6.9 KiB
Python
177 lines
6.9 KiB
Python
"""ONNX Runtime flavor reconciliation tests (GPU wins).
|
|
|
|
The invariant under test: a bundle install must never downgrade the shared
|
|
venv's ONNX Runtime from the GPU build to the CPU build. Both PyPI packages
|
|
(`onnxruntime` and `onnxruntime-gpu`) install into the SAME site-packages
|
|
directory (`onnxruntime/`), so a bundle that carries the CPU build (e.g.
|
|
transcription, via faster-whisper's transitive dependency) overwrites the GPU
|
|
build's native libraries file-by-file during move_tree, while the stale
|
|
`onnxruntime_gpu-*.dist-info` metadata survives. pip then claims the GPU build
|
|
is installed but `CUDAExecutionProvider` is gone and every ONNX tool silently
|
|
runs on CPU (snapotter-hq/SnapOtter#490).
|
|
"""
|
|
|
|
import importlib.util
|
|
import os
|
|
|
|
CUDA_PROVIDER_LIB = "libonnxruntime_providers_cuda.so"
|
|
|
|
|
|
def load_installer():
|
|
script_path = os.path.join(os.path.dirname(__file__), "..", "install_feature.py")
|
|
spec = importlib.util.spec_from_file_location("install_feature_onnx_under_test", script_path)
|
|
module = importlib.util.module_from_spec(spec)
|
|
assert spec.loader is not None
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def make_onnxruntime(sp, flavor, version="1.20.1"):
|
|
"""Lay out a fake onnxruntime install of the given flavor under sp."""
|
|
capi = sp / "onnxruntime" / "capi"
|
|
capi.mkdir(parents=True, exist_ok=True)
|
|
(sp / "onnxruntime" / "__init__.py").write_text(flavor)
|
|
(capi / "onnxruntime_pybind11_state.so").write_text(flavor)
|
|
if flavor == "gpu":
|
|
(capi / CUDA_PROVIDER_LIB).write_text("cuda")
|
|
dist_info = sp / f"onnxruntime_gpu-{version}.dist-info"
|
|
dist_info.mkdir()
|
|
(dist_info / "METADATA").write_text("Name: onnxruntime-gpu")
|
|
else:
|
|
dist_info = sp / f"onnxruntime-{version}.dist-info"
|
|
dist_info.mkdir()
|
|
(dist_info / "METADATA").write_text("Name: onnxruntime")
|
|
|
|
|
|
def dist_infos(sp, prefix):
|
|
return sorted(
|
|
n for n in os.listdir(sp)
|
|
if n.startswith(prefix) and n.endswith(".dist-info")
|
|
)
|
|
|
|
|
|
def cpu_dist_infos(sp):
|
|
return [n for n in dist_infos(sp, "onnxruntime-")]
|
|
|
|
|
|
def gpu_dist_infos(sp):
|
|
return [n for n in dist_infos(sp, "onnxruntime_gpu-")]
|
|
|
|
|
|
def test_incoming_cpu_bundle_cannot_clobber_gpu_install(tmp_path):
|
|
"""The #490 scenario: GPU bundle installed first, transcription second."""
|
|
installer = load_installer()
|
|
staging = tmp_path / "staging"
|
|
venv_sp = tmp_path / "venv-sp"
|
|
staging.mkdir()
|
|
venv_sp.mkdir()
|
|
make_onnxruntime(venv_sp, "gpu")
|
|
make_onnxruntime(staging, "cpu")
|
|
# The rest of the incoming bundle must survive untouched.
|
|
(staging / "faster_whisper").mkdir()
|
|
(staging / "faster_whisper" / "__init__.py").write_text("fw")
|
|
|
|
installer.reconcile_onnxruntime(str(staging), str(venv_sp))
|
|
installer.move_tree(str(staging), str(venv_sp))
|
|
|
|
# The GPU build survives: CUDA provider lib intact, core lib not downgraded.
|
|
assert (venv_sp / "onnxruntime" / "capi" / CUDA_PROVIDER_LIB).exists()
|
|
assert (venv_sp / "onnxruntime" / "capi" / "onnxruntime_pybind11_state.so").read_text() == "gpu"
|
|
# Metadata stays truthful: GPU dist-info only, no CPU dist-info shipped in.
|
|
assert gpu_dist_infos(venv_sp) == ["onnxruntime_gpu-1.20.1.dist-info"]
|
|
assert cpu_dist_infos(venv_sp) == []
|
|
# The bundle's own packages still landed.
|
|
assert (venv_sp / "faster_whisper" / "__init__.py").read_text() == "fw"
|
|
|
|
|
|
def test_incoming_gpu_bundle_replaces_cpu_and_clears_stale_metadata(tmp_path):
|
|
"""Reverse order: transcription first, then a GPU bundle. GPU wins."""
|
|
installer = load_installer()
|
|
staging = tmp_path / "staging"
|
|
venv_sp = tmp_path / "venv-sp"
|
|
staging.mkdir()
|
|
venv_sp.mkdir()
|
|
make_onnxruntime(venv_sp, "cpu")
|
|
make_onnxruntime(staging, "gpu")
|
|
|
|
installer.reconcile_onnxruntime(str(staging), str(venv_sp))
|
|
installer.move_tree(str(staging), str(venv_sp))
|
|
|
|
assert (venv_sp / "onnxruntime" / "capi" / CUDA_PROVIDER_LIB).exists()
|
|
assert (venv_sp / "onnxruntime" / "capi" / "onnxruntime_pybind11_state.so").read_text() == "gpu"
|
|
# The stale CPU dist-info is gone, so pip metadata matches reality.
|
|
assert cpu_dist_infos(venv_sp) == []
|
|
assert gpu_dist_infos(venv_sp) == ["onnxruntime_gpu-1.20.1.dist-info"]
|
|
|
|
|
|
def test_cpu_bundle_into_empty_venv_installs_normally(tmp_path):
|
|
"""No GPU build anywhere (e.g. transcription alone): nothing to protect."""
|
|
installer = load_installer()
|
|
staging = tmp_path / "staging"
|
|
venv_sp = tmp_path / "venv-sp"
|
|
staging.mkdir()
|
|
venv_sp.mkdir()
|
|
make_onnxruntime(staging, "cpu")
|
|
|
|
installer.reconcile_onnxruntime(str(staging), str(venv_sp))
|
|
installer.move_tree(str(staging), str(venv_sp))
|
|
|
|
assert (venv_sp / "onnxruntime" / "capi" / "onnxruntime_pybind11_state.so").read_text() == "cpu"
|
|
assert cpu_dist_infos(venv_sp) == ["onnxruntime-1.20.1.dist-info"]
|
|
|
|
|
|
def test_cpu_reinstall_over_cpu_is_untouched(tmp_path):
|
|
"""arm64 and CPU-only stacks: CPU-over-CPU merges are business as usual."""
|
|
installer = load_installer()
|
|
staging = tmp_path / "staging"
|
|
venv_sp = tmp_path / "venv-sp"
|
|
staging.mkdir()
|
|
venv_sp.mkdir()
|
|
make_onnxruntime(venv_sp, "cpu")
|
|
make_onnxruntime(staging, "cpu")
|
|
|
|
installer.reconcile_onnxruntime(str(staging), str(venv_sp))
|
|
installer.move_tree(str(staging), str(venv_sp))
|
|
|
|
assert (venv_sp / "onnxruntime" / "capi" / "onnxruntime_pybind11_state.so").read_text() == "cpu"
|
|
assert cpu_dist_infos(venv_sp) == ["onnxruntime-1.20.1.dist-info"]
|
|
|
|
|
|
def test_gpu_reinstall_over_gpu_is_untouched(tmp_path):
|
|
installer = load_installer()
|
|
staging = tmp_path / "staging"
|
|
venv_sp = tmp_path / "venv-sp"
|
|
staging.mkdir()
|
|
venv_sp.mkdir()
|
|
make_onnxruntime(venv_sp, "gpu")
|
|
make_onnxruntime(staging, "gpu")
|
|
|
|
installer.reconcile_onnxruntime(str(staging), str(venv_sp))
|
|
installer.move_tree(str(staging), str(venv_sp))
|
|
|
|
assert (venv_sp / "onnxruntime" / "capi" / CUDA_PROVIDER_LIB).exists()
|
|
assert gpu_dist_infos(venv_sp) == ["onnxruntime_gpu-1.20.1.dist-info"]
|
|
|
|
|
|
def test_staging_with_both_flavors_ships_gpu_metadata_only(tmp_path):
|
|
"""Defensive: a bundle built with both flavors (GPU installed last, so the
|
|
package files are the GPU build) must not ship the stale CPU metadata."""
|
|
installer = load_installer()
|
|
staging = tmp_path / "staging"
|
|
venv_sp = tmp_path / "venv-sp"
|
|
staging.mkdir()
|
|
venv_sp.mkdir()
|
|
make_onnxruntime(staging, "gpu")
|
|
# Simulate the leftover CPU dist-info next to the GPU files.
|
|
cpu_meta = staging / "onnxruntime-1.20.1.dist-info"
|
|
cpu_meta.mkdir()
|
|
(cpu_meta / "METADATA").write_text("Name: onnxruntime")
|
|
|
|
installer.reconcile_onnxruntime(str(staging), str(venv_sp))
|
|
installer.move_tree(str(staging), str(venv_sp))
|
|
|
|
# The GPU package files ship; the stale CPU metadata does not.
|
|
assert (venv_sp / "onnxruntime" / "capi" / CUDA_PROVIDER_LIB).exists()
|
|
assert cpu_dist_infos(venv_sp) == []
|
|
assert gpu_dist_infos(venv_sp) == ["onnxruntime_gpu-1.20.1.dist-info"]
|