| from __future__ import annotations |
|
|
| import atexit |
| import json |
| import os |
| from pathlib import Path |
| from typing import Optional |
|
|
| import torch |
|
|
| try: |
| import triton |
| import triton.language as tl |
| from triton.language.extra.cuda import libdevice as tl_libdevice |
|
|
| _TRITON_AVAILABLE = True |
| except Exception: |
| triton = None |
| tl = None |
| tl_libdevice = None |
| _TRITON_AVAILABLE = False |
|
|
|
|
| _ENV_ENABLE = "WAN2GP_QUANTO_INT8_TRITON" |
| _ENV_AUTOTUNE_ENABLE = "WAN2GP_QUANTO_INT8_AUTOTUNE" |
| _ENV_AUTOTUNE_DEBUG = "WAN2GP_QUANTO_INT8_AUTOTUNE_DEBUG" |
| _ENV_AUTOTUNE_MAX_M = "WAN2GP_QUANTO_INT8_AUTOTUNE_MAX_M" |
| _ENV_AUTOTUNE_MAX_SHAPES = "WAN2GP_QUANTO_INT8_AUTOTUNE_MAX_SHAPES" |
| _ENV_AUTOTUNE_WARMUP = "WAN2GP_QUANTO_INT8_AUTOTUNE_WARMUP" |
| _ENV_AUTOTUNE_ITERS = "WAN2GP_QUANTO_INT8_AUTOTUNE_ITERS" |
| _ENV_AUTOTUNE_MIN_SPEEDUP = "WAN2GP_QUANTO_INT8_AUTOTUNE_MIN_SPEEDUP" |
| _ENV_AUTOTUNE_CACHE = "WAN2GP_QUANTO_INT8_AUTOTUNE_CACHE" |
| _ENV_AUTOTUNE_VALIDATE = "WAN2GP_QUANTO_INT8_AUTOTUNE_VALIDATE" |
| _ENV_AUTOTUNE_MAX_ABS_ERR = "WAN2GP_QUANTO_INT8_AUTOTUNE_MAX_ABS_ERR" |
| _ENV_AUTOTUNE_MAX_REL_ERR = "WAN2GP_QUANTO_INT8_AUTOTUNE_MAX_REL_ERR" |
| _ENV_AUTOTUNE_LOCK_FUSED_BLOCK_K = "WAN2GP_QUANTO_INT8_AUTOTUNE_LOCK_FUSED_BLOCK_K" |
| _IS_AVAILABLE = None |
| _CONFIG_LEN = 5 |
| _AUTOTUNE_CACHE_VERSION = 2 |
| _AUTOTUNE_CACHE_LOADED = False |
| _AUTOTUNE_CACHE_DIRTY = False |
| _AUTOTUNE_CONFIG_CACHE: dict[str, tuple[int, int, int, int, int]] = {} |
| _AUTOTUNE_SESSION_CACHE: dict[tuple[int, str, str], tuple[int, int, int, int, int]] = {} |
| _AUTOTUNE_SEEN_SLOTS: set[tuple[int, str, str]] = set() |
| _AUTOTUNE_SLOTS_TUNED = 0 |
| _AUTOTUNE_DEBUG_OVERRIDE: Optional[bool] = None |
|
|
| |
| _TRITON_SMALL_M_CONFIGS = { |
| (2048, 4096): (2, 32, 256, 8, 5), |
| (2048, 2048): (1, 32, 64, 2, 4), |
| (2048, 12288): (8, 64, 256, 8, 4), |
| (6144, 2048): (1, 32, 512, 4, 5), |
| } |
| _TRITON_TINY_M_SHAPE_CONFIGS = { |
| (2, 3072, 3072): (2, 128, 64, 8, 4), |
| (4, 3072, 3072): (4, 256, 64, 4, 4), |
| (2, 3072, 1024): (2, 256, 128, 8, 4), |
| (4, 3072, 1024): (2, 256, 128, 8, 4), |
| (2, 3072, 1536): (2, 64, 64, 4, 4), |
| (4, 3072, 1536): (2, 256, 128, 8, 4), |
| (2, 3072, 8192): (2, 128, 64, 8, 4), |
| (4, 3072, 8192): (2, 128, 64, 8, 4), |
| (2, 8192, 3072): (4, 128, 64, 4, 4), |
| (4, 8192, 3072): (2, 128, 128, 8, 4), |
| } |
| _TRITON_TINY_M_PAIR_CONFIGS = { |
| (3072, 3072): (2, 128, 64, 8, 4), |
| (3072, 1024): (2, 256, 128, 8, 4), |
| (3072, 1536): (2, 64, 64, 4, 4), |
| (3072, 8192): (2, 128, 64, 8, 4), |
| (8192, 3072): (4, 128, 64, 4, 4), |
| } |
| _TRITON_SMALL_M_DEFAULT = (4, 256, 64, 4, 4) |
| _TRITON_SMALL_M_K3072_DEFAULT = (2, 64, 64, 4, 4) |
| _TRITON_MID_M_DEFAULT = (32, 128, 64, 8, 4) |
| _TRITON_LARGE_M_DEFAULT = (64, 128, 64, 8, 4) |
| _TRITON_LARGE_M_SHAPE_CONFIGS = { |
| |
| (3840, 2048): (64, 256, 64, 8, 4), |
| (3840, 15360): (64, 256, 64, 8, 4), |
| (3840, 4096): (64, 256, 64, 8, 4), |
| (4096, 3840): (64, 256, 64, 8, 4), |
| (15360, 3840): (64, 256, 64, 8, 4), |
| |
| (4096, 4096): (64, 256, 64, 8, 4), |
| (4096, 10240): (64, 256, 64, 8, 4), |
| (10240, 4096): (64, 256, 64, 8, 4), |
| } |
|
|
| _AUTOTUNE_SLOT_REPS = { |
| "tiny_k3072_default": ((2, 3072, 2048), (4, 3072, 4096)), |
| "tiny_default": ((2, 4096, 1536), (4, 4096, 1536)), |
| "mid_default": ((32, 2048, 4096), (32, 3072, 3072), (32, 4096, 4096)), |
| "large_n_ge_2048": ((512, 4096, 4096), (3840, 3840, 4096)), |
| "large_default": ((128, 4096, 1024), (192, 3072, 1536)), |
| } |
| _RUNTIME_PROBE_MAX_N = 4096 |
|
|
|
|
| def _env_flag(name: str, default: str = "1") -> bool: |
| val = os.environ.get(name, default) |
| return str(val).strip().lower() in ("1", "true", "yes", "on") |
|
|
|
|
| def _parse_version(ver: str) -> tuple[int, int]: |
| try: |
| parts = ver.split(".") |
| return int(parts[0]), int(parts[1]) |
| except Exception: |
| return (0, 0) |
|
|
|
|
| def _is_stream_capturing() -> bool: |
| if not torch.cuda.is_available(): |
| return False |
| try: |
| return bool(torch.cuda.is_current_stream_capturing()) |
| except Exception: |
| return False |
|
|
|
|
| def _env_int(name: str, default: int) -> int: |
| try: |
| return int(os.environ.get(name, str(default))) |
| except Exception: |
| return default |
|
|
|
|
| def _env_float(name: str, default: float) -> float: |
| try: |
| return float(os.environ.get(name, str(default))) |
| except Exception: |
| return default |
|
|
|
|
| def _autotune_debug(msg: str) -> None: |
| if _AUTOTUNE_DEBUG_OVERRIDE is None: |
| debug_on = _env_flag(_ENV_AUTOTUNE_DEBUG, "0") |
| else: |
| debug_on = bool(_AUTOTUNE_DEBUG_OVERRIDE) |
| if debug_on: |
| print(f"[WAN2GP][INT8][autotune] {msg}") |
|
|
|
|
| def set_autotune_debug(enabled: Optional[bool] = None) -> None: |
| global _AUTOTUNE_DEBUG_OVERRIDE |
| _AUTOTUNE_DEBUG_OVERRIDE = None if enabled is None else bool(enabled) |
|
|
|
|
| def _runtime_compatible() -> bool: |
| if not (_TRITON_AVAILABLE and torch.cuda.is_available()): |
| return False |
| try: |
| cc_major, _ = torch.cuda.get_device_capability() |
| except Exception: |
| return False |
|
|
| |
| if cc_major < 8: |
| return False |
|
|
| |
| triton_ver = _parse_version(getattr(triton, "__version__", "0.0")) |
| if cc_major >= 12 and triton_ver < (3, 6): |
| return False |
|
|
| return True |
|
|
|
|
| def is_available() -> bool: |
| global _IS_AVAILABLE |
| if _IS_AVAILABLE is None: |
| _IS_AVAILABLE = bool(_runtime_compatible() and _env_flag(_ENV_ENABLE, "1")) |
| return _IS_AVAILABLE |
|
|
|
|
| def _select_static_triton_int8_config(m: int, k: int, n: int) -> tuple[int, int, int, int, int]: |
| if m <= 4: |
| cfg = _TRITON_TINY_M_SHAPE_CONFIGS.get((m, k, n)) |
| if cfg is not None: |
| return cfg |
| cfg = _TRITON_TINY_M_PAIR_CONFIGS.get((k, n)) |
| if cfg is not None: |
| return cfg |
| cfg = _TRITON_SMALL_M_CONFIGS.get((k, n)) |
| if cfg is not None: |
| return cfg |
| if k == 3072: |
| return _TRITON_SMALL_M_K3072_DEFAULT |
| return _TRITON_SMALL_M_DEFAULT |
| if m < 64: |
| return _TRITON_MID_M_DEFAULT |
| if m >= 256: |
| cfg = _TRITON_LARGE_M_SHAPE_CONFIGS.get((k, n)) |
| if cfg is not None: |
| return cfg |
| if n >= 2048: |
| return (64, 256, 64, 8, 4) |
| return _TRITON_LARGE_M_DEFAULT |
|
|
|
|
| def _dedup_shapes(shapes: tuple[tuple[int, int, int], ...]) -> tuple[tuple[int, int, int], ...]: |
| out: list[tuple[int, int, int]] = [] |
| seen: set[tuple[int, int, int]] = set() |
| for shape in shapes: |
| if not isinstance(shape, (list, tuple)) or len(shape) != 3: |
| continue |
| try: |
| m, k, n = (int(shape[0]), int(shape[1]), int(shape[2])) |
| except Exception: |
| continue |
| if m <= 0 or k <= 0 or n <= 0: |
| continue |
| key = (m, k, n) |
| if key in seen: |
| continue |
| seen.add(key) |
| out.append(key) |
| return tuple(out) |
|
|
|
|
| def _resolve_autotune_slot(m: int, k: int, n: int) -> tuple[str, tuple[tuple[int, int, int], ...]]: |
| baseline = _select_static_triton_int8_config(m, k, n) |
| if m <= 4: |
| if (m, k, n) in _TRITON_TINY_M_SHAPE_CONFIGS: |
| slot_id = f"tiny_shape|m={m}|k={k}|n={n}" |
| reps = ((m, k, n),) |
| elif (k, n) in _TRITON_TINY_M_PAIR_CONFIGS: |
| slot_id = f"tiny_pair|k={k}|n={n}" |
| reps = ((2, k, n), (4, k, n)) |
| elif (k, n) in _TRITON_SMALL_M_CONFIGS: |
| slot_id = f"tiny_small_pair|k={k}|n={n}" |
| reps = ((2, k, n), (4, k, n)) |
| elif k == 3072: |
| slot_id = "tiny_k3072_default" |
| reps = _AUTOTUNE_SLOT_REPS[slot_id] |
| else: |
| slot_id = "tiny_default" |
| reps = _AUTOTUNE_SLOT_REPS[slot_id] |
| elif m < 64: |
| slot_id = "mid_default" |
| reps = _AUTOTUNE_SLOT_REPS[slot_id] |
| elif m >= 256 and (k, n) in _TRITON_LARGE_M_SHAPE_CONFIGS: |
| slot_id = f"large_hot_pair|k={k}|n={n}" |
| reps = ((512, k, n), (3840, k, n)) |
| elif m >= 256 and n >= 2048: |
| slot_id = "large_n_ge_2048" |
| reps = _AUTOTUNE_SLOT_REPS[slot_id] |
| else: |
| slot_id = "large_default" |
| reps = _AUTOTUNE_SLOT_REPS[slot_id] |
|
|
| filtered = [shape for shape in _dedup_shapes(reps) if _select_static_triton_int8_config(shape[0], shape[1], shape[2]) == baseline] |
| if len(filtered) == 0: |
| filtered = [(m, k, n)] |
| return slot_id, tuple(filtered) |
|
|
|
|
| def _normalize_config(cfg) -> Optional[tuple[int, int, int, int, int]]: |
| if not isinstance(cfg, (list, tuple)) or len(cfg) != _CONFIG_LEN: |
| return None |
| try: |
| c0, c1, c2, c3, c4 = (int(v) for v in cfg) |
| except Exception: |
| return None |
| if c0 <= 0 or c1 <= 0 or c2 <= 0 or c3 <= 0 or c4 <= 0: |
| return None |
| return (c0, c1, c2, c3, c4) |
|
|
|
|
| def _autotune_cache_path() -> Path: |
| default_path = str(Path.home() / ".triton" / "autotune" / "wan2gp_int8_autotune_cache.json") |
| return Path(os.environ.get(_ENV_AUTOTUNE_CACHE, default_path)).expanduser() |
|
|
|
|
| def _load_autotune_cache() -> None: |
| global _AUTOTUNE_CACHE_LOADED, _AUTOTUNE_CONFIG_CACHE |
| if _AUTOTUNE_CACHE_LOADED: |
| return |
| _AUTOTUNE_CACHE_LOADED = True |
| cache_path = _autotune_cache_path() |
| try: |
| payload = json.loads(cache_path.read_text(encoding="utf-8")) |
| except Exception: |
| return |
| entries = payload.get("entries", {}) |
| if not isinstance(entries, dict): |
| return |
| parsed = {} |
| for key, raw_cfg in entries.items(): |
| if not isinstance(key, str): |
| continue |
| cfg = _normalize_config(raw_cfg) |
| if cfg is not None: |
| parsed[key] = cfg |
| _AUTOTUNE_CONFIG_CACHE = parsed |
|
|
|
|
| def _save_autotune_cache() -> None: |
| global _AUTOTUNE_CACHE_DIRTY |
| if not _AUTOTUNE_CACHE_DIRTY: |
| return |
| cache_path = _autotune_cache_path() |
| try: |
| cache_path.parent.mkdir(parents=True, exist_ok=True) |
| tmp_path = Path(f"{cache_path}.tmp") |
| payload = {"version": _AUTOTUNE_CACHE_VERSION, "entries": {key: list(cfg) for key, cfg in _AUTOTUNE_CONFIG_CACHE.items()}} |
| tmp_path.write_text(json.dumps(payload, sort_keys=True), encoding="utf-8") |
| tmp_path.replace(cache_path) |
| _AUTOTUNE_CACHE_DIRTY = False |
| except Exception as exc: |
| _autotune_debug(f"cache write failed: {exc}") |
|
|
|
|
| def _device_index(device: Optional[torch.device]) -> int: |
| if device is not None and device.type == "cuda" and device.index is not None: |
| return int(device.index) |
| return int(torch.cuda.current_device()) |
|
|
|
|
| def _device_fingerprint(device_index: int) -> str: |
| props = torch.cuda.get_device_properties(device_index) |
| triton_ver = getattr(triton, "__version__", "0.0") |
| return ( |
| f"{props.name}|cc={props.major}.{props.minor}|sm={props.multi_processor_count}|" |
| f"torch={torch.__version__}|triton={triton_ver}|wan2gp_int8_cache_v={_AUTOTUNE_CACHE_VERSION}" |
| ) |
|
|
|
|
| def _autotune_slot_cache_key(device_index: int, kernel_kind: str, slot_id: str) -> str: |
| return f"{_device_fingerprint(device_index)}|{kernel_kind}|slot={slot_id}" |
|
|
|
|
| def _autotune_legacy_shape_cache_key(device_index: int, kernel_kind: str, m: int, k: int, n: int) -> str: |
| return f"{_device_fingerprint(device_index)}|{kernel_kind}|{m}|{k}|{n}" |
|
|
|
|
| def _get_cached_config(device_index: int, kernel_kind: str, slot_id: str, m: int, k: int, n: int) -> Optional[tuple[int, int, int, int, int]]: |
| global _AUTOTUNE_CACHE_DIRTY |
| _load_autotune_cache() |
| slot_key = _autotune_slot_cache_key(device_index, kernel_kind, slot_id) |
| cfg = _AUTOTUNE_CONFIG_CACHE.get(slot_key) |
| if cfg is not None: |
| return cfg |
| legacy_key = _autotune_legacy_shape_cache_key(device_index, kernel_kind, m, k, n) |
| legacy_cfg = _AUTOTUNE_CONFIG_CACHE.get(legacy_key) |
| if legacy_cfg is None: |
| return None |
| _AUTOTUNE_CONFIG_CACHE[slot_key] = legacy_cfg |
| _AUTOTUNE_CACHE_DIRTY = True |
| return legacy_cfg |
|
|
|
|
| def _set_cached_config(device_index: int, kernel_kind: str, slot_id: str, cfg: tuple[int, int, int, int, int]) -> None: |
| global _AUTOTUNE_CACHE_DIRTY |
| _load_autotune_cache() |
| key = _autotune_slot_cache_key(device_index, kernel_kind, slot_id) |
| if _AUTOTUNE_CONFIG_CACHE.get(key) == cfg: |
| return |
| _AUTOTUNE_CONFIG_CACHE[key] = cfg |
| _AUTOTUNE_CACHE_DIRTY = True |
| _save_autotune_cache() |
|
|
|
|
| def _drop_cached_config(device_index: int, kernel_kind: str, slot_id: str, m: int, k: int, n: int) -> None: |
| global _AUTOTUNE_CACHE_DIRTY |
| _load_autotune_cache() |
| keys = ( |
| _autotune_slot_cache_key(device_index, kernel_kind, slot_id), |
| _autotune_legacy_shape_cache_key(device_index, kernel_kind, m, k, n), |
| ) |
| removed = False |
| for key in keys: |
| if key in _AUTOTUNE_CONFIG_CACHE: |
| del _AUTOTUNE_CONFIG_CACHE[key] |
| removed = True |
| if removed: |
| _AUTOTUNE_CACHE_DIRTY = True |
|
|
|
|
| def _config_compatible_with_baseline( |
| kind: str, |
| baseline: tuple[int, int, int, int, int], |
| cfg: tuple[int, int, int, int, int], |
| ) -> bool: |
| if kind == "fused" and _env_flag(_ENV_AUTOTUNE_LOCK_FUSED_BLOCK_K, "1"): |
| |
| return int(cfg[2]) == int(baseline[2]) |
| return True |
|
|
|
|
| def _runtime_probe_shape( |
| kind: str, |
| m: int, |
| k: int, |
| n: int, |
| baseline: tuple[int, int, int, int, int], |
| ) -> tuple[int, int, int, tuple[int, int, int, int, int]]: |
| if n <= _RUNTIME_PROBE_MAX_N: |
| return m, k, n, baseline |
| probe_candidates = [] |
| for probe_n in (_RUNTIME_PROBE_MAX_N, 3072, 2048, 1536, 1024, 768, 512, 256, 128, 64): |
| if probe_n >= n: |
| continue |
| if probe_n % 8 != 0: |
| continue |
| probe_candidates.append(probe_n) |
| for probe_n in probe_candidates: |
| probe_baseline = _select_static_triton_int8_config(m, k, probe_n) |
| if not _config_compatible_with_baseline(kind, baseline, probe_baseline): |
| continue |
| if not _config_compatible_with_baseline(kind, probe_baseline, baseline): |
| continue |
| return m, k, probe_n, probe_baseline |
| return m, k, n, baseline |
|
|
|
|
| def _candidate_configs( |
| baseline: tuple[int, int, int, int, int], |
| m: int, |
| k: int, |
| n: int, |
| *, |
| kind: str, |
| ) -> list[tuple[int, int, int, int, int]]: |
| out = [baseline] |
| if m <= 4: |
| out.extend( |
| [ |
| (1, 64, 64, 2, 4), |
| (1, 128, 64, 4, 4), |
| (2, 64, 64, 4, 4), |
| (2, 128, 64, 4, 4), |
| (2, 128, 128, 8, 4), |
| (2, 256, 64, 8, 4), |
| (4, 128, 64, 4, 4), |
| (4, 256, 64, 4, 4), |
| (8, 128, 64, 4, 4), |
| ] |
| ) |
| shape_cfg = _TRITON_TINY_M_SHAPE_CONFIGS.get((m, k, n)) |
| if shape_cfg is not None: |
| out.append(shape_cfg) |
| pair_cfg = _TRITON_TINY_M_PAIR_CONFIGS.get((k, n)) |
| if pair_cfg is not None: |
| out.append(pair_cfg) |
| elif m <= 16: |
| out.extend([(8, 128, 64, 4, 4), (8, 256, 64, 8, 4), (16, 128, 64, 8, 4), (16, 256, 64, 8, 4), (32, 128, 64, 8, 4)]) |
| dedup: list[tuple[int, int, int, int, int]] = [] |
| seen = set() |
| for cfg in out: |
| norm = _normalize_config(cfg) |
| if norm is None or norm in seen: |
| continue |
| if not _config_compatible_with_baseline(kind, baseline, norm): |
| continue |
| seen.add(norm) |
| dedup.append(norm) |
| return dedup |
|
|
|
|
| def _looks_like_unsupported_dot_tile(cfg: tuple[int, int, int, int, int]) -> bool: |
| block_m, block_n, block_k, _, _ = cfg |
| |
| return block_m < 16 or block_n < 16 or block_k < 32 |
|
|
|
|
| def _compile_recovery_candidates( |
| kind: str, |
| baseline: tuple[int, int, int, int, int], |
| preferred: tuple[int, int, int, int, int], |
| m: int, |
| k: int, |
| n: int, |
| ) -> list[tuple[int, int, int, int, int]]: |
| block_k = max(32, int(baseline[2])) |
| if block_k % 32 != 0: |
| block_k = ((block_k + 31) // 32) * 32 |
| conservative_large_tiles = [ |
| (16, 32, block_k, 4, 4), |
| (16, 64, block_k, 4, 4), |
| (16, 128, block_k, 4, 4), |
| (32, 32, block_k, 4, 4), |
| (32, 64, block_k, 8, 4), |
| (32, 128, block_k, 8, 4), |
| (64, 64, block_k, 8, 4), |
| (64, 128, block_k, 8, 4), |
| ] |
| raw = [preferred] |
| raw.extend(_candidate_configs(baseline, m, k, n, kind=kind)) |
| raw.extend(conservative_large_tiles) |
|
|
| dedup: list[tuple[int, int, int, int, int]] = [] |
| seen = set() |
| for cfg in raw: |
| norm = _normalize_config(cfg) |
| if norm is None or norm in seen: |
| continue |
| if not _config_compatible_with_baseline(kind, baseline, norm): |
| continue |
| seen.add(norm) |
| dedup.append(norm) |
|
|
| if baseline not in dedup: |
| dedup.append(baseline) |
|
|
| if len(dedup) <= 1: |
| return dedup |
|
|
| head = dedup[0] |
| tail = dedup[1:] |
| non_tiny = [cfg for cfg in tail if not _looks_like_unsupported_dot_tile(cfg)] |
| tiny = [cfg for cfg in tail if _looks_like_unsupported_dot_tile(cfg)] |
| return [head, *non_tiny, *tiny] |
|
|
|
|
| def _launch_candidate(kind: str, cfg: tuple[int, int, int, int, int], tensors: tuple[torch.Tensor, ...], m: int, n: int, k: int) -> None: |
| block_m, block_n, block_k, num_warps, num_stages = cfg |
| grid = (triton.cdiv(m, block_m), triton.cdiv(n, block_n)) |
| if kind == "fused": |
| x_mm_c, qweight_c, b_scale_c, out = tensors |
| _fused_dynamic_int8_blockscale_gemm_kernel[grid]( |
| x_mm_c, |
| qweight_c, |
| b_scale_c, |
| out, |
| m, |
| n, |
| k, |
| x_mm_c.stride(0), |
| x_mm_c.stride(1), |
| qweight_c.stride(0), |
| qweight_c.stride(1), |
| out.stride(0), |
| out.stride(1), |
| block_m=block_m, |
| block_n=block_n, |
| block_k=block_k, |
| num_warps=num_warps, |
| num_stages=num_stages, |
| ) |
| return |
| a_int8_c, b_int8_c, a_scale_c, b_scale_c, out = tensors |
| _scaled_int8_gemm_kernel[grid]( |
| a_int8_c, |
| b_int8_c, |
| a_scale_c, |
| b_scale_c, |
| out, |
| m, |
| n, |
| k, |
| a_int8_c.stride(0), |
| a_int8_c.stride(1), |
| b_int8_c.stride(0), |
| b_int8_c.stride(1), |
| out.stride(0), |
| out.stride(1), |
| block_m=block_m, |
| block_n=block_n, |
| block_k=block_k, |
| num_warps=num_warps, |
| num_stages=num_stages, |
| ) |
|
|
|
|
| def _create_bench_tensors(kind: str, device: torch.device, m: int, k: int, n: int) -> tuple[torch.Tensor, ...]: |
| if kind == "fused": |
| x_mm_c = torch.randn((m, k), device=device, dtype=torch.bfloat16) |
| qweight_c = torch.randint(-128, 128, (n, k), device=device, dtype=torch.int8) |
| b_scale_c = torch.rand((n,), device=device, dtype=torch.float32).add_(1e-4) |
| out = torch.empty((m, n), device=device, dtype=torch.bfloat16) |
| return (x_mm_c, qweight_c, b_scale_c, out) |
| a_int8_c = torch.randint(-128, 128, (m, k), device=device, dtype=torch.int8) |
| b_int8_c = torch.randint(-128, 128, (n, k), device=device, dtype=torch.int8) |
| a_scale_c = torch.rand((m,), device=device, dtype=torch.float32).add_(1e-4) |
| b_scale_c = torch.rand((n,), device=device, dtype=torch.float32).add_(1e-4) |
| out = torch.empty((m, n), device=device, dtype=torch.bfloat16) |
| return (a_int8_c, b_int8_c, a_scale_c, b_scale_c, out) |
|
|
|
|
| def _run_candidate_once_with_error( |
| kind: str, |
| cfg: tuple[int, int, int, int, int], |
| tensors: tuple[torch.Tensor, ...], |
| m: int, |
| k: int, |
| n: int, |
| ) -> tuple[Optional[torch.Tensor], Optional[Exception]]: |
| try: |
| if kind == "fused": |
| x_mm_c, qweight_c, b_scale_c, _ = tensors |
| out = torch.empty((m, n), device=x_mm_c.device, dtype=torch.bfloat16) |
| _launch_candidate(kind, cfg, (x_mm_c, qweight_c, b_scale_c, out), m, n, k) |
| torch.cuda.synchronize(x_mm_c.device) |
| return out, None |
| a_int8_c, b_int8_c, a_scale_c, b_scale_c, _ = tensors |
| out = torch.empty((m, n), device=a_int8_c.device, dtype=torch.bfloat16) |
| _launch_candidate(kind, cfg, (a_int8_c, b_int8_c, a_scale_c, b_scale_c, out), m, n, k) |
| torch.cuda.synchronize(a_int8_c.device) |
| return out, None |
| except Exception as exc: |
| _autotune_debug(f"single-run failed for {kind} shape=({m},{k},{n}) cfg={cfg}: {exc}") |
| return None, exc |
|
|
|
|
| def _run_candidate_once(kind: str, cfg: tuple[int, int, int, int, int], tensors: tuple[torch.Tensor, ...], m: int, k: int, n: int) -> Optional[torch.Tensor]: |
| out, _ = _run_candidate_once_with_error(kind, cfg, tensors, m, k, n) |
| return out |
|
|
|
|
| def _ensure_compile_compatible_config( |
| kind: str, |
| device_index: int, |
| slot_id: str, |
| preferred: tuple[int, int, int, int, int], |
| baseline: tuple[int, int, int, int, int], |
| m: int, |
| k: int, |
| n: int, |
| rep_shapes: tuple[tuple[int, int, int], ...], |
| ) -> tuple[tuple[int, int, int, int, int], Optional[Exception]]: |
| device = torch.device("cuda", device_index) |
| _ = rep_shapes |
| probe_m, probe_k, probe_n, _ = _runtime_probe_shape(kind, m, k, n, baseline) |
| |
| |
| probe_shapes = ((probe_m, probe_k, probe_n),) |
| tensors_by_shape = {shape: _create_bench_tensors(kind, device, *shape) for shape in probe_shapes} |
| candidates = _compile_recovery_candidates(kind, baseline, preferred, m, k, n) |
| last_error: Optional[Exception] = None |
|
|
| for cfg in candidates: |
| all_ok = True |
| for probe_m, probe_k, probe_n in probe_shapes: |
| _, probe_err = _run_candidate_once_with_error( |
| kind, |
| cfg, |
| tensors_by_shape[(probe_m, probe_k, probe_n)], |
| probe_m, |
| probe_k, |
| probe_n, |
| ) |
| if probe_err is not None: |
| all_ok = False |
| last_error = probe_err |
| break |
| if all_ok: |
| if cfg != preferred: |
| _autotune_debug( |
| f"compile recovery picked {cfg} for {kind} slot={slot_id} shape=({m},{k},{n}) " |
| f"instead of {preferred}" |
| ) |
| return cfg, None |
|
|
| if last_error is not None: |
| _autotune_debug( |
| f"compile recovery failed for {kind} slot={slot_id} shape=({m},{k},{n}); " |
| f"keeping {preferred}. last_error={last_error}" |
| ) |
| return preferred, last_error |
|
|
|
|
| def _candidate_matches_baseline( |
| baseline_out: torch.Tensor, |
| candidate_out: torch.Tensor, |
| *, |
| max_abs_limit: float, |
| rel_limit: float, |
| ) -> tuple[bool, float, float]: |
| if not torch.isfinite(candidate_out).all().item(): |
| return False, float("inf"), float("inf") |
| base_f = baseline_out.float() |
| cand_f = candidate_out.float() |
| diff = (base_f - cand_f).abs() |
| max_abs = float(diff.max().item()) |
| denom = base_f.abs().mean().clamp_min(1e-6) |
| rel = float((diff.mean() / denom).item()) |
| return (max_abs <= max_abs_limit and rel <= rel_limit), max_abs, rel |
|
|
|
|
| def _validate_config( |
| kind: str, |
| device: torch.device, |
| m: int, |
| k: int, |
| n: int, |
| baseline: tuple[int, int, int, int, int], |
| cfg: tuple[int, int, int, int, int], |
| ) -> bool: |
| if not _config_compatible_with_baseline(kind, baseline, cfg): |
| return False |
| if cfg == baseline: |
| return True |
| if not _env_flag(_ENV_AUTOTUNE_VALIDATE, "1"): |
| return True |
| max_abs_limit = max(0.0, _env_float(_ENV_AUTOTUNE_MAX_ABS_ERR, 0.25)) |
| rel_limit = max(0.0, _env_float(_ENV_AUTOTUNE_MAX_REL_ERR, 0.001)) |
| tensors = _create_bench_tensors(kind, device, m, k, n) |
| baseline_out = _run_candidate_once(kind, baseline, tensors, m, k, n) |
| candidate_out = _run_candidate_once(kind, cfg, tensors, m, k, n) |
| if baseline_out is None or candidate_out is None: |
| return False |
| ok, max_abs, rel = _candidate_matches_baseline( |
| baseline_out, |
| candidate_out, |
| max_abs_limit=max_abs_limit, |
| rel_limit=rel_limit, |
| ) |
| if not ok: |
| _autotune_debug( |
| f"rejecting config {cfg} for {kind} shape=({m},{k},{n}) " |
| f"vs baseline {baseline}: max_abs={max_abs:.6f}, rel={rel:.6f}" |
| ) |
| return ok |
|
|
|
|
| def _benchmark_config_ms(kind: str, cfg: tuple[int, int, int, int, int], tensors: tuple[torch.Tensor, ...], device: torch.device, m: int, k: int, n: int) -> Optional[float]: |
| warmup = max(1, _env_int(_ENV_AUTOTUNE_WARMUP, 2)) |
| iters = max(1, _env_int(_ENV_AUTOTUNE_ITERS, 5)) |
| try: |
| for _ in range(warmup): |
| _launch_candidate(kind, cfg, tensors, m, n, k) |
| torch.cuda.synchronize(device) |
| start = torch.cuda.Event(enable_timing=True) |
| end = torch.cuda.Event(enable_timing=True) |
| start.record() |
| for _ in range(iters): |
| _launch_candidate(kind, cfg, tensors, m, n, k) |
| end.record() |
| end.synchronize() |
| return float(start.elapsed_time(end)) / float(iters) |
| except Exception as exc: |
| _autotune_debug(f"benchmark failed for {kind} shape=({m},{k},{n}) cfg={cfg}: {exc}") |
| return None |
|
|
|
|
| def _can_tune_slot(slot_key: tuple[int, str, str]) -> bool: |
| global _AUTOTUNE_SLOTS_TUNED |
| if slot_key in _AUTOTUNE_SEEN_SLOTS: |
| return True |
| max_shapes = max(0, _env_int(_ENV_AUTOTUNE_MAX_SHAPES, 32)) |
| if _AUTOTUNE_SLOTS_TUNED >= max_shapes: |
| return False |
| _AUTOTUNE_SEEN_SLOTS.add(slot_key) |
| _AUTOTUNE_SLOTS_TUNED += 1 |
| return True |
|
|
|
|
| def _benchmark_slot_config_ms( |
| kind: str, |
| cfg: tuple[int, int, int, int, int], |
| device: torch.device, |
| rep_shapes: tuple[tuple[int, int, int], ...], |
| ) -> Optional[float]: |
| total = 0.0 |
| count = 0 |
| for rep_m, rep_k, rep_n in rep_shapes: |
| rep_baseline = _select_static_triton_int8_config(rep_m, rep_k, rep_n) |
| if not _validate_config(kind, device, rep_m, rep_k, rep_n, rep_baseline, cfg): |
| return None |
| tensors = _create_bench_tensors(kind, device, rep_m, rep_k, rep_n) |
| ms = _benchmark_config_ms(kind, cfg, tensors, device, rep_m, rep_k, rep_n) |
| if ms is None: |
| return None |
| total += ms |
| count += 1 |
| if count == 0: |
| return None |
| return total / float(count) |
|
|
|
|
| def _autotune_config( |
| kind: str, |
| device_index: int, |
| m: int, |
| k: int, |
| n: int, |
| baseline: tuple[int, int, int, int, int], |
| slot_id: str, |
| rep_shapes: tuple[tuple[int, int, int], ...], |
| ) -> tuple[int, int, int, int, int]: |
| device = torch.device("cuda", device_index) |
| cached = _get_cached_config(device_index, kind, slot_id, m, k, n) |
| if cached is not None: |
| probe_m, probe_k, probe_n, probe_baseline = _runtime_probe_shape(kind, m, k, n, baseline) |
| if _validate_config(kind, device, probe_m, probe_k, probe_n, probe_baseline, cached): |
| return cached |
| _drop_cached_config(device_index, kind, slot_id, m, k, n) |
| slot_key = (device_index, kind, slot_id) |
| if not _can_tune_slot(slot_key): |
| _autotune_debug(f"slot budget reached; keeping baseline for {kind} slot={slot_id} shape=({m},{k},{n})") |
| return baseline |
|
|
| rep_m, rep_k, rep_n = rep_shapes[0] |
| rep_baseline = _select_static_triton_int8_config(rep_m, rep_k, rep_n) |
| candidate_seed = rep_baseline if _config_compatible_with_baseline(kind, baseline, rep_baseline) else baseline |
| candidates = _candidate_configs(candidate_seed, rep_m, rep_k, rep_n, kind=kind) |
| if baseline not in candidates: |
| candidates = [baseline, *candidates] |
|
|
| results: dict[tuple[int, int, int, int, int], float] = {} |
| for cfg in candidates: |
| ms = _benchmark_slot_config_ms(kind, cfg, device, rep_shapes) |
| if ms is not None: |
| results[cfg] = ms |
| baseline_ms = results.get(baseline) |
| if baseline_ms is None: |
| if len(results) > 0: |
| recovered_cfg, recovered_ms = min(results.items(), key=lambda item: item[1]) |
| _set_cached_config(device_index, kind, slot_id, recovered_cfg) |
| _autotune_debug( |
| f"baseline config failed for {kind} slot={slot_id} shape=({m},{k},{n}); " |
| f"using first compilable cfg={recovered_cfg} (ms={recovered_ms:.4f})" |
| ) |
| return recovered_cfg |
| _set_cached_config(device_index, kind, slot_id, baseline) |
| _autotune_debug( |
| f"no compilable configs found during autotune for {kind} slot={slot_id} shape=({m},{k},{n}); " |
| f"keeping baseline {baseline}" |
| ) |
| return baseline |
| best_cfg, best_ms = min(results.items(), key=lambda item: item[1]) |
| min_speedup = max(1.0, _env_float(_ENV_AUTOTUNE_MIN_SPEEDUP, 1.02)) |
| use_best = best_cfg != baseline and best_ms > 0.0 and (baseline_ms / best_ms) >= min_speedup |
| picked = best_cfg if use_best else baseline |
| probe_m, probe_k, probe_n, probe_baseline = _runtime_probe_shape(kind, m, k, n, baseline) |
| if not _validate_config(kind, device, probe_m, probe_k, probe_n, probe_baseline, picked): |
| picked = baseline |
| _set_cached_config(device_index, kind, slot_id, picked) |
| if use_best: |
| _autotune_debug( |
| f"picked {picked} over baseline {baseline} for {kind} slot={slot_id} shape=({m},{k},{n}), " |
| f"baseline_ms={baseline_ms:.4f}, tuned_ms={best_ms:.4f}, speedup={baseline_ms / best_ms:.3f}x" |
| ) |
| else: |
| _autotune_debug( |
| f"kept baseline {baseline} for {kind} slot={slot_id} shape=({m},{k},{n}), " |
| f"baseline_ms={baseline_ms:.4f}, best_cfg={best_cfg}, best_ms={best_ms:.4f}" |
| ) |
| return picked |
|
|
|
|
| def _select_triton_int8_config( |
| m: int, |
| k: int, |
| n: int, |
| *, |
| device: Optional[torch.device] = None, |
| kernel_kind: str = "fused", |
| ) -> tuple[int, int, int, int, int]: |
| baseline = _select_static_triton_int8_config(m, k, n) |
| if not is_available() or not torch.cuda.is_available(): |
| return baseline |
| try: |
| device_index = _device_index(device) |
| except Exception: |
| return baseline |
| slot_id, rep_shapes = _resolve_autotune_slot(m, k, n) |
| session_key = (device_index, kernel_kind, slot_id) |
| cached = _AUTOTUNE_SESSION_CACHE.get(session_key) |
| if cached is not None: |
| if _config_compatible_with_baseline(kernel_kind, baseline, cached): |
| return cached |
| _AUTOTUNE_SESSION_CACHE.pop(session_key, None) |
| cached_cfg = _get_cached_config(device_index, kernel_kind, slot_id, m, k, n) |
| if cached_cfg is not None and _config_compatible_with_baseline(kernel_kind, baseline, cached_cfg): |
| if not _is_stream_capturing(): |
| compile_safe, _ = _ensure_compile_compatible_config( |
| kernel_kind, |
| device_index, |
| slot_id, |
| cached_cfg, |
| baseline, |
| m, |
| k, |
| n, |
| rep_shapes, |
| ) |
| if compile_safe != cached_cfg: |
| _set_cached_config(device_index, kernel_kind, slot_id, compile_safe) |
| _AUTOTUNE_SESSION_CACHE[session_key] = compile_safe |
| return compile_safe |
| _AUTOTUNE_SESSION_CACHE[session_key] = cached_cfg |
| return cached_cfg |
| if _is_stream_capturing(): |
| |
| |
| return baseline |
|
|
| autotune_enabled = _env_flag(_ENV_AUTOTUNE_ENABLE, "1") |
| max_m = _env_int(_ENV_AUTOTUNE_MAX_M, -1) |
| if autotune_enabled and not (max_m >= 0 and m > max_m): |
| preferred = _autotune_config(kernel_kind, device_index, m, k, n, baseline, slot_id, rep_shapes) |
| else: |
| preferred = baseline |
|
|
| compile_safe, compile_err = _ensure_compile_compatible_config( |
| kernel_kind, |
| device_index, |
| slot_id, |
| preferred, |
| baseline, |
| m, |
| k, |
| n, |
| rep_shapes, |
| ) |
|
|
| picked = compile_safe |
| if compile_safe != preferred: |
| _set_cached_config(device_index, kernel_kind, slot_id, compile_safe) |
| elif compile_err is not None: |
| _autotune_debug( |
| f"compile probe could not find an alternative for {kernel_kind} slot={slot_id} " |
| f"shape=({m},{k},{n}); will keep {preferred}" |
| ) |
| _AUTOTUNE_SESSION_CACHE[session_key] = picked |
| return picked |
|
|
|
|
| atexit.register(_save_autotune_cache) |
|
|
|
|
| if _TRITON_AVAILABLE: |
|
|
| @triton.jit |
| def _fused_dynamic_int8_gemm_kernel( |
| a_ptr, |
| b_ptr, |
| s_ptr, |
| c_ptr, |
| m, |
| n, |
| k, |
| stride_am, |
| stride_ak, |
| stride_bn, |
| stride_bk, |
| stride_cm, |
| stride_cn, |
| block_m: tl.constexpr, |
| block_n: tl.constexpr, |
| block_k: tl.constexpr, |
| ): |
| pid_m = tl.program_id(0) |
| pid_n = tl.program_id(1) |
| offs_m = pid_m * block_m + tl.arange(0, block_m) |
| offs_n = pid_n * block_n + tl.arange(0, block_n) |
| offs_k = tl.arange(0, block_k) |
|
|
| |
| row_amax = tl.zeros((block_m,), dtype=tl.float32) |
| for k0 in range(0, k, block_k): |
| kk = k0 + offs_k |
| a = tl.load( |
| a_ptr + offs_m[:, None] * stride_am + kk[None, :] * stride_ak, |
| mask=(offs_m[:, None] < m) & (kk[None, :] < k), |
| other=0, |
| ).to(tl.float32) |
| row_amax = tl.maximum(row_amax, tl.max(tl.abs(a), axis=1)) |
|
|
| row_scale = row_amax / 127.0 |
| row_scale = tl.where(row_scale > 0.0, row_scale, 1.0) |
| row_inv_scale = 1.0 / row_scale |
|
|
| |
| acc = tl.zeros((block_m, block_n), dtype=tl.int32) |
| for k0 in range(0, k, block_k): |
| kk = k0 + offs_k |
| a = tl.load( |
| a_ptr + offs_m[:, None] * stride_am + kk[None, :] * stride_ak, |
| mask=(offs_m[:, None] < m) & (kk[None, :] < k), |
| other=0, |
| ).to(tl.float32) |
| a = a * row_inv_scale[:, None] |
| |
| a = tl_libdevice.rint(a) |
| a = tl.maximum(tl.minimum(a, 127.0), -128.0).to(tl.int8) |
|
|
| |
| b = tl.load( |
| b_ptr + offs_n[None, :] * stride_bn + kk[:, None] * stride_bk, |
| mask=(offs_n[None, :] < n) & (kk[:, None] < k), |
| other=0, |
| ).to(tl.int8) |
| acc += tl.dot(a, b) |
|
|
| scales = tl.load(s_ptr + offs_n, mask=offs_n < n, other=0).to(tl.float32) |
| out = acc.to(tl.float32) * row_scale[:, None] * scales[None, :] |
| tl.store( |
| c_ptr + offs_m[:, None] * stride_cm + offs_n[None, :] * stride_cn, |
| out, |
| mask=(offs_m[:, None] < m) & (offs_n[None, :] < n), |
| ) |
|
|
| @triton.jit |
| def _fused_dynamic_int8_blockscale_gemm_kernel( |
| a_ptr, |
| b_ptr, |
| s_ptr, |
| c_ptr, |
| m, |
| n, |
| k, |
| stride_am, |
| stride_ak, |
| stride_bn, |
| stride_bk, |
| stride_cm, |
| stride_cn, |
| block_m: tl.constexpr, |
| block_n: tl.constexpr, |
| block_k: tl.constexpr, |
| ): |
| pid_m = tl.program_id(0) |
| pid_n = tl.program_id(1) |
| offs_m = pid_m * block_m + tl.arange(0, block_m) |
| offs_n = pid_n * block_n + tl.arange(0, block_n) |
| offs_k = tl.arange(0, block_k) |
|
|
| acc = tl.zeros((block_m, block_n), dtype=tl.float32) |
| for k0 in range(0, k, block_k): |
| kk = k0 + offs_k |
| a = tl.load( |
| a_ptr + offs_m[:, None] * stride_am + kk[None, :] * stride_ak, |
| mask=(offs_m[:, None] < m) & (kk[None, :] < k), |
| other=0, |
| ).to(tl.float32) |
| row_amax = tl.max(tl.abs(a), axis=1) |
| row_scale = row_amax / 127.0 |
| row_scale = tl.where(row_scale > 0.0, row_scale, 1.0) |
| a = a / row_scale[:, None] |
| a = tl_libdevice.rint(a) |
| a = tl.maximum(tl.minimum(a, 127.0), -128.0).to(tl.int8) |
|
|
| b = tl.load( |
| b_ptr + offs_n[None, :] * stride_bn + kk[:, None] * stride_bk, |
| mask=(offs_n[None, :] < n) & (kk[:, None] < k), |
| other=0, |
| ).to(tl.int8) |
|
|
| dot_i32 = tl.dot(a, b) |
| acc += dot_i32.to(tl.float32) * row_scale[:, None] |
|
|
| scales = tl.load(s_ptr + offs_n, mask=offs_n < n, other=0).to(tl.float32) |
| out = acc * scales[None, :] |
| tl.store( |
| c_ptr + offs_m[:, None] * stride_cm + offs_n[None, :] * stride_cn, |
| out, |
| mask=(offs_m[:, None] < m) & (offs_n[None, :] < n), |
| ) |
|
|
| @triton.jit |
| def _scaled_int8_gemm_kernel( |
| a_ptr, |
| b_ptr, |
| a_scales_ptr, |
| b_scales_ptr, |
| c_ptr, |
| m, |
| n, |
| k, |
| stride_am, |
| stride_ak, |
| stride_bn, |
| stride_bk, |
| stride_cm, |
| stride_cn, |
| block_m: tl.constexpr, |
| block_n: tl.constexpr, |
| block_k: tl.constexpr, |
| ): |
| pid_m = tl.program_id(0) |
| pid_n = tl.program_id(1) |
| offs_m = pid_m * block_m + tl.arange(0, block_m) |
| offs_n = pid_n * block_n + tl.arange(0, block_n) |
| offs_k = tl.arange(0, block_k) |
|
|
| acc = tl.zeros((block_m, block_n), dtype=tl.int32) |
| for k0 in range(0, k, block_k): |
| kk = k0 + offs_k |
| a = tl.load( |
| a_ptr + offs_m[:, None] * stride_am + kk[None, :] * stride_ak, |
| mask=(offs_m[:, None] < m) & (kk[None, :] < k), |
| other=0, |
| ).to(tl.int8) |
| |
| b = tl.load( |
| b_ptr + offs_n[None, :] * stride_bn + kk[:, None] * stride_bk, |
| mask=(offs_n[None, :] < n) & (kk[:, None] < k), |
| other=0, |
| ).to(tl.int8) |
| acc += tl.dot(a, b) |
|
|
| a_scales = tl.load(a_scales_ptr + offs_m, mask=offs_m < m, other=1).to(tl.float32) |
| b_scales = tl.load(b_scales_ptr + offs_n, mask=offs_n < n, other=1).to(tl.float32) |
| out = acc.to(tl.float32) * a_scales[:, None] * b_scales[None, :] |
| tl.store( |
| c_ptr + offs_m[:, None] * stride_cm + offs_n[None, :] * stride_cn, |
| out, |
| mask=(offs_m[:, None] < m) & (offs_n[None, :] < n), |
| ) |
|
|
|
|
| def _flatten_scale(scale: torch.Tensor) -> torch.Tensor: |
| if scale.ndim == 2 and scale.shape[1] == 1: |
| return scale.view(-1) |
| if scale.ndim == 1: |
| return scale |
| return scale.reshape(-1) |
|
|
|
|
| def _expand_or_validate_scale(scale: torch.Tensor, expected: int) -> torch.Tensor: |
| scale = _flatten_scale(scale) |
| if scale.numel() == 1: |
| return scale.reshape(1).expand(expected) |
| if scale.numel() != expected: |
| raise RuntimeError(f"Scale length mismatch: expected {expected}, got {scale.numel()}") |
| return scale |
|
|
|
|
| def _fused_quant_scaled_mm_common( |
| x2d: torch.Tensor, |
| qweight: torch.Tensor, |
| b_scale: torch.Tensor, |
| *, |
| k: int, |
| n: int, |
| stride_bn: int, |
| stride_bk: int, |
| out_dtype: torch.dtype, |
| ) -> torch.Tensor: |
| m = x2d.shape[0] |
| out = torch.empty((m, n), device=x2d.device, dtype=out_dtype) |
| x_mm_c = x2d if x2d.is_contiguous() else x2d.contiguous() |
| b_scale_c = b_scale if b_scale.is_contiguous() else b_scale.contiguous() |
|
|
| block_m, block_n, block_k, num_warps, num_stages = _select_triton_int8_config(m, k, n, device=x2d.device, kernel_kind="fused") |
| grid = (triton.cdiv(m, block_m), triton.cdiv(n, block_n)) |
| _fused_dynamic_int8_blockscale_gemm_kernel[grid]( |
| x_mm_c, |
| qweight, |
| b_scale_c, |
| out, |
| m, |
| n, |
| k, |
| x_mm_c.stride(0), |
| x_mm_c.stride(1), |
| stride_bn, |
| stride_bk, |
| out.stride(0), |
| out.stride(1), |
| block_m=block_m, |
| block_n=block_n, |
| block_k=block_k, |
| num_warps=num_warps, |
| num_stages=num_stages, |
| ) |
| return out |
|
|
|
|
| def fused_quant_scaled_mm( |
| x2d: torch.Tensor, |
| qweight: torch.Tensor, |
| qweight_scale: torch.Tensor, |
| out_dtype: Optional[torch.dtype] = None, |
| ) -> torch.Tensor: |
| if not is_available(): |
| raise RuntimeError("Triton backend not available") |
| if x2d.ndim != 2: |
| raise RuntimeError("x2d must be 2D") |
| if qweight.ndim != 2: |
| raise RuntimeError("qweight must be 2D [N, K]") |
| if x2d.dtype not in (torch.bfloat16, torch.float16, torch.float32): |
| raise RuntimeError("x2d must be bf16/fp16/fp32") |
| if qweight.dtype != torch.int8: |
| raise RuntimeError("qweight must be int8") |
| if not x2d.is_cuda or not qweight.is_cuda: |
| raise RuntimeError("fused_quant_scaled_mm requires CUDA tensors") |
|
|
| m, k = x2d.shape |
| n, k2 = qweight.shape |
| if k != k2: |
| raise RuntimeError(f"Triton int8 GEMM shape mismatch: x={x2d.shape}, w={qweight.shape}") |
|
|
| b_scale = _expand_or_validate_scale(qweight_scale, n) |
| if b_scale.device != x2d.device or b_scale.dtype != torch.float32: |
| b_scale = b_scale.to(device=x2d.device, dtype=torch.float32) |
| elif not b_scale.is_contiguous(): |
| b_scale = b_scale.contiguous() |
| if x2d.dtype not in (torch.bfloat16, torch.float16, torch.float32): |
| raise RuntimeError(f"Unsupported activation dtype for fused path: {x2d.dtype}") |
|
|
| out_dtype = out_dtype or x2d.dtype |
| qweight_c = qweight if qweight.is_contiguous() else qweight.contiguous() |
| return _fused_quant_scaled_mm_common( |
| x2d, |
| qweight_c, |
| b_scale, |
| k=k, |
| n=n, |
| stride_bn=qweight_c.stride(0), |
| stride_bk=qweight_c.stride(1), |
| out_dtype=out_dtype, |
| ) |
|
|
|
|
| def fused_quant_scaled_mm_transposed( |
| x2d: torch.Tensor, |
| qweight_t: torch.Tensor, |
| qweight_scale: torch.Tensor, |
| out_dtype: Optional[torch.dtype] = None, |
| ) -> torch.Tensor: |
| if not is_available(): |
| raise RuntimeError("Triton backend not available") |
| if x2d.ndim != 2: |
| raise RuntimeError("x2d must be 2D") |
| if qweight_t.ndim != 2: |
| raise RuntimeError("qweight_t must be 2D [K, N]") |
| if x2d.dtype not in (torch.bfloat16, torch.float16, torch.float32): |
| raise RuntimeError("x2d must be bf16/fp16/fp32") |
| if qweight_t.dtype != torch.int8: |
| raise RuntimeError("qweight_t must be int8") |
| if not x2d.is_cuda or not qweight_t.is_cuda: |
| raise RuntimeError("fused_quant_scaled_mm_transposed requires CUDA tensors") |
|
|
| m, k = x2d.shape |
| k2, n = qweight_t.shape |
| if k != k2: |
| raise RuntimeError(f"Triton int8 GEMM shape mismatch: x={x2d.shape}, w_t={qweight_t.shape}") |
|
|
| b_scale = _expand_or_validate_scale(qweight_scale, n) |
| if b_scale.device != x2d.device or b_scale.dtype != torch.float32: |
| b_scale = b_scale.to(device=x2d.device, dtype=torch.float32) |
| elif not b_scale.is_contiguous(): |
| b_scale = b_scale.contiguous() |
| if x2d.dtype not in (torch.bfloat16, torch.float16, torch.float32): |
| raise RuntimeError(f"Unsupported activation dtype for fused path: {x2d.dtype}") |
|
|
| out_dtype = out_dtype or x2d.dtype |
| qweight_t_c = qweight_t if qweight_t.is_contiguous() else qweight_t.contiguous() |
| return _fused_quant_scaled_mm_common( |
| x2d, |
| qweight_t_c, |
| b_scale, |
| k=k, |
| n=n, |
| stride_bn=qweight_t_c.stride(1), |
| stride_bk=qweight_t_c.stride(0), |
| out_dtype=out_dtype, |
| ) |
|
|
|
|
| def scaled_int8_mm( |
| a_int8: torch.Tensor, |
| b_int8: torch.Tensor, |
| a_scale: torch.Tensor, |
| b_scale: torch.Tensor, |
| out_dtype: Optional[torch.dtype] = None, |
| ) -> torch.Tensor: |
| if not is_available(): |
| raise RuntimeError("Triton backend not available") |
| if a_int8.ndim != 2: |
| raise RuntimeError("a_int8 must be 2D") |
| if b_int8.ndim != 2: |
| raise RuntimeError("b_int8 must be 2D [N, K]") |
| if a_int8.dtype != torch.int8 or b_int8.dtype != torch.int8: |
| raise RuntimeError("scaled_int8_mm requires int8 activations and int8 weights") |
| if not a_int8.is_cuda or not b_int8.is_cuda: |
| raise RuntimeError("scaled_int8_mm requires CUDA tensors") |
|
|
| m, k = a_int8.shape |
| n, k2 = b_int8.shape |
| if k != k2: |
| raise RuntimeError(f"Triton int8 GEMM shape mismatch: a={a_int8.shape}, w={b_int8.shape}") |
|
|
| a_scale = _expand_or_validate_scale(a_scale, m) |
| b_scale = _expand_or_validate_scale(b_scale, n) |
| if a_scale.device != a_int8.device or a_scale.dtype != torch.float32: |
| a_scale = a_scale.to(device=a_int8.device, dtype=torch.float32) |
| elif not a_scale.is_contiguous(): |
| a_scale = a_scale.contiguous() |
| if b_scale.device != a_int8.device or b_scale.dtype != torch.float32: |
| b_scale = b_scale.to(device=a_int8.device, dtype=torch.float32) |
| elif not b_scale.is_contiguous(): |
| b_scale = b_scale.contiguous() |
|
|
| out_dtype = out_dtype or torch.bfloat16 |
| out = torch.empty((m, n), device=a_int8.device, dtype=out_dtype) |
| a_int8_c = a_int8 if a_int8.is_contiguous() else a_int8.contiguous() |
| b_int8_c = b_int8 if b_int8.is_contiguous() else b_int8.contiguous() |
| a_scale_c = a_scale if a_scale.is_contiguous() else a_scale.contiguous() |
| b_scale_c = b_scale if b_scale.is_contiguous() else b_scale.contiguous() |
|
|
| block_m, block_n, block_k, num_warps, num_stages = _select_triton_int8_config(m, k, n, device=a_int8.device, kernel_kind="scaled") |
| grid = (triton.cdiv(m, block_m), triton.cdiv(n, block_n)) |
| _scaled_int8_gemm_kernel[grid]( |
| a_int8_c, |
| b_int8_c, |
| a_scale_c, |
| b_scale_c, |
| out, |
| m, |
| n, |
| k, |
| a_int8_c.stride(0), |
| a_int8_c.stride(1), |
| b_int8_c.stride(0), |
| b_int8_c.stride(1), |
| out.stride(0), |
| out.stride(1), |
| block_m=block_m, |
| block_n=block_n, |
| block_k=block_k, |
| num_warps=num_warps, |
| num_stages=num_stages, |
| ) |
| return out |
|
|