MORPH-AI commited on
Commit
e233e2d
·
1 Parent(s): 82f262a

feat: re-apply dynamic MoE expansion, multi-head CoT, plugin architecture, improved MoD

Browse files
Files changed (2) hide show
  1. src/architecture.py +163 -16
  2. src/runtime.py +18 -1
src/architecture.py CHANGED
@@ -80,10 +80,22 @@ class MorphConfig:
80
  lora_dropout: float = 0.05
81
  # MoE
82
  num_experts: int = 4
 
83
  expert_hidden: int = 512
84
  moe_top_k: int = 2
85
  moe_aux_weight: float = 0.01
86
  moe_prune_threshold: float = 0.02
 
 
 
 
 
 
 
 
 
 
 
87
  # memory
88
  memory_size: int = 1024
89
  memory_dim: int = 768
@@ -122,6 +134,8 @@ class MorphConfig:
122
  draft_layers: int = 2
123
  # RoPE scaling for extended context
124
  rope_scaling: Optional[dict] = None
 
 
125
  # training
126
  max_seq_len: int = 2048
127
  # quantization
@@ -387,6 +401,40 @@ class MultiStepReasoner(nn.Module):
387
  return out, scratch[-1]
388
 
389
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
  # ---------------------------------------------------------------------------
391
  # Code structure awareness
392
  # ---------------------------------------------------------------------------
@@ -539,27 +587,32 @@ class QuantizedMemoryModule(MemoryModule):
539
 
540
  class MixtureOfDepths(nn.Module):
541
  """MoD: per-token gating to dynamically skip transformer layers.
542
- Uses a lightweight router to predict keep_prob per token, reducing
543
- compute by ~30-50% with minimal accuracy loss.
544
  """
545
 
546
- def __init__(self, hidden_dim: int, mod_hidden: int, keep_prob: float = 0.8, dropout: float = 0.1):
 
547
  super().__init__()
548
  self.keep_prob = keep_prob
 
 
549
  self.router = nn.Sequential(
550
  nn.Linear(hidden_dim, mod_hidden),
551
  nn.GELU(),
552
  nn.LayerNorm(mod_hidden),
553
  nn.Linear(mod_hidden, 1),
554
- nn.Sigmoid(),
555
  )
556
  self.dropout = nn.Dropout(dropout)
557
 
558
  def forward(self, hidden: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
559
  B, T, H = hidden.shape
560
- probs = self.router(hidden.detach()) # (B, T, 1)
 
561
  mask = torch.bernoulli(probs * 0.5 + self.keep_prob * 0.5).expand_as(hidden)
562
  mask = self.dropout(mask)
 
 
563
  return hidden * mask, probs
564
 
565
 
@@ -610,20 +663,23 @@ class MemoryEfficientAttention(nn.Module):
610
 
611
 
612
  class DynamicMoEBlock(nn.Module):
613
- """Sparse MoE with dynamic expert pruning and load-balancing."""
614
 
615
  def __init__(self, hidden_dim: int, num_experts: int, expert_hidden: int,
616
- top_k: int, prune_threshold: float = 0.02):
617
  super().__init__()
618
  self.num_experts = num_experts
 
619
  self.top_k = top_k
620
  self.prune_threshold = prune_threshold
 
621
  self.gate = nn.Linear(hidden_dim, num_experts, bias=False)
622
  self.experts = nn.ModuleList([
623
  Expert(hidden_dim, expert_hidden) for _ in range(num_experts)
624
  ])
625
  self.expert_usage = torch.zeros(num_experts)
626
  self._pruned = set()
 
627
 
628
  def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
629
  B, T, H = x.shape
@@ -643,7 +699,8 @@ class DynamicMoEBlock(nn.Module):
643
  sel = routing[:, i] > 0
644
  if sel.any():
645
  out[sel] += routing[sel, i].unsqueeze(-1) * expert(flat[sel])
646
- self.expert_usage[i] += sel.sum().item()
 
647
 
648
  f_i = routing.mean(0)
649
  P_i = probs.mean(0)
@@ -651,17 +708,40 @@ class DynamicMoEBlock(nn.Module):
651
 
652
  return out.view(B, T, H), aux
653
 
654
- def prune_experts(self):
655
- """Prune experts with usage below threshold (called periodically during training)."""
656
  total = self.expert_usage.sum()
657
  if total == 0:
 
658
  return
 
659
  usage_ratios = self.expert_usage / total
660
- for i, ratio in enumerate(usage_ratios):
661
- if ratio < self.prune_threshold and len(self._pruned) < self.num_experts - 1:
 
 
662
  self._pruned.add(i)
663
- print(f"Pruned expert {i} (usage ratio {ratio:.4f})")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
664
  self.expert_usage.zero_()
 
665
 
666
 
667
  class MultimodalFusion(nn.Module):
@@ -954,14 +1034,22 @@ class MorphModel(nn.Module):
954
  self.verifier = VerifierHead(hidden_dim)
955
  self.skill_module = SkillTokenModule(self.cfg, hidden_dim)
956
  self.depth_module = DepthEmbeddings(self.cfg, hidden_dim)
957
- self.moe_block = DynamicMoEBlock(hidden_dim, self.cfg.num_experts, self.cfg.expert_hidden, self.cfg.moe_top_k, self.cfg.moe_prune_threshold)
 
 
 
 
958
  self.memory = QuantizedMemoryModule(self.cfg.memory_size, self.cfg.memory_dim, hidden_dim, self.cfg.memory_quantize, self.cfg.memory_quant_bits)
959
- self.mod = MixtureOfDepths(hidden_dim, self.cfg.mod_hidden, self.cfg.mod_keep_prob, self.cfg.mod_dropout)
 
 
 
960
  self.multimodal_fusion = MultimodalFusion(self.cfg, hidden_dim)
961
  self.tool_use = ToolUseModule(self.cfg, hidden_dim)
962
  self.document_module = DocumentModule(self.cfg, hidden_dim)
963
  self.video_module = VideoModule(self.cfg, hidden_dim)
964
  self.code_sandbox = CodeSandbox(self.cfg.sandbox_timeout, self.cfg.sandbox_max_memory)
 
965
 
966
  # cast novel components to the base model's compute dtype
967
  self._dtype = self.base_model_raw.model.embed_tokens.weight.dtype
@@ -969,13 +1057,51 @@ class MorphModel(nn.Module):
969
  self.coordinator, self.reasoner, self.code_bias, self.scratchpad,
970
  self.verifier, self.skill_module, self.depth_module, self.moe_block,
971
  self.memory, self.mod, self.multimodal_fusion, self.tool_use,
972
- self.document_module, self.video_module,
973
  ):
974
  mod.to(self._dtype)
975
 
976
  self.vocab_size = vocab_size
977
  self.base_model = None
978
  self._skill_lora_modules: Dict[str, nn.Module] = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
979
 
980
  # ---- gradient-checkpointing passthrough (Trainer calls these on the top model) ----
981
 
@@ -1086,6 +1212,9 @@ class MorphModel(nn.Module):
1086
  # ---- System-2 reasoning loop ----
1087
  reasoned, scratch = self.reasoner(base_hidden, steps)
1088
 
 
 
 
1089
  # ---- subsystem gates ----
1090
  g_think, g_code, g_mem, g_scratch = gates[:, 0], gates[:, 1], gates[:, 2], gates[:, 3]
1091
  thresh = self.cfg.adaptive_threshold
@@ -1335,9 +1464,13 @@ class MorphModel(nn.Module):
1335
  "coordinator", "reasoner", "code_bias", "scratchpad", "verifier",
1336
  "skill_module", "depth_module", "moe_block", "memory",
1337
  "mod", "multimodal_fusion", "tool_use", "document_module", "video_module",
 
1338
  ):
1339
  for k, v in getattr(self, name).state_dict().items():
1340
  sd[f"{name}.{k}"] = v
 
 
 
1341
  if self.base_model is not None:
1342
  try:
1343
  from peft import get_peft_model_state_dict
@@ -1351,10 +1484,16 @@ class MorphModel(nn.Module):
1351
  "coordinator", "reasoner", "code_bias", "scratchpad", "verifier",
1352
  "skill_module", "depth_module", "moe_block", "memory",
1353
  "mod", "multimodal_fusion", "tool_use", "document_module", "video_module",
 
1354
  ):
1355
  sub = {k[len(name) + 1:]: v for k, v in sd.items() if k.startswith(name + ".")}
1356
  if sub:
1357
  getattr(self, name).load_state_dict(sub)
 
 
 
 
 
1358
  if self.base_model is not None:
1359
  peft_sd = {k: v for k, v in sd.items() if k.startswith("base_model")}
1360
  if peft_sd:
@@ -1381,6 +1520,9 @@ class MorphModel(nn.Module):
1381
  "tool_use": self.tool_use.state_dict(),
1382
  "document_module": self.document_module.state_dict(),
1383
  "video_module": self.video_module.state_dict(),
 
 
 
1384
  "config": self.cfg,
1385
  },
1386
  f"{path}/morph_components.pt",
@@ -1401,9 +1543,14 @@ class MorphModel(nn.Module):
1401
  "coordinator", "reasoner", "code_bias", "scratchpad", "verifier",
1402
  "skill_module", "depth_module", "moe_block", "memory",
1403
  "mod", "multimodal_fusion", "tool_use", "document_module", "video_module",
 
1404
  ):
1405
  if name in ckpt:
1406
  getattr(self, name).load_state_dict(ckpt[name])
 
 
 
 
1407
  self.novel_trained = True
1408
  else:
1409
  trainer_ckpt = self._find_trainer_checkpoint(path)
 
80
  lora_dropout: float = 0.05
81
  # MoE
82
  num_experts: int = 4
83
+ max_experts: int = 64
84
  expert_hidden: int = 512
85
  moe_top_k: int = 2
86
  moe_aux_weight: float = 0.01
87
  moe_prune_threshold: float = 0.02
88
+ moe_expand_threshold: float = 0.15
89
+ # MoD - Mixture of Depths
90
+ use_mod: bool = True
91
+ mod_hidden: int = 128
92
+ mod_dropout: float = 0.1
93
+ mod_keep_prob: float = 0.8
94
+ mod_temperature: float = 1.0
95
+ mod_temperature_anneal: float = 0.995
96
+ # multi-head CoT reasoning
97
+ num_cot_heads: int = 4
98
+ cot_hidden: int = 256
99
  # memory
100
  memory_size: int = 1024
101
  memory_dim: int = 768
 
134
  draft_layers: int = 2
135
  # RoPE scaling for extended context
136
  rope_scaling: Optional[dict] = None
137
+ # plugin architecture
138
+ plugin_dir: Optional[str] = None
139
  # training
140
  max_seq_len: int = 2048
141
  # quantization
 
401
  return out, scratch[-1]
402
 
403
 
404
+ class MultiHeadCoT(nn.Module):
405
+ """Multi-head chain-of-thought reasoning: generates N parallel reasoning paths
406
+ and fuses them for higher accuracy on complex tasks."""
407
+
408
+ def __init__(self, config: MorphConfig, hidden_dim: int):
409
+ super().__init__()
410
+ self.num_heads = config.num_cot_heads
411
+ cot_dim = config.cot_hidden
412
+ self.heads = nn.ModuleList([
413
+ nn.Sequential(
414
+ nn.Linear(hidden_dim, cot_dim),
415
+ nn.GELU(),
416
+ nn.LayerNorm(cot_dim),
417
+ nn.Linear(cot_dim, hidden_dim),
418
+ ) for _ in range(self.num_heads)
419
+ ])
420
+ self.fusion = nn.Sequential(
421
+ nn.Linear(hidden_dim * (self.num_heads + 1), hidden_dim),
422
+ nn.GELU(),
423
+ nn.LayerNorm(hidden_dim),
424
+ nn.Linear(hidden_dim, hidden_dim),
425
+ )
426
+ nn.init.zeros_(self.fusion[-1].weight)
427
+ nn.init.zeros_(self.fusion[-1].bias)
428
+
429
+ def forward(self, hidden: torch.Tensor) -> torch.Tensor:
430
+ B, T, H = hidden.shape
431
+ paths = [hidden]
432
+ for head in self.heads:
433
+ paths.append(head(hidden))
434
+ fused = self.fusion(torch.cat(paths, dim=-1))
435
+ return hidden + fused # Residual connection
436
+
437
+
438
  # ---------------------------------------------------------------------------
439
  # Code structure awareness
440
  # ---------------------------------------------------------------------------
 
587
 
588
  class MixtureOfDepths(nn.Module):
589
  """MoD: per-token gating to dynamically skip transformer layers.
590
+ Uses a lightweight router with temperature annealing for adaptive layer skipping,
591
+ reducing compute by ~30-50% with minimal accuracy loss.
592
  """
593
 
594
+ def __init__(self, hidden_dim: int, mod_hidden: int, keep_prob: float = 0.8,
595
+ dropout: float = 0.1, temperature: float = 1.0, temperature_anneal: float = 0.995):
596
  super().__init__()
597
  self.keep_prob = keep_prob
598
+ self.temperature = temperature
599
+ self.temperature_anneal = temperature_anneal
600
  self.router = nn.Sequential(
601
  nn.Linear(hidden_dim, mod_hidden),
602
  nn.GELU(),
603
  nn.LayerNorm(mod_hidden),
604
  nn.Linear(mod_hidden, 1),
 
605
  )
606
  self.dropout = nn.Dropout(dropout)
607
 
608
  def forward(self, hidden: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
609
  B, T, H = hidden.shape
610
+ logits = self.router(hidden.detach()) # (B, T, 1)
611
+ probs = torch.sigmoid(logits / self.temperature)
612
  mask = torch.bernoulli(probs * 0.5 + self.keep_prob * 0.5).expand_as(hidden)
613
  mask = self.dropout(mask)
614
+ if self.training:
615
+ self.temperature = max(0.1, self.temperature * self.temperature_anneal)
616
  return hidden * mask, probs
617
 
618
 
 
663
 
664
 
665
  class DynamicMoEBlock(nn.Module):
666
+ """Sparse MoE with dynamic expert expansion, pruning, and load-balancing."""
667
 
668
  def __init__(self, hidden_dim: int, num_experts: int, expert_hidden: int,
669
+ top_k: int, prune_threshold: float = 0.02, expand_threshold: float = 0.15, max_experts: int = 64):
670
  super().__init__()
671
  self.num_experts = num_experts
672
+ self.max_experts = max_experts
673
  self.top_k = top_k
674
  self.prune_threshold = prune_threshold
675
+ self.expand_threshold = expand_threshold
676
  self.gate = nn.Linear(hidden_dim, num_experts, bias=False)
677
  self.experts = nn.ModuleList([
678
  Expert(hidden_dim, expert_hidden) for _ in range(num_experts)
679
  ])
680
  self.expert_usage = torch.zeros(num_experts)
681
  self._pruned = set()
682
+ self._expansion_count = 0
683
 
684
  def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
685
  B, T, H = x.shape
 
699
  sel = routing[:, i] > 0
700
  if sel.any():
701
  out[sel] += routing[sel, i].unsqueeze(-1) * expert(flat[sel])
702
+ if i < len(self.expert_usage):
703
+ self.expert_usage[i] += sel.sum().item()
704
 
705
  f_i = routing.mean(0)
706
  P_i = probs.mean(0)
 
708
 
709
  return out.view(B, T, H), aux
710
 
711
+ def prune_and_expand_experts(self):
712
+ """Dynamically prune underused experts and clone overused ones."""
713
  total = self.expert_usage.sum()
714
  if total == 0:
715
+ self.expert_usage.zero_()
716
  return
717
+
718
  usage_ratios = self.expert_usage / total
719
+ active_experts = [i for i in range(len(self.experts)) if i not in self._pruned]
720
+
721
+ for i in active_experts:
722
+ if usage_ratios[i] < self.prune_threshold and len(self._pruned) < len(self.experts) - 1:
723
  self._pruned.add(i)
724
+ print(f"Pruned expert {i} (usage {usage_ratios[i]:.4f})")
725
+
726
+ if len(self.experts) < self.max_experts:
727
+ avg_usage = usage_ratios[active_experts].mean().item()
728
+ for i in active_experts:
729
+ if usage_ratios[i] > self.expand_threshold and len(self.experts) < self.max_experts:
730
+ new_expert = Expert(
731
+ self.experts[i].in_proj.in_features,
732
+ self.experts[i].in_proj.out_features
733
+ )
734
+ new_expert.load_state_dict(self.experts[i].state_dict())
735
+ with torch.no_grad():
736
+ for param in new_expert.parameters():
737
+ param.add_(torch.randn_like(param) * 0.01)
738
+ self.experts.append(new_expert)
739
+ self.expert_usage = torch.cat([self.expert_usage, torch.zeros(1)])
740
+ self._expansion_count += 1
741
+ print(f"Expanded expert {i} -> new expert {len(self.experts)-1}")
742
+
743
  self.expert_usage.zero_()
744
+ print(f"Active experts: {len(self.experts) - len(self._pruned)}/{len(self.experts)}")
745
 
746
 
747
  class MultimodalFusion(nn.Module):
 
1034
  self.verifier = VerifierHead(hidden_dim)
1035
  self.skill_module = SkillTokenModule(self.cfg, hidden_dim)
1036
  self.depth_module = DepthEmbeddings(self.cfg, hidden_dim)
1037
+ self.moe_block = DynamicMoEBlock(
1038
+ hidden_dim, self.cfg.num_experts, self.cfg.expert_hidden,
1039
+ self.cfg.moe_top_k, self.cfg.moe_prune_threshold,
1040
+ self.cfg.moe_expand_threshold, self.cfg.max_experts
1041
+ )
1042
  self.memory = QuantizedMemoryModule(self.cfg.memory_size, self.cfg.memory_dim, hidden_dim, self.cfg.memory_quantize, self.cfg.memory_quant_bits)
1043
+ self.mod = MixtureOfDepths(
1044
+ hidden_dim, self.cfg.mod_hidden, self.cfg.mod_keep_prob,
1045
+ self.cfg.mod_dropout, self.cfg.mod_temperature, self.cfg.mod_temperature_anneal
1046
+ )
1047
  self.multimodal_fusion = MultimodalFusion(self.cfg, hidden_dim)
1048
  self.tool_use = ToolUseModule(self.cfg, hidden_dim)
1049
  self.document_module = DocumentModule(self.cfg, hidden_dim)
1050
  self.video_module = VideoModule(self.cfg, hidden_dim)
1051
  self.code_sandbox = CodeSandbox(self.cfg.sandbox_timeout, self.cfg.sandbox_max_memory)
1052
+ self.cot_reasoner = MultiHeadCoT(self.cfg, hidden_dim)
1053
 
1054
  # cast novel components to the base model's compute dtype
1055
  self._dtype = self.base_model_raw.model.embed_tokens.weight.dtype
 
1057
  self.coordinator, self.reasoner, self.code_bias, self.scratchpad,
1058
  self.verifier, self.skill_module, self.depth_module, self.moe_block,
1059
  self.memory, self.mod, self.multimodal_fusion, self.tool_use,
1060
+ self.document_module, self.video_module, self.code_sandbox, self.cot_reasoner,
1061
  ):
1062
  mod.to(self._dtype)
1063
 
1064
  self.vocab_size = vocab_size
1065
  self.base_model = None
1066
  self._skill_lora_modules: Dict[str, nn.Module] = {}
1067
+ self._plugins: Dict[str, nn.Module] = {}
1068
+
1069
+ # Load plugins from plugin_dir if specified
1070
+ if self.cfg.plugin_dir:
1071
+ self.load_plugins(self.cfg.plugin_dir)
1072
+
1073
+ def load_plugins(self, plugin_dir: str):
1074
+ """Load custom capability plugins from a directory."""
1075
+ import os
1076
+ import importlib.util
1077
+ plugin_path = Path(plugin_dir)
1078
+ if not plugin_path.exists():
1079
+ print(f"Plugin directory not found: {plugin_dir}")
1080
+ return
1081
+
1082
+ for file in plugin_path.glob("*.py"):
1083
+ if file.name.startswith("_"):
1084
+ continue
1085
+ try:
1086
+ spec = importlib.util.spec_from_file_location(file.stem, file)
1087
+ mod = importlib.util.module_from_spec(spec)
1088
+ spec.loader.exec_module(mod)
1089
+ for attr_name in dir(mod):
1090
+ attr = getattr(mod, attr_name)
1091
+ if isinstance(attr, type) and issubclass(attr, nn.Module) and attr is not nn.Module:
1092
+ plugin_name = getattr(attr, 'plugin_name', attr_name)
1093
+ plugin_instance = attr(self.cfg, hidden_dim=self.base_model_raw.config.hidden_size)
1094
+ setattr(self, f"plugin_{plugin_name}", plugin_instance)
1095
+ self._plugins[plugin_name] = plugin_instance
1096
+ plugin_instance.to(self._dtype)
1097
+ print(f"Loaded plugin: {plugin_name} from {file.name}")
1098
+ except Exception as e:
1099
+ print(f"Failed to load plugin {file.name}: {e}")
1100
+ self._plugins: Dict[str, nn.Module] = {}
1101
+
1102
+ # Load plugins from plugin_dir if specified
1103
+ if self.cfg.plugin_dir:
1104
+ self.load_plugins(self.cfg.plugin_dir)
1105
 
1106
  # ---- gradient-checkpointing passthrough (Trainer calls these on the top model) ----
1107
 
 
1212
  # ---- System-2 reasoning loop ----
1213
  reasoned, scratch = self.reasoner(base_hidden, steps)
1214
 
1215
+ # ---- multi-head CoT reasoning ----
1216
+ reasoned = self.cot_reasoner(reasoned)
1217
+
1218
  # ---- subsystem gates ----
1219
  g_think, g_code, g_mem, g_scratch = gates[:, 0], gates[:, 1], gates[:, 2], gates[:, 3]
1220
  thresh = self.cfg.adaptive_threshold
 
1464
  "coordinator", "reasoner", "code_bias", "scratchpad", "verifier",
1465
  "skill_module", "depth_module", "moe_block", "memory",
1466
  "mod", "multimodal_fusion", "tool_use", "document_module", "video_module",
1467
+ "code_sandbox", "cot_reasoner",
1468
  ):
1469
  for k, v in getattr(self, name).state_dict().items():
1470
  sd[f"{name}.{k}"] = v
1471
+ for plugin_name, plugin in self._plugins.items():
1472
+ for k, v in plugin.state_dict().items():
1473
+ sd[f"plugin_{plugin_name}.{k}"] = v
1474
  if self.base_model is not None:
1475
  try:
1476
  from peft import get_peft_model_state_dict
 
1484
  "coordinator", "reasoner", "code_bias", "scratchpad", "verifier",
1485
  "skill_module", "depth_module", "moe_block", "memory",
1486
  "mod", "multimodal_fusion", "tool_use", "document_module", "video_module",
1487
+ "code_sandbox", "cot_reasoner",
1488
  ):
1489
  sub = {k[len(name) + 1:]: v for k, v in sd.items() if k.startswith(name + ".")}
1490
  if sub:
1491
  getattr(self, name).load_state_dict(sub)
1492
+ for plugin_name in self._plugins:
1493
+ prefix = f"plugin_{plugin_name}."
1494
+ sub = {k[len(prefix):]: v for k, v in sd.items() if k.startswith(prefix)}
1495
+ if sub:
1496
+ self._plugins[plugin_name].load_state_dict(sub)
1497
  if self.base_model is not None:
1498
  peft_sd = {k: v for k, v in sd.items() if k.startswith("base_model")}
1499
  if peft_sd:
 
1520
  "tool_use": self.tool_use.state_dict(),
1521
  "document_module": self.document_module.state_dict(),
1522
  "video_module": self.video_module.state_dict(),
1523
+ "code_sandbox": self.code_sandbox.state_dict(),
1524
+ "cot_reasoner": self.cot_reasoner.state_dict(),
1525
+ **{f"plugin_{k}": v.state_dict() for k, v in self._plugins.items()},
1526
  "config": self.cfg,
1527
  },
1528
  f"{path}/morph_components.pt",
 
1543
  "coordinator", "reasoner", "code_bias", "scratchpad", "verifier",
1544
  "skill_module", "depth_module", "moe_block", "memory",
1545
  "mod", "multimodal_fusion", "tool_use", "document_module", "video_module",
1546
+ "code_sandbox", "cot_reasoner",
1547
  ):
1548
  if name in ckpt:
1549
  getattr(self, name).load_state_dict(ckpt[name])
1550
+ for plugin_name in self._plugins:
1551
+ key = f"plugin_{plugin_name}"
1552
+ if key in ckpt:
1553
+ self._plugins[plugin_name].load_state_dict(ckpt[key])
1554
  self.novel_trained = True
1555
  else:
1556
  trainer_ckpt = self._find_trainer_checkpoint(path)
src/runtime.py CHANGED
@@ -101,12 +101,13 @@ class MorphRuntime:
101
  Audio (ASR + TTS), Tools, Documents, Video, Multimodal fusion.
102
  """
103
 
104
- def __init__(self, model_path: str, use_4bit: bool = True, use_cpu: bool = False):
105
  self.model_path = Path(model_path)
106
  self.use_4bit = use_4bit and not use_cpu
107
  self.use_cpu = use_cpu
108
  self.skills: Dict[str, Skill] = {}
109
  self.active_skill: Optional[str] = None
 
110
 
111
  # v5 pipeline layers
112
  self.fsm = RuntimeFSM()
@@ -278,6 +279,8 @@ class MorphRuntime:
278
 
279
  def _load_model(self):
280
  config = MorphConfig()
 
 
281
  self.model = MorphModel(config)
282
 
283
  # base model was loaded bf16 by MorphModel; load the trained adapter
@@ -294,7 +297,21 @@ class MorphRuntime:
294
  self.device = torch.device("cpu")
295
 
296
  self.model = self.model.to(self.device)
 
 
 
 
 
 
297
  print(f"Model loaded on {self.device}")
 
 
 
 
 
 
 
 
298
 
299
  def chat(self, prompt: str, max_tokens: int = 512, temperature: float = 0.7,
300
  skill: Optional[str] = None, image_path: Optional[str] = None,
 
101
  Audio (ASR + TTS), Tools, Documents, Video, Multimodal fusion.
102
  """
103
 
104
+ def __init__(self, model_path: str, use_4bit: bool = True, use_cpu: bool = False, plugin_dir: Optional[str] = None):
105
  self.model_path = Path(model_path)
106
  self.use_4bit = use_4bit and not use_cpu
107
  self.use_cpu = use_cpu
108
  self.skills: Dict[str, Skill] = {}
109
  self.active_skill: Optional[str] = None
110
+ self.plugin_dir = plugin_dir
111
 
112
  # v5 pipeline layers
113
  self.fsm = RuntimeFSM()
 
279
 
280
  def _load_model(self):
281
  config = MorphConfig()
282
+ if self.plugin_dir:
283
+ config.plugin_dir = self.plugin_dir
284
  self.model = MorphModel(config)
285
 
286
  # base model was loaded bf16 by MorphModel; load the trained adapter
 
297
  self.device = torch.device("cpu")
298
 
299
  self.model = self.model.to(self.device)
300
+
301
+ # Enable extended context if configured
302
+ if hasattr(self.model.base_model_raw, 'config') and hasattr(self.model.base_model_raw.config, 'rope_scaling'):
303
+ if self.model.base_model_raw.config.rope_scaling:
304
+ print(f"Extended context enabled: {self.model.base_model_raw.config.rope_scaling}")
305
+
306
  print(f"Model loaded on {self.device}")
307
+ if self.model._plugins:
308
+ print(f"Loaded plugins: {list(self.model._plugins.keys())}")
309
+
310
+ def expand_moe_experts(self):
311
+ """Dynamically expand MoE experts based on usage patterns."""
312
+ if hasattr(self.model, 'moe_block') and hasattr(self.model.moe_block, 'prune_and_expand_experts'):
313
+ self.model.moe_block.prune_and_expand_experts()
314
+ print(f"MoE expanded. Total experts: {len(self.model.moe_block.experts)}")
315
 
316
  def chat(self, prompt: str, max_tokens: int = 512, temperature: float = 0.7,
317
  skill: Optional[str] = None, image_path: Optional[str] = None,