[diffusion] feat: support multiple LoRA adapters loading and application (#16667)

Co-authored-by: niehen6174 <niehen.6174@gmail.com>
Co-authored-by: Mick <mickjagger19@icloud.com>
This commit is contained in:
WenhaoZhang
2026-01-10 23:32:45 +08:00
committed by GitHub
co-authored by niehen6174 Mick
parent 76d4881794
commit 5c72be1e51
10 changed files with 578 additions and 180 deletions
@@ -529,6 +529,31 @@
"expected_avg_denoise_ms": 94.15,
"expected_median_denoise_ms": 102.03
},
"zimage_image_t2i_multi_lora": {
"stages_ms": {
"InputValidationStage": 0.04,
"TextEncodingStage": 103.81,
"ConditioningStage": 0.01,
"TimestepPreparationStage": 1.3,
"LatentPreparationStage": 0.11,
"DenoisingStage": 813.7,
"DecodingStage": 34.51
},
"denoise_step_ms": {
"0": 30.35,
"1": 74.53,
"2": 99.34,
"3": 100.92,
"4": 99.46,
"5": 100.57,
"6": 99.72,
"7": 100.86,
"8": 103.87
},
"expected_e2e_ms": 955.14,
"expected_avg_denoise_ms": 89.96,
"expected_median_denoise_ms": 99.72
},
"zimage_image_t2i_2_gpus": {
"stages_ms": {
"InputValidationStage": 0.08,
@@ -556,6 +556,76 @@ Consider updating perf_baselines.json with the snippets below:
assert resp.status_code == 200, f"Dynamic set_lora failed: {resp.text}"
logger.info("[Dynamic LoRA] set_lora succeeded for %s", case.id)
def _test_multi_lora_e2e(
self,
ctx: ServerContext,
case: DiffusionTestCase,
generate_fn: Callable[[str, openai.Client], str],
first_lora_path: str,
second_lora_path: str,
) -> None:
"""
Test multiple LoRA adapters with different set_lora input scenarios.
Tests: basic multi-LoRA, different strengths, cached adapters, switch back to single.
"""
base_url = f"http://localhost:{ctx.port}/v1"
client = OpenAI(base_url=base_url, api_key="dummy")
# Test 1: Basic multi-LoRA with list format
resp = requests.post(
f"{base_url}/set_lora",
json={
"lora_nickname": ["default", "lora2"],
"lora_path": [first_lora_path, second_lora_path],
"target": "all",
"strength": [1.0, 1.0],
},
)
assert (
resp.status_code == 200
), f"set_lora with multiple adapters failed: {resp.text}"
assert generate_fn(case.id, client) is not None
# Test 2: Different strengths
resp = requests.post(
f"{base_url}/set_lora",
json={
"lora_nickname": ["default", "lora2"],
"lora_path": [first_lora_path, second_lora_path],
"target": "all",
"strength": [0.8, 0.5],
},
)
assert (
resp.status_code == 200
), f"set_lora with different strengths failed: {resp.text}"
assert generate_fn(case.id, client) is not None
# Test 3: Different targets
requests.post(f"{base_url}/set_lora", json={"lora_nickname": "default"})
resp = requests.post(
f"{base_url}/set_lora",
json={
"lora_nickname": ["default", "lora2"],
"lora_path": [first_lora_path, second_lora_path],
"target": ["transformer", "transformer_2"],
"strength": [0.8, 0.5],
},
)
assert (
resp.status_code == 200
), f"set_lora with cached adapters failed: {resp.text}"
assert generate_fn(case.id, client) is not None
# Test 4: Switch back to single LoRA
resp = requests.post(f"{base_url}/set_lora", json={"lora_nickname": "default"})
assert (
resp.status_code == 200
), f"set_lora back to single adapter failed: {resp.text}"
assert generate_fn(case.id, client) is not None
logger.info("[Multi-LoRA] All multi-LoRA tests passed for %s", case.id)
def _test_v1_models_endpoint(
self, ctx: ServerContext, case: DiffusionTestCase
) -> None:
@@ -681,3 +751,21 @@ Consider updating perf_baselines.json with the snippets below:
# LoRA API functionality test with E2E validation (only for LoRA-enabled cases)
if case.server_args.lora_path or case.server_args.dynamic_lora_path:
self._test_lora_api_functionality(diffusion_server, case, generate_fn)
# Test dynamic LoRA switching (requires a second LoRA adapter)
if case.server_args.second_lora_path:
self._test_lora_dynamic_switch_e2e(
diffusion_server,
case,
generate_fn,
case.server_args.second_lora_path,
)
# Test multi-LoRA functionality
self._test_multi_lora_e2e(
diffusion_server,
case,
generate_fn,
case.server_args.lora_path,
case.server_args.second_lora_path,
)
@@ -155,6 +155,9 @@ class DiffusionServerArgs:
dynamic_lora_path: str | None = (
None # LoRA path for dynamic loading test (loaded via set_lora after startup)
)
second_lora_path: str | None = (
None # Second LoRA adapter path for multi-LoRA testing
)
# misc
enable_warmup: bool = False
@@ -361,6 +364,17 @@ ONE_GPU_CASES_A: list[DiffusionTestCase] = [
),
T2I_sampling_params,
),
# Multi-LoRA test case for Z-Image-Turbo
DiffusionTestCase(
"zimage_image_t2i_multi_lora",
DiffusionServerArgs(
model_path="Tongyi-MAI/Z-Image-Turbo",
modality="image",
lora_path="reverentelusarca/elusarca-anime-style-lora-z-image-turbo",
second_lora_path="tarn59/pixel_art_style_lora_z_image_turbo",
),
T2I_sampling_params,
),
# === Text and Image to Image (TI2I) ===
DiffusionTestCase(
"qwen_image_edit_ti2i",