sparkle/mlx_streaming/prep/split_experts.py
fiser_jun 4745f264b2
2026-08-04 14:34:00 +08:00

70 lines
2.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""路线 B 离线工具:把模型里堆叠的 switch_mlp 专家权重按专家拆成 per-expert 小文件。
拆分后每个文件 layer{L:02d}_expert{E:03d}.safetensors 含扁平 dict
gate_proj.weight / gate_proj.scales / gate_proj.biases
up_proj.weight / up_proj.scales / up_proj.biases
down_proj.weight / down_proj.scales / down_proj.biases
(非量化模型则只有 .weight可能还有 .bias
一次拆分、逐专家物化,全程低内存。
"""
import os
import sys
import json
import mlx.core as mx
PROJ_NAMES = ["gate_proj", "up_proj", "down_proj"]
def split_switch_glu(switch_glu, out_dir: str, layer: int) -> int:
"""把一个 SwitchGLU 的三组 SwitchLinear 沿专家维拆成 per-expert 文件。返回专家数。"""
os.makedirs(out_dir, exist_ok=True)
E = switch_glu.gate_proj.num_experts
for e in range(E):
d = {}
for proj_name in PROJ_NAMES:
proj = getattr(switch_glu, proj_name)
for pname, p in proj.parameters().items():
if isinstance(p, mx.array) and p.ndim >= 1 and p.shape[0] == E:
d[f"{proj_name}.{pname}"] = p[e]
mx.eval(d) # 只物化这一个专家
path = os.path.join(out_dir, f"layer{layer:02d}_expert{e:03d}.safetensors")
mx.save_safetensors(path, d)
return E
def split_model(model_path: str, out_dir: str) -> dict:
"""加载模型lazy并把所有 MoE 层的专家拆到 out_dir。返回 {dims/统计}。"""
from mlx_lm import load
model, _ = load(model_path, lazy=True)
os.makedirs(out_dir, exist_ok=True)
moe_layers = []
dims = None
for l, layer in enumerate(model.layers):
mlp = getattr(layer, "mlp", None)
if mlp is not None and hasattr(mlp, "switch_mlp") and hasattr(mlp, "gate"):
sm = mlp.switch_mlp
split_switch_glu(sm, out_dir, l)
moe_layers.append(l)
if dims is None:
gp = sm.gate_proj
dims = {
"hidden": gp.input_dims,
"moe_intermediate": gp.output_dims,
"num_experts": gp.num_experts,
"group_size": getattr(gp, "group_size", None),
"bits": getattr(gp, "bits", None),
}
meta = {"out_dir": out_dir, "moe_layers": moe_layers, "dims": dims}
with open(os.path.join(out_dir, "_split_meta.json"), "w") as f:
json.dump(meta, f, ensure_ascii=False, indent=2)
return meta
if __name__ == "__main__":
mp = sys.argv[1]
od = sys.argv[2] if len(sys.argv) > 2 else "/tmp/mlx_qwen3_experts"
m = split_model(mp, od)
print(json.dumps(m, ensure_ascii=False, indent=2))