Keep the sd.cpp text encoder on CPU under Metal
macos-14 loads FLUX.2-klein-4B Q2_K natively on mps and then dies on the first
generation with exit code -6:
ggml_metal_op_encode_impl: error: unsupported op 'RMS_NORM' -> ggml_abort
LLMEmbedder::encode_prompt -> LLMRunner::compute -> GGMLRunner::compute
ggml's Metal backend gates RMS_NORM on contiguous rows and aborts the process
when that does not hold, with no per-op CPU fallback, so any LLM text encoder
(Qwen3 for FLUX.2 and Z-Image, T5 for FLUX.1) takes sd-server down. The encoder
runs once per prompt while the DiT runs every step, so pinning only the encoder
keeps Metal for the part that matters. UNSLOTH_DIFFUSION_SD_CPP_METAL_TE_GPU=1
opts back in once ggml grows the kernel.
This commit is contained in:
parent
147d323912
commit
ca5ae684ad
2 changed files with 77 additions and 1 deletions
|
|
@ -120,6 +120,34 @@ def native_speed_flags(speed_mode: Optional[str]) -> list[str]:
|
|||
raise ValueError(f"native speed_mode must be one of {NATIVE_SPEED_MODES}, got '{speed_mode}'")
|
||||
|
||||
|
||||
# Kill switch for the Metal text-encoder placement below (1/true keeps the encoder on Metal).
|
||||
_METAL_TE_ON_GPU_ENV = "UNSLOTH_DIFFUSION_SD_CPP_METAL_TE_GPU"
|
||||
|
||||
|
||||
def metal_text_encoder_flags() -> list[str]:
|
||||
"""Keep the TEXT ENCODER on CPU when sd.cpp runs on Apple Metal, else nothing.
|
||||
|
||||
ggml's Metal backend gates RMS_NORM on contiguous rows and calls ``GGML_ABORT`` when that does
|
||||
not hold, with no per-op CPU fallback, so an LLM text encoder (Qwen3 for FLUX.2 / Z-Image, T5
|
||||
for FLUX.1) takes the whole sd-server process down mid-generation:
|
||||
|
||||
ggml_metal_op_encode_impl: error: unsupported op 'RMS_NORM' -> ggml_abort
|
||||
LLMEmbedder::encode_prompt -> LLMRunner::compute -> GGMLRunner::compute
|
||||
|
||||
Observed on macos-14 arm64 with FLUX.2-klein-4B Q2_K: the model loads on ``mps`` and the first
|
||||
generation dies with exit code -6. The encoder runs once per prompt while the DiT runs every
|
||||
step, so pinning only the encoder keeps Metal for the part that matters. Set
|
||||
``UNSLOTH_DIFFUSION_SD_CPP_METAL_TE_GPU=1`` to opt back in once ggml grows the kernel."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
if sys.platform != "darwin":
|
||||
return []
|
||||
if os.environ.get(_METAL_TE_ON_GPU_ENV, "").strip().lower() in ("1", "true", "yes", "on"):
|
||||
return []
|
||||
return ["--clip-on-cpu"]
|
||||
|
||||
|
||||
def offload_flags(
|
||||
policy: str,
|
||||
*,
|
||||
|
|
@ -239,8 +267,10 @@ def build_sd_cpp_command(
|
|||
cmd += ["--output", output_path]
|
||||
if threads is not None:
|
||||
cmd += ["--threads", str(int(threads))]
|
||||
offload = list(offload or [])
|
||||
if offload:
|
||||
cmd += list(offload)
|
||||
cmd += offload
|
||||
cmd += [f for f in metal_text_encoder_flags() if f not in offload]
|
||||
if verbose:
|
||||
cmd += ["-v"]
|
||||
if extra_args:
|
||||
|
|
@ -344,6 +374,7 @@ def build_sd_cpp_server_command(
|
|||
cmd += offload
|
||||
# De-dup speed flags against offload (may already include --diffusion-fa).
|
||||
cmd += [f for f in native_speed_flags(native_speed) if f not in offload]
|
||||
cmd += [f for f in metal_text_encoder_flags() if f not in offload]
|
||||
if verbose:
|
||||
cmd += ["-v"]
|
||||
if extra_args:
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ from core.inference.sd_cpp_args import (
|
|||
build_sd_cpp_command,
|
||||
build_sd_cpp_server_command,
|
||||
build_sd_cpp_upscale_command,
|
||||
metal_text_encoder_flags,
|
||||
native_speed_flags,
|
||||
offload_flags,
|
||||
text_encoder_flags_for_family,
|
||||
|
|
@ -47,6 +48,50 @@ def test_te_flags_by_family():
|
|||
assert text_encoder_flags_for_family("unknown") == ()
|
||||
|
||||
|
||||
# ── Metal text-encoder placement ────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_metal_keeps_the_text_encoder_off_the_gpu(monkeypatch):
|
||||
# ggml's Metal backend aborts the process on RMS_NORM with non-contiguous rows and has no
|
||||
# per-op CPU fallback, so an LLM text encoder killed sd-server mid-generation on macOS
|
||||
# (observed on macos-14 with FLUX.2-klein-4B Q2_K: loads on mps, first generation exits -6).
|
||||
monkeypatch.delenv("UNSLOTH_DIFFUSION_SD_CPP_METAL_TE_GPU", raising = False)
|
||||
monkeypatch.setattr("sys.platform", "darwin")
|
||||
assert metal_text_encoder_flags() == ["--clip-on-cpu"]
|
||||
for other in ("linux", "win32"):
|
||||
monkeypatch.setattr("sys.platform", other)
|
||||
assert metal_text_encoder_flags() == []
|
||||
# Opt back in once ggml grows the kernel.
|
||||
monkeypatch.setattr("sys.platform", "darwin")
|
||||
monkeypatch.setenv("UNSLOTH_DIFFUSION_SD_CPP_METAL_TE_GPU", "1")
|
||||
assert metal_text_encoder_flags() == []
|
||||
|
||||
|
||||
def test_metal_text_encoder_flag_reaches_both_command_builders(monkeypatch):
|
||||
monkeypatch.delenv("UNSLOTH_DIFFUSION_SD_CPP_METAL_TE_GPU", raising = False)
|
||||
monkeypatch.setattr("sys.platform", "darwin")
|
||||
files = SdCppModelFiles(diffusion_model = "/m/x.gguf")
|
||||
server = build_sd_cpp_server_command(
|
||||
binary = "sd-server", files = files, host = "127.0.0.1", port = 1234
|
||||
)
|
||||
cli = build_sd_cpp_command(
|
||||
binary = "sd-cli", files = files, params = SdCppGenParams(prompt = "x"),
|
||||
output_path = "/o/x.png",
|
||||
)
|
||||
assert server.count("--clip-on-cpu") == 1
|
||||
assert cli.count("--clip-on-cpu") == 1
|
||||
# An offload policy that already pins the encoder must not emit it twice.
|
||||
dual = build_sd_cpp_server_command(
|
||||
binary = "sd-server", files = files, host = "127.0.0.1", port = 1234,
|
||||
offload = offload_flags("model"),
|
||||
)
|
||||
assert dual.count("--clip-on-cpu") == 1
|
||||
monkeypatch.setattr("sys.platform", "linux")
|
||||
assert "--clip-on-cpu" not in build_sd_cpp_server_command(
|
||||
binary = "sd-server", files = files, host = "127.0.0.1", port = 1234
|
||||
)
|
||||
|
||||
|
||||
# ── offload policy -> sd-cli flags ──────────────────────────────────────────
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue