239 lines
6.7 KiB
Python
239 lines
6.7 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from fouroversix import ModelQuantizationConfig, ScaleRule
|
|
|
|
from ...resources import FOUROVERSIX_CACHE_PATH, app, cache_volume, hf_secret
|
|
from ..experiment import Experiment
|
|
from ..utils import PTQMethod
|
|
from .rtn import RTNEvaluatorImpl, rtn_img
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
with rtn_img.imports():
|
|
import torch
|
|
import torch.nn as nn
|
|
from fouroversix import (
|
|
FourOverSixLinear,
|
|
QuantizedModule,
|
|
fp4_matmul,
|
|
quantize_model,
|
|
)
|
|
from transformers import AutoModelForCausalLM
|
|
|
|
|
|
ALPHA_CANDIDATES = [x / 10 for x in range(11)]
|
|
WIKITEXT_TRAIN = "wikitext_train"
|
|
|
|
|
|
class FourOverSixLinearWithSmoothing(FourOverSixLinear):
|
|
"""
|
|
Drop-in replacement for `FourOverSixLinear` that implements SmoothQuant-style
|
|
scaling.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
*args: list[Any],
|
|
smoothquant_alpha: float,
|
|
**kwargs: dict[str, Any],
|
|
) -> None:
|
|
super().__init__(*args, **kwargs)
|
|
self.smoothquant_alpha = smoothquant_alpha
|
|
|
|
def apply_ptq(self) -> None:
|
|
"""
|
|
Override the parent method to do nothing, since we need the high-precision
|
|
weight when doing PTQ with SmoothQuant.
|
|
"""
|
|
|
|
def forward(self, input: torch.Tensor) -> torch.Tensor:
|
|
"""Forward pass with SmoothQuant-style scaling."""
|
|
|
|
out = torch.empty(
|
|
*input.shape[:-1],
|
|
self.weight.shape[0],
|
|
device=input.device,
|
|
dtype=self.config.output_dtype.torch_dtype(),
|
|
)
|
|
|
|
fprop_activation_config = self.config.get_activation_config()
|
|
fprop_weight_config = self.config.get_weight_config(
|
|
block_scale_2d=self.config.weight_scale_2d,
|
|
)
|
|
|
|
for i in range(input.shape[0]):
|
|
s = (input[i].abs().max(dim=0).values ** self.smoothquant_alpha) / (
|
|
self.weight.abs().max(dim=0).values ** (1 - self.smoothquant_alpha)
|
|
)
|
|
|
|
out[i] = fp4_matmul(
|
|
input[i] / s[None, :],
|
|
self.weight * s[None, :],
|
|
out_dtype=self.config.output_dtype,
|
|
input_config=fprop_activation_config,
|
|
other_config=fprop_weight_config,
|
|
)
|
|
|
|
if self.bias is not None:
|
|
out = out + self.bias
|
|
|
|
return out
|
|
|
|
|
|
@app.cls(
|
|
image=rtn_img,
|
|
gpu="B200",
|
|
secrets=[hf_secret],
|
|
timeout=24 * 60 * 60,
|
|
volumes={FOUROVERSIX_CACHE_PATH.as_posix(): cache_volume},
|
|
)
|
|
class SmoothQuantEvaluator(RTNEvaluatorImpl):
|
|
"""Evaluate a model using SmoothQuant."""
|
|
|
|
@classmethod
|
|
def get_calibration_tasks(
|
|
cls,
|
|
model_name: str,
|
|
session: Session,
|
|
**kwargs: dict[str, Any],
|
|
) -> list[dict[str, Any]]:
|
|
"""
|
|
Get the kwargs for tasks that should be used to calibrate the given model for
|
|
this PTQ method before running evaluation.
|
|
"""
|
|
|
|
smoothquant_alpha = get_smoothquant_alpha(
|
|
model_name,
|
|
kwargs.get("activation_scale_rule"),
|
|
kwargs.get("weight_scale_rule"),
|
|
session,
|
|
)
|
|
|
|
calibration_experiments = get_calibration_experiments(
|
|
model_name,
|
|
kwargs.get("activation_scale_rule"),
|
|
kwargs.get("weight_scale_rule"),
|
|
session,
|
|
)
|
|
|
|
if smoothquant_alpha is None:
|
|
return [
|
|
{
|
|
"smoothquant_alpha": candidate_alpha,
|
|
"tasks": [WIKITEXT_TRAIN],
|
|
}
|
|
for candidate_alpha in ALPHA_CANDIDATES
|
|
if not any(
|
|
experiment.smoothquant_alpha == candidate_alpha
|
|
for experiment in calibration_experiments
|
|
)
|
|
]
|
|
|
|
return []
|
|
|
|
@classmethod
|
|
def get_calibrated_kwargs(
|
|
cls,
|
|
model_name: str,
|
|
session: Session,
|
|
**kwargs: dict[str, Any],
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Get the calibrated kwargs for the given model and scale rules. If this model
|
|
has not yet been calibrated with these scale rules, an error will be raised.
|
|
"""
|
|
|
|
smoothquant_alpha = get_smoothquant_alpha(
|
|
model_name,
|
|
kwargs.get("activation_scale_rule"),
|
|
kwargs.get("weight_scale_rule"),
|
|
session,
|
|
)
|
|
|
|
if smoothquant_alpha is None:
|
|
msg = (
|
|
"SmoothQuant has not been calibrated for this combination of model and "
|
|
"scale rules"
|
|
)
|
|
raise ValueError(msg)
|
|
|
|
return {"smoothquant_alpha": smoothquant_alpha}
|
|
|
|
def quantize_model(
|
|
self,
|
|
model_name: str,
|
|
*,
|
|
device: str,
|
|
save_path: Path, # noqa: ARG002
|
|
smoothquant_alpha: float,
|
|
quantization_config: ModelQuantizationConfig,
|
|
trust_remote_code: bool,
|
|
) -> AutoModelForCausalLM:
|
|
"""Quantize a model using SmoothQuant."""
|
|
|
|
# Replace FourOverSixLinear with FourOverSixLinearWithSmoothing
|
|
QuantizedModule.register(
|
|
nn.Linear,
|
|
replace_existing_modules_in_registry=True,
|
|
)(FourOverSixLinearWithSmoothing)
|
|
|
|
model = AutoModelForCausalLM.from_pretrained(
|
|
model_name,
|
|
device_map=device,
|
|
trust_remote_code=trust_remote_code,
|
|
)
|
|
|
|
quantize_model(model, quantization_config, smoothquant_alpha=smoothquant_alpha)
|
|
return model
|
|
|
|
|
|
def get_calibration_experiments(
|
|
model_name: str,
|
|
activation_scale_rule: ScaleRule,
|
|
weight_scale_rule: ScaleRule,
|
|
db_session: Session,
|
|
) -> list[Experiment]:
|
|
return (
|
|
db_session.query(Experiment)
|
|
.filter(
|
|
Experiment.ptq_method == PTQMethod.smoothquant.value,
|
|
Experiment.task == WIKITEXT_TRAIN,
|
|
Experiment.model_name == model_name,
|
|
Experiment.activation_scale_rule == activation_scale_rule.value,
|
|
Experiment.weight_scale_rule == weight_scale_rule.value,
|
|
Experiment.smoothquant_alpha.isnot(None),
|
|
)
|
|
.all()
|
|
)
|
|
|
|
|
|
def get_smoothquant_alpha(
|
|
model_name: str,
|
|
activation_scale_rule: ScaleRule,
|
|
weight_scale_rule: ScaleRule,
|
|
session: Session,
|
|
) -> float | None:
|
|
calibration_experiments = get_calibration_experiments(
|
|
model_name,
|
|
activation_scale_rule,
|
|
weight_scale_rule,
|
|
session,
|
|
)
|
|
|
|
if not all(
|
|
any(
|
|
experiment.smoothquant_alpha == alpha
|
|
for experiment in calibration_experiments
|
|
)
|
|
for alpha in ALPHA_CANDIDATES
|
|
):
|
|
return None
|
|
|
|
return min(calibration_experiments, key=lambda x: x.metric_value).smoothquant_alpha
|