mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-15 12:50:42 -07:00
Follow-up to #361. The original fallback silently mapped unknown numpy dtypes to torch.float32, which would reinterpret the memcpy'd bytes in the wrong dtype and corrupt data (e.g. fp16 tensors from some TTS engines) rather than erroring loudly. - Hoist dtype_map out of the inner function so it's built once - Add float16, complex64, complex128 mappings - Raise TypeError on unknown dtype instead of silent float32 fallback Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
96 lines
3.3 KiB
Python
96 lines
3.3 KiB
Python
"""
|
|
PyInstaller runtime hook: numpy 2.x / torch ABI mismatch fix.
|
|
|
|
Problem
|
|
-------
|
|
torch is compiled against numpy 1.x headers. numpy 2.x changed the version
|
|
number returned by PyArray_GetNDArrayCVersion() (0x01000009 → 0x02000000),
|
|
so torch's is_numpy_available() returns False and every torch.from_numpy()
|
|
call raises:
|
|
|
|
RuntimeError: Numpy is not available
|
|
|
|
This surfaces as:
|
|
|
|
ValueError: Unable to create tensor, you should probably activate
|
|
padding with 'padding=True'
|
|
|
|
during TTS generation (EncodecFeatureExtractor → BatchFeature.convert_to_tensors).
|
|
|
|
Fix
|
|
---
|
|
Runtime hooks execute after PyInstaller's FrozenImporter is registered, so
|
|
frozen torch/numpy are importable here. We start a background thread that
|
|
waits for torch to finish loading then wraps torch.from_numpy with a ctypes
|
|
memmove fallback that bypasses the C-level numpy ABI check entirely.
|
|
|
|
This approach works with any numpy version and is safer than binary-patching
|
|
libtorch_python.dylib (which risks PyArray_Descr struct layout mismatches).
|
|
"""
|
|
|
|
import sys
|
|
import threading
|
|
|
|
|
|
def _patch_torch_from_numpy():
|
|
import time
|
|
|
|
for _ in range(7200): # poll up to 360 s at 50 ms intervals
|
|
time.sleep(0.05)
|
|
torch = sys.modules.get("torch")
|
|
if torch is None or not hasattr(torch, "from_numpy"):
|
|
continue
|
|
if getattr(torch, "_vb_from_numpy_patched", False):
|
|
return
|
|
try:
|
|
import ctypes
|
|
import numpy as np
|
|
|
|
_orig = torch.from_numpy
|
|
|
|
# Explicit numpy → torch dtype map. Silent fallback to float32 on
|
|
# unknown dtypes would reinterpret the memcpy'd bytes as fp32 and
|
|
# silently corrupt data (e.g. fp16 tensors from some TTS engines),
|
|
# so we raise instead.
|
|
dtype_map = {
|
|
"float16": _t.float16,
|
|
"float32": _t.float32,
|
|
"float64": _t.float64,
|
|
"int8": _t.int8,
|
|
"int16": _t.int16,
|
|
"int32": _t.int32,
|
|
"int64": _t.int64,
|
|
"uint8": _t.uint8,
|
|
"bool": _t.bool,
|
|
"complex64": _t.complex64,
|
|
"complex128": _t.complex128,
|
|
}
|
|
|
|
def _safe_from_numpy(
|
|
arr, _orig=_orig, _c=ctypes, _np=np, _t=torch, _map=dtype_map
|
|
):
|
|
try:
|
|
return _orig(arr)
|
|
except RuntimeError:
|
|
a = _np.ascontiguousarray(arr)
|
|
key = str(a.dtype)
|
|
if key not in _map:
|
|
raise TypeError(
|
|
f"pyi_rth_numpy_compat: unsupported numpy dtype "
|
|
f"{key!r} in torch.from_numpy fallback; add an "
|
|
f"explicit mapping rather than silently copying "
|
|
f"bytes into the wrong dtype."
|
|
)
|
|
out = _t.empty(list(a.shape), dtype=_map[key])
|
|
_c.memmove(out.data_ptr(), a.ctypes.data, a.nbytes)
|
|
return out
|
|
|
|
torch.from_numpy = _safe_from_numpy
|
|
torch._vb_from_numpy_patched = True
|
|
except Exception:
|
|
pass
|
|
return
|
|
|
|
|
|
threading.Thread(target=_patch_torch_from_numpy, daemon=True).start()
|