Merge remote-tracking branch 'origin/diffusion-fp16-accum' into fold-integration
This commit is contained in:
commit
1081808ae7
2 changed files with 228 additions and 3 deletions
|
|
@ -69,6 +69,8 @@ def snapshot_backend_flags() -> Optional[dict]:
|
|||
matmul = getattr(getattr(torch.backends, "cuda", None), "matmul", None)
|
||||
if matmul is not None and hasattr(matmul, "allow_tf32"):
|
||||
state["matmul_tf32"] = bool(matmul.allow_tf32)
|
||||
if matmul is not None and hasattr(matmul, "allow_fp16_accumulation"):
|
||||
state["matmul_fp16_accum"] = bool(matmul.allow_fp16_accumulation)
|
||||
cudnn = getattr(torch.backends, "cudnn", None)
|
||||
if cudnn is not None:
|
||||
if hasattr(cudnn, "allow_tf32"):
|
||||
|
|
@ -95,9 +97,9 @@ def restore_backend_flags(state: Optional[dict]) -> None:
|
|||
except Exception: # noqa: BLE001 — best-effort per-flag restore
|
||||
pass
|
||||
|
||||
_set(
|
||||
getattr(getattr(torch.backends, "cuda", None), "matmul", None), "allow_tf32", "matmul_tf32"
|
||||
)
|
||||
matmul = getattr(getattr(torch.backends, "cuda", None), "matmul", None)
|
||||
_set(matmul, "allow_tf32", "matmul_tf32")
|
||||
_set(matmul, "allow_fp16_accumulation", "matmul_fp16_accum")
|
||||
cudnn = getattr(torch.backends, "cudnn", None)
|
||||
_set(cudnn, "allow_tf32", "cudnn_tf32")
|
||||
_set(cudnn, "benchmark", "cudnn_benchmark")
|
||||
|
|
@ -179,6 +181,7 @@ def apply_speed_optims(
|
|||
"channels_last": False,
|
||||
"cudnn_benchmark": False,
|
||||
"tf32": False,
|
||||
"fp16_accum": False,
|
||||
"fused_qkv": False,
|
||||
"compiled": False,
|
||||
"compiled_dequant": False,
|
||||
|
|
@ -203,6 +206,21 @@ def apply_speed_optims(
|
|||
if on_cuda:
|
||||
applied["cudnn_benchmark"] = _enable_cudnn_benchmark(logger)
|
||||
|
||||
# Consumer-only: fp16 GEMMs accumulate in fp16 (~2x on GeForce-class parts, whose
|
||||
# fp32-accumulate rate is halved; datacenter HBM parts gain nothing and keep the
|
||||
# safer fp32 accumulate). Only affects fp16 matmuls -- bf16 loads were measured
|
||||
# bit-identical with the flag on across every family (36/36 same-seed A/B cases),
|
||||
# so on the quality-neutral tiers the flag engages only when the compute dtype is
|
||||
# NOT fp16. On an fp16 pipeline (the pre-Ampere fallback dtype) the same harness
|
||||
# measured real same-seed drift (mean 2-5% on SDXL / FLUX), so fp16 compute gets
|
||||
# the 2x accumulate only under ``max``, the tier that already trades exactness for
|
||||
# speed. Guarded by a per-family deny-list fed by the overflow validation harness
|
||||
# and the UNSLOTH_DISABLE_FP16_ACCUM kill switch.
|
||||
if on_cuda:
|
||||
applied["fp16_accum"] = _enable_fp16_accumulation(
|
||||
family, logger, dtype = getattr(target, "dtype", None), speed_mode = mode
|
||||
)
|
||||
|
||||
# --- the compile lever, remapped per tier ----------------------------------------
|
||||
# default = LIGHT compile: for a GGUF model, compile ONLY the dequant op chain
|
||||
# (~70-80% of eager GGUF time) -- cheap, VRAM-free, resolution-invariant; the
|
||||
|
|
@ -330,6 +348,61 @@ def _enable_tf32(logger: Any) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
# Families the overflow validation harness (scripts/fp16_accum_validate.py) found to
|
||||
# produce non-finite activations or NEW black frames under fp16 accumulation. Empty by
|
||||
# measurement: across all six families the harness found no overflow anywhere -- bf16
|
||||
# loads are bit-identical with the flag on, and fp16 loads stay finite (their same-seed
|
||||
# drift is why fp16 compute is additionally gated to the ``max`` tier below).
|
||||
_FP16_ACCUM_DENY: frozenset[str] = frozenset()
|
||||
|
||||
|
||||
def _enable_fp16_accumulation(
|
||||
family: Any,
|
||||
logger: Any,
|
||||
*,
|
||||
dtype: Any = None,
|
||||
speed_mode: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""Turn on fp16-accumulated fp16 GEMMs for consumer GPUs, where they run ~2x the
|
||||
fp32-accumulate rate (datacenter HBM parts are not throughput-nerfed, so they keep
|
||||
the safer default). Gated on: the torch build exposing the flag (2.10+), a
|
||||
consumer-class device, the family not being deny-listed by the overflow harness,
|
||||
the UNSLOTH_DISABLE_FP16_ACCUM kill switch being unset, and -- when the pipeline
|
||||
compute dtype IS fp16, the only case where the accumulator width changes results --
|
||||
the ``max`` tier (measured same-seed drift: mean 2-5%; bf16 loads are bit-identical
|
||||
so they engage on any tier). The caller's snapshot/restore pair returns the
|
||||
process-wide flag to its prior value on unload."""
|
||||
import os
|
||||
|
||||
if os.environ.get("UNSLOTH_DISABLE_FP16_ACCUM", "").strip().lower() in (
|
||||
"1",
|
||||
"true",
|
||||
"yes",
|
||||
"on",
|
||||
):
|
||||
return False
|
||||
name = str(getattr(family, "name", family or "")).lower()
|
||||
if name in _FP16_ACCUM_DENY:
|
||||
return False
|
||||
if str(dtype).replace("torch.", "") == "float16" and speed_mode != SPEED_MAX:
|
||||
return False
|
||||
try:
|
||||
import torch
|
||||
|
||||
matmul = torch.backends.cuda.matmul
|
||||
if not hasattr(matmul, "allow_fp16_accumulation"):
|
||||
return False
|
||||
from .diffusion_transformer_quant import _is_consumer_gpu
|
||||
|
||||
if not _is_consumer_gpu():
|
||||
return False
|
||||
matmul.allow_fp16_accumulation = True
|
||||
return True
|
||||
except Exception as exc: # noqa: BLE001 — optimisation only
|
||||
_warn(logger, "fp16_accum", exc)
|
||||
return False
|
||||
|
||||
|
||||
def _fuse_qkv(pipe: Any, logger: Any) -> bool:
|
||||
for owner in (pipe, getattr(pipe, "transformer", None)):
|
||||
fn = getattr(owner, "fuse_qkv_projections", None)
|
||||
|
|
|
|||
|
|
@ -211,6 +211,7 @@ def test_speed_off_applies_nothing(monkeypatch):
|
|||
"fused_qkv": False,
|
||||
"compiled": False,
|
||||
"compiled_dequant": False,
|
||||
"fp16_accum": False,
|
||||
}
|
||||
assert pipe.vae.mem_format is None and pipe.compiled is False
|
||||
# off must not touch any process-wide flag (bit-identical reference path).
|
||||
|
|
@ -351,3 +352,154 @@ def test_apply_tolerates_missing_optims(monkeypatch):
|
|||
bare, _target(), is_gguf = False, family = _family(), speed_mode = SPEED_MAX
|
||||
)
|
||||
assert applied["channels_last"] is False and applied["fused_qkv"] is False
|
||||
|
||||
|
||||
# ── fp16 accumulation (consumer fp16-GEMM fast path) ──────────────────────────
|
||||
|
||||
|
||||
def _stub_torch_fp16_accum(
|
||||
monkeypatch,
|
||||
*,
|
||||
consumer = True,
|
||||
with_flag = True,
|
||||
):
|
||||
torch = types.ModuleType("torch")
|
||||
torch.bfloat16 = "bfloat16"
|
||||
torch.channels_last = "channels_last"
|
||||
matmul_attrs = {"allow_tf32": False}
|
||||
if with_flag:
|
||||
matmul_attrs["allow_fp16_accumulation"] = False
|
||||
torch.backends = types.SimpleNamespace(
|
||||
cuda = types.SimpleNamespace(matmul = types.SimpleNamespace(**matmul_attrs)),
|
||||
cudnn = types.SimpleNamespace(allow_tf32 = False, benchmark = False),
|
||||
)
|
||||
monkeypatch.setitem(sys.modules, "torch", torch)
|
||||
import core.inference.diffusion_transformer_quant as tq
|
||||
|
||||
monkeypatch.setattr(tq, "_is_consumer_gpu", lambda device = None: consumer)
|
||||
return torch
|
||||
|
||||
|
||||
def test_snapshot_captures_fp16_accum_when_present(monkeypatch):
|
||||
torch = _stub_torch_fp16_accum(monkeypatch)
|
||||
torch.backends.cuda.matmul.allow_fp16_accumulation = True
|
||||
snap = snapshot_backend_flags()
|
||||
assert snap["matmul_fp16_accum"] is True
|
||||
torch.backends.cuda.matmul.allow_fp16_accumulation = False
|
||||
restore_backend_flags(snap)
|
||||
assert torch.backends.cuda.matmul.allow_fp16_accumulation is True
|
||||
|
||||
|
||||
def test_snapshot_skips_fp16_accum_on_older_torch(monkeypatch):
|
||||
_stub_torch_fp16_accum(monkeypatch, with_flag = False)
|
||||
snap = snapshot_backend_flags()
|
||||
assert "matmul_fp16_accum" not in snap
|
||||
restore_backend_flags(snap) # nothing to restore, no error
|
||||
|
||||
|
||||
def test_fp16_accum_engages_on_consumer_cuda(monkeypatch):
|
||||
torch = _stub_torch_fp16_accum(monkeypatch, consumer = True)
|
||||
_stub_gguf_accel(monkeypatch)
|
||||
applied = apply_speed_optims(
|
||||
_Pipe(), _target(), is_gguf = True, family = _family(), speed_mode = "default"
|
||||
)
|
||||
assert applied["fp16_accum"] is True
|
||||
assert torch.backends.cuda.matmul.allow_fp16_accumulation is True
|
||||
|
||||
|
||||
def test_fp16_accum_skipped_on_datacenter(monkeypatch):
|
||||
torch = _stub_torch_fp16_accum(monkeypatch, consumer = False)
|
||||
_stub_gguf_accel(monkeypatch)
|
||||
applied = apply_speed_optims(
|
||||
_Pipe(), _target(), is_gguf = True, family = _family(), speed_mode = "default"
|
||||
)
|
||||
assert applied["fp16_accum"] is False
|
||||
assert torch.backends.cuda.matmul.allow_fp16_accumulation is False
|
||||
|
||||
|
||||
def test_fp16_accum_respects_kill_switch(monkeypatch):
|
||||
_stub_torch_fp16_accum(monkeypatch, consumer = True)
|
||||
_stub_gguf_accel(monkeypatch)
|
||||
monkeypatch.setenv("UNSLOTH_DISABLE_FP16_ACCUM", "1")
|
||||
applied = apply_speed_optims(
|
||||
_Pipe(), _target(), is_gguf = True, family = _family(), speed_mode = "default"
|
||||
)
|
||||
assert applied["fp16_accum"] is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["TRUE", "Yes", "On", " true "])
|
||||
def test_fp16_accum_kill_switch_is_case_insensitive(monkeypatch, value):
|
||||
# The documented safety escape hatch must honor the common boolean spellings, not only
|
||||
# lowercase "1"/"true"/"yes": an operator setting UNSLOTH_DISABLE_FP16_ACCUM=TRUE to stop
|
||||
# fp16-accumulation drift would otherwise be silently ignored.
|
||||
_stub_torch_fp16_accum(monkeypatch, consumer = True)
|
||||
_stub_gguf_accel(monkeypatch)
|
||||
monkeypatch.setenv("UNSLOTH_DISABLE_FP16_ACCUM", value)
|
||||
applied = apply_speed_optims(
|
||||
_Pipe(), _target(), is_gguf = True, family = _family(), speed_mode = "default"
|
||||
)
|
||||
assert applied["fp16_accum"] is False
|
||||
|
||||
|
||||
def test_fp16_accum_respects_family_deny_list(monkeypatch):
|
||||
_stub_torch_fp16_accum(monkeypatch, consumer = True)
|
||||
_stub_gguf_accel(monkeypatch)
|
||||
monkeypatch.setattr(ds_mod, "_FP16_ACCUM_DENY", frozenset({"fragile-family"}))
|
||||
fam = types.SimpleNamespace(supports_torch_compile = True, name = "fragile-family")
|
||||
applied = apply_speed_optims(_Pipe(), _target(), is_gguf = True, family = fam, speed_mode = "default")
|
||||
assert applied["fp16_accum"] is False
|
||||
|
||||
|
||||
def test_fp16_accum_skipped_when_flag_missing(monkeypatch):
|
||||
_stub_torch_fp16_accum(monkeypatch, consumer = True, with_flag = False)
|
||||
_stub_gguf_accel(monkeypatch)
|
||||
applied = apply_speed_optims(
|
||||
_Pipe(), _target(), is_gguf = True, family = _family(), speed_mode = "default"
|
||||
)
|
||||
assert applied["fp16_accum"] is False
|
||||
|
||||
|
||||
def test_fp16_accum_not_touched_off_cuda(monkeypatch):
|
||||
torch = _stub_torch_fp16_accum(monkeypatch, consumer = True)
|
||||
applied = apply_speed_optims(
|
||||
_Pipe(),
|
||||
_target(device = "mps"),
|
||||
is_gguf = False,
|
||||
family = _family(),
|
||||
speed_mode = "eager",
|
||||
)
|
||||
assert applied["fp16_accum"] is False
|
||||
assert torch.backends.cuda.matmul.allow_fp16_accumulation is False
|
||||
|
||||
|
||||
def test_fp16_accum_denied_on_fp16_dtype_below_max(monkeypatch):
|
||||
# fp16 compute is where the accumulator width actually changes results (measured
|
||||
# same-seed drift, mean 2-5%): the quality-neutral tiers must refuse it.
|
||||
torch = _stub_torch_fp16_accum(monkeypatch, consumer = True)
|
||||
_stub_gguf_accel(monkeypatch)
|
||||
for mode in ("eager", "default"):
|
||||
applied = apply_speed_optims(
|
||||
_Pipe(),
|
||||
_target(dtype = "float16"),
|
||||
is_gguf = True,
|
||||
family = _family(),
|
||||
speed_mode = mode,
|
||||
)
|
||||
assert applied["fp16_accum"] is False
|
||||
assert torch.backends.cuda.matmul.allow_fp16_accumulation is False
|
||||
|
||||
|
||||
def test_fp16_accum_allowed_on_fp16_dtype_under_max(monkeypatch):
|
||||
# max already trades exactness for speed (conv algos, max-autotune), so the 2x
|
||||
# fp16 accumulate joins that tier for fp16 pipelines.
|
||||
torch = _stub_torch_fp16_accum(monkeypatch, consumer = True)
|
||||
_stub_gguf_accel(monkeypatch)
|
||||
applied = apply_speed_optims(
|
||||
_Pipe(with_compile = True, with_fuse = True),
|
||||
_target(dtype = "float16"),
|
||||
is_gguf = True,
|
||||
family = _family(),
|
||||
speed_mode = "MAX",
|
||||
)
|
||||
assert applied["fp16_accum"] is True
|
||||
assert torch.backends.cuda.matmul.allow_fp16_accumulation is True
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue