执行摘要
- 一句话:新增 PEFT LoRA 语义支持,fail-closed 拒绝无法表示的语义
- 推荐动作:值得精读。关注
peft_adapter.py 的 fail-closed 设计、normalize_peft_keys 的前缀/槽位处理、scale_fused_sections 对融合 section 的逐层 alpha 实现,以及如何在不新增 CLI 的情况下读取 adapter_config.json。
功能与动机
PR body 说明这是对 #35774 的后续,目标是在现有 LoRA 格式转换之前先规范化常见的 PEFT 模型包装器和命名适配器槽位,应用 use_rslora 和 per-layer alpha_pattern,并对无法用 native diffusion LoRA 层表示的 DoRA 和辅助/运行时 PEFT 特性 fail-closed,避免静默运行普通 LoRA 数学产生错误结果。
实现拆解
- 新增
pipelines_core/lora/peft_adapter.py,作为 PEFT 兼容层核心:load_peft_config(读取同目录 adapter_config.json)、get_peft_lora_alpha(校验 alpha 为正整数)、normalize_peft_keys(剥离 base_model.model. / peft_model.base_model.model. 前缀和命名槽位如 lora_A.default.weight → lora_A.weight)、_validate_peft_features(fail-closed 校验)、apply_peft_config(应用 rslora/alpha_pattern 缩放)、scale_fused_sections(处理融合投影 section 的逐层缩放)。
- 在
format_adapter.py 的 normalize_lora_state_dict 中新增 adapter_config 参数,并在格式检测前先执行 normalize_peft_keys,格式转换后执行 apply_peft_config,使所有 LoRA 格式统一走 PEFT 语义归一化。
- 在
pipeline.py 的 load_lora_adapter 中移除手写 json 读取逻辑,统一走 load_peft_config + get_peft_lora_alpha;_store_fused_lora_groups 引入 scale_fused_sections,使融合投影(如 attn.qkv 多 section)也能保留逐层 alpha。
- 目录重构:
lora_pipeline.py → pipelines_core/lora/pipeline.py,lora_format_adapter.py → pipelines_core/lora/format_adapter.py,并更新 pipelines_core/__init__.py、gpu_worker.py、各 pipeline 文件等共 17 处导入路径。
- 测试配套:新增
test/unit/test_lora_peft.py(rslora 缩放保持 delta、unsupported fail-closed、非法 alpha fail-closed),扩展 test/unit/test_fused_lora_compose.py(逐层 alpha 的融合 section 数值一致性)。其中第 2、3 步是关键行为变化,第 4 步是纯组织重构,PR body 说明相对 main 的生产文件 churn 为 +51/-31,其中 17 对行是目录移动的导入修改。
关键文件:
python/sglang/multimodal_gen/runtime/pipelines_core/lora/peft_adapter.py(模块 PEFT适配;类别 source;类型 core-logic;符号 load_peft_config, get_peft_lora_alpha, normalize_peft_keys, _validate_peft_features): 新增核心 PEFT 适配模块,包含键名规范化、配置校验、缩放计算与 fail-closed 校验,是本 PR 的核心实现。
python/sglang/multimodal_gen/runtime/pipelines_core/lora/pipeline.py(模块 LoRA管线;类别 source;类型 rename-or-move;符号 _store_fused_lora_groups, load_lora_adapter): LoRA 加载入口与融合分组写入逻辑修改,接入 PEFT 配置读取和逐层 alpha 缩放。
python/sglang/multimodal_gen/runtime/pipelines_core/lora/format_adapter.py(模块 格式转换;类别 source;类型 rename-or-move;符号 normalize_lora_state_dict): 格式转换入口接入 PEFT 键名规范化和配置应用,使所有 LoRA 格式统一支持 PEFT 语义。
python/sglang/multimodal_gen/test/unit/test_lora_peft.py(模块 单元测试;类别 test;类型 test-coverage;符号 test_peft_wrapper_slot_and_rslora_scaling_preserve_delta, test_unsupported_peft_runtime_semantics_fail_closed, test_invalid_peft_lora_alpha_fails_closed): 新增 PEFT 语义的针对性测试,覆盖 rslora 缩放、fail-closed 拒绝和不合法 alpha。
python/sglang/multimodal_gen/test/unit/test_fused_lora_compose.py(模块 融合测试;类别 test;类型 test-coverage;符号 test_fused_sections_preserve_per_layer_alpha): 扩展融合 LoRA 组合测试,验证逐层 alpha 在融合投影 section 上的数值一致性。
python/sglang/multimodal_gen/runtime/pipelines_core/__init__.py(模块 包导出;类别 source;类型 dependency-wiring): 导出入口调整,指向新的 LoRA pipeline 子包路径。
关键符号:load_peft_config, get_peft_lora_alpha, normalize_peft_keys, _validate_peft_features, apply_peft_config, scale_fused_sections, normalize_lora_state_dict, _store_fused_lora_groups, load_lora_adapter
关键源码片段
python/sglang/multimodal_gen/runtime/pipelines_core/lora/peft_adapter.py
新增核心 PEFT 适配模块,包含键名规范化、配置校验、缩放计算与 fail-closed 校验,是本 PR 的核心实现。
"""PEFT 检查点语义适配到 native diffusion LoRA 层。"""
# 正则匹配命名适配器槽位,例如 `lora_A.default.weight` 中的 `default`
_ADAPTER_SLOT = re.compile(r"(\.lora_[AB])\.([^.]+)\.weight$")
# 常见的 PEFT 包装前缀,按顺序尝试
_WRAPPER_PREFIXES = ("peft_model.base_model.model.", "base_model.model.")
def load_peft_config(weight_path: str) -> dict[str, Any]:
"""读取 LoRA 权重同目录下的 adapter_config.json,不存在时返回空字典。"""
path = Path(weight_path).with_name("adapter_config.json")
if not path.is_file():
return {}
with path.open(encoding="utf-8") as file:
config = json.load(file)
if not isinstance(config, dict):
raise ValueError("PEFT adapter_config.json must contain a JSON object")
return config
def get_peft_lora_alpha(config: Mapping[str, Any]) -> int | None:
"""校验并提取 lora_alpha,必须是正整数,否则抛出 ValueError。"""
alpha = config.get("lora_alpha")
if alpha is None:
return None
if (
isinstance(alpha, bool)
or not isinstance(alpha, (int, float))
or alpha <= 0
or isinstance(alpha, float)
and not alpha.is_integer()
):
raise ValueError("PEFT lora_alpha must be a positive integer")
return int(alpha)
def normalize_peft_keys(state_dict: Mapping[str, torch.Tensor]) -> dict[str, torch.Tensor]:
"""剥离统一的 PEFT 包装前缀和命名适配器槽位,得到规范化键名。"""
# 选择能匹配所有键的最长包装前缀,避免误删
prefix = next(
(
prefix
for prefix in _WRAPPER_PREFIXES
if state_dict and all(name.startswith(prefix) for name in state_dict)
),
"",
)
normalized: dict[str, torch.Tensor] = {}
slots = set()
has_bare_weights = False
for name, tensor in state_dict.items():
name = name.removeprefix(prefix)
match = _ADAPTER_SLOT.search(name)
if match is not None:
slots.add(match.group(2)) # 记录命名的 adapter 槽位名
elif name.endswith((".lora_A.weight", ".lora_B.weight")):
has_bare_weights = True # 存在未命名的裸权重
target = _ADAPTER_SLOT.sub(r"\1.weight", name) # 移除槽位名,如 lora_A.default.weight -> lora_A.weight
if target in normalized:
raise ValueError(
"LoRA checkpoint contains multiple PEFT adapter slots for "
f"the same tensor: {target!r}"
)
normalized[target] = tensor
if len(slots) > 1:
raise ValueError(
f"LoRA checkpoint contains multiple PEFT adapter slots: {sorted(slots)}"
)
if slots and has_bare_weights:
raise ValueError("LoRA checkpoint mixes named and unnamed PEFT adapter slots")
return normalized
python/sglang/multimodal_gen/runtime/pipelines_core/lora/pipeline.py
LoRA 加载入口与融合分组写入逻辑修改,接入 PEFT 配置读取和逐层 alpha 缩放。
def _store_fused_lora_groups(
adapter: dict[str, torch.Tensor],
to_merge_params: dict[Hashable, dict[Any, Any]],
adapter_alpha: int | None,
device: torch.device | str,
) -> None:
"""将延迟收拢的融合 lora_A/B 分组写回 adapter 字典。"""
for a_key, a_parts in list(to_merge_params.items()):
if not str(a_key).endswith(".lora_A"):
continue
base = str(a_key)[: -len(".lora_A")]
b_key = f"{base}.lora_B"
b_parts = to_merge_params.get(b_key)
n = max(a_parts) + 1
# 要求 A/B 分组的索引都是连续的 0..n-1,否则跳过不融合
if (
b_parts is None
or set(a_parts) != set(range(n))
or set(b_parts) != set(range(n))
):
continue
a_list = [a_parts[i] for i in range(n)]
b_list = [b_parts[i] for i in range(n)]
# 若存在逐层 alpha_pattern,缩放 B 部分后再拼接,保留 PEFT 语义
scaled_b = scale_fused_sections(
a_parts,
b_parts,
to_merge_params.get(f"{base}.alpha", {}),
adapter_alpha,
)
a, b, fused_alpha = stack_or_compose_fused_lora(
a_list, scaled_b or b_list, None if scaled_b else adapter_alpha
)
if scaled_b:
# 逐层缩放后统一 alpha 不能再用 r_eff,改为总行数
fused_alpha = a.shape[-2]
adapter[str(a_key)] = a.to(device)
adapter[b_key] = b.to(device)
if fused_alpha is not None:
adapter[f"{base}.alpha"] = torch.tensor(float(fused_alpha), device=device)
python/sglang/multimodal_gen/runtime/pipelines_core/lora/format_adapter.py
格式转换入口接入 PEFT 键名规范化和配置应用,使所有 LoRA 格式统一支持 PEFT 语义。
def normalize_lora_state_dict(
state_dict: Mapping[str, torch.Tensor],
logger: Optional[logging.Logger] = None,
*,
adapter_config: Mapping[str, Any] | None = None,
) -> Dict[str, torch.Tensor]:
"""Normalize any supported LoRA format into a single canonical layout."""
log = logger or globals()["logger"]
# 先剥离 PEFT 包装前缀和命名槽位,再做格式检测,保证检测逻辑不受 PEFT 包装干扰
state_dict = normalize_peft_keys(state_dict)
keys = list(state_dict.keys())
log.info(
"[LoRAFormatAdapter] normalize_lora_state_dict called, #keys=%d",
len(keys),
)
if keys:
log.info(
"[LoRAFormatAdapter] before convert, sample keys (<=20): %s",
", ".join(_sample_keys(keys, 20)),
)
fmt = detect_lora_format_from_state_dict(state_dict)
log.info("[LoRAFormatAdapter] detected format: %s", fmt)
normalized = convert_lora_state_dict_by_format(state_dict, fmt, log)
# 格式转换后再应用 PEFT 配置,将 use_rslora/alpha_pattern 等语义写入权重
normalized = apply_peft_config(normalized, adapter_config or {})
norm_keys = list(normalized.keys())
if norm_keys:
log.info(
"[LoRAFormatAdapter] after convert, sample keys (<=20): %s",
", ".join(_sample_keys(norm_keys, 20)),
)
return normalized
评论区精华
本 PR 没有 review 评论。设计取舍在 body 中说明:PEFT 语义不支持的特性 fail before weight injection rather than silently running ordinary LoRA math;pipelines_core/lora/__init__.py 不 re-export 符号以避免隐式依赖与循环导入。Issue 评论只有 /tag-and-rerun-ci,无技术讨论。
风险与影响
- 风险:行为风险:
normalize_peft_keys 要求所有键共享同一个包装前缀,若检查点混用裸权重和命名槽位会直接报错;这是有意 fail-closed,但可能让原本勉强能跑的权重被拒绝。alpha_pattern 按正则匹配目标层,误匹配会导致缩放错误。兼容风险:lora_pipeline.py 等旧路径被移动,外部直接 from ...lora_pipeline import ... 的代码会失效,仓库内已同步,但下游用户若引用旧路径需更新。数值风险:scale_fused_sections 改变了融合 LoRA 的叠加方式(按层缩放后再拼接),测试验证了数值一致,但覆盖面限于测试中的配置。无性能影响:新增逻辑只在加载期执行,不影响推理。
- 影响:对用户:支持 PEFT 格式 LoRA(含 use_rslora、alpha_pattern),语义更准确;DoRA 等会得到明确报错而非错误结果。对系统:LoRA 加载路径统一,新增一个适配层,无性能影响。对团队:代码组织更清晰,为后续扩展更多 PEFT 特性奠定基础。影响范围限于 diffusion 模块的 LoRA 加载链路。
- 风险标记:核心路径变更, fail-closed 可能拒绝原本可用的权重, 目录移动影响外部导入, alpha_pattern 正则匹配风险, 融合 section 数值需回归验证
关联脉络
- PR #35774 [diffusion] Resolve LoRA weight sources deterministically: 本 PR 是其后续,延续 LoRA 权重加载的确定性处理,并新增 PEFT 语义支持。
参与讨论