Skip to content

Commit 10acd6b

Browse files
committed
parallel LoRA weight loading
Signed-off-by: Yibin Li <109242046+yibinl-nvidia@users.noreply.github.com>
1 parent 059de9c commit 10acd6b

1 file changed

Lines changed: 100 additions & 33 deletions

File tree

tensorrt_llm/_torch/visual_gen/models/ltx2/pipeline_ltx2_two_stages.py

Lines changed: 100 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
import json
66
import math
7+
import os
78
import time
9+
from concurrent.futures import ThreadPoolExecutor
810
from typing import Any, Dict, List, Optional, Union
911

1012
import safetensors.torch
@@ -39,13 +41,45 @@
3941

4042
STAGE_2_DISTILLED_SIGMA_VALUES = [0.909375, 0.725, 0.421875, 0.0]
4143
_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"
4246

4347

4448
# ---------------------------------------------------------------------------
4549
# LoRA helpers
4650
# ---------------------------------------------------------------------------
4751

4852

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+
4983
def _load_lora_deltas(
5084
lora_path: str,
5185
transformer: torch.nn.Module,
@@ -109,39 +143,28 @@ def _strip(key, suffixes):
109143
if suffix:
110144
strip_prefixes.append(suffix)
111145

146+
model_params = {n for n, _ in transformer.named_parameters()}
112147
deltas: Dict[str, torch.Tensor] = {}
148+
skipped_non_targets = 0
113149
for base_name in down_keys:
114150
if base_name not in up_keys:
115151
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+
116158
A = down_keys[base_name] # (rank, in_features)
117159
B = up_keys[base_name] # (out_features, rank)
118160
rank = A.shape[0]
119161
alpha = alpha_dict.get(base_name, float(rank))
120162
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-
138163
delta = (B.float() @ A.float()) * scale
139164
deltas[param_name] = delta
140165

141166
# Fuse separate to_q / to_k / to_v deltas into a single qkv_proj
142167
# 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")
145168
fused_keys: set = set()
146169
qkv_groups: Dict[str, List[str]] = {}
147170
for key in list(deltas.keys()):
@@ -169,7 +192,10 @@ def _strip(key, suffixes):
169192
for key in fused_keys:
170193
del deltas[key]
171194

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+
)
173199
return deltas
174200

175201

@@ -610,17 +636,52 @@ def load_standard_components(
610636
skip_components: Optional[list] = None,
611637
**kwargs,
612638
) -> None:
613-
super().load_standard_components(
614-
checkpoint_dir,
615-
device,
616-
skip_components=skip_components,
617-
**kwargs,
618-
)
619-
620639
dtype = self.model_config.torch_dtype
621640
spatial_upsampler_path = self.model_config.extra_attrs.get("spatial_upsampler_path", "")
622641
distilled_lora_path = self.model_config.extra_attrs.get("distilled_lora_path", "")
623642

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:
624685
# --- Spatial upsampler ---
625686
if spatial_upsampler_path:
626687
logger.info(f"Loading spatial upsampler from {spatial_upsampler_path}...")
@@ -659,12 +720,18 @@ def load_standard_components(
659720
# --- Distilled LoRA (pre-compute deltas) ---
660721
self._distilled_lora_deltas: Dict[str, torch.Tensor] = {}
661722
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()
668735
logger.info(
669736
f"Distilled LoRA ready: {len(self._distilled_lora_deltas)} parameter deltas"
670737
)

0 commit comments

Comments
 (0)