|
4 | 4 |
|
5 | 5 | import json |
6 | 6 | import math |
| 7 | +import os |
7 | 8 | import time |
| 9 | +from concurrent.futures import ThreadPoolExecutor |
8 | 10 | from typing import Any, Dict, List, Optional, Union |
9 | 11 |
|
10 | 12 | import safetensors.torch |
|
39 | 41 |
|
40 | 42 | STAGE_2_DISTILLED_SIGMA_VALUES = [0.909375, 0.725, 0.421875, 0.0] |
41 | 43 | _FP8_DTYPES = (torch.float8_e4m3fn, torch.float8_e5m2) |
| 44 | +_QKV_SUFFIXES = (".to_q", ".to_k", ".to_v") |
| 45 | +_DISABLE_OVERLAP_LORA_LOAD_ENV = "TRTLLM_LTX2_DISABLE_OVERLAP_LORA_LOAD" |
42 | 46 |
|
43 | 47 |
|
44 | 48 | # --------------------------------------------------------------------------- |
45 | 49 | # LoRA helpers |
46 | 50 | # --------------------------------------------------------------------------- |
47 | 51 |
|
48 | 52 |
|
| 53 | +def _map_lora_param_name(base_name: str, strip_prefixes: List[str]) -> str: |
| 54 | + param_name = base_name |
| 55 | + for prefix in strip_prefixes: |
| 56 | + if param_name.startswith(prefix): |
| 57 | + param_name = param_name[len(prefix) :] |
| 58 | + break |
| 59 | + |
| 60 | + # Apply the same key remapping as LTXModel.load_weights() so LoRA delta |
| 61 | + # keys match TRT-LLM parameter names. |
| 62 | + for ff_prefix in (".ff.", ".audio_ff."): |
| 63 | + if ff_prefix + "net.0.proj" in param_name: |
| 64 | + param_name = param_name.replace(ff_prefix + "net.0.proj", ff_prefix + "up_proj") |
| 65 | + elif ff_prefix + "net.2" in param_name: |
| 66 | + param_name = param_name.replace(ff_prefix + "net.2", ff_prefix + "down_proj") |
| 67 | + param_name = param_name.replace(".q_norm.", ".norm_q.") |
| 68 | + param_name = param_name.replace(".k_norm.", ".norm_k.") |
| 69 | + return param_name |
| 70 | + |
| 71 | + |
| 72 | +def _has_lora_target(param_name: str, model_params: set[str]) -> bool: |
| 73 | + if param_name in model_params or f"{param_name}.weight" in model_params: |
| 74 | + return True |
| 75 | + |
| 76 | + for suffix in _QKV_SUFFIXES: |
| 77 | + if param_name.endswith(suffix): |
| 78 | + attn_prefix = param_name[: -len(suffix)] |
| 79 | + return f"{attn_prefix}.qkv_proj.weight" in model_params |
| 80 | + return False |
| 81 | + |
| 82 | + |
49 | 83 | def _load_lora_deltas( |
50 | 84 | lora_path: str, |
51 | 85 | transformer: torch.nn.Module, |
@@ -109,39 +143,28 @@ def _strip(key, suffixes): |
109 | 143 | if suffix: |
110 | 144 | strip_prefixes.append(suffix) |
111 | 145 |
|
| 146 | + model_params = {n for n, _ in transformer.named_parameters()} |
112 | 147 | deltas: Dict[str, torch.Tensor] = {} |
| 148 | + skipped_non_targets = 0 |
113 | 149 | for base_name in down_keys: |
114 | 150 | if base_name not in up_keys: |
115 | 151 | continue |
| 152 | + |
| 153 | + param_name = _map_lora_param_name(base_name, strip_prefixes) |
| 154 | + if not _has_lora_target(param_name, model_params): |
| 155 | + skipped_non_targets += 1 |
| 156 | + continue |
| 157 | + |
116 | 158 | A = down_keys[base_name] # (rank, in_features) |
117 | 159 | B = up_keys[base_name] # (out_features, rank) |
118 | 160 | rank = A.shape[0] |
119 | 161 | alpha = alpha_dict.get(base_name, float(rank)) |
120 | 162 | scale = strength * alpha / rank |
121 | | - |
122 | | - param_name = base_name |
123 | | - for prefix in strip_prefixes: |
124 | | - if param_name.startswith(prefix): |
125 | | - param_name = param_name[len(prefix) :] |
126 | | - break |
127 | | - |
128 | | - # Apply the same key remapping as LTXModel.load_weights() so |
129 | | - # that LoRA delta keys match TRT-LLM parameter names. |
130 | | - for ff_prefix in (".ff.", ".audio_ff."): |
131 | | - if ff_prefix + "net.0.proj" in param_name: |
132 | | - param_name = param_name.replace(ff_prefix + "net.0.proj", ff_prefix + "up_proj") |
133 | | - elif ff_prefix + "net.2" in param_name: |
134 | | - param_name = param_name.replace(ff_prefix + "net.2", ff_prefix + "down_proj") |
135 | | - param_name = param_name.replace(".q_norm.", ".norm_q.") |
136 | | - param_name = param_name.replace(".k_norm.", ".norm_k.") |
137 | | - |
138 | 163 | delta = (B.float() @ A.float()) * scale |
139 | 164 | deltas[param_name] = delta |
140 | 165 |
|
141 | 166 | # Fuse separate to_q / to_k / to_v deltas into a single qkv_proj |
142 | 167 | # delta when the transformer uses QKV fusion (FUSE_QKV mode). |
143 | | - model_params = {n for n, _ in transformer.named_parameters()} |
144 | | - _QKV_SUFFIXES = (".to_q", ".to_k", ".to_v") |
145 | 168 | fused_keys: set = set() |
146 | 169 | qkv_groups: Dict[str, List[str]] = {} |
147 | 170 | for key in list(deltas.keys()): |
@@ -169,7 +192,10 @@ def _strip(key, suffixes): |
169 | 192 | for key in fused_keys: |
170 | 193 | del deltas[key] |
171 | 194 |
|
172 | | - logger.info(f"Loaded {len(deltas)} LoRA deltas from {lora_path} (strength={strength})") |
| 195 | + logger.info( |
| 196 | + f"Loaded {len(deltas)} LoRA deltas from {lora_path} " |
| 197 | + f"(strength={strength}, skipped_non_targets={skipped_non_targets})" |
| 198 | + ) |
173 | 199 | return deltas |
174 | 200 |
|
175 | 201 |
|
@@ -610,17 +636,52 @@ def load_standard_components( |
610 | 636 | skip_components: Optional[list] = None, |
611 | 637 | **kwargs, |
612 | 638 | ) -> None: |
613 | | - super().load_standard_components( |
614 | | - checkpoint_dir, |
615 | | - device, |
616 | | - skip_components=skip_components, |
617 | | - **kwargs, |
618 | | - ) |
619 | | - |
620 | 639 | dtype = self.model_config.torch_dtype |
621 | 640 | spatial_upsampler_path = self.model_config.extra_attrs.get("spatial_upsampler_path", "") |
622 | 641 | distilled_lora_path = self.model_config.extra_attrs.get("distilled_lora_path", "") |
623 | 642 |
|
| 643 | + lora_executor = None |
| 644 | + lora_future = None |
| 645 | + disable_overlap_lora_load = os.getenv(_DISABLE_OVERLAP_LORA_LOAD_ENV, "0") == "1" |
| 646 | + if distilled_lora_path and not disable_overlap_lora_load: |
| 647 | + logger.info(f"Starting distilled LoRA pre-compute from {distilled_lora_path}...") |
| 648 | + lora_executor = ThreadPoolExecutor(max_workers=1) |
| 649 | + lora_future = lora_executor.submit( |
| 650 | + _load_lora_deltas, |
| 651 | + distilled_lora_path, |
| 652 | + self.transformer, |
| 653 | + self._TRANSFORMER_PREFIX, |
| 654 | + ) |
| 655 | + |
| 656 | + try: |
| 657 | + super().load_standard_components( |
| 658 | + checkpoint_dir, |
| 659 | + device, |
| 660 | + skip_components=skip_components, |
| 661 | + **kwargs, |
| 662 | + ) |
| 663 | + |
| 664 | + self._load_two_stage_components( |
| 665 | + device, |
| 666 | + dtype, |
| 667 | + spatial_upsampler_path, |
| 668 | + distilled_lora_path, |
| 669 | + lora_future, |
| 670 | + disable_overlap_lora_load, |
| 671 | + ) |
| 672 | + finally: |
| 673 | + if lora_executor is not None: |
| 674 | + lora_executor.shutdown(wait=True) |
| 675 | + |
| 676 | + def _load_two_stage_components( |
| 677 | + self, |
| 678 | + device: torch.device, |
| 679 | + dtype: torch.dtype, |
| 680 | + spatial_upsampler_path: str, |
| 681 | + distilled_lora_path: str, |
| 682 | + lora_future, |
| 683 | + disable_overlap_lora_load: bool, |
| 684 | + ) -> None: |
624 | 685 | # --- Spatial upsampler --- |
625 | 686 | if spatial_upsampler_path: |
626 | 687 | logger.info(f"Loading spatial upsampler from {spatial_upsampler_path}...") |
@@ -659,12 +720,18 @@ def load_standard_components( |
659 | 720 | # --- Distilled LoRA (pre-compute deltas) --- |
660 | 721 | self._distilled_lora_deltas: Dict[str, torch.Tensor] = {} |
661 | 722 | if distilled_lora_path: |
662 | | - logger.info(f"Loading distilled LoRA from {distilled_lora_path}...") |
663 | | - self._distilled_lora_deltas = _load_lora_deltas( |
664 | | - distilled_lora_path, |
665 | | - self.transformer, |
666 | | - transformer_prefix=self._TRANSFORMER_PREFIX, |
667 | | - ) |
| 723 | + if disable_overlap_lora_load: |
| 724 | + logger.info(f"Loading distilled LoRA from {distilled_lora_path}...") |
| 725 | + self._distilled_lora_deltas = _load_lora_deltas( |
| 726 | + distilled_lora_path, |
| 727 | + self.transformer, |
| 728 | + self._TRANSFORMER_PREFIX, |
| 729 | + ) |
| 730 | + else: |
| 731 | + logger.info("Waiting for distilled LoRA pre-compute...") |
| 732 | + if lora_future is None: |
| 733 | + raise RuntimeError("Distilled LoRA pre-compute was not started.") |
| 734 | + self._distilled_lora_deltas = lora_future.result() |
668 | 735 | logger.info( |
669 | 736 | f"Distilled LoRA ready: {len(self._distilled_lora_deltas)} parameter deltas" |
670 | 737 | ) |
|
0 commit comments