Text Generation
Transformers
Safetensors
PyTorch
English
lamoe
mixture-of-experts
custom-code
conversational
Instructions to use IMvision12/lamoe-247 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use IMvision12/lamoe-247 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="IMvision12/lamoe-247") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# pip install -U transformers accelerate # Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("IMvision12/lamoe-247", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use IMvision12/lamoe-247 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "IMvision12/lamoe-247" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "IMvision12/lamoe-247", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/IMvision12/lamoe-247
- SGLang
How to use IMvision12/lamoe-247 with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "IMvision12/lamoe-247" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "IMvision12/lamoe-247", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "IMvision12/lamoe-247" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "IMvision12/lamoe-247", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use IMvision12/lamoe-247 with Docker Model Runner:
docker model run hf.co/IMvision12/lamoe-247
Keep model artifacts on Hub and Python implementation in GitHub source
Browse files- README.md +23 -22
- __init__.py +0 -1
- cache.py +0 -37
- caching_lamoe.py +0 -81
- config.json +0 -4
- configuration_lamoe.py +0 -49
- core_config.py +0 -130
- feedforward.py +0 -116
- gated_deltanet.py +0 -221
- layers.py +0 -89
- licenses/tokenizer-profile-MIT.txt +22 -0
- licenses/tokenizer_profiles.py +0 -55
- losses.py +0 -82
- masking.py +0 -84
- mla.py +0 -108
- modeling_lamoe.py +0 -250
- modeling_lamoe_core.py +0 -357
- normalization.py +0 -21
- release-manifest.json +10 -68
- rotary.py +0 -26
README.md
CHANGED
|
@@ -30,44 +30,45 @@ checkpoint `step-00129185`, used for the reported local benchmarks.
|
|
| 30 |
|
| 31 |

|
| 32 |
|
| 33 |
-
##
|
| 34 |
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
|
|
|
| 38 |
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
```python
|
| 42 |
-
from pathlib import Path
|
| 43 |
import torch
|
|
|
|
| 44 |
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 45 |
|
| 46 |
-
|
| 47 |
-
|
|
|
|
| 48 |
model = AutoModelForCausalLM.from_pretrained(
|
| 49 |
-
|
| 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 |
-
|
| 65 |
-
|
| 66 |
-
The
|
| 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
|
| 94 |
|
| 95 |
## Evaluation
|
| 96 |
|
|
|
|
| 30 |
|
| 31 |

|
| 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-
|
| 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":
|
| 31 |
-
"sha256": "
|
| 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/
|
| 98 |
-
"bytes":
|
| 99 |
-
"sha256": "
|
| 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":
|
| 131 |
-
"sha256": "
|
| 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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|