IMvision12 commited on
Commit
89bbaf8
·
verified ·
1 Parent(s): ced76af

Keep model artifacts on Hub and Python implementation in GitHub source

Browse files
README.md CHANGED
@@ -30,44 +30,45 @@ checkpoint `step-00129185`, used for the reported local benchmarks.
30
 
31
  ![LaMoE architecture](docs/architecture/model.png)
32
 
33
- ## Transformers usage
34
 
35
- This export supports `AutoConfig`, `AutoTokenizer` and `AutoModelForCausalLM` with
36
- `trust_remote_code=True`. It was tested with Transformers **5.17.0** and PyTorch
37
- **2.11.0** on CPU. Install the versions in `requirements.txt` in your own environment.
 
38
 
39
- Load this folder directly:
 
 
 
 
 
 
40
 
41
  ```python
42
- from pathlib import Path
43
  import torch
 
44
  from transformers import AutoTokenizer, AutoModelForCausalLM
45
 
46
- path = str(Path("transformer-lamoe").resolve())
47
- tokenizer = AutoTokenizer.from_pretrained(path, trust_remote_code=True)
 
48
  model = AutoModelForCausalLM.from_pretrained(
49
- path, trust_remote_code=True, dtype=torch.float32,
50
  ).eval()
51
-
52
  inputs = tokenizer.apply_chat_template(
53
  [{"role": "user", "content": "Explain binary search."}],
54
- tokenize=True,
55
- add_generation_prompt=True,
56
- return_tensors="pt",
57
- return_dict=True,
58
  )
59
  with torch.inference_mode():
60
  output = model.generate(**inputs, max_new_tokens=64, do_sample=False)
61
  print(tokenizer.decode(output[0, inputs["input_ids"].shape[1]:], skip_special_tokens=True))
62
  ```
63
 
64
- After uploading this export to `IMvision12/lamoe-247`, replace `path` with that
65
- Hub ID. Pin `revision` to the published commit for a fixed code/weight version.
66
- The existing Hub model is not changed by creating this folder.
67
-
68
- The original trained weights and `tokenizer.json` are preserved byte-for-byte.
69
- Only the export configuration and integration files are new. No retraining was
70
- performed. No identity facts or system prompt are injected by the wrapper.
71
 
72
  ## Supported behavior and boundaries
73
 
@@ -90,7 +91,7 @@ performed. No identity facts or system prompt are injected by the wrapper.
90
  that special case. Tool definitions require an explicit initial system message.
91
  The template renders tool messages only; it never executes a tool.
92
  - The original training source and original inference bundle remain separate.
93
- This folder contains no datasets, collector code or optimizer/RNG states.
94
 
95
  ## Evaluation
96
 
 
30
 
31
  ![LaMoE architecture](docs/architecture/model.png)
32
 
33
+ ## Load with the LaMoE source package
34
 
