-
Notifications
You must be signed in to change notification settings - Fork 517
Single gpu disk offload PTQ for DSR1/Ultra #2008
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
eb08b94
9f2b679
ce143d9
841ea5f
0c62df6
9164813
ffb16e8
1cc0ec8
5fc72c5
8721fdc
96b2d90
fe09a30
7e1de04
d000b18
b2289de
dcc0d7d
3175d37
57ab0ab
d5615d7
8d51c8c
f5badf3
adc851d
d501d30
ae6a0f5
baf037d
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 |
|---|---|---|
|
|
@@ -959,6 +959,94 @@ def from_quantized_weight( | |
| raise NotImplementedError(f"quantization format {quantization} not supported") | ||
|
|
||
|
|
||
| _KV_CACHE_REPLACEMENTS: dict[str, str] = { | ||
| "k_bmm_quantizer._amax": "k_proj.k_scale", | ||
| "v_bmm_quantizer._amax": "v_proj.v_scale", | ||
| "k_bmm_quantizer._bias_value": "k_proj.k_bias", | ||
| "v_bmm_quantizer._bias_value": "v_proj.v_bias", | ||
| "input_quantizer._pre_quant_scale": "pre_quant_scale", | ||
| } | ||
| _QLORA_REPLACEMENTS: dict[str, str] = { | ||
| **_KV_CACHE_REPLACEMENTS, | ||
| "base_layer.weight": "weight", | ||
| "base_layer.input_scale": "input_scale", | ||
| "base_layer.weight_scale": "weight_scale", | ||
| } | ||
| _BASE_SKIP_KEYS: tuple[str, ...] = ( | ||
| "output_quantizer", | ||
| "_amax", | ||
| "_bias_value", | ||
| "input_quantizer._pre_quant_scale", | ||
| "weight_shape", | ||
| ) | ||
| _QLORA_SKIP_KEYS: tuple[str, ...] = (*_BASE_SKIP_KEYS, "base_layer") | ||
|
|
||
|
|
||
| def _maybe_squeeze_scale(key: str, value: Any) -> Any: | ||
| """Squeeze a leading dim=1 from 3-D scale tensors of shape (1, n, m).""" | ||
| if ( | ||
| "scale" in key | ||
| and isinstance(value, torch.Tensor) | ||
| and value.dim() == 3 | ||
| and value.shape[0] == 1 | ||
| ): | ||
| return value.squeeze(0) | ||
| return value | ||
|
|
||
|
|
||
| def _postprocess_single_tensor( | ||
| key: str, | ||
| value: torch.Tensor, | ||
| kv_cache_max_bound: float, | ||
| kv_cache_format: str | None, | ||
| is_modelopt_qlora: bool = False, | ||
| ) -> tuple[str | None, torch.Tensor | None]: | ||
| """Per-tensor subset of :func:`postprocess_state_dict`, for streaming export. | ||
|
|
||
| Returns ``(new_key, new_value)`` to emit, or ``(None, None)`` to skip. | ||
| Tied-weight dedup is NOT performed here; callers should pre-compute alias | ||
| keys from ``model._tied_weights_keys`` and filter them at the call site. | ||
| """ | ||
| replacements = _QLORA_REPLACEMENTS if is_modelopt_qlora else _KV_CACHE_REPLACEMENTS | ||
| skip_keys = _QLORA_SKIP_KEYS if is_modelopt_qlora else _BASE_SKIP_KEYS | ||
|
|
||
| # Skip problematic VL model parameters | ||
| if key == "vision_model.radio_model.summary_idxs": | ||
| return None, None | ||
|
|
||
| # Skip real quant parameters | ||
| if any(key.endswith("weight_quantizer." + q) for q in RealQuantLinear.list_of_scale_tensors): | ||
| return None, None | ||
|
|
||
| # Skip LoRA adapters for QLoRA models | ||
| if is_modelopt_qlora and "lora" in key: | ||
| return None, None | ||
|
|
||
| # Keys not related to quantizers: keep as-is | ||
| if all(sk not in key for sk in skip_keys): | ||
| return key, _maybe_squeeze_scale(key, value) | ||
|
|
||
| # Apply replacements if the key matches any suffix in the replacements dict | ||
| for old_suffix, new_suffix in replacements.items(): | ||
| if key.endswith(old_suffix): | ||
| prefix = key[: -len(old_suffix)] | ||
| if "_amax" in key: | ||
| assert kv_cache_format in [KV_CACHE_FP8, KV_CACHE_NVFP4, KV_CACHE_NVFP4_AFFINE], ( | ||
| "Invalid KV cache quantization format." | ||
| ) | ||
| assert kv_cache_max_bound > 0, "Maxbound must be greater than zero." | ||
| value = value.float() / kv_cache_max_bound | ||
| if kv_cache_format == KV_CACHE_FP8 and value.item() > 0.5: | ||
|
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. 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
ast-grep outline modelopt/torch/export/quant_utils.py --view expanded | sed -n '1,240p'
printf '\n--- slice around reported lines ---\n'
sed -n '1000,1060p' modelopt/torch/export/quant_utils.py
printf '\n--- nearby references to kv_cache_format and value ---\n'
rg -n "kv_cache_format|value\.item\(|value\b" modelopt/torch/export/quant_utils.pyRepository: NVIDIA/Model-Optimizer Length of output: 7807 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path("modelopt/torch/export/quant_utils.py")
lines = p.read_text().splitlines()
for start, end in [(1015, 1055), (920, 980)]:
print(f"\n--- {p}:{start}-{end} ---")
for i in range(start-1, min(end, len(lines))):
print(f"{i+1:5d}: {lines[i]}")
PYRepository: NVIDIA/Model-Optimizer Length of output: 4885 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- uses of _postprocess_single_tensor ---'
rg -n "_postprocess_single_tensor\(" -S modelopt/torch/export/quant_utils.py modelopt || true
printf '\n%s\n' '--- streaming/state-dict path around postprocess_state_dict ---'
sed -n '1050,1145p' modelopt/torch/export/quant_utils.py
printf '\n%s\n' '--- any explicit cpu()/to(\"cpu\") around this path ---'
rg -n "to\\(['\"]cpu['\"]\\)|\\.cpu\\(" modelopt/torch/export/quant_utils.pyRepository: NVIDIA/Model-Optimizer Length of output: 4390 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '1060,1115p' modelopt/torch/export/unified_export_hf.py
printf '\n%s\n' '--- surrounding helpers/entrypoints ---'
rg -n "stream|state_dict|_postprocess_single_tensor|postprocess_state_dict" modelopt/torch/export/unified_export_hf.pyRepository: NVIDIA/Model-Optimizer Length of output: 7192 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- enable_weight_access_and_writeback definition ---'
rg -n "def enable_weight_access_and_writeback|class enable_weight_access_and_writeback|enable_weight_access_and_writeback\(" modelopt -S
printf '\n%s\n' '--- streaming export around the offload context ---'
sed -n '1088,1188p' modelopt/torch/export/unified_export_hf.py
printf '\n%s\n' '--- surrounding offload helper docs ---'
sed -n '1,260p' modelopt/torch/export/unified_export_hf.pyRepository: NVIDIA/Model-Optimizer Length of output: 19523 Avoid the 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| logger.warning( | ||
| "Large KV activations detected. Quantized KV cache may lead to higher accuracy drop." | ||
| ) | ||
| new_key = prefix + new_suffix | ||
| return new_key, _maybe_squeeze_scale(new_key, value) | ||
|
|
||
| # Key has a skip_key but no replacement matched — drop it | ||
| return None, None | ||
|
|
||
|
|
||
| def postprocess_state_dict( | ||
| state_dict: dict, | ||
| maxbound: float, | ||
|
|
@@ -976,31 +1064,8 @@ def postprocess_state_dict( | |
| Returns: | ||
| The filtered state_dict without unnecessary keys like '_amax' and non KV cache output quantizers. | ||
| """ | ||
| replacements = { | ||
| "k_bmm_quantizer._amax": "k_proj.k_scale", | ||
| "v_bmm_quantizer._amax": "v_proj.v_scale", | ||
| "k_bmm_quantizer._bias_value": "k_proj.k_bias", | ||
| "v_bmm_quantizer._bias_value": "v_proj.v_bias", | ||
| "input_quantizer._pre_quant_scale": "pre_quant_scale", | ||
| } | ||
| skip_keys = [ | ||
| "output_quantizer", | ||
| "_amax", | ||
| "_bias_value", | ||
| "input_quantizer._pre_quant_scale", | ||
| "weight_shape", | ||
| ] | ||
|
|
||
| # For modelopt-trained LoRA models, we need to remove the base_layer prefix from the keys for deployment | ||
| if is_modelopt_qlora: | ||
| replacements.update( | ||
| { | ||
| "base_layer.weight": "weight", | ||
| "base_layer.input_scale": "input_scale", | ||
| "base_layer.weight_scale": "weight_scale", | ||
| } | ||
| ) | ||
| skip_keys.append("base_layer") | ||
| replacements = _QLORA_REPLACEMENTS if is_modelopt_qlora else _KV_CACHE_REPLACEMENTS | ||
| skip_keys = _QLORA_SKIP_KEYS if is_modelopt_qlora else _BASE_SKIP_KEYS | ||
|
|
||
| post_state_dict = {} | ||
|
|
||
|
|
@@ -1036,15 +1101,7 @@ def postprocess_state_dict( | |
| post_state_dict[prefix + new_suffix] = value | ||
| break | ||
|
|
||
| # Squeeze scales with a leading dimension of 1 | ||
| for key, value in post_state_dict.items(): | ||
| if ( | ||
| "scale" in key | ||
| and isinstance(value, torch.Tensor) | ||
| and value.dim() == 3 | ||
| and value.shape[0] == 1 | ||
| ): | ||
| post_state_dict[key] = value.squeeze(0) | ||
| post_state_dict = {k: _maybe_squeeze_scale(k, v) for k, v in post_state_dict.items()} | ||
|
|
||
| # remove real quant parameters from the state dict | ||
| keys_to_delete = [] | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.