punsaisuwan commited on
Commit
c13d0c1
·
verified ·
1 Parent(s): 968bf96

Upload folder using huggingface_hub

Browse files
README.md ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model: mistralai/Mistral-Small-3.1-24B-Instruct-2503
4
+ tags:
5
+ - lora
6
+ - mlx
7
+ - mixture-of-experts
8
+ - code-generation
9
+ - python
10
+ - typescript
11
+ ---
12
+
13
+ # FrankenMoE: Python + TypeScript Expert LoRA Adapters
14
+
15
+ Dual LoRA adapters ที่ fine-tune บน 4-bit quantized MLX version ของ
16
+ `mistralai/Mistral-Small-3.1-24B-Instruct-2503` แยกตามภาษาโปรแกรมมิ่ง
17
+ พร้อม Dispatcher script สำหรับ hot-swap ระหว่าง Expert แบบไม่ต้องโหลด
18
+ Base Model ใหม่
19
+
20
+ ## Experts
21
+
22
+ | Expert | Language | Rank | Target Modules | Checkpoint |
23
+ |---|---|---|---|---|
24
+ | expert-1-python-v2 | Python | 16 | q,k,v,o_proj | iter 500 |
25
+ | expert-2-typescript | TypeScript | 8 | default (incl. MoE switch layers) | iter 100 |
26
+
27
+ ## Usage
28
+
29
+ ต้องมี Base Model 4-bit อยู่ในเครื่อง (หรือดาวน์โหลดจาก [link ต้นทาง])
30
+
31
+ \`\`\`bash
32
+ python frankenmoe_dispatcher.py
33
+ \`\`\`
34
+
35
+ Dispatcher จะตรวจจับภาษาจาก Prompt และสลับ LoRA Adapter อัตโนมัติ
36
+ โดยจัดการทั้ง \`LoRALinear\` (attention) และ \`LoRASwitchLinear\`
37
+ (MoE expert MLP) อย่างถูกต้อง
38
+
39
+ ## Training Details
40
+
41
+ - **Expert 1 (Python)**: แก้ไขปัญหา rank/capacity ไม่พอ (v1 broken ที่ rank=8)
42
+ โดยอัปเกรดเป็น rank=16, scale=8.0, ครอบคลุม 4 attention projections
43
+ - **Expert 2 (TypeScript)**: แก้ไข NaN loss (SAFE_PAD_TOKEN_ID=198) และ
44
+ Catastrophic Interference ระหว่าง function/type guard bias ผ่าน
45
+ dataset rebalancing และลด learning rate เป็น 1.5e-5
adapters/expert-1-python-v2/adapter_config.json ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "adapter_path": "./adapters/expert-1-python-v2",
3
+ "batch_size": 1,
4
+ "config": "lora_config_expert1_v2.yaml",
5
+ "data": "./datasets/expert-1-python",
6
+ "fine_tune_type": "lora",
7
+ "grad_accumulation_steps": 1,
8
+ "grad_checkpoint": true,
9
+ "iters": 500,
10
+ "learning_rate": 1e-05,
11
+ "lora_parameters": {
12
+ "rank": 16,
13
+ "dropout": 0.0,
14
+ "scale": 8.0,
15
+ "keys": [
16
+ "self_attn.q_proj",
17
+ "self_attn.k_proj",
18
+ "self_attn.v_proj",
19
+ "self_attn.o_proj"
20
+ ]
21
+ },
22
+ "lr_schedule": null,
23
+ "mask_prompt": false,
24
+ "max_seq_length": 2048,
25
+ "model": "./output-moe-mlx-4bit",
26
+ "num_layers": 16,
27
+ "optimizer": "adam",
28
+ "optimizer_config": {
29
+ "adam": {},
30
+ "adamw": {},
31
+ "muon": {},
32
+ "sgd": {},
33
+ "adafactor": {}
34
+ },
35
+ "project_name": null,
36
+ "report_to": null,
37
+ "resume_adapter_file": null,
38
+ "save_every": 50,
39
+ "seed": 0,
40
+ "steps_per_eval": 200,
41
+ "steps_per_report": 10,
42
+ "test": false,
43
+ "test_batches": 500,
44
+ "train": true,
45
+ "val_batches": 25
46
+ }
adapters/expert-1-python-v2/adapters.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bf27527b12cf66f6c375b67b015b70bf0fead0a89b08b09dbc519f92956f17f8
3
+ size 33568580
adapters/expert-2-typescript/adapter_config.json ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "adapter_path": "adapters/expert-2-typescript",
3
+ "batch_size": 1,
4
+ "config": null,
5
+ "data": "datasets/expert-2-typescript",
6
+ "fine_tune_type": "lora",
7
+ "grad_accumulation_steps": 1,
8
+ "grad_checkpoint": false,
9
+ "iters": 150,
10
+ "learning_rate": 1e-05,
11
+ "lora_parameters": {
12
+ "rank": 8,
13
+ "dropout": 0.0,
14
+ "scale": 20.0
15
+ },
16
+ "lr_schedule": null,
17
+ "mask_prompt": false,
18
+ "max_seq_length": 2048,
19
+ "model": "output-moe-mlx-4bit",
20
+ "num_layers": 16,
21
+ "optimizer": "adam",
22
+ "optimizer_config": {
23
+ "adam": {},
24
+ "adamw": {},
25
+ "muon": {},
26
+ "sgd": {},
27
+ "adafactor": {}
28
+ },
29
+ "project_name": null,
30
+ "report_to": null,
31
+ "resume_adapter_file": "adapters/expert-2-typescript/0000400_adapters.safetensors",
32
+ "save_every": 25,
33
+ "seed": 0,
34
+ "steps_per_eval": 25,
35
+ "steps_per_report": 10,
36
+ "test": false,
37
+ "test_batches": 500,
38
+ "train": true,
39
+ "val_batches": 25
40
+ }
adapters/expert-2-typescript/adapters.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f0aba896a07bdc398e29920ab40e69f35812e61d396c79a26952c604e86c611c
3
+ size 126137095
frankenmoe_dispatcher.py ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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)