hamer-p150 / code /hamer /native /__init__.py
changh95's picture
Optimized build (2026-10-03): model call 3.49 ms, device 3.36 ms
58ba6df verified
Raw History Blame Contribute Delete
11.1 kB
# SPDX-License-Identifier: Apache-2.0
"""Round 14 (P1): native host preprocess -- ``resize.c`` (crop + grey pad + PIL BILINEAR box resize + mirror in one call,
bit-identical to the PIL path), built lazily with the host C compiler into a per-user cache and loaded with ctypes.
Nothing here is required: if there is no compiler, the build fails, or the load-time self-check (random crops vs the PIL
path, bit for bit) fails, :func:`crop_resize` is ``None`` and preprocess keeps the PIL path (same bytes, slower).
``HAMER_PRE_NATIVE=0`` disables it.
"""
from __future__ import annotations
import ctypes
import hashlib
import os
import subprocess
import tempfile
import threading
from pathlib import Path
from typing import Optional
import numpy as np
_SRCS = [Path(__file__).with_name("resize.c")]
# no -ffast-math, no FP contraction: the filter coefficients must be computed with Pillow's exact double arithmetic
_CFLAGS = ["-O3", "-march=native", "-ffp-contract=off", "-fno-fast-math", "-shared", "-fPIC"]
_LOCK = threading.Lock()
_FN = None
_FN_PS = None # round 15: hamer_crop_resize_ps (any source pixel stride) when the zero-copy PIL path self-checked
_IM2COL = None
_TRIED = False
STATUS = "not loaded"
def _cache_dir() -> Path:
d = os.environ.get("HAMER_NATIVE_DIR")
if d:
return Path(d)
base = os.environ.get("XDG_CACHE_HOME") or str(Path.home() / ".cache")
return Path(base) / "hamer-native"
def _build() -> Optional[Path]:
src = b"".join(p.read_bytes() for p in _SRCS)
cc = os.environ.get("CC", "cc")
tag = hashlib.sha1(src + " ".join(_CFLAGS).encode() + os.uname().machine.encode()).hexdigest()[:16]
for d in (_cache_dir(), Path(tempfile.gettempdir()) / f"hamer-native-{os.getuid()}"):
so = d / f"hamer_resize_{tag}.so"
if so.exists():
return so
try:
d.mkdir(parents=True, exist_ok=True)
tmp = d / f".{so.name}.{os.getpid()}"
subprocess.run([cc, *_CFLAGS, "-o", str(tmp), *map(str, _SRCS)], check=True, capture_output=True, timeout=120)
os.replace(tmp, so)
return so
except Exception: # noqa: BLE001 - no compiler / read-only dir: try the next dir, then give up
continue
return None
def _self_check(fn) -> bool:
"""The native path must reproduce the PIL path bit for bit (random frames / boxes / both hands, edge-crossing boxes)."""
from PIL import Image
from hamer import preprocess as pp
rng = np.random.default_rng(1234)
for i in range(24):
H, W = (int(v) for v in rng.integers(48, 400, 2))
a = rng.integers(0, 256, (H, W, 3), dtype=np.uint8)
img = Image.fromarray(a)
w, h = rng.uniform(4, W), rng.uniform(4, H)
x1, y1 = rng.uniform(-w / 2, W - w / 2), rng.uniform(-h / 2, H - h / 2)
bbox, rf, right = (x1, y1, x1 + w, y1 + h), float(rng.choice([2.0, 1.0, 3.3])), bool(i % 2)
if pp.hamer_bbox_size(bbox, rf) < 2:
continue
crop, meta = pp._square_crop(img, bbox, rf) # noqa: SLF001
ref = pp.resize_crop_hw(crop, right, bands=1)
got = _call(fn, a, bbox, rf, right)
got_chw = _call(fn, a, bbox, rf, right, chw=True)
if got is None or not np.array_equal(ref, got[0]) or got[1] != meta:
return False
if got_chw is None or not np.array_equal(ref.transpose(2, 0, 1), got_chw[0]) or got_chw[1] != meta:
return False
return True
def _call(fn, a: np.ndarray, bbox, rescale_factor: float, is_right: bool, chw: bool = False):
"""(u8 HWC [256, 192, 3] or planar CHW [3, 256, 192] when chw, (cx, cy, bbox_size)) or None when the PIL path must handle it (degenerate boxes)."""
if a.ndim != 3 or a.shape[2] != 3 or a.dtype != np.uint8 or a.strides[1:] != (3, 1):
return None
H, W = a.shape[:2]
return _call_ptr(fn, a.ctypes.data, H, W, a.strides[0], 3, bbox, rescale_factor, is_right, chw)
def _call_ptr(fn, ptr: int, H: int, W: int, row_stride: int, ps: int, bbox, rescale_factor: float, is_right: bool,
chw: bool = False):
"""_call on a raw u8 image pointer with ``ps`` bytes per pixel (3: packed RGB; 4: Pillow's RGBX rows)."""
from hamer.preprocess import hamer_bbox_size
x1, y1, x2, y2 = bbox
cx, cy = (x1 + x2) / 2.0, (y1 + y2) / 2.0
bs = hamer_bbox_size(bbox, rescale_factor)
half = bs / 2.0
S = int(round(bs))
# exactly _square_crop's integer boxes
l, t = max(0, int(round(cx - half))), max(0, int(round(cy - half)))
r, b = min(W, int(round(cx + half))), min(H, int(round(cy + half)))
px, py = max(0, int(round(-(cx - half)))), max(0, int(round(-(cy - half))))
if S < 2 or r <= l or b <= t:
return None
out = np.empty((3, 256, 192) if chw else (256, 192, 3), np.uint8)
args = (S, l, t, r - l, b - t, px, py, 0 if is_right else 1, 1 if chw else 0, out.ctypes.data)
rc = fn(ptr, H, W, row_stride, *args) if ps == 3 and fn is not _FN_PS else fn(ptr, H, W, row_stride, ps, *args)
if rc != 0:
return None
return out, (cx, cy, bs)
def load():
"""The ctypes function (or None). Built / checked once per process."""
global _FN, _FN_PS, _IM2COL, _TRIED, STATUS
with _LOCK:
if _TRIED:
return _FN
_TRIED = True
pre_off = os.environ.get("HAMER_PRE_NATIVE", "1").strip().lower() in ("0", "false", "no", "off")
try:
so = _build()
if so is None:
STATUS = "no compiler / build failed: PIL path"
return None
lib = ctypes.CDLL(str(so))
fn = lib.hamer_crop_resize
fn.restype = ctypes.c_int
fn.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_long] + [ctypes.c_int] * 9 + [ctypes.c_void_p]
ic = lib.hamer_im2col_tile_bf16
ic.restype = ctypes.c_int
ic.argtypes = [ctypes.c_void_p, ctypes.c_long, ctypes.c_long, ctypes.c_long, ctypes.c_int, ctypes.c_int,
ctypes.c_int, ctypes.c_void_p]
_IM2COL = ic # checked by its caller (TtHamer._setup_fast_io) against the torch path
if pre_off:
STATUS = "off (HAMER_PRE_NATIVE=0)"
return None
if not _self_check(fn):
STATUS = "self-check FAILED (not bit-identical to PIL): PIL path"
return None
_FN = fn
STATUS = f"on ({so.name}, self-check bit-identical)"
if os.environ.get("HAMER_PRE_ZEROCOPY", "1").strip().lower() not in ("0", "false", "no", "off"):
fps = lib.hamer_crop_resize_ps
fps.restype = ctypes.c_int
fps.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_long, ctypes.c_int] \
+ [ctypes.c_int] * 9 + [ctypes.c_void_p]
_FN_PS = fps
if _self_check_zc():
STATUS += "; zero-copy PIL buffer on"
else:
_FN_PS = None
STATUS += "; zero-copy PIL buffer off (self-check failed)"
except Exception as e: # noqa: BLE001
STATUS = f"load failed ({e!r}): PIL path"
_FN = None
return _FN
def im2col_tile_fn():
"""Round 14: ``hamer_im2col_tile_bf16`` (u8 crop -> bf16 TILE im2col of the centred crop) or None."""
load()
return _IM2COL
def crop_resize(a: np.ndarray, bbox, rescale_factor: float, is_right: bool, chw: bool = False):
fn = load()
if fn is None:
return None
return _call(fn, a, bbox, rescale_factor, is_right, chw)
# ---------------------------------------------------------------------------------------------------------------------
# round 15: the decoded frame read IN PLACE (no np.asarray copy, ~0.08 ms per 512x334 frame). Pillow stores an RGB image
# as 4-byte RGBX pixels; its Arrow C export (Pillow >= 11.2, a single-block image) hands out the pixel buffer pointer.
class _ArrowArray(ctypes.Structure):
pass
_ArrowArray._fields_ = [("length", ctypes.c_int64), ("null_count", ctypes.c_int64), ("offset", ctypes.c_int64),
("n_buffers", ctypes.c_int64), ("n_children", ctypes.c_int64),
("buffers", ctypes.POINTER(ctypes.c_void_p)),
("children", ctypes.POINTER(ctypes.POINTER(_ArrowArray))), ("dictionary", ctypes.c_void_p),
("release", ctypes.c_void_p), ("private_data", ctypes.c_void_p)]
_CAP_GET = ctypes.pythonapi.PyCapsule_GetPointer
_CAP_GET.restype = ctypes.c_void_p
_CAP_GET.argtypes = [ctypes.py_object, ctypes.c_char_p]
def _rgbx_view(img):
"""(capsules to keep alive, pointer, H, W) of an RGB PIL image's RGBX pixel buffer, or None."""
if img.mode != "RGB" or not hasattr(img, "__arrow_c_array__"):
return None
try:
img.load()
sch, arr = img.__arrow_c_array__()
a = ctypes.cast(_CAP_GET(arr, b"arrow_array"), ctypes.POINTER(_ArrowArray)).contents
W, H = img.size
if a.n_children != 1 or a.length != H * W or a.offset != 0:
return None
c = a.children[0].contents
if c.n_buffers != 2 or c.length != H * W * 4 or c.offset != 0 or not c.buffers[1]:
return None
return (sch, arr), c.buffers[1], H, W
except Exception: # noqa: BLE001 - older Pillow / multi-block image: the copying path
return None
def _self_check_zc() -> bool:
"""The zero-copy path must give the bytes of the np.asarray path (random frames / boxes / both hands)."""
from PIL import Image
rng = np.random.default_rng(4321)
for i in range(16):
H, W = (int(v) for v in rng.integers(48, 400, 2))
a = rng.integers(0, 256, (H, W, 3), dtype=np.uint8)
img = Image.fromarray(a)
w, h = rng.uniform(4, W), rng.uniform(4, H)
x1, y1 = rng.uniform(-w / 2, W - w / 2), rng.uniform(-h / 2, H - h / 2)
bbox, rf, right = (x1, y1, x1 + w, y1 + h), float(rng.choice([2.0, 1.0, 3.3])), bool(i % 2)
ref = _call(_FN, a, bbox, rf, right, chw=True)
v = _rgbx_view(img)
if v is None:
return False
got = _call_ptr(_FN_PS, v[1], v[2], v[3], v[3] * 4, 4, bbox, rf, right, chw=True)
if (ref is None) != (got is None) or (ref is not None and (not np.array_equal(ref[0], got[0]) or ref[1] != got[1])):
return False
return True
def crop_resize_image(img, bbox, rescale_factor: float, is_right: bool, chw: bool = False):
"""crop_resize on a decoded PIL image, reading its pixel buffer in place when possible (round 15)."""
fn = load()
if fn is None:
return None
if _FN_PS is not None:
v = _rgbx_view(img)
if v is not None:
keep, ptr, H, W = v
res = _call_ptr(_FN_PS, ptr, H, W, W * 4, 4, bbox, rescale_factor, is_right, chw)
del keep
return res
return _call(fn, np.asarray(img), bbox, rescale_factor, is_right, chw)