[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:
co-authored by
niehen6174
Mick
parent
76d4881794
commit
5c72be1e51
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user