Files
wehub-resource-sync 770d92cb1f
Lint / lint (push) Waiting to run
Windows CI / Windows (push) Waiting to run
Build Docs / Deploy Docs (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:23:58 +08:00

266 lines
11 KiB
Python

"""Python entrypoint of compilation."""
import dataclasses
from io import StringIO
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Tuple # noqa: UP035
from tvm import IRModule, relax, tirx
from tvm.ir.transform import Pass, PassContext
from tvm.relax.frontend import nn
from tvm.target import Target
from mlc_llm import compiler_pass as _ # noqa: F401
from mlc_llm import op as op_ext
from mlc_llm.cli.model_metadata import _report_memory_usage
from mlc_llm.model import Model
from mlc_llm.quantization import Quantization
from mlc_llm.support import logging
from mlc_llm.support.config import ConfigBase
from mlc_llm.support.style import bold
from .compiler_flags import ModelConfigOverride, OptimizationFlags
logger = logging.getLogger(__name__)
@dataclasses.dataclass
class CompileArgs:
"""Arguments to MLC LLM's compiler."""
config: Path
quantization: Quantization
model: Model
target: Target
opt: OptimizationFlags
build_func: Callable[[IRModule, "CompileArgs", Pass], None]
system_lib_prefix: str
output: Path
overrides: ModelConfigOverride
debug_dump: Optional[Path]
def __post_init__(self) -> None:
self.opt.update(self.target, self.quantization)
def display(self) -> None:
"""Display the arguments to stdout."""
out = StringIO()
print(f"{bold('Compiling with arguments:')}", file=out)
print(f" {bold('--config'):<25} {self.config}", file=out)
print(f" {bold('--quantization'):<25} {self.quantization}", file=out)
print(f" {bold('--model-type'):<25} {self.model.name}", file=out)
print(f" {bold('--target'):<25} {self.target.export()}", file=out)
print(f" {bold('--opt'):<25} {self.opt}", file=out)
print(f' {bold("--system-lib-prefix"):<25} "{self.system_lib_prefix}"', file=out)
print(f" {bold('--output'):<25} {self.output}", file=out)
print(f" {bold('--overrides'):<25} {self.overrides}", file=out)
# As it's debug only, no need to display
# print(f" {bold('--debug-dump'):<25} {self.debug_dump}", file=out)
print(out.getvalue().rstrip())
def _apply_preproc_to_params_and_check_pipeline(
named_params: List[Tuple[str, nn.Parameter]], # noqa: UP006
model_config,
) -> Dict[str, tirx.PrimFunc]: # noqa: UP006
extra_tirs: Dict[str, tirx.PrimFunc] = {} # noqa: UP006
for name, param in named_params:
preprocs = param.attrs.get("preprocs", [])
shard_strategy = param.attrs.get("shard_strategy", None)
if shard_strategy is not None and model_config.tensor_parallel_shards > 1:
preprocs.append(
shard_strategy.gen_shard_info(
shards=model_config.tensor_parallel_shards,
weight=param,
)
)
if shard_strategy.name not in extra_tirs:
extra_tirs[shard_strategy.name] = shard_strategy.gen_tir(
shards=model_config.tensor_parallel_shards,
weight=param,
)
param.attrs["preprocs"] = preprocs
pipeline_parallel_stages = getattr(model_config, "pipeline_parallel_stages", 1)
if pipeline_parallel_stages != 1:
assert "pipeline_stages" in param.attrs, (
f'The pipeline stage is undefined for parameter "{name}" when the number '
f"of pipeline parallel stages is {pipeline_parallel_stages}"
)
param.attrs["pipeline_stages"] = (
[0]
if "pipeline_stages" not in param.attrs
else list(set(param.attrs["pipeline_stages"]))
)
return extra_tirs
def _infer_kv_state_kind(model_type) -> str:
if "rwkv" in model_type:
return "rnn_state"
if "medusa" in model_type:
return "none"
if "qwen3_5" in model_type:
return "hybrid"
return "kv_cache"
def _compile(args: CompileArgs, model_config: ConfigBase):
def _get_variable_bounds(model_config) -> Dict[str, int]: # noqa: UP006
if hasattr(model_config, "sliding_window_size"):
return {
"rolling_cache_len": model_config.sliding_window_size,
"kv_seq_len": model_config.sliding_window_size + model_config.prefill_chunk_size,
"seq_len": model_config.prefill_chunk_size,
"batch_size": getattr(model_config, "max_batch_size", 1),
}
return {
"total_seq_len": model_config.context_window_size,
"seq_len": model_config.prefill_chunk_size,
"batch_size": getattr(model_config, "max_batch_size", 1),
}
def _get_param_metadata(name: str, param: nn.Parameter) -> Dict[str, Any]: # noqa: UP006
return {
"name": name,
# Record dynamic shape as -1 (e.g. vocab_size)
"shape": [s if isinstance(s, int) else s.name for s in param.shape],
"dtype": str(param.dtype),
"preprocs": param.attrs["preprocs"],
"pipeline_stages": param.attrs.get("pipeline_stages", [0]),
}
logger.info("TOP LEVEL MODEL CONFIG BEFORE OVERRIDES: %s", str(model_config))
_kwargs = getattr(model_config, "kwargs", {})
model_config = args.overrides.apply(model_config)
with args.target:
op_ext.enable(
target=args.target,
flashinfer=args.opt.flashinfer,
faster_transformer=args.opt.faster_transformer,
cutlass=args.opt.cutlass,
)
# Step 1. Create the quantized model
logger.info("Creating model from: %s", model_config)
if (
args.quantization.kind == "ft-quant"
and hasattr(model_config, "tensor_parallel_shards")
and model_config.tensor_parallel_shards > 1
):
raise NotImplementedError
if (
hasattr(args.quantization, "linear_weight_layout")
and args.quantization.linear_weight_layout == "KN"
and hasattr(model_config, "tensor_parallel_shards")
and model_config.tensor_parallel_shards > 1
):
raise NotImplementedError(
"KN layout (q3f16_0 and q4f16_0) is not supported for tensor parallelism"
)
model, _ = args.model.quantize[args.quantization.kind](model_config, args.quantization)
# Step 2. Exporting the model to TVM
logger.info("Exporting the model to TVM compiler")
mod, named_params, ext_mods = model.export_tvm(
spec=model.get_default_spec(),
allow_extern=True,
)
# Step 3. Running relax compilation pipeline
logger.info("Running optimizations using TVM")
additional_tirs = _apply_preproc_to_params_and_check_pipeline(named_params, model_config)
variable_bounds = _get_variable_bounds(model_config)
cuda_graph_symbolic_capture_hints = {
"batch_decode": ["batch_size"],
"batch_decode_to_last_hidden_states": ["batch_size"],
"batch_verify": ["batch_size", "seq_len"],
"batch_verify_to_last_hidden_states": ["batch_size", "seq_len"],
}
avs = _kwargs.get("active_vocab_size", None)
if avs is not None and avs <= 0:
avs = None
metadata = {
"model_type": args.model.name,
"quantization": args.quantization.name,
"context_window_size": getattr(model_config, "context_window_size", -1),
"sliding_window_size": getattr(model_config, "sliding_window_size", -1),
"attention_sink_size": getattr(model_config, "attention_sink_size", -1),
"prefill_chunk_size": model_config.prefill_chunk_size,
"tensor_parallel_shards": model_config.tensor_parallel_shards,
"pipeline_parallel_stages": getattr(model_config, "pipeline_parallel_stages", 1),
"disaggregation": getattr(model_config, "disaggregation", False),
"kv_state_kind": _infer_kv_state_kind(args.model.name),
"max_batch_size": getattr(model_config, "max_batch_size", 1),
"active_vocab_size": avs,
"model_task": args.model.model_task,
}
if args.model.embedding_metadata:
metadata["embedding_metadata"] = dataclasses.asdict(args.model.embedding_metadata)
logger.info("Registering metadata: %s", metadata)
metadata["params"] = [_get_param_metadata(name, param) for name, param in named_params]
pass_config = {"relax.backend.use_cuda_graph": args.opt.cudagraph}
# TODO: Remove this workaround when the TVM CSE regression is fixed.
# Temporary workaround for TVM CSE regression that can produce
# dangling `cse_v*` vars during host codegen.
pass_config["tirx.disable_cse_tir"] = True
with PassContext(config=pass_config):
args.build_func(
mod,
args,
pipeline=relax.get_pipeline(
"mlc_llm",
target=args.target,
flashinfer=args.opt.flashinfer,
cublas_gemm=args.opt.cublas_gemm,
faster_transformer=args.opt.faster_transformer,
allreduce_strategy=args.opt.ipc_allreduce_strategy,
variable_bounds=variable_bounds,
cuda_graph_symbolic_capture_hints=cuda_graph_symbolic_capture_hints,
additional_tirs=additional_tirs,
ext_mods=ext_mods,
metadata=metadata,
debug_dump=args.debug_dump,
),
)
_report_memory_usage(metadata=metadata, config=model_config)
logger.info("Generated: %s", bold(str(args.output)))
def compile(
config: Dict[str, Any], # noqa: UP006
quantization: Quantization,
model_type: Model,
target: Target,
opt: OptimizationFlags,
build_func: Callable[[IRModule, CompileArgs, Pass], None],
system_lib_prefix: str,
output: Path,
overrides: ModelConfigOverride,
debug_dump: Optional[Path] = None,
):
"""Compile a model given its configuration and quantization format to a specific target."""
avs = None
if "active_vocab_size" in config:
avs = config.pop("active_vocab_size")
logger.info("Active vocab size from input config: %s", str(avs))
if "model_config" in config:
model_config = config.pop("model_config")
model_config.update(config)
model_config = model_type.config.from_dict(model_config)
else:
model_config = model_type.config.from_dict(config)
model_config.kwargs = {"active_vocab_size": avs} if avs is not None else {}
args = CompileArgs(
model_config,
quantization,
model_type,
target,
opt,
build_func,
system_lib_prefix,
output,
overrides,
debug_dump,
)
args.display()
_compile(args, model_config)