35
+ **All Python implementation code is maintained in the [GitHub source repository](https://github.com/IMvision12/LaMoE).**
36
+ This model repository holds weights, tokenizer/configuration files and documentation only.
37
+ LaMoE is a custom architecture, so install its source package before loading it.
38
+ `trust_remote_code=True` by itself cannot load this code-free model repository.
39
 
40
+ Download the source from GitHub, open the source folder, and install it in your own environment:
41
+
42
+ ```sh
43
+ python -m pip install -e ".[transformers]"
44
+ ```
45
+
46
+ Then register the installed implementation and load the model:
47
 
48
  ```python
 
49
  import torch
50
+ from lamoe.transformers import register
51
  from transformers import AutoTokenizer, AutoModelForCausalLM
52
 
53
+ register()
54
+ repo = "IMvision12/lamoe-247" # Or a local downloaded model-files folder.
55
+ tokenizer = AutoTokenizer.from_pretrained(repo, trust_remote_code=False)
56
  model = AutoModelForCausalLM.from_pretrained(
57
+ repo, trust_remote_code=False, dtype=torch.float32,
58
  ).eval()
 
59
  inputs = tokenizer.apply_chat_template(
60
  [{"role": "user", "content": "Explain binary search."}],
61
+ tokenize=True, add_generation_prompt=True, return_tensors="pt", return_dict=True,
 
 
 
62
  )
63
  with torch.inference_mode():
64
  output = model.generate(**inputs, max_new_tokens=64, do_sample=False)
65
  print(tokenizer.decode(output[0, inputs["input_ids"].shape[1]:], skip_special_tokens=True))
66
  ```
67
 
68
+ The tested environment is Transformers **5.17.0** and PyTorch **2.11.0**, using CPU.
69
+ Pin the Hub `revision` and source version for a fixed model/code combination.
70
+ The original trained weights and frozen tokenizer are unchanged. No retraining
71
+ or identity/system prompt injection is performed by the adapter.
 
 
 
72
 
73
  ## Supported behavior and boundaries
74
 
 
91
  that special case. Tool definitions require an explicit initial system message.
92
  The template renders tool messages only; it never executes a tool.
93
  - The original training source and original inference bundle remain separate.
94
+ This model repository contains no Python implementation, datasets, collector code or optimizer/RNG states.
95
 
96
  ## Evaluation
97
 
__init__.py DELETED
@@ -1 +0,0 @@
1
- """LaMoE model repository; load its config/model through the AutoClass mappings."""
 
 
cache.py DELETED
@@ -1,37 +0,0 @@
1
- """Inference state. MLA keeps latents; GDN keeps recurrent and convolution state."""
2
-
3
- from dataclasses import dataclass
4
-
5
- import torch
6
-
7
-
8
- @dataclass(frozen=True)
9
- class MLACache:
10
- latent: torch.Tensor # [batch, past_length, kv_rank], after RMSNorm
11
- rotary_key: torch.Tensor # [batch, past_length, rotary_dim], after RoPE
12
-
13
-
14
- @dataclass(frozen=True)
15
- class GDNCache:
16
- state: torch.Tensor # [batch, heads, value_dim, key_dim], FP32
17
- conv_q: torch.Tensor # [batch, kernel_width - 1, heads * key_dim]
18
- conv_k: torch.Tensor
19
- conv_v: torch.Tensor
20
-
21
-
22
- @dataclass(frozen=True)
23
- class LayerCache:
24
- mla: MLACache
25
- gdn: GDNCache | None = None
26
-
27
-
28
- @dataclass(frozen=True)
29
- class ModelCache:
30
- layers: tuple[LayerCache, ...]
31
- document_ids: torch.Tensor # [batch, past_length], internal contiguous runs
32
- last_segment_ids: torch.Tensor
33
- last_position_ids: torch.Tensor
34
-
35
- @property
36
- def length(self) -> int:
37
- return self.document_ids.shape[1]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
caching_lamoe.py DELETED
@@ -1,81 +0,0 @@
1
- """Transformers Cache adapter retaining MLA, GDN and convolution histories."""
2
-
3
- from dataclasses import replace
4
-
5
- import torch
6
- from transformers.cache_utils import Cache
7
-
8
- from .cache import GDNCache, LayerCache, MLACache, ModelCache
9
-
10
-
11
- class LaMoECache(Cache):
12
- def __init__(self, state: ModelCache | None = None, max_length=2048):
13
- super().__init__(layers=[])
14
- self.state = state
15
- self.max_length = max_length
16
-
17
- def get_seq_length(self, layer_idx=0):
18
- return 0 if self.state is None else self.state.length
19
-
20
- def get_max_length(self, layer_idx=0):
21
- return self.max_length
22
-
23
- def get_max_cache_shape(self, layer_idx=0):
24
- return self.max_length
25
-
26
- @property
27
- def is_compileable(self):
28
- return False
29
-
30
- @property
31
- def is_initialized(self):
32
- return self.state is not None
33
-
34
- @property
35
- def max_batch_size(self):
36
- return 0 if self.state is None else self.state.document_ids.shape[0]
37
-
38
- @property
39
- def max_cache_len(self):
40
- return self.max_length
41
-
42
- def _map(self, function):
43
- if self.state is None:
44
- return
45
- layers = []
46
- for layer in self.state.layers:
47
- mla = MLACache(function(layer.mla.latent), function(layer.mla.rotary_key))
48
- gdn = None
49
- if layer.gdn is not None:
50
- old = layer.gdn
51
- gdn = GDNCache(
52
- function(old.state),
53
- function(old.conv_q),
54
- function(old.conv_k),
55
- function(old.conv_v),
56
- )
57
- layers.append(LayerCache(mla, gdn))
58
- self.state = replace(
59
- self.state,
60
- layers=tuple(layers),
61
- document_ids=function(self.state.document_ids),
62
- last_segment_ids=function(self.state.last_segment_ids),
63
- last_position_ids=function(self.state.last_position_ids),
64
- )
65
-
66
- def reorder_cache(self, beam_idx: torch.LongTensor):
67
- self._map(lambda tensor: tensor.index_select(0, beam_idx.to(tensor.device)))
68
-
69
- def batch_select_indices(self, indices):
70
- self.reorder_cache(indices)
71
-
72
- def batch_repeat_interleave(self, repeats):
73
- self._map(lambda tensor: tensor.repeat_interleave(repeats, dim=0))
74
-
75
- def reset(self):
76
- self.state = None
77
-
78
- def crop(self, max_length):
79
- raise NotImplementedError(
80
- "LaMoE recurrent states cannot be cropped; assisted/speculative decoding is unsupported"
81
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
config.json CHANGED
@@ -2,10 +2,6 @@
2
  "architectures": [
3
  "LaMoEForCausalLM"
4
  ],
5
- "auto_map": {
6
- "AutoConfig": "configuration_lamoe.LaMoEConfig",
7
- "AutoModelForCausalLM": "modeling_lamoe.LaMoEForCausalLM"
8
- },
9
  "bos_token_id": 2,
10
  "dense_ffn_width": 2048,
11
  "eos_token_id": [
 
2
  "architectures": [
3
  "LaMoEForCausalLM"
4
  ],
 
 
 
 
5
  "bos_token_id": 2,
6
  "dense_ffn_width": 2048,
7
  "eos_token_id": [
configuration_lamoe.py DELETED
@@ -1,49 +0,0 @@
1
- """Transformers configuration for the unchanged LaMoE architecture."""
2
-
3
- from dataclasses import fields
4
-
5
- from transformers import PreTrainedConfig
6
-
7
- from .core_config import LaMoEConfig as CoreConfig
8
-
9
-
10
- class LaMoEConfig(PreTrainedConfig):
11
- model_type = "lamoe"
12
- keys_to_ignore_at_inference = ["past_key_values"]
13
- attribute_map = {
14
- "num_hidden_layers": "num_layers",
15
- "num_attention_heads": "mla_heads",
16
- }
17
-
18
- def __init__(self, **kwargs):
19
- defaults = CoreConfig().to_dict()
20
- defaults["gdn_backend"] = "reference"
21
- values = {name: kwargs.pop(name, default) for name, default in defaults.items()}
22
- validated = CoreConfig(**values)
23
- if kwargs.pop("tie_word_embeddings", True) is not True:
24
- raise ValueError("LaMoE requires tied embedding/output weights")
25
- if kwargs.pop("is_encoder_decoder", False):
26
- raise ValueError("LaMoE is a decoder-only model")
27
- kwargs.pop("is_decoder", None)
28
- use_cache = kwargs.pop("use_cache", True)
29
- super().__init__(
30
- tie_word_embeddings=True,
31
- is_encoder_decoder=False,
32
- is_decoder=True,
33
- bos_token_id=kwargs.pop("bos_token_id", 2),
34
- eos_token_id=kwargs.pop("eos_token_id", [3, 10]),
35
- pad_token_id=kwargs.pop("pad_token_id", 0),
36
- **kwargs,
37
- )
38
- for name, value in validated.to_dict().items():
39
- setattr(self, name, value)
40
- self.use_cache = use_cache
41
-
42
- @property
43
- def num_moe_layers(self):
44
- return self.num_layers // 2
45
-
46
- def to_core_config(self):
47
- return CoreConfig(
48
- **{item.name: getattr(self, item.name) for item in fields(CoreConfig)}
49
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core_config.py DELETED
@@ -1,130 +0,0 @@
1
- """Architecture configuration for the LaMoE v0.4 specification."""
2
-
3
- import json
4
- import math
5
- from dataclasses import asdict, dataclass, fields
6
- from pathlib import Path
7
-
8
-
9
- @dataclass(frozen=True)
10
- class LaMoEConfig:
11
- vocab_size: int = 32_000
12
- hidden_size: int = 768
13
- num_layers: int = 16
14
- max_position_embeddings: int = 2048
15
- mla_heads: int = 12
16
- q_rank: int = 192
17
- kv_rank: int = 192
18
- qk_content_dim: int = 48
19
- qk_rope_dim: int = 16
20
- value_head_dim: int = 64
21
- rope_theta: float = 10_000.0
22
- rms_norm_eps: float = 1e-6
23
- hybrid_layers: tuple[int, ...] = (4, 8, 12, 16) # One-based.
24
- gdn_heads: int = 4
25
- gdn_key_dim: int = 64
26
- gdn_value_dim: int = 64
27
- gdn_conv_width: int = 4
28
- gdn_backend: str = "auto"
29
- mla_backend: str = "sdpa"
30
- moe_backend: str = "reference"
31
- fusion_gate_init: float = 0.1
32
- mla_head_gate: bool = False
33
- head_gate_bias_init: float | None = None
34
- dense_ffn_width: int = 2048
35
- num_experts: int = 8
36
- experts_per_token: int = 2
37
- expert_ffn_width: int = 1024
38
- shared_ffn_width: int = 512
39
- initializer_std: float = 0.02
40
- router_initializer_std: float = 0.01
41
- load_balance_coef: float = 0.01
42
- router_z_coef: float = 0.001
43
-
44
- def __post_init__(self):
45
- object.__setattr__(self, "hybrid_layers", tuple(self.hybrid_layers))
46
- for field in fields(self):
47
- value = getattr(self, field.name)
48
- if field.type is int and (type(value) is not int or value <= 0):
49
- raise ValueError(f"{field.name} must be a positive integer")
50
- if self.qk_rope_dim % 2:
51
- raise ValueError("qk_rope_dim must be even")
52
- if self.experts_per_token > self.num_experts:
53
- raise ValueError("experts_per_token exceeds num_experts")
54
- if len(set(self.hybrid_layers)) != len(self.hybrid_layers) or any(
55
- type(i) is not int or not 1 <= i <= self.num_layers
56
- for i in self.hybrid_layers
57
- ):
58
- raise ValueError(
59
- "hybrid_layers must contain unique, one-based layer numbers"
60
- )
61
- if self.gdn_backend not in ("auto", "reference", "fla"):
62
- raise ValueError("gdn_backend must be auto, reference, or fla")
63
- if self.mla_backend not in ("sdpa", "flash_varlen"):
64
- raise ValueError("mla_backend must be sdpa or flash_varlen")
65
- if self.moe_backend not in ("reference", "batched"):
66
- raise ValueError("moe_backend must be reference or batched")
67
- if not 0 < self.fusion_gate_init < 1:
68
- raise ValueError("fusion_gate_init must be strictly between zero and one")
69
- for name in (
70
- "rope_theta",
71
- "rms_norm_eps",
72
- "initializer_std",
73
- "router_initializer_std",
74
- ):
75
- if not math.isfinite(getattr(self, name)) or getattr(self, name) <= 0:
76
- raise ValueError(f"{name} must be positive and finite")
77
- for name in ("load_balance_coef", "router_z_coef"):
78
- if not math.isfinite(getattr(self, name)) or getattr(self, name) < 0:
79
- raise ValueError(f"{name} must be nonnegative and finite")
80
- if self.mla_head_gate and self.head_gate_bias_init is None:
81
- raise ValueError(
82
- "Choose head_gate_bias_init explicitly when enabling the experimental MLA gate"
83
- )
84
- if self.head_gate_bias_init is not None and not math.isfinite(
85
- self.head_gate_bias_init
86
- ):
87
- raise ValueError("head_gate_bias_init must be finite")
88
-
89
- @property
90
- def num_moe_layers(self) -> int:
91
- return self.num_layers // 2
92
-
93
- @classmethod
94
- def tiny(cls, **overrides):
95
- """Small dimensions for development; preserves the four-layer pattern."""
96
- values = {
97
- "vocab_size": 128,
98
- "hidden_size": 32,
99
- "num_layers": 4,
100
- "max_position_embeddings": 64,
101
- "mla_heads": 2,
102
- "q_rank": 8,
103
- "kv_rank": 8,
104
- "qk_content_dim": 12,
105
- "qk_rope_dim": 4,
106
- "value_head_dim": 16,
107
- "hybrid_layers": (4,),
108
- "gdn_heads": 2,
109
- "gdn_key_dim": 8,
110
- "gdn_value_dim": 8,
111
- "dense_ffn_width": 64,
112
- "num_experts": 4,
113
- "expert_ffn_width": 32,
114
- "shared_ffn_width": 16,
115
- "gdn_backend": "reference",
116
- }
117
- values.update(overrides)
118
- return cls(**values)
119
-
120
- def to_dict(self) -> dict:
121
- return asdict(self)
122
-
123
- def save(self, path: str | Path) -> None:
124
- Path(path).write_text(
125
- json.dumps(self.to_dict(), indent=2) + "\n", encoding="utf-8"
126
- )
127
-
128
- @classmethod
129
- def load(cls, path: str | Path):
130
- return cls(**json.loads(Path(path).read_text(encoding="utf-8")))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
feedforward.py DELETED
@@ -1,116 +0,0 @@
1
- """SwiGLU and a dropless top-k MoE with one always-active shared expert."""
2
-
3
- from dataclasses import dataclass
4
-
5
- import torch
6
- from torch import nn
7
- from torch.nn import functional as F
8
-
9
- from .core_config import LaMoEConfig
10
-
11
-
12
- class SwiGLU(nn.Module):
13
- def __init__(self, hidden_size, intermediate_size):
14
- super().__init__()
15
- self.gate_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
16
- self.up_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
17
- self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)
18
-
19
- def forward(self, x):
20
- return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
21
-
22
-
23
- @dataclass(frozen=True)
24
- class RouterStatistics:
25
- assignment_counts: torch.Tensor
26
- probability_sum: torch.Tensor
27
- z_loss_sum: torch.Tensor
28
- token_count: int
29
-
30
-
31
- class MixtureOfExperts(nn.Module):
32
- """Portable expert dispatch. Grouped GPU dispatch remains a performance task."""
33
-
34
- def __init__(self, config: LaMoEConfig):
35
- super().__init__()
36
- self.config = config
37
- self.router = nn.Linear(config.hidden_size, config.num_experts, bias=False)
38
- self.shared_expert = SwiGLU(config.hidden_size, config.shared_ffn_width)
39
- self.experts = nn.ModuleList(
40
- [
41
- SwiGLU(config.hidden_size, config.expert_ffn_width)
42
- for _ in range(config.num_experts)
43
- ]
44
- )
45
-
46
- def forward(self, x, valid):
47
- flat = x.reshape(-1, x.shape[-1])
48
- token_indices = valid.reshape(-1).nonzero(as_tuple=True)[0]
49
- tokens = flat.index_select(0, token_indices)
50
- with torch.autocast(device_type=x.device.type, enabled=False):
51
- logits = F.linear(tokens.float(), self.router.weight.float())
52
- chosen_logits, chosen_experts = logits.topk(
53
- self.config.experts_per_token, dim=-1
54
- )
55
- weights = chosen_logits.softmax(dim=-1)
56
- statistics = RouterStatistics(
57
- torch.bincount(
58
- chosen_experts.flatten(), minlength=self.config.num_experts
59
- ).float(),
60
- logits.softmax(dim=-1).sum(0),
61
- logits.logsumexp(dim=-1).square().sum(),
62
- tokens.shape[0],
63
- )
64
- result = self.shared_expert(tokens)
65
- # No capacity limit: every selected token/expert pair is evaluated exactly once.
66
- if self.config.moe_backend == "batched" and tokens.shape[0]:
67
- result = result + self._batched(tokens, chosen_experts, weights)
68
- for expert_id, expert in enumerate(
69
- self.experts if self.config.moe_backend == "reference" else []
70
- ):
71
- selected_tokens, slots = (chosen_experts == expert_id).nonzero(
72
- as_tuple=True
73
- )
74
- if selected_tokens.numel():
75
- expert_output = expert(tokens.index_select(0, selected_tokens))
76
- contribution = expert_output * weights[selected_tokens, slots, None].to(
77
- expert_output.dtype
78
- )
79
- result = result.index_add(0, selected_tokens, contribution)
80
- output = torch.zeros_like(flat).index_copy(
81
- 0, token_indices, result.to(flat.dtype)
82
- )
83
- return output.reshape_as(x), statistics
84
-
85
- def _batched(self, tokens, choices, weights):
86
- # Dropless padded BMM. Only active experts are stacked so empty experts
87
- # retain grad=None and do not change optimizer momentum/decay behavior.
88
- e = choices.flatten()
89
- order = e.argsort(stable=True)
90
- counts = torch.bincount(e, minlength=len(self.experts))
91
- active = counts.nonzero(as_tuple=True)[0].tolist()
92
- c = counts[active]
93
- width = int(c.max())
94
- group = torch.repeat_interleave(torch.arange(len(active), device=e.device), c)
95
- start = c.cumsum(0) - c
96
- slot = torch.arange(e.numel(), device=e.device) - torch.repeat_interleave(
97
- start, c
98
- )
99
- tok = order // self.config.experts_per_token
100
- location = group * width + slot
101
- padded = tokens.new_zeros(len(active) * width, tokens.shape[-1]).index_copy(
102
- 0, location, tokens[tok]
103
- )
104
- padded = padded.reshape(len(active), width, -1)
105
-
106
- def weight(name):
107
- return torch.stack(
108
- [getattr(self.experts[i], name).weight for i in active]
109
- ).transpose(1, 2)
110
-
111
- hidden = F.silu(torch.bmm(padded, weight("gate_proj"))) * torch.bmm(
112
- padded, weight("up_proj")
113
- )
114
- out = torch.bmm(hidden, weight("down_proj")).flatten(0, 1)[location]
115
- out = out * weights.flatten()[order, None].to(out.dtype)
116
- return torch.zeros_like(tokens).index_add(0, tok, out.to(tokens.dtype))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
gated_deltanet.py DELETED
@@ -1,221 +0,0 @@
1
- """Gated DeltaNet branch with explicit decay, write, and document resets.
2
-
3
- The differentiable PyTorch recurrence is the CPU correctness implementation.
4
- CUDA auto mode requires FLA's chunk_gated_delta_rule; no slow silent fallback.
5
- """
6
-
7
- import math
8
-
9
- import torch
10
- from torch import nn
11
- from torch.nn import functional as F
12
-
13
- from .cache import GDNCache
14
- from .core_config import LaMoEConfig
15
- from .normalization import RMSNorm
16
-
17
-
18
- class CausalDepthwiseConv(nn.Module):
19
- def __init__(self, channels, width):
20
- super().__init__()
21
- self.width = width
22
- self.weight = nn.Parameter(torch.empty(channels, 1, width))
23
- nn.init.uniform_(self.weight, -1 / math.sqrt(width), 1 / math.sqrt(width))
24
-
25
- def forward(self, x, layout, history=None):
26
- batch, length, channels = x.shape
27
- keep = self.width - 1
28
- if history is None:
29
- history = x.new_zeros(batch, keep, channels)
30
- full = torch.cat((history, x * layout.valid[..., None]), dim=1)
31
- result = torch.zeros_like(x)
32
- for tap in range(self.width):
33
- lag = self.width - tap - 1
34
- permitted = layout.valid & (layout.position_ids >= lag)
35
- result = (
36
- result
37
- + full[:, tap : tap + length]
38
- * self.weight[:, 0, tap].to(x.dtype)
39
- * permitted[..., None]
40
- )
41
- tail = full[:, -keep:] if keep else full[:, :0]
42
- if keep:
43
- lags = torch.arange(keep - 1, -1, -1, device=x.device)
44
- tail = tail * (lags[None, :] <= layout.position_ids[:, -1:])[..., None]
45
- return F.silu(result), tail
46
-
47
-
48
- def reference_delta_rule(q, k, v, log_decay, beta, layout, initial_state=None):
49
- """Sequential FP32 reference; state is [batch, heads, value, key]."""
50
- batch, length, heads, key_dim = q.shape
51
- with torch.autocast(device_type=q.device.type, enabled=False):
52
- q, k, v = q.float(), k.float(), v.float()
53
- state = (
54
- q.new_zeros(batch, heads, v.shape[-1], key_dim)
55
- if initial_state is None
56
- else initial_state.float()
57
- )
58
- outputs = []
59
- for t in range(length):
60
- state = torch.where(layout.reset[:, t, None, None, None], 0.0, state)
61
- decayed = state * log_decay[:, t].exp()[..., None, None]
62
- error = v[:, t] - (decayed * k[:, t, :, None, :]).sum(-1)
63
- updated = (
64
- decayed
65
- + beta[:, t, :, None, None] * error[..., None] * k[:, t, :, None, :]
66
- )
67
- state = torch.where(layout.valid[:, t, None, None, None], updated, state)
68
- read = (state * q[:, t, :, None, :]).sum(-1) * (key_dim**-0.5)
69
- outputs.append(read * layout.valid[:, t, None, None])
70
- return torch.stack(outputs, dim=1), state
71
-
72
-
73
- def fla_delta_rule(
74
- q, k, v, log_decay, beta, layout, initial_state=None, use_cache=False
75
- ):
76
- """Unpad runs into independent FLA sequences; restore output batch shape.
77
-
78
- API reference: fla/ops/gated_delta_rule/chunk.py, checked 2026-09-27.
79
- The CUDA path still requires numerical comparison and profiling on the A100.
80
- """
81
- if q.device.type != "cuda" or q.dtype not in (torch.bfloat16, torch.float16):
82
- raise ValueError(
83
- "FLA requires CUDA and BF16/FP16 autocast; use reference for CPU checks"
84
- )
85
- try:
86
- from fla.ops.gated_delta_rule import chunk_gated_delta_rule
87
- except ImportError as exc:
88
- raise RuntimeError(
89
- "Install a compatible flash-linear-attention on the GPU host, or explicitly select gdn_backend='reference' for debugging"
90
- ) from exc
91
- batch, length, heads, key_dim = q.shape
92
- indices = layout.valid.reshape(-1).nonzero(as_tuple=True)[0]
93
- if indices.numel() == 0:
94
- return v * 0, v.new_zeros(
95
- batch, heads, v.shape[-1], key_dim, dtype=torch.float32
96
- )
97
- starts = layout.reset.clone()
98
- starts[:, 0] = layout.valid[:, 0]
99
- flat_starts = starts.reshape(-1)[indices].nonzero(as_tuple=True)[0]
100
- cu_seqlens = torch.cat((flat_starts, flat_starts.new_tensor([indices.numel()]))).to(
101
- torch.int32
102
- )
103
-
104
- def pack(x):
105
- return x.flatten(0, 1).index_select(0, indices).unsqueeze(0).contiguous()
106
-
107
- state_in = None
108
- if initial_state is not None:
109
- original_starts = indices[flat_starts]
110
- rows = original_starts // length
111
- continuing = (original_starts % length == 0) & ~layout.reset[rows, 0]
112
- state_in = initial_state[rows] * continuing[:, None, None, None]
113
- out, state_out = chunk_gated_delta_rule(
114
- q=pack(q),
115
- k=pack(k),
116
- v=pack(v),
117
- g=pack(log_decay),
118
- beta=pack(beta),
119
- scale=key_dim**-0.5,
120
- initial_state=state_in,
121
- output_final_state=use_cache,
122
- state_v_first=True,
123
- cu_seqlens=cu_seqlens,
124
- use_qk_l2norm_in_kernel=False,
125
- use_gate_in_kernel=False,
126
- use_beta_sigmoid_in_kernel=False,
127
- allow_neg_eigval=False,
128
- )
129
- out = v.new_zeros(batch * length, heads, v.shape[-1]).index_copy(
130
- 0, indices, out.squeeze(0)
131
- )
132
- final = None
133
- if use_cache:
134
- # Cached inference is unpadded, so every row has at least one sequence.
135
- last_sequence = starts.sum(dim=1).cumsum(0) - 1
136
- final = state_out[last_sequence].float()
137
- return out.reshape_as(v), final
138
-
139
-
140
- class GatedDeltaNet(nn.Module):
141
- def __init__(self, config: LaMoEConfig):
142
- super().__init__()
143
- self.config = config
144
- d, heads = config.hidden_size, config.gdn_heads
145
- key_width, value_width = (
146
- heads * config.gdn_key_dim,
147
- heads * config.gdn_value_dim,
148
- )
149
- self.q_proj = nn.Linear(d, key_width, bias=False)
150
- self.k_proj = nn.Linear(d, key_width, bias=False)
151
- self.v_proj = nn.Linear(d, value_width, bias=False)
152
- self.z_proj = nn.Linear(d, value_width, bias=False)
153
- self.a_proj = nn.Linear(d, heads, bias=False)
154
- self.beta_proj = nn.Linear(d, heads, bias=False)
155
- self.out_proj = nn.Linear(value_width, d, bias=False)
156
- self.conv_q = CausalDepthwiseConv(key_width, config.gdn_conv_width)
157
- self.conv_k = CausalDepthwiseConv(key_width, config.gdn_conv_width)
158
- self.conv_v = CausalDepthwiseConv(value_width, config.gdn_conv_width)
159
- self.A_log = nn.Parameter(torch.empty(heads))
160
- self.dt_bias = nn.Parameter(torch.empty(heads))
161
- self.output_norm = RMSNorm(config.gdn_value_dim, config.rms_norm_eps)
162
- self.reset_dynamics()
163
-
164
- def reset_dynamics(self):
165
- with torch.no_grad():
166
- self.A_log.copy_(torch.empty_like(self.A_log).uniform_(1, 16).log())
167
- delta = (
168
- torch.empty_like(self.dt_bias)
169
- .uniform_(math.log(0.001), math.log(0.1))
170
- .exp()
171
- )
172
- self.dt_bias.copy_(delta + torch.log(-torch.expm1(-delta)))
173
- for conv in (self.conv_q, self.conv_k, self.conv_v):
174
- nn.init.uniform_(
175
- conv.weight, -1 / math.sqrt(conv.width), 1 / math.sqrt(conv.width)
176
- )
177
-
178
- def forward(self, u, layout, cache: GDNCache | None = None, use_cache=False):
179
- cfg = self.config
180
- batch, length, _ = u.shape
181
- q, hist_q = self.conv_q(
182
- self.q_proj(u), layout, None if cache is None else cache.conv_q
183
- )
184
- k, hist_k = self.conv_k(
185
- self.k_proj(u), layout, None if cache is None else cache.conv_k
186
- )
187
- v, hist_v = self.conv_v(
188
- self.v_proj(u), layout, None if cache is None else cache.conv_v
189
- )
190
- q = q.reshape(batch, length, cfg.gdn_heads, cfg.gdn_key_dim)
191
- k = k.reshape_as(q)
192
- v = v.reshape(batch, length, cfg.gdn_heads, cfg.gdn_value_dim)
193
- with torch.autocast(device_type=u.device.type, enabled=False):
194
- q = F.normalize(q.float(), p=2, dim=-1, eps=1e-6).to(v.dtype)
195
- k = F.normalize(k.float(), p=2, dim=-1, eps=1e-6).to(v.dtype)
196
- decay_input = F.linear(u.float(), self.a_proj.weight.float())
197
- log_decay = -self.A_log.float().exp() * F.softplus(
198
- decay_input + self.dt_bias.float()
199
- )
200
- beta = F.linear(u.float(), self.beta_proj.weight.float()).sigmoid()
201
- initial = None if cache is None else cache.state
202
- backend = cfg.gdn_backend
203
- if backend == "auto":
204
- backend = "fla" if u.device.type == "cuda" else "reference"
205
- if backend == "fla":
206
- result, state = fla_delta_rule(
207
- q, k, v, log_decay, beta, layout, initial, use_cache
208
- )
209
- else:
210
- result, state = reference_delta_rule(
211
- q, k, v, log_decay, beta, layout, initial
212
- )
213
- z = self.z_proj(u).reshape_as(v)
214
- result = self.output_norm(result.to(v.dtype)) * F.silu(z)
215
- result = self.out_proj(result.flatten(2)) * layout.valid[..., None]
216
- updated = None
217
- if use_cache:
218
- updated = GDNCache(
219
- state.detach(), hist_q.detach(), hist_k.detach(), hist_v.detach()
220
- )
221
- return result, updated
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
layers.py DELETED
@@ -1,89 +0,0 @@
1
- """Pre-RMSNorm decoder blocks with separate attention and expert routers."""
2
-
3
- import torch
4
- from torch import nn
5
- from torch.nn import functional as F
6
-
7
- from .cache import LayerCache
8
- from .core_config import LaMoEConfig
9
- from .feedforward import MixtureOfExperts, SwiGLU
10
- from .gated_deltanet import GatedDeltaNet
11
- from .mla import MultiHeadLatentAttention
12
- from .normalization import RMSNorm
13
-
14
-
15
- class DecoderLayer(nn.Module):
16
- def __init__(self, config: LaMoEConfig, layer_number: int):
17
- super().__init__()
18
- self.layer_number = layer_number
19
- self.attention_norm = RMSNorm(config.hidden_size, config.rms_norm_eps)
20
- self.ffn_norm = RMSNorm(config.hidden_size, config.rms_norm_eps)
21
- self.mla = MultiHeadLatentAttention(config)
22
- self.gdn = (
23
- GatedDeltaNet(config) if layer_number in config.hybrid_layers else None
24
- )
25
- self.attention_router = (
26
- nn.Linear(config.hidden_size, 1, bias=True)
27
- if self.gdn is not None
28
- else None
29
- )
30
- self.ffn = (
31
- MixtureOfExperts(config)
32
- if layer_number % 2 == 0
33
- else SwiGLU(config.hidden_size, config.dense_ffn_width)
34
- )
35
-
36
- def forward(self, x, layout, cache: LayerCache | None = None, use_cache=False):
37
- u = self.attention_norm(x)
38
- attention, mla_cache = self.mla(
39
- u, layout, None if cache is None else cache.mla, use_cache
40
- )
41
- gdn_cache, gate_mean = None, None
42
- if self.gdn is not None:
43
- recurrent, gdn_cache = self.gdn(
44
- u, layout, None if cache is None else cache.gdn, use_cache
45
- )
46
- with torch.autocast(device_type=x.device.type, enabled=False):
47
- gate = F.linear(
48
- u.float(),
49
- self.attention_router.weight.float(),
50
- self.attention_router.bias.float(),
51
- ).sigmoid()
52
- gate = gate.to(attention.dtype)
53
- if getattr(self, "collect_diagnostics", False):
54
- from lamoe.train.diagnostics import distribution
55
-
56
- valid = layout.valid
57
- self.diagnostics = distribution(gate[valid])
58
- self.diagnostics.update(
59
- weighted_mla_rms=float(
60
- ((1 - gate) * attention)
61
- .detach()
62
- .float()[valid]
63
- .square()
64
- .mean()
65
- .sqrt()
66
- ),
67
- weighted_gdn_rms=float(
68
- (gate * recurrent)
69
- .detach()
70
- .float()[valid]
71
- .square()
72
- .mean()
73
- .sqrt()
74
- ),
75
- )
76
- attention = (1 - gate) * attention + gate * recurrent
77
- gate_mean = (
78
- gate.detach().float().squeeze(-1) * layout.valid
79
- ).sum() / layout.valid.sum().clamp_min(1)
80
- x = x + attention
81
- v = self.ffn_norm(x)
82
- statistics = None
83
- if isinstance(self.ffn, MixtureOfExperts):
84
- ffn, statistics = self.ffn(v, layout.valid)
85
- else:
86
- ffn = self.ffn(v)
87
- x = (x + ffn) * layout.valid[..., None]
88
- updated = LayerCache(mla_cache, gdn_cache) if use_cache else None
89
- return x, updated, statistics, gate_mean
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
licenses/tokenizer-profile-MIT.txt ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The derived DeepSeek-V3 Split expressions below retain this upstream notice.
2
+ MIT License
3
+
4
+ Copyright (c) 2023 DeepSeek
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
licenses/tokenizer_profiles.py DELETED
@@ -1,55 +0,0 @@
1
- # The derived DeepSeek-V3 Split expressions below retain this upstream notice.
2
- # MIT License
3
- #
4
- # Copyright (c) 2023 DeepSeek
5
- #
6
- # Permission is hereby granted, free of charge, to any person obtaining a copy
7
- # of this software and associated documentation files (the "Software"), to deal
8
- # in the Software without restriction, including without limitation the rights
9
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- # copies of the Software, and to permit persons to whom the Software is
11
- # furnished to do so, subject to the following conditions:
12
- #
13
- # The above copyright notice and this permission notice shall be included in all
14
- # copies or substantial portions of the Software.
15
- #
16
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
- # SOFTWARE.
23
-
24
- """Local pre-tokenizer recipes; neither profile downloads a pretrained vocabulary."""
25
-
26
- PROFILES = ("bytelevel", "deepseek_v3")
27
- DEFAULT_PROFILE = "deepseek_v3"
28
- DEEPSEEK_SOURCE = (
29
- "https://huggingface.co/deepseek-ai/DeepSeek-V3/blob/"
30
- "e815299b0bcbac849fa540c768ef21845365c9eb/tokenizer.json"
31
- )
32
- # Exact Split expressions and order from the pinned DeepSeek-V3 tokenizer above.
33
- DEEPSEEK_PATTERNS = (
34
- r"\p{N}{1,3}",
35
- "[\u4e00-\u9fa5\u3040-\u309f\u30a0-\u30ff]+",
36
- r"""[!"#$%&'()*+,\-./:;<=>?@\[\\\]^_`{|}~][A-Za-z]+|[^\r\n\p{L}\p{P}\p{S}]?[\p{L}\p{M}]+| ?[\p{P}\p{S}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+""".replace(
37
- r"\r", "\r"
38
- ).replace(r"\n", "\n"),
39
- )
40
-
41
-
42
- def make_pre_tokenizer(profile):
43
- from tokenizers import Regex, pre_tokenizers
44
-
45
- if profile == "bytelevel":
46
- return pre_tokenizers.ByteLevel(add_prefix_space=False)
47
- if profile == "deepseek_v3":
48
- return pre_tokenizers.Sequence(
49
- [
50
- pre_tokenizers.Split(Regex(pattern), behavior="isolated")
51
- for pattern in DEEPSEEK_PATTERNS
52
- ]
53
- + [pre_tokenizers.ByteLevel(add_prefix_space=False, use_regex=False)]
54
- )
55
- raise ValueError(f"Unknown tokenizer profile: {profile}; choose from {PROFILES}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
losses.py DELETED
@@ -1,82 +0,0 @@
1
- """Next-token CE and router auxiliary losses with explicit reduction counts."""
2
-
3
- import torch
4
- from torch.nn import functional as F
5
-
6
-
7
- def chunked_linear_loss(hidden, weight, labels, layout, chunk_size):
8
- """Exact CE with checkpointed token chunks: never retain B*T*V logits."""
9
- from torch.utils.checkpoint import checkpoint
10
-
11
- if (
12
- labels.shape != hidden.shape[:2]
13
- or labels.dtype != torch.long
14
- or labels.device != hidden.device
15
- ):
16
- raise ValueError("labels must match hidden states and use int64")
17
- eligible = layout.valid[:, :-1] & layout.valid[:, 1:]
18
- eligible &= layout.document_ids[:, :-1] == layout.document_ids[:, 1:]
19
- eligible &= labels[:, 1:] != -100
20
- targets = labels[:, 1:].masked_fill(~eligible, -100).reshape(-1)
21
- x = hidden[:, :-1].reshape(-1, hidden.shape[-1])
22
- total = hidden.float().sum() * 0
23
-
24
- def chunk(a, w, y):
25
- return F.cross_entropy(
26
- F.linear(a, w).float(), y, ignore_index=-100, reduction="sum"
27
- )
28
-
29
- for start in range(0, len(targets), chunk_size):
30
- args = (
31
- x[start : start + chunk_size],
32
- weight,
33
- targets[start : start + chunk_size],
34
- )
35
- total = total + (
36
- checkpoint(chunk, *args, use_reentrant=False)
37
- if torch.is_grad_enabled()
38
- else chunk(*args)
39
- )
40
- count = eligible.sum()
41
- return total / count.clamp_min(1), total, count
42
-
43
-
44
- def causal_language_model_loss(logits, labels, layout):
45
- """Labels are unshifted; -100 masks prompts or other unsupervised targets."""
46
- if (
47
- labels.shape != logits.shape[:2]
48
- or labels.dtype != torch.long
49
- or labels.device != logits.device
50
- ):
51
- raise ValueError("labels must be int64 and match input_ids shape and device")
52
- targets = labels[:, 1:].clone()
53
- eligible = layout.valid[:, :-1] & layout.valid[:, 1:]
54
- eligible &= layout.document_ids[:, :-1] == layout.document_ids[:, 1:]
55
- eligible &= targets != -100
56
- targets.masked_fill_(~eligible, -100)
57
- count = eligible.sum()
58
- # reduction='sum' remains finite even if every target is masked or T == 1.
59
- loss_sum = F.cross_entropy(
60
- logits[:, :-1].float().reshape(-1, logits.shape[-1]),
61
- targets.reshape(-1),
62
- ignore_index=-100,
63
- reduction="sum",
64
- )
65
- return loss_sum / count.clamp_min(1), loss_sum, count
66
-
67
-
68
- def router_losses(statistics, previous_counts, zero):
69
- balances, z_losses = [], []
70
- for index, stats in enumerate(statistics):
71
- # Clone so an explicit accumulation update before backward cannot mutate saved tensors.
72
- counts = (
73
- previous_counts[index].detach().float().clone()
74
- + stats.assignment_counts.detach()
75
- )
76
- fractions = counts / counts.sum().clamp_min(1)
77
- probabilities = stats.probability_sum / max(stats.token_count, 1)
78
- balances.append(probabilities.numel() * (fractions * probabilities).sum())
79
- z_losses.append(stats.z_loss_sum / max(stats.token_count, 1))
80
- if not balances:
81
- return zero, zero
82
- return torch.stack(balances).mean(), torch.stack(z_losses).mean()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
masking.py DELETED
@@ -1,84 +0,0 @@
1
- """One boundary contract for attention, recurrent memory, convolution, and loss."""
2
-
3
- from dataclasses import dataclass
4
-
5
- import torch
6
-
7
- from .cache import ModelCache
8
-
9
-
10
- @dataclass(frozen=True)
11
- class SequenceLayout:
12
- valid: torch.Tensor
13
- segment_ids: torch.Tensor
14
- document_ids: torch.Tensor
15
- position_ids: torch.Tensor
16
- reset: torch.Tensor
17
- attention_bias: torch.Tensor | None
18
- is_causal: bool
19
-
20
-
21
- def build_layout(
22
- input_ids,
23
- attention_mask=None,
24
- segment_ids=None,
25
- past: ModelCache | None = None,
26
- materialize_attention=True,
27
- ):
28
- if input_ids.ndim != 2 or input_ids.dtype != torch.long or not all(input_ids.shape):
29
- raise ValueError("input_ids must be a nonempty int64 [batch, length] tensor")
30
- batch, length = input_ids.shape
31
- device = input_ids.device
32
- if attention_mask is None:
33
- valid = torch.ones_like(input_ids, dtype=torch.bool)
34
- else:
35
- if attention_mask.shape != input_ids.shape or attention_mask.device != device:
36
- raise ValueError("attention_mask must match input_ids shape and device")
37
- if not torch.all((attention_mask == 0) | (attention_mask == 1)):
38
- raise ValueError("attention_mask must contain only zero and one")
39
- valid = attention_mask.bool()
40
- if past is not None and not valid.all():
41
- raise ValueError("Cached inference currently requires unpadded inputs")
42
- if segment_ids is None:
43
- segment_ids = (
44
- torch.zeros_like(input_ids)
45
- if past is None
46
- else past.last_segment_ids[:, None].expand_as(input_ids)
47
- )
48
- if (
49
- segment_ids.shape != input_ids.shape
50
- or segment_ids.device != device
51
- or segment_ids.dtype != torch.long
52
- ):
53
- raise ValueError(
54
- "segment_ids must be int64 and match input_ids shape and device"
55
- )
56
- if (segment_ids[valid] < 0).any():
57
- raise ValueError("Valid tokens require nonnegative segment IDs")
58
-
59
- reset = valid.clone()
60
- reset[:, 1:] &= (~valid[:, :-1]) | (segment_ids[:, 1:] != segment_ids[:, :-1])
61
- base_doc = torch.full((batch,), -1, device=device, dtype=torch.long)
62
- previous_position = torch.full_like(base_doc, -1)
63
- if past is not None:
64
- reset[:, 0] &= segment_ids[:, 0] != past.last_segment_ids
65
- base_doc = past.document_ids[:, -1]
66
- previous_position = past.last_position_ids
67
- docs = base_doc[:, None] + reset.long().cumsum(dim=1)
68
- docs = docs.masked_fill(~valid, -1)
69
- indices = torch.arange(length, device=device).expand(batch, -1)
70
- starts = torch.where(reset, indices, -previous_position[:, None] - 1)
71
- positions = (indices - starts.cummax(dim=1).values).masked_fill(~valid, 0)
72
-
73
- # Use SDPA's causal fast path only for square, unpadded, single-document input.
74
- simple = past is None and bool(valid.all()) and not bool(reset[:, 1:].any())
75
- mask = None
76
- if not simple and materialize_attention:
77
- past_len = 0 if past is None else past.length
78
- all_docs = docs if past is None else torch.cat((past.document_ids, docs), dim=1)
79
- keys = torch.arange(past_len + length, device=device)
80
- causal = keys[None, :] <= (past_len + indices[0])[:, None]
81
- mask = valid[:, :, None] & (docs[:, :, None] == all_docs[:, None, :]) & causal
82
- # SDPA True means a permitted key. Explicit offset avoids decode's upper-left trap.
83
- mask = mask[:, None, :, :]
84
- return SequenceLayout(valid, segment_ids, docs, positions, reset, mask, simple)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
mla.py DELETED
@@ -1,108 +0,0 @@
1
- """Multi-head latent attention with a compressed inference cache."""
2
-
3
- import torch
4
- from torch import nn
5
- from torch.nn import functional as F
6
-
7
- from .cache import MLACache
8
- from .core_config import LaMoEConfig
9
- from .normalization import RMSNorm
10
- from .rotary import RotaryEmbedding
11
-
12
-
13
- class MultiHeadLatentAttention(nn.Module):
14
- def __init__(self, config: LaMoEConfig):
15
- super().__init__()
16
- self.config = config
17
- d, heads = config.hidden_size, config.mla_heads
18
- self.q_down = nn.Linear(d, config.q_rank, bias=False)
19
- self.q_norm = RMSNorm(config.q_rank, config.rms_norm_eps)
20
- self.q_up = nn.Linear(
21
- config.q_rank,
22
- heads * (config.qk_content_dim + config.qk_rope_dim),
23
- bias=False,
24
- )
25
- self.kv_down = nn.Linear(d, config.kv_rank, bias=False)
26
- self.kv_norm = RMSNorm(config.kv_rank, config.rms_norm_eps)
27
- self.k_up = nn.Linear(config.kv_rank, heads * config.qk_content_dim, bias=False)
28
- self.v_up = nn.Linear(config.kv_rank, heads * config.value_head_dim, bias=False)
29
- self.k_rope = nn.Linear(d, config.qk_rope_dim, bias=False)
30
- self.out_proj = nn.Linear(heads * config.value_head_dim, d, bias=False)
31
- self.rope = RotaryEmbedding(config.qk_rope_dim, config.rope_theta)
32
- self.head_gate = (
33
- nn.Linear(d, heads, bias=True) if config.mla_head_gate else None
34
- )
35
-
36
- def forward(self, u, layout, cache: MLACache | None = None, use_cache=False):
37
- cfg = self.config
38
- batch, length, _ = u.shape
39
- query = self.q_up(self.q_norm(self.q_down(u))).reshape(
40
- batch, length, cfg.mla_heads, -1
41
- )
42
- content_q, rotary_q = query.split((cfg.qk_content_dim, cfg.qk_rope_dim), dim=-1)
43
- query = torch.cat((content_q, self.rope(rotary_q, layout.position_ids)), dim=-1)
44
- latent = self.kv_norm(self.kv_down(u))
45
- rotary_k = self.rope(self.k_rope(u).unsqueeze(2), layout.position_ids).squeeze(
46
- 2
47
- )
48
- if cache is not None:
49
- if (
50
- cache.latent.dtype != latent.dtype
51
- or cache.rotary_key.dtype != rotary_k.dtype
52
- ):
53
- raise ValueError(
54
- "Keep the same precision/autocast mode for every cached call"
55
- )
56
- latent = torch.cat((cache.latent, latent), dim=1)
57
- rotary_k = torch.cat((cache.rotary_key, rotary_k), dim=1)
58
- key = self.k_up(latent).reshape(batch, -1, cfg.mla_heads, cfg.qk_content_dim)
59
- key = torch.cat(
60
- (key, rotary_k.unsqueeze(2).expand(-1, -1, cfg.mla_heads, -1)), dim=-1
61
- )
62
- value = self.v_up(latent).reshape(batch, -1, cfg.mla_heads, cfg.value_head_dim)
63
- if cfg.mla_backend == "flash_varlen" and not use_cache:
64
- result = flash_varlen(query, key, value, layout)
65
- else:
66
- result = F.scaled_dot_product_attention(
67
- query.transpose(1, 2),
68
- key.transpose(1, 2),
69
- value.transpose(1, 2),
70
- attn_mask=layout.attention_bias,
71
- is_causal=layout.is_causal,
72
- dropout_p=0.0,
73
- ).transpose(1, 2)
74
- if self.head_gate is not None:
75
- with torch.autocast(device_type=u.device.type, enabled=False):
76
- gate = F.linear(
77
- u.float(),
78
- self.head_gate.weight.float(),
79
- self.head_gate.bias.float(),
80
- ).sigmoid()
81
- result = result * gate.to(result.dtype).unsqueeze(-1)
82
- result = self.out_proj(result.reshape(batch, length, -1))
83
- result = result * layout.valid[..., None]
84
- updated = MLACache(latent.detach(), rotary_k.detach()) if use_cache else None
85
- return result, updated
86
-
87
-
88
- def flash_varlen(q, k, v, layout):
89
- """FA2 packed independent causal documents, never an unmasked fallback."""
90
- if q.device.type != "cuda" or q.dtype not in (torch.bfloat16, torch.float16):
91
- raise ValueError("flash_varlen requires CUDA BF16/FP16 autocast")
92
- from flash_attn import flash_attn_varlen_func
93
-
94
- indices = layout.valid.flatten().nonzero(as_tuple=True)[0]
95
- if indices.numel() == 0:
96
- return v * 0
97
- starts = layout.reset.clone()
98
- starts[:, 0] = layout.valid[:, 0]
99
- boundaries = starts.flatten()[indices].nonzero(as_tuple=True)[0]
100
- cu = torch.cat((boundaries, boundaries.new_tensor([indices.numel()]))).to(
101
- torch.int32
102
- )
103
- maximum = int((cu[1:] - cu[:-1]).max())
104
- pack = lambda t: t.flatten(0, 1).index_select(0, indices).contiguous()
105
- out = flash_attn_varlen_func(
106
- pack(q), pack(k), pack(v), cu, cu, maximum, maximum, dropout_p=0.0, causal=True
107
- )
108
- return torch.zeros_like(v.flatten(0, 1)).index_copy(0, indices, out).reshape_as(v)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
modeling_lamoe.py DELETED
@@ -1,250 +0,0 @@
1
- """Transformers adapter for the original LaMoE modules and weight names."""
2
-
3
- import copy
4
-
5
- import torch
6
- from transformers import GenerationMixin, PreTrainedModel
7
- from transformers.modeling_outputs import CausalLMOutputWithPast
8
- from transformers.utils import logging
9
-
10
- from .caching_lamoe import LaMoECache
11
- from .configuration_lamoe import LaMoEConfig
12
- from .masking import build_layout
13
- from .modeling_lamoe_core import LaMoEForCausalLM as CoreModel
14
-
15
- logger = logging.get_logger(__name__)
16
-
17
-
18
- class LaMoEForCausalLM(PreTrainedModel, GenerationMixin):
19
- config_class = LaMoEConfig
20
- base_model_prefix = ""
21
- main_input_name = "input_ids"
22
- _no_split_modules = ["DecoderLayer"]
23
- _tied_weights_keys = {"lm_head.weight": "embedding.weight"}
24
- _supports_sdpa = False
25
-
26
- def __init__(self, config):
27
- super().__init__(config)
28
- core = CoreModel(config.to_core_config())
29
- # Keep the checkpoint's original parameter names, so its safetensors
30
- # file can be copied byte-for-byte without rewriting trained weights.
31
- self.embedding = core.embedding
32
- self.layers = core.layers
33
- self.final_norm = core.final_norm
34
- self.lm_head = core.lm_head
35
- self.register_buffer("balance_counts", core.balance_counts)
36
- self.gradient_checkpointing = False
37
- self.post_init()
38
-
39
- def get_input_embeddings(self):
40
- return self.embedding
41
-
42
- def set_input_embeddings(self, value):
43
- if value.weight.shape != self.embedding.weight.shape:
44
- raise ValueError(
45
- "Vocabulary resizing is not supported by this frozen export"
46
- )
47
- self.embedding = value
48
- self.lm_head.weight = value.weight
49
-
50
- def get_output_embeddings(self):
51
- return self.lm_head
52
-
53
- def set_output_embeddings(self, value):
54
- if value.weight.shape != self.embedding.weight.shape:
55
- raise ValueError(
56
- "Output embedding shape disagrees with the frozen vocabulary"
57
- )
58
- self.lm_head = value
59
- self.embedding.weight = value.weight
60
-
61
- def _validate_cache(self, cache, input_ids):
62
- return CoreModel._validate_cache(self, cache, input_ids)
63
-
64
- def reset_router_counts(self):
65
- return CoreModel.reset_router_counts(self)
66
-
67
- def accumulate_router_counts(self, output):
68
- return CoreModel.accumulate_router_counts(self, output)
69
-
70
- @classmethod
71
- def _supports_default_dynamic_cache(cls):
72
- return False
73
-
74
- def forward(
75
- self,
76
- input_ids=None,
77
- attention_mask=None,
78
- position_ids=None,
79
- past_key_values=None,
80
- inputs_embeds=None,
81
- labels=None,
82
- use_cache=None,
83
- return_dict=True,
84
- output_attentions=False,
85
- output_hidden_states=False,
86
- logits_to_keep=0,
87
- segment_ids=None,
88
- token_type_ids=None,
89
- **kwargs,
90
- ):
91
- if inputs_embeds is not None or input_ids is None:
92
- raise ValueError(
93
- "LaMoE currently requires input_ids; inputs_embeds is unsupported"
94
- )
95
- if output_attentions or output_hidden_states:
96
- raise NotImplementedError(
97
- "This adapter does not expose attention maps or intermediate hidden states"
98
- )
99
- if token_type_ids is not None and bool((token_type_ids != 0).any()):
100
- raise ValueError("LaMoE does not use token type IDs")
101
- # Current Transformers may pass num_items_in_batch with labels. The
102
- # original model computes its own valid-target loss normalization.
103
- unknown = set(kwargs) - {"num_items_in_batch"}
104
- if unknown:
105
- raise TypeError(f"Unsupported LaMoE forward arguments: {sorted(unknown)}")
106
- if past_key_values is not None and not isinstance(past_key_values, LaMoECache):
107
- raise TypeError("Use LaMoECache, not a standard attention KV cache")
108
- core_past = None if past_key_values is None else past_key_values.state
109
- use_cache = bool(self.config.use_cache if use_cache is None else use_cache)
110
- if labels is not None or self.training:
111
- if core_past is not None:
112
- raise ValueError(
113
- "Training/labeled loss does not accept an inference cache"
114
- )
115
- use_cache = False
116
- elif torch.is_grad_enabled() and core_past is None:
117
- use_cache = False
118
- current_mask = attention_mask
119
- if attention_mask is not None and attention_mask.shape != input_ids.shape:
120
- past_length = 0 if core_past is None else core_past.length
121
- if attention_mask.shape != (
122
- input_ids.shape[0],
123
- past_length + input_ids.shape[1],
124
- ):
125
- raise ValueError(
126
- "attention_mask must cover the current input or full cached context"
127
- )
128
- if core_past is not None and not bool(
129
- attention_mask[:, :past_length].all()
130
- ):
131
- raise ValueError("Cached prefixes must be unpadded")
132
- current_mask = attention_mask[:, -input_ids.shape[1] :]
133
- if (
134
- current_mask is not None
135
- and not bool(current_mask.bool().all())
136
- and use_cache
137
- ):
138
- if core_past is not None:
139
- raise ValueError("Padded inputs cannot be added to an existing cache")
140
- logger.warning_once(
141
- "LaMoE padded batches use full-sequence decoding without a cache."
142
- )
143
- use_cache = False
144
- if position_ids is not None:
145
- canonical = build_layout(
146
- input_ids,
147
- current_mask,
148
- segment_ids,
149
- core_past,
150
- materialize_attention=False,
151
- ).position_ids
152
- if position_ids.shape != canonical.shape or not torch.equal(
153
- position_ids, canonical
154
- ):
155
- raise ValueError(
156
- "Explicit position_ids must match LaMoE's document-aware positions"
157
- )
158
- out = CoreModel.forward(
159
- self,
160
- input_ids,
161
- attention_mask=current_mask,
162
- segment_ids=segment_ids,
163
- labels=labels,
164
- past_key_values=core_past,
165
- use_cache=use_cache,
166
- logits_to_keep=0 if labels is not None else logits_to_keep,
167
- )
168
- cache = None
169
- if out.past_key_values is not None:
170
- cache = (
171
- past_key_values
172
- if past_key_values is not None
173
- else LaMoECache(max_length=self.config.max_position_embeddings)
174
- )
175
- cache.state = out.past_key_values
176
- if not return_dict:
177
- result = (out.logits, cache) if cache is not None else (out.logits,)
178
- return ((out.loss,) + result) if out.loss is not None else result
179
- return CausalLMOutputWithPast(
180
- loss=out.loss, logits=out.logits, past_key_values=cache
181
- )
182
-
183
- def prepare_inputs_for_generation(
184
- self,
185
- input_ids,
186
- past_key_values=None,
187
- attention_mask=None,
188
- use_cache=True,
189
- **kwargs,
190
- ):
191
- length = 0 if past_key_values is None else past_key_values.get_seq_length()
192
- if length and input_ids.shape[1] > length:
193
- input_ids = input_ids[:, length:]
194
- if length and input_ids.shape[1] != 1:
195
- # A caller can directly forward a chunk with a cache, but the public
196
- # generate interface appends one token per decoding iteration.
197
- raise ValueError(
198
- "Generation with a cache requires one newly appended token"
199
- )
200
- result = dict(
201
- input_ids=input_ids.contiguous(),
202
- past_key_values=past_key_values,
203
- attention_mask=attention_mask,
204
- use_cache=use_cache,
205
- logits_to_keep=kwargs.get("logits_to_keep", 1),
206
- )
207
- if kwargs.get("position_ids") is not None:
208
- result["position_ids"] = kwargs["position_ids"][:, -input_ids.shape[1] :]
209
- return result
210
-
211
- @torch.no_grad()
212
- def generate(self, inputs=None, generation_config=None, **kwargs):
213
- cfg = copy.deepcopy(generation_config or self.generation_config)
214
- if kwargs.get("assistant_model") is not None or cfg.is_assistant:
215
- raise NotImplementedError(
216
- "Assisted/speculative decoding requires rollback of recurrent states and is unsupported"
217
- )
218
- if kwargs.get("cache_implementation", cfg.cache_implementation) is not None:
219
- raise ValueError(
220
- "LaMoE uses its own recurrent/latent cache; cache_implementation must be unset"
221
- )
222
- ids = kwargs.get("input_ids", inputs)
223
- if ids is None:
224
- ids = torch.tensor([[self.config.bos_token_id]], device=self.device)
225
- inputs = ids
226
- past = kwargs.get("past_key_values")
227
- prefix_length = ids.shape[1]
228
- if past is not None:
229
- prefix_length = max(prefix_length, past.get_seq_length() + 1)
230
- remaining = self.config.max_position_embeddings - prefix_length
231
- if remaining <= 0:
232
- raise ValueError(
233
- "Prompt reaches/exceeds LaMoE's context; it will not be truncated"
234
- )
235
- requested = kwargs.get("max_new_tokens", cfg.max_new_tokens)
236
- if requested is not None:
237
- if type(requested) is not int or requested < 1:
238
- raise ValueError("max_new_tokens must be a positive integer")
239
- if requested > remaining:
240
- logger.warning_once(
241
- "LaMoE caps output to the remaining context allowance."
242
- )
243
- kwargs["max_new_tokens"] = min(requested, remaining)
244
- else:
245
- kwargs["max_length"] = min(
246
- kwargs.get("max_length", cfg.max_length),
247
- self.config.max_position_embeddings,
248
- )
249
- model_kwargs = cfg.update(**kwargs)
250
- return super().generate(inputs=inputs, generation_config=cfg, **model_kwargs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
modeling_lamoe_core.py DELETED
@@ -1,357 +0,0 @@
1
- """LaMoE causal language model, initialization, checkpoint IO, and diagnostics."""
2
-
3
- import math
4
- from dataclasses import dataclass, field
5
- from pathlib import Path
6
-
7
- import torch
8
- from torch import nn
9
-
10
- from .cache import ModelCache
11
- from .core_config import LaMoEConfig
12
- from .feedforward import MixtureOfExperts, RouterStatistics, SwiGLU
13
- from .gated_deltanet import GatedDeltaNet
14
- from .layers import DecoderLayer
15
- from .losses import causal_language_model_loss, chunked_linear_loss, router_losses
16
- from .masking import build_layout
17
- from .normalization import RMSNorm
18
-
19
-
20
- @dataclass
21
- class LaMoEOutput:
22
- logits: torch.Tensor | None
23
- loss: torch.Tensor | None
24
- ce_loss: torch.Tensor | None
25
- ce_loss_sum: torch.Tensor | None
26
- target_token_count: torch.Tensor | None
27
- input_token_count: torch.Tensor
28
- balance_loss: torch.Tensor
29
- router_z_loss: torch.Tensor
30
- router_statistics: tuple[RouterStatistics, ...]
31
- fusion_gate_means: tuple[torch.Tensor, ...]
32
- past_key_values: ModelCache | None
33
- _counts_accumulated: bool = field(default=False, repr=False)
34
-
35
-
36
- class LaMoEForCausalLM(nn.Module):
37
- def __init__(self, config: LaMoEConfig | None = None):
38
- super().__init__()
39
- self.config = config or LaMoEConfig()
40
- self.gradient_checkpointing = False
41
- cfg = self.config
42
- self.embedding = nn.Embedding(cfg.vocab_size, cfg.hidden_size)
43
- self.layers = nn.ModuleList(
44
- [DecoderLayer(cfg, number) for number in range(1, cfg.num_layers + 1)]
45
- )
46
- self.final_norm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps)
47
- self.lm_head = nn.Linear(cfg.hidden_size, cfg.vocab_size, bias=False)
48
- self.lm_head.weight = self.embedding.weight
49
- self.register_buffer(
50
- "balance_counts",
51
- torch.zeros(cfg.num_moe_layers, cfg.num_experts, dtype=torch.float32),
52
- )
53
- self.reset_parameters()
54
-
55
- def reset_parameters(self):
56
- cfg = self.config
57
- seen = set()
58
- # The tied matrix is initialized once, as well as counted once.
59
- for module in self.modules():
60
- if isinstance(module, (nn.Linear, nn.Embedding)):
61
- if id(module.weight) not in seen:
62
- nn.init.normal_(module.weight, std=cfg.initializer_std)
63
- seen.add(id(module.weight))
64
- if getattr(module, "bias", None) is not None:
65
- nn.init.zeros_(module.bias)
66
- elif isinstance(module, RMSNorm):
67
- nn.init.ones_(module.weight)
68
- residual_std = cfg.initializer_std / math.sqrt(2 * cfg.num_layers)
69
- for module in self.modules():
70
- if isinstance(module, SwiGLU):
71
- nn.init.normal_(module.down_proj.weight, std=residual_std)
72
- elif isinstance(module, GatedDeltaNet):
73
- module.reset_dynamics()
74
- nn.init.normal_(module.out_proj.weight, std=residual_std)
75
- elif isinstance(module, MixtureOfExperts):
76
- nn.init.normal_(module.router.weight, std=cfg.router_initializer_std)
77
- for layer in self.layers:
78
- nn.init.normal_(layer.mla.out_proj.weight, std=residual_std)
79
- if layer.attention_router is not None:
80
- nn.init.zeros_(layer.attention_router.weight)
81
- nn.init.constant_(
82
- layer.attention_router.bias,
83
- math.log(cfg.fusion_gate_init / (1 - cfg.fusion_gate_init)),
84
- )
85
- if layer.mla.head_gate is not None:
86
- nn.init.zeros_(layer.mla.head_gate.weight)
87
- nn.init.constant_(layer.mla.head_gate.bias, cfg.head_gate_bias_init)
88
- self.reset_router_counts()
89
-
90
- def _validate_cache(self, cache, input_ids):
91
- cfg = self.config
92
- batch = input_ids.shape[0]
93
- if not isinstance(cache, ModelCache) or len(cache.layers) != cfg.num_layers:
94
- raise ValueError("Cache must contain one entry per decoder layer")
95
- if (
96
- cache.document_ids.ndim != 2
97
- or cache.document_ids.shape[0] != batch
98
- or cache.length == 0
99
- ):
100
- raise ValueError("Cache batch shape or length is invalid")
101
- tensors = [cache.document_ids, cache.last_segment_ids, cache.last_position_ids]
102
- if cache.last_segment_ids.shape != (
103
- batch,
104
- ) or cache.last_position_ids.shape != (batch,):
105
- raise ValueError("Cache metadata shape is invalid")
106
- if any(t.dtype != torch.long for t in tensors):
107
- raise ValueError("Cache metadata must use int64")
108
- for layer, item in zip(self.layers, cache.layers):
109
- if item.mla.latent.shape != (
110
- batch,
111
- cache.length,
112
- cfg.kv_rank,
113
- ) or item.mla.rotary_key.shape != (batch, cache.length, cfg.qk_rope_dim):
114
- raise ValueError("MLA cache shape does not match model configuration")
115
- tensors.extend((item.mla.latent, item.mla.rotary_key))
116
- if (item.gdn is None) != (layer.gdn is None):
117
- raise ValueError("GDN cache placement does not match hybrid layers")
118
- if item.gdn is not None:
119
- g = item.gdn
120
- key_shape = (
121
- batch,
122
- cfg.gdn_conv_width - 1,
123
- cfg.gdn_heads * cfg.gdn_key_dim,
124
- )
125
- val_shape = (
126
- batch,
127
- cfg.gdn_conv_width - 1,
128
- cfg.gdn_heads * cfg.gdn_value_dim,
129
- )
130
- if (
131
- g.state.shape
132
- != (batch, cfg.gdn_heads, cfg.gdn_value_dim, cfg.gdn_key_dim)
133
- or g.state.dtype != torch.float32
134
- ):
135
- raise ValueError("GDN cache must contain FP32 value-by-key states")
136
- if (
137
- g.conv_q.shape != key_shape
138
- or g.conv_k.shape != key_shape
139
- or g.conv_v.shape != val_shape
140
- ):
141
- raise ValueError("GDN convolution cache shape is invalid")
142
- tensors.extend((g.state, g.conv_q, g.conv_k, g.conv_v))
143
- if any(t.device != input_ids.device for t in tensors):
144
- raise ValueError("Cache and input_ids must be on the same device")
145
-
146
- def forward(
147
- self,
148
- input_ids,
149
- *,
150
- attention_mask=None,
151
- segment_ids=None,
152
- labels=None,
153
- past_key_values: ModelCache | None = None,
154
- use_cache=False,
155
- logits_to_keep=0,
156
- loss_chunk_size=0,
157
- return_logits=True,
158
- ):
159
- """Run packed pretraining/SFT or unpadded cached inference.
160
-
161
- segment_ids change at document boundaries within each row. Labels are
162
- unshifted input IDs, with -100 at unsupervised positions. logits_to_keep
163
- is an inference-only option (zero means every position).
164
- """
165
- if (
166
- input_ids.ndim != 2
167
- or input_ids.dtype != torch.long
168
- or not all(input_ids.shape)
169
- ):
170
- raise ValueError(
171
- "input_ids must be a nonempty int64 [batch, length] tensor"
172
- )
173
- if past_key_values is not None and not use_cache:
174
- raise ValueError("past_key_values requires use_cache=True")
175
- if use_cache and (
176
- self.training or torch.is_grad_enabled() or labels is not None
177
- ):
178
- raise ValueError(
179
- "Caching is for model.eval() inside torch.no_grad()/inference_mode(), without labels"
180
- )
181
- if self.training and (
182
- self.embedding.weight.dtype != torch.float32
183
- or self.balance_counts.dtype != torch.float32
184
- ):
185
- raise ValueError(
186
- "Keep model parameters/buffers in FP32 for training; use BF16 autocast"
187
- )
188
- if (
189
- type(logits_to_keep) is not int
190
- or logits_to_keep < 0
191
- or (logits_to_keep and labels is not None)
192
- ):
193
- raise ValueError(
194
- "logits_to_keep must be nonnegative and cannot be used with labels"
195
- )
196
- if past_key_values is not None:
197
- self._validate_cache(past_key_values, input_ids)
198
- past_length = 0 if past_key_values is None else past_key_values.length
199
- if past_length + input_ids.shape[1] > self.config.max_position_embeddings:
200
- raise ValueError(
201
- "Input plus cache exceeds max_position_embeddings; start a new context"
202
- )
203
- if type(loss_chunk_size) is not int or loss_chunk_size < 0:
204
- raise ValueError("loss_chunk_size must be a nonnegative integer")
205
- if not return_logits and (labels is None or not loss_chunk_size):
206
- raise ValueError("Omitting logits requires labels and chunked loss")
207
- layout = build_layout(
208
- input_ids,
209
- attention_mask,
210
- segment_ids,
211
- past_key_values,
212
- materialize_attention=use_cache
213
- or self.config.mla_backend != "flash_varlen",
214
- )
215
- if use_cache and not layout.valid.all():
216
- raise ValueError("Cached inference currently requires unpadded inputs")
217
- x = self.embedding(input_ids) * layout.valid[..., None]
218
- caches, routers, gates = [], [], []
219
- for index, layer in enumerate(self.layers):
220
- past_layer = (
221
- None if past_key_values is None else past_key_values.layers[index]
222
- )
223
- if self.gradient_checkpointing and self.training:
224
- from torch.utils.checkpoint import checkpoint
225
-
226
- x, updated, statistics, gate = checkpoint(
227
- layer, x, layout, past_layer, use_cache, use_reentrant=False
228
- )
229
- else:
230
- x, updated, statistics, gate = layer(x, layout, past_layer, use_cache)
231
- if use_cache:
232
- caches.append(updated)
233
- if statistics is not None:
234
- routers.append(statistics)
235
- if gate is not None:
236
- gates.append(gate)
237
- x = self.final_norm(x)
238
- logits = (
239
- self.lm_head(x[:, -logits_to_keep:] if logits_to_keep else x)
240
- if return_logits
241
- else None
242
- )
243
- zero = x.float().sum() * 0
244
- history_counts = (
245
- self.balance_counts
246
- if self.training
247
- else torch.zeros_like(self.balance_counts)
248
- )
249
- balance, router_z = router_losses(routers, history_counts, zero)
250
- loss = ce = ce_sum = target_count = None
251
- if labels is not None:
252
- if loss_chunk_size:
253
- ce, ce_sum, target_count = chunked_linear_loss(
254
- x, self.lm_head.weight, labels, layout, loss_chunk_size
255
- )
256
- else:
257
- ce, ce_sum, target_count = causal_language_model_loss(
258
- logits, labels, layout
259
- )
260
- loss = (
261
- ce
262
- + self.config.load_balance_coef * balance
263
- + self.config.router_z_coef * router_z
264
- )
265
- updated_cache = None
266
- if use_cache:
267
- all_docs = (
268
- layout.document_ids
269
- if past_key_values is None
270
- else torch.cat(
271
- (past_key_values.document_ids, layout.document_ids), dim=1
272
- )
273
- )
274
- updated_cache = ModelCache(
275
- tuple(caches),
276
- all_docs.detach(),
277
- layout.segment_ids[:, -1].detach(),
278
- layout.position_ids[:, -1].detach(),
279
- )
280
- return LaMoEOutput(
281
- logits,
282
- loss,
283
- ce,
284
- ce_sum,
285
- target_count,
286
- layout.valid.sum(),
287
- balance,
288
- router_z,
289
- tuple(routers),
290
- tuple(gates),
291
- updated_cache,
292
- )
293
-
294
- @torch.no_grad()
295
- def accumulate_router_counts(self, output: LaMoEOutput):
296
- """Call once per training microbatch, outside forward/checkpoint replay."""
297
- if not self.training:
298
- raise ValueError("Router count accumulation is only for training")
299
- if output._counts_accumulated:
300
- raise ValueError("This microbatch's router counts were already accumulated")
301
- if len(output.router_statistics) != self.config.num_moe_layers:
302
- raise ValueError("Output router count does not match this model")
303
- for buffer, stats in zip(self.balance_counts, output.router_statistics):
304
- buffer.add_(stats.assignment_counts.detach())
305
- output._counts_accumulated = True
306
-
307
- @torch.no_grad()
308
- def reset_router_counts(self):
309
- """Call after every optimizer step, including a discarded/aborted window."""
310
- self.balance_counts.zero_()
311
-
312
- def parameter_summary(self) -> dict:
313
- total = sum(p.numel() for p in self.parameters())
314
- inactive = (
315
- self.config.num_moe_layers
316
- * (self.config.num_experts - self.config.experts_per_token)
317
- * 3
318
- * self.config.hidden_size
319
- * self.config.expert_ffn_width
320
- )
321
- return {
322
- "total_parameters": total,
323
- "active_path_parameters": total - inactive,
324
- "layers": self.config.num_layers,
325
- "hybrid_layers": list(self.config.hybrid_layers),
326
- "moe_layers": list(range(2, self.config.num_layers + 1, 2)),
327
- "mla_head_gate": self.config.mla_head_gate,
328
- "tied_embeddings": self.lm_head.weight is self.embedding.weight,
329
- }
330
-
331
- def save_pretrained(self, directory: str | Path):
332
- """Local model/config snapshot, not a resumable trainer checkpoint."""
333
- directory = Path(directory)
334
- directory.mkdir(parents=True, exist_ok=True)
335
- self.config.save(directory / "config.json")
336
- from safetensors.torch import save_model
337
-
338
- save_model(self, str(directory / "model.safetensors"))
339
-
340
- @classmethod
341
- def from_pretrained(cls, directory: str | Path):
342
- directory = Path(directory)
343
- model = cls(LaMoEConfig.load(directory / "config.json"))
344
- if (directory / "model.safetensors").exists():
345
- from safetensors.torch import load_model
346
-
347
- load_model(model, str(directory / "model.safetensors"), strict=True)
348
- return model
349
- state = torch.load(
350
- directory / "model.pt", map_location="cpu", weights_only=True
351
- )
352
- if not torch.equal(state["embedding.weight"], state["lm_head.weight"]):
353
- raise ValueError(
354
- "Checkpoint embedding and output weights disagree despite weight tying"
355
- )
356
- model.load_state_dict(state, strict=True)
357
- return model
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
normalization.py DELETED
@@ -1,21 +0,0 @@
1
- """RMS normalization with stable accumulation and no additive bias."""
2
-
3
- import torch
4
- from torch import nn
5
-
6
-
7
- class RMSNorm(nn.Module):
8
- def __init__(self, width: int, eps: float = 1e-6):
9
- super().__init__()
10
- self.weight = nn.Parameter(torch.ones(width))
11
- self.eps = eps
12
-
13
- def forward(self, x):
14
- accumulation_dtype = (
15
- torch.float64 if x.dtype == torch.float64 else torch.float32
16
- )
17
- value = x.to(accumulation_dtype)
18
- value = value * torch.rsqrt(
19
- value.square().mean(dim=-1, keepdim=True) + self.eps
20
- )
21
- return (value * self.weight.to(accumulation_dtype)).to(x.dtype)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
release-manifest.json CHANGED
@@ -1,23 +1,13 @@
1
  {
2
- "format": "lamoe-transformers-export-v1",
3
  "source_checkpoint": "step-00129185",
4
  "weights_unchanged": true,
5
  "tokenizer_unchanged": true,
 
 
6
  "tested_transformers": "5.17.0",
7
  "self_excluded": true,
8
  "files": {
9
- "__init__.py": {
10
- "bytes": 84,
11
- "sha256": "52b6f0d86ff3318787f7e305ebaf10bd303ba44c0fd2d0716d6d653ec95a8360"
12
- },
13
- "cache.py": {
14
- "bytes": 968,
15
- "sha256": "6416a4eb3480f88d772d99a7592eb31584edcd63e2f1ff63c9826d2876795686"
16
- },
17
- "caching_lamoe.py": {
18
- "bytes": 2498,
19
- "sha256": "486a31173ba273912eff394effb422a821ab50847329b25fe39c0adc3f3427cf"
20
- },
21
  "chat-template.json": {
22
  "bytes": 1273,
23
  "sha256": "f003bc186ac97b4b506276f3378a03b4f8209f09580e33b3b0f49f6dd1b7e7fb"
@@ -27,16 +17,8 @@
27
  "sha256": "a8c204f8ba1c4bc0faf466a354c84bb66da6b0b645ce63ce7fddeb40b7e587cc"
28
  },
29
  "config.json": {
30
- "bytes": 1285,
31
- "sha256": "2380a13139dc3ad03051499cdf70553fbf7197e60c3502bb4f0af4f861200127"
32
- },
33
- "configuration_lamoe.py": {
34
- "bytes": 1727,
35
- "sha256": "4191e0f9a40465ff2de37514e53c786632093a30e0802e5cf5cae0c852d29f7a"
36
- },
37
- "core_config.py": {
38
- "bytes": 4829,
39
- "sha256": "4fe3b8734bf07aa5a5519b4d731a067860d46f906bfbb4e9fcf6bcccce13c06a"
40
  },
41
  "docs/architecture/model.png": {
42
  "bytes": 667406,
@@ -74,61 +56,25 @@
74
  "bytes": 170779,
75
  "sha256": "5d7924bd596eabc3fd979be4d87bca9b38ed7f19cdae02d39a47377606ce05b5"
76
  },
77
- "feedforward.py": {
78
- "bytes": 4790,
79
- "sha256": "299e79f1bdd5f98ba71ce4d078adb494480649316b98b770105b075226011181"
80
- },
81
- "gated_deltanet.py": {
82
- "bytes": 9173,
83
- "sha256": "b67195e4426adc74c4225931b23183e1de544fa283f81c0a1ab9680f963ef7cd"
84
- },
85
  "generation_config.json": {
86
  "bytes": 200,
87
  "sha256": "fee0d6bed5428a3ac3ca748d927ad1e05cbc25ee719b851d03059f8454b72595"
88
  },
89
- "layers.py": {
90
- "bytes": 3444,
91
- "sha256": "43e1bfaf6f7b146899897e16738579366875fe66a220d92e7a847e98c959d0a1"
92
- },
93
  "LICENSE": {
94
  "bytes": 11560,
95
  "sha256": "3ddf9be5c28fe27dad143a5dc76eea25222ad1dd68934a047064e56ed2fa40c5"
96
  },
97
- "licenses/tokenizer_profiles.py": {
98
- "bytes": 2442,
99
- "sha256": "02304b9ce81f065a008b344d250526189fbea407d3ddc5964e4293fd668b203d"
100
- },
101
- "losses.py": {
102
- "bytes": 3163,
103
- "sha256": "190470e54f269d0872b7a0262f1e5a5cdd4091e4e5e46efd3e105ec78cd2cb19"
104
- },
105
- "masking.py": {
106
- "bytes": 3501,
107
- "sha256": "61a371576bd249b410b09e689ed551a8cee80e1dae59e256a177b505879500ca"
108
- },
109
- "mla.py": {
110
- "bytes": 4825,
111
- "sha256": "fe2bd8129f1f58036b1525f1e12c740bb947d45425e424034e528378482cd05b"
112
  },
113
  "model.safetensors": {
114
  "bytes": 990653160,
115
  "sha256": "3be1caedbd50f04fea5f1bbfd9baae443409be4e6100d128a7c1c276522a958c"
116
  },
117
- "modeling_lamoe.py": {
118
- "bytes": 10117,
119
- "sha256": "83427dcc06586f216f543b2352503bfe22e9aeb44eccd8f1627817945336e989"
120
- },
121
- "modeling_lamoe_core.py": {
122
- "bytes": 14838,
123
- "sha256": "9cc49991641657f58a3c43640582fcec246344c886f7941387582ea6064643a0"
124
- },
125
- "normalization.py": {
126
- "bytes": 659,
127
- "sha256": "2baca53c66951b455677af985505e21df658650f190d4e839019aabd58448ece"
128
- },
129
  "README.md": {
130
- "bytes": 6939,
131
- "sha256": "3f196c3a3aa2155e9c32ea7f9190203e87c2086565bc45b23b32ca6260017a0a"
132
  },
133
  "requirements.txt": {
134
  "bytes": 198,
@@ -138,10 +84,6 @@
138
  "bytes": 3046,
139
  "sha256": "dec3178474b40e74c43b2179d3c658e41b949e097dc0041a7e6b66470be7352d"
140
  },
141
- "rotary.py": {
142
- "bytes": 999,
143
- "sha256": "caa302a72694e1cc1e7122b26cb9ef2232010d9005a18c345d0c46c60b97eaad"
144
- },
145
  "special_tokens_map.json": {
146
  "bytes": 300,
147
  "sha256": "63b1bbaf0ebe9e9cbf8fc32f3b838b791e8e17725c4ef983ac45df033b0a3afd"
 
1
  {
2
+ "format": "lamoe-model-files-v1",
3
  "source_checkpoint": "step-00129185",
4
  "weights_unchanged": true,
5
  "tokenizer_unchanged": true,
6
+ "requires_lamoe_package": true,
7
+ "python_code_included": false,
8
  "tested_transformers": "5.17.0",
9
  "self_excluded": true,
10
  "files": {
 
 
 
 
 
 
 
 
 
 
 
 
11
  "chat-template.json": {
12
  "bytes": 1273,
13
  "sha256": "f003bc186ac97b4b506276f3378a03b4f8209f09580e33b3b0f49f6dd1b7e7fb"
 
17
  "sha256": "a8c204f8ba1c4bc0faf466a354c84bb66da6b0b645ce63ce7fddeb40b7e587cc"
18
  },
19
  "config.json": {
20
+ "bytes": 1145,
21
+ "sha256": "ef1a66b8fe0c6ca7301a1f6acc56cf24c309013e932a29bdd1e8348a2ec508f3"
 
 
 
 
 
 
 
 
22
  },
23
  "docs/architecture/model.png": {
24
  "bytes": 667406,
 
56
  "bytes": 170779,
57
  "sha256": "5d7924bd596eabc3fd979be4d87bca9b38ed7f19cdae02d39a47377606ce05b5"
58
  },
 
 
 
 
 
 
 
 
59
  "generation_config.json": {
60
  "bytes": 200,
61
  "sha256": "fee0d6bed5428a3ac3ca748d927ad1e05cbc25ee719b851d03059f8454b72595"
62
  },
 
 
 
 
63
  "LICENSE": {
64
  "bytes": 11560,
65
  "sha256": "3ddf9be5c28fe27dad143a5dc76eea25222ad1dd68934a047064e56ed2fa40c5"
66
  },
67
+ "licenses/tokenizer-profile-MIT.txt": {
68
+ "bytes": 1164,
69
+ "sha256": "535c8522d19af483d3c79f447137e05600619b84a786cbaf13941d6a64ce73c3"
 
 
 
 
 
 
 
 
 
 
 
 
70
  },
71
  "model.safetensors": {
72
  "bytes": 990653160,
73
  "sha256": "3be1caedbd50f04fea5f1bbfd9baae443409be4e6100d128a7c1c276522a958c"
74
  },
 
 
 
 
 
 
 
 
 
 
 
 
75
  "README.md": {
76
+ "bytes": 7191,
77
+ "sha256": "1b7a3920e2748d1399909776041f9b7c8d458b257574155f2dd080a49e203ecb"
78
  },
79
  "requirements.txt": {
80
  "bytes": 198,
 
84
  "bytes": 3046,
85
  "sha256": "dec3178474b40e74c43b2179d3c658e41b949e097dc0041a7e6b66470be7352d"
86
  },
 
 
 
 
87
  "special_tokens_map.json": {
88
  "bytes": 300,
89
  "sha256": "63b1bbaf0ebe9e9cbf8fc32f3b838b791e8e17725c4ef983ac45df033b0a3afd"
rotary.py DELETED
@@ -1,26 +0,0 @@
1
- """RoPE on the decoupled rotary coordinates of MLA only."""
2
-
3
- import torch
4
- from torch import nn
5
-
6
-
7
- class RotaryEmbedding(nn.Module):
8
- def __init__(self, dim: int, theta: float):
9
- super().__init__()
10
- self.dim = dim
11
- self.theta = theta
12
-
13
- def forward(self, x, positions):
14
- # Recompute the small frequency vector in FP32 even after model.bfloat16().
15
- with torch.autocast(device_type=x.device.type, enabled=False):
16
- inv_freq = self.theta ** (
17
- -torch.arange(0, self.dim, 2, device=x.device, dtype=torch.float32)
18
- / self.dim
19
- )
20
- angle = positions.float()[..., None] * inv_freq
21
- cosine, sine = angle.cos()[:, :, None, :], angle.sin()[:, :, None, :]
22
- even, odd = x.float()[..., 0::2], x.float()[..., 1::2]
23
- rotated = torch.stack(
24
- (even * cosine - odd * sine, even * sine + odd * cosine), dim=-1
25
- )
26
- return rotated.flatten(-2).to(x.dtype)