Instructions to use punsaisuwan/frankenmoe-python-typescript with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use punsaisuwan/frankenmoe-python-typescript with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] huggingface-cli download --local-dir frankenmoe-python-typescript punsaisuwan/frankenmoe-python-typescript
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
Remove legacy dispatcher (superseded by moe_orchestrator.py)
Browse files- frankenmoe_dispatcher.py +0 -161
frankenmoe_dispatcher.py
DELETED
|
@@ -1,161 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
FrankenMoE Dispatcher — Rebuild-on-Swap Architecture (v4: Handle MoE Switch Layers)
|
| 3 |
-
แก้ปัญหา 'Can't convert layer of type LoRASwitchLinear to LoRA'
|
| 4 |
-
โดยตรวจจับทั้ง LoRALinear (Attention) และ LoRASwitchLinear (MoE Expert MLP)
|
| 5 |
-
"""
|
| 6 |
-
import json
|
| 7 |
-
from mlx.utils import tree_unflatten
|
| 8 |
-
from mlx_lm import load, generate
|
| 9 |
-
from mlx_lm.tuner.utils import linear_to_lora_layers
|
| 10 |
-
|
| 11 |
-
# 🔑 Import ทั้ง 2 Class ที่ mlx_lm ใช้ห่อ LoRA
|
| 12 |
-
# LoRALinear -> ใช้กับ Attention Layer ปกติ (q,k,v,o_proj)
|
| 13 |
-
# LoRASwitchLinear -> ใช้กับ MoE Switch/Expert MLP Layer โดยเฉพาะ
|
| 14 |
-
LORA_WRAPPER_TYPES = []
|
| 15 |
-
try:
|
| 16 |
-
from mlx_lm.tuner.lora import LoRALinear
|
| 17 |
-
LORA_WRAPPER_TYPES.append(LoRALinear)
|
| 18 |
-
except ImportError:
|
| 19 |
-
pass
|
| 20 |
-
try:
|
| 21 |
-
from mlx_lm.tuner.lora import LoRASwitchLinear
|
| 22 |
-
LORA_WRAPPER_TYPES.append(LoRASwitchLinear)
|
| 23 |
-
except ImportError:
|
| 24 |
-
pass
|
| 25 |
-
|
| 26 |
-
LORA_WRAPPER_TYPES = tuple(LORA_WRAPPER_TYPES)
|
| 27 |
-
print(f"🔎 ตรวจพบ LoRA Wrapper Types ที่ต้องจัดการ: {[t.__name__ for t in LORA_WRAPPER_TYPES]}")
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
class FrankenMoEDispatcher:
|
| 31 |
-
def __init__(self, base_model_path="output-moe-mlx-4bit", max_num_layers=16):
|
| 32 |
-
print(f"🔄 กำลังโหลด Base Model: {base_model_path} ...")
|
| 33 |
-
self.model, self.tokenizer = load(base_model_path)
|
| 34 |
-
self.max_num_layers = max_num_layers
|
| 35 |
-
self.current_expert = None
|
| 36 |
-
|
| 37 |
-
self.experts = {
|
| 38 |
-
"python": {
|
| 39 |
-
"config_path": "adapters/expert-1-python-v2/adapter_config.json",
|
| 40 |
-
"weights_path": "adapters/expert-1-python-v2/0000500_adapters.safetensors",
|
| 41 |
-
},
|
| 42 |
-
"typescript": {
|
| 43 |
-
"config_path": "adapters/expert-2-typescript/adapter_config.json",
|
| 44 |
-
"weights_path": "adapters/expert-2-typescript/0000100_adapters.safetensors",
|
| 45 |
-
},
|
| 46 |
-
}
|
| 47 |
-
|
| 48 |
-
# Snapshot ทุก Module ใน Layer เป้าหมาย (Attention + MoE Switch + อะไรก็ตาม)
|
| 49 |
-
self._original_layers = {}
|
| 50 |
-
total_layers = len(self.model.layers)
|
| 51 |
-
self._layer_range = range(total_layers - max_num_layers, total_layers)
|
| 52 |
-
for real_idx in self._layer_range:
|
| 53 |
-
l = self.model.layers[real_idx]
|
| 54 |
-
for k, m in l.named_modules():
|
| 55 |
-
self._original_layers[(real_idx, k)] = m
|
| 56 |
-
|
| 57 |
-
print(f"✅ Base Model พร้อมใช้งาน (Snapshot {len(self._original_layers)} modules)\n")
|
| 58 |
-
|
| 59 |
-
def _restore_original_layers(self):
|
| 60 |
-
"""สแกนหา Module ที่เป็น LoRALinear หรือ LoRASwitchLinear แล้ว Restore กลับเป็นของเดิม"""
|
| 61 |
-
restored_count = 0
|
| 62 |
-
restored_types = {}
|
| 63 |
-
for real_idx in self._layer_range:
|
| 64 |
-
l = self.model.layers[real_idx]
|
| 65 |
-
restore_pairs = []
|
| 66 |
-
for k, m in l.named_modules():
|
| 67 |
-
if isinstance(m, LORA_WRAPPER_TYPES):
|
| 68 |
-
orig = self._original_layers.get((real_idx, k))
|
| 69 |
-
if orig is not None:
|
| 70 |
-
restore_pairs.append((k, orig))
|
| 71 |
-
restored_count += 1
|
| 72 |
-
type_name = type(m).__name__
|
| 73 |
-
restored_types[type_name] = restored_types.get(type_name, 0) + 1
|
| 74 |
-
if restore_pairs:
|
| 75 |
-
l.update_modules(tree_unflatten(restore_pairs))
|
| 76 |
-
return restored_count, restored_types
|
| 77 |
-
|
| 78 |
-
def load_expert(self, expert_name: str):
|
| 79 |
-
if expert_name == self.current_expert:
|
| 80 |
-
return
|
| 81 |
-
|
| 82 |
-
if expert_name not in self.experts:
|
| 83 |
-
raise ValueError(f"ไม่รู้จัก Expert: {expert_name}")
|
| 84 |
-
|
| 85 |
-
info = self.experts[expert_name]
|
| 86 |
-
with open(info["config_path"]) as f:
|
| 87 |
-
adapter_config = json.load(f)
|
| 88 |
-
|
| 89 |
-
num_layers = adapter_config.get("num_layers", 16)
|
| 90 |
-
lora_parameters = adapter_config.get("lora_parameters", {})
|
| 91 |
-
|
| 92 |
-
print(f"🔧 กำลังสลับไปใช้ Expert: '{expert_name}' "
|
| 93 |
-
f"(rank={lora_parameters.get('rank')}, "
|
| 94 |
-
f"scale={lora_parameters.get('scale')}, "
|
| 95 |
-
f"keys={lora_parameters.get('keys', 'default')})")
|
| 96 |
-
|
| 97 |
-
n, types_breakdown = self._restore_original_layers()
|
| 98 |
-
print(f" ↳ Restored {n} module(s) กลับเป็นของเดิม | breakdown: {types_breakdown}")
|
| 99 |
-
|
| 100 |
-
linear_to_lora_layers(self.model, num_layers, lora_parameters, use_dora=False)
|
| 101 |
-
self.model.load_weights(info["weights_path"], strict=False)
|
| 102 |
-
self.model.eval()
|
| 103 |
-
|
| 104 |
-
self.current_expert = expert_name
|
| 105 |
-
print(f"✅ พร้อมใช้งาน Expert: '{expert_name}'\n")
|
| 106 |
-
|
| 107 |
-
def route(self, prompt: str) -> str:
|
| 108 |
-
ts_signals = [
|
| 109 |
-
"typescript", "interface", "type guard", "generic", ": string",
|
| 110 |
-
": number", "utility type", "conditional type", "mapped type",
|
| 111 |
-
]
|
| 112 |
-
py_signals = [
|
| 113 |
-
"python", "def ", "decorator", "list comprehension",
|
| 114 |
-
"generator", "context manager",
|
| 115 |
-
]
|
| 116 |
-
prompt_lower = prompt.lower()
|
| 117 |
-
ts_score = sum(1 for kw in ts_signals if kw in prompt_lower)
|
| 118 |
-
py_score = sum(1 for kw in py_signals if kw in prompt_lower)
|
| 119 |
-
return "typescript" if ts_score > py_score else "python"
|
| 120 |
-
|
| 121 |
-
def generate_response(self, prompt: str, max_tokens: int = 300):
|
| 122 |
-
expert_name = self.route(prompt)
|
| 123 |
-
self.load_expert(expert_name)
|
| 124 |
-
|
| 125 |
-
messages = [{"role": "user", "content": prompt}]
|
| 126 |
-
formatted_prompt = self.tokenizer.apply_chat_template(
|
| 127 |
-
messages, add_generation_prompt=True, tokenize=False
|
| 128 |
-
)
|
| 129 |
-
response = generate(
|
| 130 |
-
self.model, self.tokenizer,
|
| 131 |
-
prompt=formatted_prompt, max_tokens=max_tokens, verbose=False
|
| 132 |
-
)
|
| 133 |
-
return expert_name, response
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
if __name__ == "__main__":
|
| 137 |
-
dispatcher = FrankenMoEDispatcher()
|
| 138 |
-
|
| 139 |
-
test_cases = [
|
| 140 |
-
"Write a Python function to check if a number is prime.",
|
| 141 |
-
"Write a TypeScript type guard function called isArray.",
|
| 142 |
-
"Write a Python decorator that logs execution time.",
|
| 143 |
-
"Create a TypeScript utility type Readonly that makes properties readonly recursively.",
|
| 144 |
-
"Write a Python generator function that yields Fibonacci numbers.",
|
| 145 |
-
"Write a TypeScript function isString that acts as a type guard.",
|
| 146 |
-
"Write a Python function using list comprehension to flatten a nested list.",
|
| 147 |
-
"Write a TypeScript debounce function using closures.",
|
| 148 |
-
]
|
| 149 |
-
|
| 150 |
-
for i, prompt in enumerate(test_cases, 1):
|
| 151 |
-
print("=" * 70)
|
| 152 |
-
print(f"โจทย์ที่ {i}: {prompt}")
|
| 153 |
-
print("=" * 70)
|
| 154 |
-
expert_used, response = dispatcher.generate_response(prompt)
|
| 155 |
-
print(f"[Router เลือก Expert: {expert_used}]")
|
| 156 |
-
print(response)
|
| 157 |
-
print()
|
| 158 |
-
|
| 159 |
-
print("=" * 70)
|
| 160 |
-
print("🎉 สลับ Expert สำเร็จทั้งหมด 8 รอบ โดยไม่มี Error")
|
| 161 |
-
print("=" * 70)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|