e93507a09c
Lockfile supply-chain audit / lockfile supply-chain audit (push) Has been cancelled
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Has been cancelled
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Has been cancelled
Windows Studio Update CI / Studio Updating Tests (push) Has been cancelled
Wheel CI / Wheel build + content sanity + import smoke (push) Has been cancelled
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Has been cancelled
MLX CI on Mac M1 / dispatch (push) Has been cancelled
Security audit / advisory audit (pip + npm + cargo) (push) Has been cancelled
Security audit / pip scan-packages :: extras (push) Has been cancelled
Security audit / pip scan-packages :: studio (push) Has been cancelled
Security audit / pip scan-packages :: hf-stack (push) Has been cancelled
Security audit / npm scan-packages (Studio frontend tarballs) (push) Has been cancelled
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Has been cancelled
Security audit / pytest tests/security (push) Has been cancelled
Security audit / npm provenance + new install-script diff (push) Has been cancelled
Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Backend CI / (Python 3.10) (push) Has been cancelled
Backend CI / (Python 3.11) (push) Has been cancelled
Backend CI / (Python 3.12) (push) Has been cancelled
Backend CI / (Python 3.13) (push) Has been cancelled
Backend CI / Repo tests (CPU) (push) Has been cancelled
Frontend CI / Frontend build + bundle sanity (push) Has been cancelled
Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Mac Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Mac Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Has been cancelled
Mac Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Has been cancelled
Mac Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Has been cancelled
Mac Studio Update CI / Studio Updating Tests (push) Has been cancelled
Studio UI CI / Chat UI Tests (push) Has been cancelled
Windows Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Windows Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Update CI / Studio Updating Tests (push) Has been cancelled
Core / Core (HF=default + TRL=default) (push) Has been cancelled
Core / Core (HF=4.57.6 + TRL<1) (push) Has been cancelled
Core / Core (HF=latest + TRL=latest) (push) Has been cancelled
Core / llama.cpp build + smoke (push) Has been cancelled
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Windows Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Windows Studio GGUF CI / JSON, images (push) Has been cancelled
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Has been cancelled
Studio export capability / capability (macos-latest) (push) Has been cancelled
Studio export capability / capability (ubuntu-latest) (push) Has been cancelled
Studio export capability / capability (windows-latest) (push) Has been cancelled
Cross-platform parity / parity (macos-latest) (push) Has been cancelled
Cross-platform parity / parity (windows-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Studio load-orchestrator CI / test (push) Has been cancelled
239 lines
8.6 KiB
Python
239 lines
8.6 KiB
Python
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
import torch
|
|
|
|
|
|
class _ExpertWeights(torch.nn.Module):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.gate_up_proj = torch.nn.Parameter(torch.zeros(2, 4, 8))
|
|
self.down_proj = torch.nn.Parameter(torch.zeros(2, 8, 4))
|
|
|
|
|
|
class _Mlp(torch.nn.Module):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.experts = _ExpertWeights()
|
|
|
|
|
|
class _FakeMoeModel(torch.nn.Module):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.config = SimpleNamespace(num_experts = 2, model_type = "qwen3_moe")
|
|
self.mlp = _Mlp()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"target_modules",
|
|
[
|
|
".*mlp.*proj",
|
|
".*ffn.*proj",
|
|
r"(?:\bmodel\.layers\.[\d]{1,}\.(?:mlp)\.(?:gate_proj|up_proj|down_proj))",
|
|
],
|
|
)
|
|
def test_regex_mlp_targets_discover_moe_parameters(target_modules):
|
|
from unsloth.models._utils import get_moe_target_parameters
|
|
assert get_moe_target_parameters(_FakeMoeModel(), target_modules) == [
|
|
"mlp.experts.gate_up_proj",
|
|
"mlp.experts.down_proj",
|
|
]
|
|
|
|
|
|
def test_explicit_dotted_module_target_does_not_discover_moe_parameters():
|
|
from unsloth.models._utils import get_moe_target_parameters
|
|
assert (
|
|
get_moe_target_parameters(
|
|
_FakeMoeModel(),
|
|
"model.layers.0.mlp.shared_expert.down_proj",
|
|
)
|
|
is None
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"target_modules",
|
|
[
|
|
# Attention-only auto-regex lists every projection leaf (incl. gate/up/down)
|
|
# but its path segment is attention-only, so experts must NOT be targeted.
|
|
r"(?:\bmodel\.layers\.[\d]{1,}\.(?:self_attn|attention|attn|mixer)\.(?:q_proj|k_proj|v_proj|o_proj|gate_proj|up_proj|down_proj))",
|
|
".*self_attn.*proj",
|
|
# An mlp path alternative with attention-only leaves is still attention-only.
|
|
r"model\.layers\.\d+\.(?:mlp|self_attn)\.(?:q_proj|k_proj|v_proj|o_proj)",
|
|
],
|
|
)
|
|
def test_attention_only_regex_does_not_discover_moe_parameters(target_modules):
|
|
from unsloth.models._utils import get_moe_target_parameters
|
|
assert get_moe_target_parameters(_FakeMoeModel(), target_modules) is None
|
|
|
|
|
|
def test_single_leaf_regex_targets_only_that_projection():
|
|
from unsloth.models._utils import get_moe_target_parameters
|
|
assert get_moe_target_parameters(_FakeMoeModel(), ".*experts.*down_proj") == [
|
|
"mlp.experts.down_proj",
|
|
]
|
|
assert get_moe_target_parameters(_FakeMoeModel(), ".*mlp.*gate_proj") == [
|
|
"mlp.experts.gate_up_proj",
|
|
]
|
|
|
|
|
|
def test_auto_regex_mlp_tag_block_discovers_moe_on_fused_models():
|
|
# get_peft_regex on a fused-expert model lists only attention Linears as
|
|
# leaves; the mlp tag block is the remaining signal of MLP finetune intent.
|
|
from unsloth.models._utils import get_moe_target_parameters
|
|
both_auto = (
|
|
r"(?:\bmodel\.layers\.[\d]{1,}\."
|
|
r"(?:self_attn|attention|attn|mixer|mlp|feed_forward|ffn|dense|mixer)\."
|
|
r"(?:(?:q_proj|k_proj|v_proj|o_proj)))"
|
|
)
|
|
assert get_moe_target_parameters(_FakeMoeModel(), both_auto) == [
|
|
"mlp.experts.gate_up_proj",
|
|
"mlp.experts.down_proj",
|
|
]
|
|
|
|
|
|
def test_explicit_attention_only_list_does_not_discover_moe_parameters():
|
|
# An explicit attention-only leaf list names no MLP projection, so experts
|
|
# must never be targeted. get_peft_model routes this ORIGINAL list (not the
|
|
# scoped regex) into detection precisely because family scoping makes
|
|
# get_peft_regex emit its full "mlp|feed_forward|ffn|dense" component block
|
|
# even for an attention-only request (see the regex below), which the
|
|
# string fallback cannot distinguish from the fused-expert auto regex.
|
|
from unsloth.models._utils import get_moe_target_parameters
|
|
|
|
attn_only_list = ["q_proj", "k_proj", "v_proj", "o_proj"]
|
|
assert get_moe_target_parameters(_FakeMoeModel(), attn_only_list) is None
|
|
assert get_moe_target_parameters(_FakeMoeModel(), tuple(attn_only_list)) is None
|
|
|
|
# The regex get_peft_regex emits for that same attention-only list under a
|
|
# vision-off family scope carries the mlp component block, so the string
|
|
# path would wrongly enable experts -- hence detection must use the list.
|
|
scoped_regex = (
|
|
r"(?:.*?(?:language|text).*?"
|
|
r"(?:self_attn|attention|attn|mixer|mlp|feed_forward|ffn|dense|mixer).*?"
|
|
r"(?:q_proj|k_proj|v_proj|o_proj))"
|
|
)
|
|
assert get_moe_target_parameters(_FakeMoeModel(), scoped_regex) == [
|
|
"mlp.experts.gate_up_proj",
|
|
"mlp.experts.down_proj",
|
|
]
|
|
|
|
|
|
def test_frozen_mlp_full_list_does_not_discover_moe_parameters():
|
|
# Regression: an explicit list that names MLP leaves together with
|
|
# finetune_mlp_modules=False must NOT train experts. get_peft_regex scopes
|
|
# the MLP leaves out (its emitted regex carries no mlp tag block), so
|
|
# detection has to key on that SCOPED regex -- keying on the original list
|
|
# would let its gate/up/down leaves silently re-enable the frozen experts.
|
|
from unsloth.models._utils import (
|
|
_select_moe_detection_targets,
|
|
get_moe_target_parameters,
|
|
)
|
|
|
|
original_list = [
|
|
"q_proj",
|
|
"k_proj",
|
|
"v_proj",
|
|
"o_proj",
|
|
"gate_proj",
|
|
"up_proj",
|
|
"down_proj",
|
|
]
|
|
# Representative of what get_peft_regex emits for that list under
|
|
# finetune_mlp_modules=False: attention-only path, no mlp component block.
|
|
scoped_regex = (
|
|
r"(?:.*?(?:language|text).*?"
|
|
r"(?:self_attn|attention|attn|mixer).*?"
|
|
r"(?:q_proj|k_proj|v_proj|o_proj))"
|
|
)
|
|
selected = _select_moe_detection_targets(
|
|
original_list,
|
|
scoped_regex,
|
|
finetune_mlp_modules = False,
|
|
finetune_language_layers = True,
|
|
)
|
|
assert selected is scoped_regex
|
|
assert get_moe_target_parameters(_FakeMoeModel(), selected) is None
|
|
|
|
|
|
def test_frozen_language_full_list_does_not_discover_moe_parameters():
|
|
# Vision-only request (finetune_language_layers=False) with a full leaf list
|
|
# must not reach the language-model experts either.
|
|
from unsloth.models._utils import (
|
|
_select_moe_detection_targets,
|
|
get_moe_target_parameters,
|
|
)
|
|
|
|
original_list = ["q_proj", "gate_proj", "up_proj", "down_proj"]
|
|
scoped_regex = (
|
|
r"(?:.*?(?:vision|visual|image).*?"
|
|
r"(?:self_attn|attention|attn|mixer).*?"
|
|
r"(?:q_proj|k_proj|v_proj|o_proj))"
|
|
)
|
|
selected = _select_moe_detection_targets(
|
|
original_list,
|
|
scoped_regex,
|
|
finetune_mlp_modules = True,
|
|
finetune_language_layers = False,
|
|
)
|
|
assert selected is scoped_regex
|
|
assert get_moe_target_parameters(_FakeMoeModel(), selected) is None
|
|
|
|
|
|
def test_in_scope_mlp_full_list_still_discovers_moe_parameters():
|
|
# With MLP and language both in scope, an explicit list that names MLP
|
|
# leaves SHOULD enable the experts (unchanged behavior): the original list
|
|
# is preferred and carries the gate/up/down intent.
|
|
from unsloth.models._utils import (
|
|
_select_moe_detection_targets,
|
|
get_moe_target_parameters,
|
|
)
|
|
|
|
original_list = [
|
|
"q_proj",
|
|
"k_proj",
|
|
"v_proj",
|
|
"o_proj",
|
|
"gate_proj",
|
|
"up_proj",
|
|
"down_proj",
|
|
]
|
|
scoped_regex = r".*self_attn.*proj" # unused: original list is preferred
|
|
selected = _select_moe_detection_targets(
|
|
original_list,
|
|
scoped_regex,
|
|
finetune_mlp_modules = True,
|
|
finetune_language_layers = True,
|
|
)
|
|
assert selected is original_list
|
|
assert get_moe_target_parameters(_FakeMoeModel(), selected) == [
|
|
"mlp.experts.gate_up_proj",
|
|
"mlp.experts.down_proj",
|
|
]
|
|
|
|
|
|
def test_attention_only_list_prefers_original_when_in_scope():
|
|
# The case the PR originally fixed: an attention-only list routed through
|
|
# get_peft_regex under a family scope (e.g. vision-off) still keeps experts
|
|
# off, because with MLP+language in scope detection uses the original
|
|
# attention-only list rather than the regex's spurious mlp component block.
|
|
from unsloth.models._utils import (
|
|
_select_moe_detection_targets,
|
|
get_moe_target_parameters,
|
|
)
|
|
|
|
attn_only_list = ["q_proj", "k_proj", "v_proj", "o_proj"]
|
|
scoped_regex = ( # carries the spurious mlp block get_peft_regex always adds
|
|
r"(?:.*?(?:language|text).*?"
|
|
r"(?:self_attn|attention|attn|mixer|mlp|feed_forward|ffn|dense).*?"
|
|
r"(?:q_proj|k_proj|v_proj|o_proj))"
|
|
)
|
|
selected = _select_moe_detection_targets(
|
|
attn_only_list,
|
|
scoped_regex,
|
|
finetune_mlp_modules = True,
|
|
finetune_language_layers = True,
|
|
)
|
|
assert selected is attn_only_list
|
|
assert get_moe_target_parameters(_FakeMoeModel(), selected) is None
|