feat: add ROCm (AMD GPU) and Intel GPU support (v0.2.12)

New image variants:
- :rocm  — InsightFace via ROCmExecutionProvider, SigLIP via PyTorch ROCm 6.3
- :intel — InsightFace via OpenVINOExecutionProvider (onnxruntime-openvino);
           Intel GPU compute runtime auto-installed from Intel graphics repo;
           OPENVINO_DEVICE=GPU opts into Arc/iGPU inference (default: CPU)

Also adds:
- pyproject-rocm.toml + uv-rocm.lock, pyproject-intel.toml + uv-intel.lock
- compose.yml device passthrough snippets for AMD and Intel
- CI: build-rocm and build-intel jobs in docker-publish.yml; all four
  variants built and tagged in release.yml
- README reworked: cleaner structure, GPU variant quick-start examples,
  OPENVINO_DEVICE env var documented
- CHANGELOG entry and version bump to 0.2.12

Fix: IntPrompt in dict literal was eagerly evaluated in the no-embedding
fallback path of _get_strategy_choice, prompting users for a custom count
regardless of which strategy they picked.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 02:31:09 +00:00
co-authored by Claude Sonnet 4.6
parent a281b4b896
commit b2be19e259
16 changed files with 4550 additions and 183 deletions
+28 -7
View File
@@ -85,15 +85,16 @@ def get_insightface_app():
return _insightface_app
_insightface_loaded = True
# Preload CUDA/cuDNN DLLs BEFORE any ORT InferenceSession is created
_preload_cuda_libs()
ctx_id = -1
insightface_home = os.environ.get("INSIGHTFACE_HOME", os.path.expanduser("~/.insightface"))
try:
import onnxruntime as ort
from insightface.app import FaceAnalysis
# Preload CUDA/cuDNN DLLs before any ORT InferenceSession is created.
# Silently no-ops on ROCm/Intel builds where preload_dlls() is absent.
_preload_cuda_libs()
# Disk cache check — lets the user know whether a download is coming
buffalo_path = Path(insightface_home) / "models" / "buffalo_l"
if buffalo_path.exists() and any(buffalo_path.iterdir()):
@@ -110,18 +111,35 @@ def get_insightface_app():
"ROCmExecutionProvider",
"MPSExecutionProvider",
"CoreMLExecutionProvider",
"OpenVINOExecutionProvider",
}
has_gpu_provider = bool(gpu_providers & set(providers))
ctx_id = -1 if _is_force_cpu() else (0 if has_gpu_provider else -1)
# For OpenVINO EP, inject device_type from env var (default CPU; set GPU for Intel Arc/iGPU)
has_openvino = "OpenVINOExecutionProvider" in providers
if has_openvino:
openvino_device = os.getenv("OPENVINO_DEVICE", "CPU")
providers = [
("OpenVINOExecutionProvider", {"device_type": openvino_device})
if p == "OpenVINOExecutionProvider" else p
for p in providers
]
logger.debug(f"OpenVINO EP: device_type={openvino_device}")
if not has_gpu_provider and not _is_force_cpu():
logger.warning(
"No GPU execution provider found — running InsightFace on CPU. "
"If you have an NVIDIA GPU, ensure the NVIDIA Container Toolkit is "
"installed and the container has GPU access (deploy.resources in compose)."
"Ensure the container has GPU access and the correct variant image is used "
"(gpu for NVIDIA, rocm for AMD, intel for Intel Arc/iGPU)."
)
device_str = "GPU" if ctx_id >= 0 else "CPU"
if ctx_id < 0:
device_str = "CPU"
elif has_openvino:
device_str = f"OpenVINO ({os.getenv('OPENVINO_DEVICE', 'CPU')})"
else:
device_str = "GPU"
logger.info(f"InsightFace Buffalo_L: loading into memory on {device_str}...")
t0 = time.time()
@@ -223,11 +241,14 @@ def get_siglip_model():
_siglip_model.eval()
# Move to GPU if available
# Move to GPU if available (ROCm builds expose torch.cuda.is_available() == True)
if not _is_force_cpu():
if torch.cuda.is_available():
_siglip_model = _siglip_model.cuda()
device_name = "CUDA GPU"
elif hasattr(torch, "xpu") and torch.xpu.is_available():
_siglip_model = _siglip_model.to("xpu")
device_name = "Intel XPU"
elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
_siglip_model = _siglip_model.to("mps")
device_name = "Apple MPS"
+8 -1
View File
@@ -144,7 +144,14 @@ def process_object_mode(
try:
model = get_yolo_model()
target_class = config.get("object_class", "dog")
device = "cpu" if os.getenv("FORCE_CPU", "").lower() in ("true", "1", "yes") else None
import torch
if os.getenv("FORCE_CPU", "").lower() in ("true", "1", "yes"):
device = "cpu"
elif hasattr(torch, "xpu") and torch.xpu.is_available():
device = "xpu"
else:
device = None # YOLO auto-selects (CUDA/ROCm/CPU)
results = model(img, verbose=False, device=device)
+5 -2
View File
@@ -58,8 +58,11 @@ def _get_strategy_choice(has_embedding: bool, entity_type: str) -> tuple[int | s
rprint(" [bold]4.[/bold] Skip")
choice = Prompt.ask("Choice", choices=["1", "2", "3", "4"], default="1")
limits = {"1": 30, "2": 100, "3": IntPrompt.ask("Enter number of images", default=30)}
return limits.get(choice, 0), "time" if choice != "4" else "skip"
if choice == "4":
return 0, "skip"
if choice == "3":
return IntPrompt.ask("Enter number of images", default=30), "time"
return {"1": 30, "2": 100}.get(choice, 30), "time"
def _resolve_strategy(strategy: str, has_embedding: bool) -> tuple[int | str, str]: