feat: Update CoreML device support and server controls
- Replace 'mps' with 'coreml' as the identifier for Apple CoreML Execution Provider across the codebase. - Remove Apple chip generation checks and ensure CoreML is available on all macOS arm64 devices. - Adjust ONNX Runtime session creation to use default CoreML configurations without forcing 'MLProgram'. - Implement support for MoGe-2 to utilize CoreML with static input shape requirements. - Enhance server panel to display restart and stop controls consistently across running states, including 'BUSY'. - Update UI tests to verify new behavior for server controls and cached model display. - Bump blendjob dependency version to 0.1.16 in packaging tests and configurations.
This commit is contained in:
@@ -10,7 +10,7 @@ type = "add-on"
|
||||
blender_version_min = "4.5.0"
|
||||
platforms = ["windows-x64", "macos-arm64", "linux-x64"]
|
||||
license = ["SPDX:GPL-3.0-or-later"]
|
||||
wheels = ["./wheels/blendjob-0.1.14-py3-none-any.whl"]
|
||||
wheels = ["./wheels/blendjob-0.1.16-py3-none-any.whl"]
|
||||
|
||||
[permissions]
|
||||
network = "Access the local Job Server and download environment dependencies and model weights"
|
||||
|
||||
@@ -17,14 +17,14 @@ class ServerPanel(bpy.types.Panel):
|
||||
|
||||
def draw(self, _context):
|
||||
layout = self.layout
|
||||
running = runtime.server_busy()
|
||||
busy = runtime.server_busy()
|
||||
if not runtime.environment_ready():
|
||||
layout.label(
|
||||
text="Environment is not installed",
|
||||
icon="ERROR",
|
||||
)
|
||||
setup_row = layout.row()
|
||||
setup_row.enabled = not running
|
||||
setup_row.enabled = not busy
|
||||
setup_row.operator(
|
||||
"anyimage.setup_ai_environment",
|
||||
text="Set Up AI Environment…",
|
||||
@@ -34,6 +34,7 @@ class ServerPanel(bpy.types.Panel):
|
||||
|
||||
status = runtime.server_status()
|
||||
state = status.get("state", "STOPPED")
|
||||
server_running = state in {"READY", "BUSY"}
|
||||
icons = {
|
||||
"READY": "RADIOBUT_ON",
|
||||
"BUSY": "TIME",
|
||||
@@ -43,7 +44,7 @@ class ServerPanel(bpy.types.Panel):
|
||||
"STOPPED": "PAUSE",
|
||||
}
|
||||
row = layout.row(align=True)
|
||||
if state in {"READY", "BUSY"}:
|
||||
if server_running:
|
||||
row.label(
|
||||
text=(
|
||||
"Running"
|
||||
@@ -52,18 +53,17 @@ class ServerPanel(bpy.types.Panel):
|
||||
),
|
||||
icon=icons[state],
|
||||
)
|
||||
if state == "READY":
|
||||
controls = row.row(align=True)
|
||||
controls.operator(
|
||||
"anyimage.restart_server",
|
||||
text="",
|
||||
icon="FILE_REFRESH",
|
||||
)
|
||||
controls.operator(
|
||||
"anyimage.stop_server",
|
||||
text="",
|
||||
icon="PAUSE",
|
||||
)
|
||||
controls = row.row(align=True)
|
||||
controls.operator(
|
||||
"anyimage.restart_server",
|
||||
text="",
|
||||
icon="FILE_REFRESH",
|
||||
)
|
||||
controls.operator(
|
||||
"anyimage.stop_server",
|
||||
text="",
|
||||
icon="PAUSE",
|
||||
)
|
||||
elif state == "STOPPED":
|
||||
row.operator(
|
||||
"anyimage.start_server",
|
||||
@@ -82,7 +82,7 @@ class ServerPanel(bpy.types.Panel):
|
||||
icon="PLAY",
|
||||
)
|
||||
|
||||
if state in {"READY", "BUSY"}:
|
||||
if server_running:
|
||||
cached = (
|
||||
status.get("resources", {})
|
||||
.get("model_manager", {})
|
||||
@@ -90,13 +90,19 @@ class ServerPanel(bpy.types.Panel):
|
||||
)
|
||||
cached_names = [name for name in cached.values() if name]
|
||||
cached_row = layout.row(align=True)
|
||||
cached_row.label(
|
||||
text=(
|
||||
f"Cached: {', '.join(cached_names)}"
|
||||
if cached_names
|
||||
else "Cached: None"
|
||||
if len(cached_names) > 1:
|
||||
cached_list = cached_row.column(align=True)
|
||||
cached_list.label(text="Cached:")
|
||||
for name in cached_names:
|
||||
cached_list.label(text=name)
|
||||
else:
|
||||
cached_row.label(
|
||||
text=(
|
||||
f"Cached: {cached_names[0]}"
|
||||
if cached_names
|
||||
else "Cached: None"
|
||||
)
|
||||
)
|
||||
)
|
||||
clear = cached_row.row(align=True)
|
||||
clear.enabled = state == "READY"
|
||||
clear.operator(
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import functools
|
||||
import platform
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import bpy
|
||||
@@ -26,55 +23,34 @@ REQUIRED_MODEL_KEYS = (
|
||||
DEVICE_LABELS = {
|
||||
"cuda": ("CUDA", "NVIDIA CUDA"),
|
||||
"directml": ("DirectML", "Windows GPU (DirectML)"),
|
||||
"mps": ("MPS", "Apple Metal (MPS)"),
|
||||
"coreml": ("CoreML", "Apple CoreML"),
|
||||
"cpu": ("CPU", "CPU"),
|
||||
}
|
||||
|
||||
|
||||
APPLE_SILICON_PATTERN = re.compile(r"\bApple M(\d+)\b", re.IGNORECASE)
|
||||
|
||||
|
||||
def parse_apple_silicon_generation(brand):
|
||||
match = APPLE_SILICON_PATTERN.search(str(brand))
|
||||
return int(match.group(1)) if match else None
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def apple_silicon_generation():
|
||||
if sys.platform != "darwin" or platform.machine().lower() not in {
|
||||
def supports_coreml():
|
||||
return sys.platform == "darwin" and platform.machine().lower() in {
|
||||
"arm64",
|
||||
"aarch64",
|
||||
}:
|
||||
return None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
("sysctl", "-n", "machdep.cpu.brand_string"),
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=2,
|
||||
)
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return None
|
||||
return parse_apple_silicon_generation(result.stdout)
|
||||
|
||||
|
||||
def supports_mps(generation=None):
|
||||
if generation is None:
|
||||
generation = apple_silicon_generation()
|
||||
return generation is not None and generation >= 4
|
||||
}
|
||||
|
||||
|
||||
def supported_devices():
|
||||
devices = runtime.environment_manifest().get("devices")
|
||||
if isinstance(devices, list) and devices:
|
||||
names = [str(device).lower() for device in devices]
|
||||
if not supports_mps():
|
||||
names = [name for name in names if name != "mps"]
|
||||
names = [
|
||||
name
|
||||
for device in devices
|
||||
if (name := str(device).lower()) in DEVICE_LABELS
|
||||
]
|
||||
if not supports_coreml():
|
||||
names = [name for name in names if name != "coreml"]
|
||||
elif "cpu" in names:
|
||||
names = ["cpu", *(name for name in names if name != "cpu")]
|
||||
return names or ["cpu"]
|
||||
if sys.platform == "win32":
|
||||
return ["directml", "cpu"]
|
||||
return ["mps", "cpu"] if supports_mps() else ["cpu"]
|
||||
return ["cpu", "coreml"] if supports_coreml() else ["cpu"]
|
||||
|
||||
|
||||
def device_enum_items(_owner, _context):
|
||||
|
||||
@@ -10,6 +10,9 @@ from .onnx_runtime import select_providers as select_providers
|
||||
ONNX_FILENAME = "model.onnx"
|
||||
MIN_TOKENS = 1200
|
||||
MAX_TOKENS = 3600
|
||||
COREML_PROVIDER_OPTIONS = {
|
||||
"RequireStaticInputShapes": "1",
|
||||
}
|
||||
|
||||
|
||||
def is_onnx_model(model_directory):
|
||||
@@ -18,7 +21,11 @@ def is_onnx_model(model_directory):
|
||||
|
||||
def create_session(model_directory, requested_device="auto"):
|
||||
path = Path(model_directory) / ONNX_FILENAME
|
||||
return create_runtime_session(path, requested_device)
|
||||
return create_runtime_session(
|
||||
path,
|
||||
requested_device,
|
||||
coreml_options=COREML_PROVIDER_OPTIONS,
|
||||
)
|
||||
|
||||
|
||||
def normalized_view_plane_uv(width, height, dtype):
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
"""Shared ONNX Runtime session helpers."""
|
||||
|
||||
COREML_PROVIDER_OPTIONS = {
|
||||
"ModelFormat": "MLProgram",
|
||||
"MLComputeUnits": "ALL",
|
||||
"RequireStaticInputShapes": "0",
|
||||
"EnableOnSubgraphs": "0",
|
||||
}
|
||||
|
||||
CUDA_PROVIDER_OPTIONS = {
|
||||
"arena_extend_strategy": "kSameAsRequested",
|
||||
}
|
||||
@@ -17,14 +10,20 @@ def select_providers(requested, available):
|
||||
providers_for_device = {
|
||||
"cuda": ("CUDAExecutionProvider", "DmlExecutionProvider"),
|
||||
"directml": ("DmlExecutionProvider",),
|
||||
"mps": ("CoreMLExecutionProvider",),
|
||||
"coreml": ("CoreMLExecutionProvider",),
|
||||
"cpu": ("CPUExecutionProvider",),
|
||||
}
|
||||
if requested != "auto":
|
||||
candidates = providers_for_device[requested]
|
||||
for provider in candidates:
|
||||
if provider in available:
|
||||
return [provider]
|
||||
providers = [provider]
|
||||
if (
|
||||
provider == "CoreMLExecutionProvider"
|
||||
and "CPUExecutionProvider" in available
|
||||
):
|
||||
providers.append("CPUExecutionProvider")
|
||||
return providers
|
||||
if "CPUExecutionProvider" in available:
|
||||
return ["CPUExecutionProvider"]
|
||||
raise RuntimeError(
|
||||
@@ -44,7 +43,12 @@ def select_providers(requested, available):
|
||||
return providers
|
||||
|
||||
|
||||
def create_session(path, requested_device="auto"):
|
||||
def create_session(
|
||||
path,
|
||||
requested_device="auto",
|
||||
*,
|
||||
coreml_options=None,
|
||||
):
|
||||
import onnxruntime
|
||||
|
||||
providers = select_providers(
|
||||
@@ -55,8 +59,8 @@ def create_session(path, requested_device="auto"):
|
||||
for provider in providers:
|
||||
if provider == "CUDAExecutionProvider":
|
||||
provider = (provider, dict(CUDA_PROVIDER_OPTIONS))
|
||||
elif provider == "CoreMLExecutionProvider":
|
||||
provider = (provider, dict(COREML_PROVIDER_OPTIONS))
|
||||
elif provider == "CoreMLExecutionProvider" and coreml_options:
|
||||
provider = (provider, dict(coreml_options))
|
||||
configured_providers.append(provider)
|
||||
return onnxruntime.InferenceSession(
|
||||
str(path),
|
||||
|
||||
Reference in New Issue
Block a user