-
Notifications
You must be signed in to change notification settings - Fork 509
Speed up compressed-tensors load-time matching (for Kimi models) #1999
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,6 +53,103 @@ | |
| SPECULATIVE_MODEL_LIST = ["Eagle", "Medusa"] | ||
|
|
||
|
|
||
| def patch_match_named_modules() -> None: | ||
| """Make compressed_tensors ``match_named_modules`` O(modules) for exact-name targets. | ||
| ``compress_model``/``decompress_model`` pass ~69k exact module names as ``targets``; the stock | ||
| nested ``for target in targets`` loop is O(modules x targets) (~100 min for Kimi) -- this is the | ||
| "Compressing model..." phase observed at load time. Bucket the exact-name (non-"re:") targets | ||
| into a set for O(1) membership; regex and class-name targets are still handled exactly as before. | ||
| The yielded module set is identical (verified on the full model: 69,300 matches, 0.10 s vs | ||
| ~100 min). | ||
| Idempotent: re-applying is a no-op (it detects the already-patched function and returns). Shared | ||
| by hf_ptq.py and multinode_ptq.py so the speedup is applied wherever a compressed-tensors | ||
| checkpoint is loaded. | ||
| """ | ||
| try: | ||
| import compressed_tensors.utils.match as _m | ||
| except Exception: | ||
| return | ||
|
|
||
| _orig = _m.match_named_modules | ||
| # Idempotent guard: if we already patched, the bound name is our wrapper, not the original. | ||
| if getattr(_orig, "_modelopt_fast_match", False): | ||
| return | ||
|
|
||
| def fast_match_named_modules(model, targets, ignore=None, fused=None, warn_on_fail=False): | ||
| targets = list(targets or []) | ||
| ignore = ignore or [] | ||
| # Fall back to the original for the rare/complex cases this fast path doesn't cover. | ||
| if fused is not None or any(not isinstance(t, str) for t in targets): | ||
| yield from _orig(model, targets, ignore, fused=fused, warn_on_fail=warn_on_fail) | ||
| return | ||
| name_set = {t for t in targets if not t.startswith("re:")} | ||
| re_targets = [t for t in targets if t.startswith("re:")] | ||
| # Determine which non-"re:" targets are class names actually present (usually 0-1). | ||
| class_names, has_linearbase = set(), False | ||
| for _, mod in model.named_modules(): | ||
| for c in mod.__class__.__mro__: | ||
| class_names.add(c.__name__) | ||
| has_linearbase = has_linearbase or c.__name__ == "LinearBase" | ||
| class_targets = [ | ||
| t for t in name_set if t in class_names or (t == "Linear" and has_linearbase) | ||
| ] | ||
| unmatched = set(targets) | ||
| internal_module_cls = getattr(_m, "InternalModule", None) | ||
| for name, module in model.named_modules(): | ||
| # Original is_match() excludes InternalModule from target matches; mirror exactly. | ||
| if internal_module_cls is not None and isinstance(module, internal_module_cls): | ||
| continue | ||
| mt = None | ||
| if name in name_set: | ||
| mt = name | ||
| else: | ||
| for t in class_targets: | ||
| if _m._match_class(module, t): | ||
| mt = t | ||
| break | ||
| if mt is None: | ||
| for t in re_targets: | ||
| if _m._match_name(name, t, None): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the relevant file and locate matcher-related symbols/usages.
git ls-files examples/hf_ptq/example_utils.py
ast-grep outline examples/hf_ptq/example_utils.py --view expanded || true
rg -n "_match_name|match_name|compressed_tensors|patch_match_named_modules" examples/hf_ptq/example_utils.py -n -C 3 || true
# Search the repository for any references to the matcher helper API.
rg -n "match_name\(|_match_name\(" -S . || trueRepository: NVIDIA/Model-Optimizer Length of output: 5174 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the relevant file section directly if needed.
sed -n '1,220p' examples/hf_ptq/example_utils.py | cat -nRepository: NVIDIA/Model-Optimizer Length of output: 10829 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect upstream API documentation/source via web if local repo lacks the dependency source.
python3 - <<'PY'
print("No-op placeholder: awaiting shell output from repository inspection.")
PYRepository: NVIDIA/Model-Optimizer Length of output: 229 🌐 Web query:
💡 Result: In the current API of the compressed-tensors library, the module matching logic has been refactored significantly to improve performance and maintainability [1][2]. The older functions and internal naming conventions, such as _match_name, have been largely replaced or consolidated [3][2]. Key updates to the matching API include: - Consolidation into match_named_modules: Much of the logic previously scattered across various helper functions has been centralized into Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
# Look for dependency pins and local imports that can reveal the expected API.
rg -n "compressed-tensors|compressed_tensors" -S pyproject.toml requirements*.txt .github examples || trueRepository: NVIDIA/Model-Optimizer Length of output: 1348 🌐 Web query:
💡 Result: In the vllm-project/compressed-tensors library, match_name is a utility function used for matching module or tensor names against specified targets [1]. It is part of the library's utility infrastructure, specifically residing within the compressed_tensors.utils.match module [2][3]. The library utilizes several matching functions to handle module and parameter targeting, including match_name, match_named_modules, match_targets, and is_narrow_match [3][4][1]. These utilities are essential for operations like applying quantization configurations, dequantization, and managing model compression [3][1]. Recent developments in the library have focused on optimizing these matching processes. For instance, the match_named_modules function was recently refactored to replace inefficient quadratic O(N x M) logic with optimized lookup structures, significantly improving performance for large models such as MoE (Mixture of Experts) architectures [2][5]. The match_name function is often used within these broader workflows, such as in converter logic, to verify specific target matches when identifying quantizable tensors or dependencies [1]. Citations:
🌐 Web query:
💡 Result: In the compressed-tensors library, the primary API for module matching is Citations:
Use the public matcher helper for regex targets.
🤖 Prompt for AI Agents |
||
| mt = t | ||
| break | ||
| if mt is not None: | ||
| unmatched.discard(mt) | ||
| if not _m.is_match(name, module, ignore, fused=None): | ||
| yield name, module | ||
| if warn_on_fail: | ||
| import logging as _logging | ||
|
|
||
| for t in unmatched: | ||
| _logging.getLogger("compressed_tensors").warning( | ||
| f"Could not match `{t}` in instance of {model.__class__.__name__}" | ||
| ) | ||
|
|
||
| fast_match_named_modules._modelopt_fast_match = True | ||
| _m.match_named_modules = fast_match_named_modules | ||
|
|
||
| # `compress_model` (and friends) did `from ...match import match_named_modules`, binding the name | ||
| # into THEIR module namespace at import time, so patching the `match` module attribute alone does | ||
| # not reach them -- confirmed via py-spy: compress_model still ran the original O(modules x | ||
| # targets) matcher despite the patch. Rebind every already-imported module that still holds the | ||
| # original ref. (Modules imported *after* this point pick up the fast version automatically.) | ||
| import contextlib | ||
| import sys | ||
|
|
||
| with contextlib.suppress(Exception): | ||
| # ensure the key importer is loaded so the sweep below can rebind it | ||
| import compressed_tensors.compressors.model_compressors.model_compressor # noqa: F401 | ||
| rebound = [] | ||
| for _modname, _mod in list(sys.modules.items()): | ||
| if _mod is None: | ||
| continue | ||
| if getattr(_mod, "match_named_modules", None) is _orig: | ||
| _mod.match_named_modules = fast_match_named_modules | ||
| rebound.append(_modname) | ||
| print(f"[match-speedup] patched match_named_modules; rebound importers: {rebound}", flush=True) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Make this multinode log rank-aware.
As per coding guidelines, “Develop distributed code with rank-aware logging such as 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
|
|
||
|
|
||
| def run_nemotron_vl_preview( | ||
| full_model, | ||
| tokenizer, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win
Do not silently disable the optimization on arbitrary import failures.
Catching every exception makes this a no-op on broken/incompatible compressed-tensors imports, sending users back to the slow load path without an actionable error. Catch only an absent optional package; re-raise other failures.
🤖 Prompt for AI Agents