Files
wehub-resource-sync 59a0a3844c
PR Test AMD / cancel-on-close (push) Has been skipped
PR Test NVIDIA ARM / scan (push) Has been skipped
PR Test NVIDIA / cancel-on-close (push) Has been skipped
PR Test AMD / scan (push) Has been skipped
PR Test NVIDIA ARM / cancel-on-close (push) Has been skipped
PR Test NVIDIA / scan (push) Has been skipped
Release Docker Images / build (cu129-torch-2.11.0) (push) Has been skipped
Release Docker Images / build (cu130-torch-2.11.0) (push) Has been skipped
Release PyPI / publish (push) Has been skipped
Scheduler Python Test / test (push) Successful in 27m19s
Docs / build (push) Successful in 28m8s
Scheduler C++ Test / test (push) Successful in 28m19s
Scheduler C++ Test / test-flat (push) Successful in 28m18s
Docs / deploy (push) Has been cancelled
PR Test AMD / finish (push) Has been cancelled
PR Test NVIDIA / finish (push) Has been cancelled
PR Test NVIDIA ARM / finish (push) Has been cancelled
PR Test NVIDIA ARM / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test AMD / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test NVIDIA / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:32:31 +08:00

206 lines
7.5 KiB
Python

# Copyright (c) 2026 LightSeek Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import annotations
import re
from collections.abc import Mapping
from typing import Any
import torch
from tokenspeed_kernel.platform import current_platform
from tokenspeed.runtime.layers.quantization.base_config import QuantizationConfig
def _is_fp4_e8m0_per_group(stage: object, *, is_dynamic: bool | None = None) -> bool:
if not isinstance(stage, Mapping):
return False
if is_dynamic is not None and stage.get("is_dynamic") is not is_dynamic:
return False
return (
str(stage.get("dtype", "")).lower() in {"fp4", "mxfp4"}
and str(stage.get("qscheme", "")).lower() == "per_group"
and stage.get("group_size") in {32, "32"}
and str(stage.get("scale_format", "")).lower() == "e8m0"
)
def _is_amd_quark_w_mxfp4_a_fp8(config: Mapping[str, Any]) -> bool:
if not isinstance(config, Mapping):
return False
if not current_platform().is_amd:
return False
if str(config.get("quant_method", "")).lower() != "quark":
return False
global_quant_config = config.get("global_quant_config") or {}
export = config.get("export") or {}
if not isinstance(global_quant_config, Mapping) or not isinstance(export, Mapping):
return False
input_tensors = global_quant_config.get("input_tensors") or {}
weight = global_quant_config.get("weight") or {}
return (
isinstance(input_tensors, Mapping)
and "fp8" in str(input_tensors.get("dtype", "")).lower()
and _is_fp4_e8m0_per_group(weight, is_dynamic=False)
and str(export.get("pack_method", "")).lower() == "reorder"
and str(export.get("weight_format", "")).lower() == "real_quantized"
)
def _is_amd_quark_dynamic_mxfp4(config: Mapping[str, Any]) -> bool:
if not isinstance(config, Mapping):
return False
if not current_platform().is_amd:
return False
if str(config.get("quant_method", "")).lower() != "quark":
return False
global_quant_config = config.get("global_quant_config") or {}
export = config.get("export") or {}
if not isinstance(global_quant_config, Mapping) or not isinstance(export, Mapping):
return False
input_tensors = global_quant_config.get("input_tensors") or {}
weight = global_quant_config.get("weight") or {}
return (
_is_fp4_e8m0_per_group(input_tensors, is_dynamic=True)
and _is_fp4_e8m0_per_group(weight, is_dynamic=False)
and str(export.get("pack_method", "")).lower() == "reorder"
and str(export.get("weight_format", "")).lower() == "real_quantized"
)
def _is_amd_quark_mxfp4_checkpoint(config: dict) -> bool:
if not isinstance(config, Mapping):
return False
return _is_amd_quark_w_mxfp4_a_fp8(config) or _is_amd_quark_dynamic_mxfp4(config)
def _iter_ignored_layer_pattern_aliases(raw: str):
yield raw
if raw.startswith("language_model."):
yield raw.removeprefix("language_model.")
return
if raw.startswith("re:"):
regex = raw[3:]
for prefix in ("language_model.", re.escape("language_model.")):
if regex.startswith(prefix):
yield f"re:{regex.removeprefix(prefix)}"
return
def _to_ignore_pattern(raw: str) -> str:
if raw.startswith("re:") or "*" not in raw:
return raw
regex = re.escape(raw).replace(r"\*", ".*")
return f"re:{regex}"
def _normalize_ignored_layer_patterns(patterns: list[str] | None) -> list[str]:
"""Normalize ignored-layer patterns into the form understood by
``should_ignore_quant_layer``.
Some exporters (notably AMD-Quark) accept shell-style globs such as
``"*lm_head"`` or ``"*self_attn*"``. ``should_ignore_quant_layer``
expects either an exact name or a regex prefixed with ``re:``. Convert
glob-like entries to regex while passing through plain literals.
"""
if not patterns:
return []
normalized: list[str] = []
seen: set[str] = set()
for raw in patterns:
if not isinstance(raw, str) or not raw:
continue
for alias in _iter_ignored_layer_pattern_aliases(raw):
pattern = _to_ignore_pattern(alias)
if pattern in seen:
continue
seen.add(pattern)
normalized.append(pattern)
return normalized
class Mxfp4Config(QuantizationConfig):
def __init__(
self,
ignored_layers: list[str] | None = None,
is_checkpoint_mxfp4_serialized: bool = False,
is_w4a8_fp8: bool = False,
use_dynamic_mxfp4_activations: bool = False,
):
super().__init__(ignored_layers=ignored_layers)
self.is_checkpoint_mxfp4_serialized = is_checkpoint_mxfp4_serialized
self.is_w4a8_fp8 = is_w4a8_fp8
self.use_dynamic_mxfp4_activations = use_dynamic_mxfp4_activations
self.group_size = 32
@classmethod
def from_config(cls, config):
quant_method = str(config.get("quant_method", "")).lower()
is_w4a8_fp8 = _is_amd_quark_w_mxfp4_a_fp8(config)
use_dynamic_mxfp4_activations = _is_amd_quark_dynamic_mxfp4(config)
is_checkpoint_mxfp4_serialized = (
"mxfp4" in quant_method or is_w4a8_fp8 or use_dynamic_mxfp4_activations
)
raw_ignored = cls.get_from_keys_or(config, ["ignored_layers", "exclude"], None)
ignored_layers = _normalize_ignored_layer_patterns(raw_ignored)
return cls(
ignored_layers=ignored_layers,
is_checkpoint_mxfp4_serialized=is_checkpoint_mxfp4_serialized,
is_w4a8_fp8=is_w4a8_fp8,
use_dynamic_mxfp4_activations=use_dynamic_mxfp4_activations,
)
@classmethod
def override_quantization_method(cls, hf_quant_cfg, user_quant) -> str | None:
"""Promote AMD Quark MXFP4 checkpoint metadata to mxfp4."""
if user_quant in {"mxfp4", None} and _is_amd_quark_mxfp4_checkpoint(
hf_quant_cfg
):
return "mxfp4"
return None
@classmethod
def get_min_capability(cls) -> int:
return 90
@classmethod
def get_name(cls) -> str:
return "mxfp4"
@classmethod
def get_supported_act_dtypes(cls) -> list[torch.dtype]:
return [torch.bfloat16, torch.float16]
@classmethod
def get_config_filenames(cls) -> list[str]:
return []
def is_static_cfg(self):
return self.is_checkpoint_mxfp4_serialized
def get_scaled_act_names(self) -> list[str]:
return []