reaperdoesntknow commited on
Commit
77d1918
·
verified ·
1 Parent(s): d209f20

Upload TAMELM

Browse files
Files changed (5) hide show
  1. README.md +199 -0
  2. config.json +47 -0
  3. generation_config.json +10 -0
  4. model.safetensors +3 -0
  5. modeling_tamelm.py +1846 -0
README.md ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: transformers
3
+ tags: []
4
+ ---
5
+
6
+ # Model Card for Model ID
7
+
8
+ <!-- Provide a quick summary of what the model is/does. -->
9
+
10
+
11
+
12
+ ## Model Details
13
+
14
+ ### Model Description
15
+
16
+ <!-- Provide a longer summary of what this model is. -->
17
+
18
+ This is the model card of a 🤗 transformers model that has been pushed on the Hub. This model card has been automatically generated.
19
+
20
+ - **Developed by:** [More Information Needed]
21
+ - **Funded by [optional]:** [More Information Needed]
22
+ - **Shared by [optional]:** [More Information Needed]
23
+ - **Model type:** [More Information Needed]
24
+ - **Language(s) (NLP):** [More Information Needed]
25
+ - **License:** [More Information Needed]
26
+ - **Finetuned from model [optional]:** [More Information Needed]
27
+
28
+ ### Model Sources [optional]
29
+
30
+ <!-- Provide the basic links for the model. -->
31
+
32
+ - **Repository:** [More Information Needed]
33
+ - **Paper [optional]:** [More Information Needed]
34
+ - **Demo [optional]:** [More Information Needed]
35
+
36
+ ## Uses
37
+
38
+ <!-- Address questions around how the model is intended to be used, including the foreseeable users of the model and those affected by the model. -->
39
+
40
+ ### Direct Use
41
+
42
+ <!-- This section is for the model use without fine-tuning or plugging into a larger ecosystem/app. -->
43
+
44
+ [More Information Needed]
45
+
46
+ ### Downstream Use [optional]
47
+
48
+ <!-- This section is for the model use when fine-tuned for a task, or when plugged into a larger ecosystem/app -->
49
+
50
+ [More Information Needed]
51
+
52
+ ### Out-of-Scope Use
53
+
54
+ <!-- This section addresses misuse, malicious use, and uses that the model will not work well for. -->
55
+
56
+ [More Information Needed]
57
+
58
+ ## Bias, Risks, and Limitations
59
+
60
+ <!-- This section is meant to convey both technical and sociotechnical limitations. -->
61
+
62
+ [More Information Needed]
63
+
64
+ ### Recommendations
65
+
66
+ <!-- This section is meant to convey recommendations with respect to the bias, risk, and technical limitations. -->
67
+
68
+ Users (both direct and downstream) should be made aware of the risks, biases and limitations of the model. More information needed for further recommendations.
69
+
70
+ ## How to Get Started with the Model
71
+
72
+ Use the code below to get started with the model.
73
+
74
+ [More Information Needed]
75
+
76
+ ## Training Details
77
+
78
+ ### Training Data
79
+
80
+ <!-- This should link to a Dataset Card, perhaps with a short stub of information on what the training data is all about as well as documentation related to data pre-processing or additional filtering. -->
81
+
82
+ [More Information Needed]
83
+
84
+ ### Training Procedure
85
+
86
+ <!-- This relates heavily to the Technical Specifications. Content here should link to that section when it is relevant to the training procedure. -->
87
+
88
+ #### Preprocessing [optional]
89
+
90
+ [More Information Needed]
91
+
92
+
93
+ #### Training Hyperparameters
94
+
95
+ - **Training regime:** [More Information Needed] <!--fp32, fp16 mixed precision, bf16 mixed precision, bf16 non-mixed precision, fp16 non-mixed precision, fp8 mixed precision -->
96
+
97
+ #### Speeds, Sizes, Times [optional]
98
+
99
+ <!-- This section provides information about throughput, start/end time, checkpoint size if relevant, etc. -->
100
+
101
+ [More Information Needed]
102
+
103
+ ## Evaluation
104
+
105
+ <!-- This section describes the evaluation protocols and provides the results. -->
106
+
107
+ ### Testing Data, Factors & Metrics
108
+
109
+ #### Testing Data
110
+
111
+ <!-- This should link to a Dataset Card if possible. -->
112
+
113
+ [More Information Needed]
114
+
115
+ #### Factors
116
+
117
+ <!-- These are the things the evaluation is disaggregating by, e.g., subpopulations or domains. -->
118
+
119
+ [More Information Needed]
120
+
121
+ #### Metrics
122
+
123
+ <!-- These are the evaluation metrics being used, ideally with a description of why. -->
124
+
125
+ [More Information Needed]
126
+
127
+ ### Results
128
+
129
+ [More Information Needed]
130
+
131
+ #### Summary
132
+
133
+
134
+
135
+ ## Model Examination [optional]
136
+
137
+ <!-- Relevant interpretability work for the model goes here -->
138
+
139
+ [More Information Needed]
140
+
141
+ ## Environmental Impact
142
+
143
+ <!-- Total emissions (in grams of CO2eq) and additional considerations, such as electricity usage, go here. Edit the suggested text below accordingly -->
144
+
145
+ Carbon emissions can be estimated using the [Machine Learning Impact calculator](https://mlco2.github.io/impact#compute) presented in [Lacoste et al. (2019)](https://arxiv.org/abs/1910.09700).
146
+
147
+ - **Hardware Type:** [More Information Needed]
148
+ - **Hours used:** [More Information Needed]
149
+ - **Cloud Provider:** [More Information Needed]
150
+ - **Compute Region:** [More Information Needed]
151
+ - **Carbon Emitted:** [More Information Needed]
152
+
153
+ ## Technical Specifications [optional]
154
+
155
+ ### Model Architecture and Objective
156
+
157
+ [More Information Needed]
158
+
159
+ ### Compute Infrastructure
160
+
161
+ [More Information Needed]
162
+
163
+ #### Hardware
164
+
165
+ [More Information Needed]
166
+
167
+ #### Software
168
+
169
+ [More Information Needed]
170
+
171
+ ## Citation [optional]
172
+
173
+ <!-- If there is a paper or blog post introducing the model, the APA and Bibtex information for that should go in this section. -->
174
+
175
+ **BibTeX:**
176
+
177
+ [More Information Needed]
178
+
179
+ **APA:**
180
+
181
+ [More Information Needed]
182
+
183
+ ## Glossary [optional]
184
+
185
+ <!-- If relevant, include terms and calculations in this section that can help readers understand the model or model card. -->
186
+
187
+ [More Information Needed]
188
+
189
+ ## More Information [optional]
190
+
191
+ [More Information Needed]
192
+
193
+ ## Model Card Authors [optional]
194
+
195
+ [More Information Needed]
196
+
197
+ ## Model Card Contact
198
+
199
+ [More Information Needed]
config.json ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "TAMELM"
4
+ ],
5
+ "attention_backend": "flex",
6
+ "attention_top_k": 2,
7
+ "auto_map": {
8
+ "AutoConfig": "modeling_tamelm.TAMEConfig",
9
+ "AutoModelForCausalLM": "modeling_tamelm.TAMELM"
10
+ },
11
+ "balance_coef": 0.01,
12
+ "bos_token_id": 1,
13
+ "depth_dim": 768,
14
+ "depth_gate_init": 0.01,
15
+ "depth_heads": 8,
16
+ "depth_mode": "attention",
17
+ "depth_slots": 16,
18
+ "dtype": "float32",
19
+ "embed_dim": 1024,
20
+ "eos_token_id": 2,
21
+ "expert_backend": "grouped",
22
+ "expert_hidden_dim": 4096,
23
+ "expert_top_k": 2,
24
+ "graph_recent": 256,
25
+ "graph_top_r": 2,
26
+ "init_scheme": "v1",
27
+ "local_window": 256,
28
+ "loss_chunk_size": 256,
29
+ "max_seq_len": 1024,
30
+ "model_type": "tamelm_two_axis",
31
+ "n_kv_heads": 4,
32
+ "n_q_heads": 16,
33
+ "num_experts": 4,
34
+ "num_layers": 12,
35
+ "pad_token_id": 0,
36
+ "rms_norm_eps": 1e-06,
37
+ "rope_type": "warped",
38
+ "router_init_std": 0.01,
39
+ "strided_every": 4,
40
+ "strided_recent": 128,
41
+ "tie_word_embeddings": true,
42
+ "tile": 128,
43
+ "tokenizer_sha256": "0d93d8f943b1a6627d62395c2fd583894cfe577b8aca1ca946495f7abba50958",
44
+ "transformers_version": "5.5.0",
45
+ "use_cache": true,
46
+ "vocab_size": 49152
47
+ }
generation_config.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "bos_token_id": 1,
4
+ "eos_token_id": 2,
5
+ "output_attentions": false,
6
+ "output_hidden_states": false,
7
+ "pad_token_id": 0,
8
+ "transformers_version": "5.5.0",
9
+ "use_cache": true
10
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:47febb594dd8e9e95a739ef251a0ab49dd3cc85a4b07445cbd9a5f5ce67db20a
3
+ size 2828580568
modeling_tamelm.py ADDED
@@ -0,0 +1,1846 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #@title Model — ready
2
+ """TAMELM model definitions: routed attention, depth attention, and dropless MoE.
3
+
4
+ This module stays self-contained for notebook execution and portable Hugging Face
5
+ exports. Definitions are ordered from configuration and low-level operations to
6
+ model components, cache/output interfaces, and the complete language model.
7
+
8
+ Routing uses shared GQA projections and original top-k softmax probabilities.
9
+ Tile routing and graph selection are causal; cached decoding retains tile state.
10
+ CUDA FlexAttention and Triton grouped GEMM are optional acceleration backends.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import ast as _ast
16
+ import inspect as _inspect
17
+ import linecache as _linecache
18
+ import math
19
+ from dataclasses import dataclass
20
+ from typing import Dict, Optional, Tuple
21
+
22
+ import torch
23
+ import torch.nn as nn
24
+ import torch.nn.functional as F
25
+ from transformers import (
26
+ AutoConfig,
27
+ AutoModelForCausalLM,
28
+ GenerationMixin,
29
+ PretrainedConfig,
30
+ PreTrainedModel,
31
+ )
32
+ from transformers.cache_utils import Cache
33
+ from transformers.utils import ModelOutput
34
+
35
+ try:
36
+ from torch.nn.attention.flex_attention import (
37
+ BlockMask,
38
+ create_block_mask,
39
+ flex_attention,
40
+ )
41
+
42
+ HAS_FLEX = True
43
+ except ImportError: # pragma: no cover
44
+ HAS_FLEX = False
45
+
46
+ try:
47
+ import triton # type: ignore
48
+ import triton.language as tl # type: ignore
49
+
50
+ HAS_TRITON = True
51
+ except ImportError:
52
+ HAS_TRITON = False
53
+
54
+
55
+ # Notebook source capture and semantic fingerprints.
56
+ # Capture source independently of execution counts, paths, and timestamps.
57
+ _NOTEBOOK_SOURCES = globals().get("_NOTEBOOK_SOURCES", {})
58
+
59
+
60
+ # =============================================================================
61
+ # Source identity helpers
62
+ # =============================================================================
63
+
64
+
65
+ def _capture_cell_source(component):
66
+ caller = _inspect.currentframe().f_back
67
+ source = "".join(_linecache.getlines(caller.f_code.co_filename))
68
+ if not source:
69
+ try:
70
+ source = get_ipython().history_manager.input_hist_raw[-1]
71
+ except (NameError, AttributeError, IndexError):
72
+ source = ""
73
+ if not source.strip():
74
+ raise RuntimeError("Cannot capture executed source; run the definition cells in Jupyter/Colab.")
75
+ _NOTEBOOK_SOURCES[component] = source
76
+
77
+
78
+ class _WithoutDocstrings(_ast.NodeTransformer):
79
+ def _strip(self, node):
80
+ self.generic_visit(node)
81
+ if (
82
+ node.body
83
+ and isinstance(node.body[0], _ast.Expr)
84
+ and isinstance(node.body[0].value, _ast.Constant)
85
+ and isinstance(node.body[0].value.value, str)
86
+ ):
87
+ node.body = node.body[1:] or [_ast.Pass()]
88
+ return node
89
+
90
+ visit_Module = _strip
91
+ visit_ClassDef = _strip
92
+ visit_FunctionDef = _strip
93
+ visit_AsyncFunctionDef = _strip
94
+
95
+
96
+ def _semantic_source(source):
97
+ return _ast.dump(
98
+ _WithoutDocstrings().visit(_ast.parse(source)), include_attributes=False
99
+ )
100
+
101
+
102
+ # Routing mechanism IDs are part of the attention/cache contract.
103
+ GLOBAL, LOCAL, STRIDED, GRAPH = 0, 1, 2, 3
104
+ MECH_NAMES = ("global", "local", "strided", "graph")
105
+
106
+
107
+ # =============================================================================
108
+ # Configuration
109
+ # =============================================================================
110
+
111
+
112
+ @dataclass
113
+ class RoutedAttentionConfig:
114
+ d_model: int
115
+ n_q_heads: int = 16
116
+ n_kv_heads: int = 4
117
+ head_dim: Optional[int] = None
118
+ top_k: int = 2
119
+ tile: int = 128 # routing granularity == FlexAttention block size
120
+ local_window: int = 256 # tokens
121
+ strided_recent: int = 128 # tokens of dense recent context for 'strided'
122
+ strided_every: int = 4 # plus every S-th tile back (tile-relative)
123
+ graph_top_r: int = 0 # past tiles picked by 'graph'; 0 = legacy 3-way router
124
+ graph_recent: int = 128 # tokens of dense recent context for 'graph'
125
+ max_seq_len: int = 2048
126
+ router_init_std: float = 1e-2
127
+ balance_coef: float = 1e-2
128
+ backend: str = "flex" # "flex" | "dense"; decode/cached paths always dense
129
+
130
+
131
+
132
+
133
+
134
+ # =============================================================================
135
+ # Shared routing precision
136
+ # =============================================================================
137
+
138
+
139
+ def router_logits_fp32(router, x):
140
+ """Keep routing decisions out of AMP; casting rounded BF16 logits is too late."""
141
+ with torch.autocast(device_type=x.device.type, enabled=False):
142
+ return F.linear(
143
+ x.float(),
144
+ router.weight.float(),
145
+ None if router.bias is None else router.bias.float(),
146
+ )
147
+
148
+
149
+ # =============================================================================
150
+ # Position transforms
151
+ # =============================================================================
152
+
153
+
154
+ class RotaryPos(nn.Module):
155
+ """Standard RoPE with interleaved pairs (same pairing convention as BlackHoleRoPE)."""
156
+
157
+ def __init__(self, head_dim: int, base: float = 10_000.0):
158
+ super().__init__()
159
+ half = head_dim // 2
160
+ self.register_buffer(
161
+ "inv", 1.0 / base ** (torch.arange(half).float() / half), persistent=False
162
+ )
163
+
164
+ def forward(self, x, start_pos: int = 0, **_): # x: [B, H, T, dh]
165
+ T = x.shape[2]
166
+ t = torch.arange(start_pos, start_pos + T, device=x.device, dtype=torch.float32)
167
+ ang = t[:, None] * self.inv[None]
168
+ cos, sin = ang.cos(), ang.sin()
169
+ xf = x.float()
170
+ x1, x2 = xf[..., 0::2], xf[..., 1::2]
171
+ return (
172
+ torch.stack([x1 * cos - x2 * sin, x1 * sin + x2 * cos], -1)
173
+ .flatten(-2)
174
+ .to(x.dtype)
175
+ )
176
+
177
+
178
+ class WarpedRoPE(nn.Module):
179
+ """RoPE on a content-defined clock, one clock per KV group.
180
+
181
+ tau_t = softplus(w . x_t + b) > 0 clock rate of token t
182
+ T_t = sum_{s<t} tau_s token time (exclusive prefix sum)
183
+ angle = omega_f * T_t
184
+
185
+ q_m . k_n then depends on T_m - T_n = sum_{n<=s<m} tau_s: only the tokens between
186
+ them, so attention is relative in warped time and independent of where a document
187
+ sits in a packed window. Causal, cumsum-parallel, O(1) decode state (the running
188
+ time). At init (w = 0, softplus(b) = 1) it is exactly standard RoPE.
189
+ """
190
+
191
+ def __init__(
192
+ self,
193
+ d_model: int,
194
+ head_dim: int,
195
+ n_q_heads: int,
196
+ n_kv_heads: int,
197
+ base: float = 10_000.0,
198
+ ):
199
+ super().__init__()
200
+ if head_dim % 2 or n_q_heads % n_kv_heads:
201
+ raise ValueError(
202
+ "WarpedRoPE needs an even head_dim and grouped query heads"
203
+ )
204
+ self.half, self.base = head_dim // 2, float(base)
205
+ self.group = n_q_heads // n_kv_heads
206
+ self.rate = nn.Linear(d_model, n_kv_heads)
207
+ self.reset_parameters()
208
+
209
+ def reset_parameters(self):
210
+ nn.init.zeros_(self.rate.weight)
211
+ nn.init.constant_(self.rate.bias, math.log(math.e - 1.0)) # softplus(b) = 1
212
+
213
+ def clock(self, x, t0=None):
214
+ """x [B,T,d] (normed). t0 [B,Hkv] time already elapsed, or None.
215
+ Returns token times [B,T,Hkv] and inclusive times [B,T,Hkv] (cache state)."""
216
+ tau = F.softplus(router_logits_fp32(self.rate, x)) # fp32
217
+ inclusive = tau.cumsum(1)
218
+ if t0 is not None:
219
+ inclusive = inclusive + t0[:, None]
220
+ return inclusive - tau, inclusive
221
+
222
+ def rotate(self, x, times): # x [B,h,T,dh]; times [B,h,T]
223
+ # Frequencies are recomputed rather than buffered: nothing here can be left
224
+ # uninitialized by meta-device / no_init_weights loading.
225
+ inv = 1.0 / self.base ** (
226
+ torch.arange(self.half, device=x.device, dtype=torch.float32) / self.half
227
+ )
228
+ ang = times[..., None] * inv
229
+ cos, sin = ang.cos(), ang.sin()
230
+ xf = x.float()
231
+ x1, x2 = xf[..., 0::2], xf[..., 1::2]
232
+ return (
233
+ torch.stack([x1 * cos - x2 * sin, x1 * sin + x2 * cos], -1)
234
+ .flatten(-2)
235
+ .to(x.dtype)
236
+ )
237
+
238
+
239
+ class BlackHoleRoPE(nn.Module):
240
+ def __init__(
241
+ self,
242
+ dim,
243
+ max_seq_len=2048,
244
+ base_vortex_strength=1.0,
245
+ energy_amplification=0.1,
246
+ theta_base=10_000.0,
247
+ adaptive_vortex=True,
248
+ discrepancy_modulation=True,
249
+ ):
250
+ super().__init__()
251
+ if dim <= 0 or dim % 2 or max_seq_len < 1:
252
+ raise ValueError(
253
+ "BlackHoleRoPE requires an even dimension and positive max_seq_len"
254
+ )
255
+ self.dim = int(dim)
256
+ self.half_dim = dim // 2
257
+ self.max_seq_len = int(max_seq_len)
258
+ self.energy_amplification = float(energy_amplification)
259
+ self.discrepancy_modulation = bool(discrepancy_modulation)
260
+ freqs = 1.0 / (
261
+ theta_base
262
+ ** (torch.arange(0, self.half_dim, dtype=torch.float32) / self.half_dim)
263
+ )
264
+ self.register_buffer("base_freqs", freqs)
265
+ self.vortex_strength = nn.Parameter(
266
+ torch.full((max_seq_len,), base_vortex_strength),
267
+ requires_grad=adaptive_vortex,
268
+ )
269
+ self.phase_shift = nn.Parameter(torch.randn(max_seq_len, self.half_dim) * 0.01)
270
+ self.radial_warp = nn.Parameter(torch.ones(max_seq_len, self.half_dim))
271
+ if discrepancy_modulation:
272
+ self.discrepancy_projector = nn.Sequential(
273
+ nn.Linear(dim, self.half_dim),
274
+ nn.Tanh(),
275
+ nn.Linear(self.half_dim, self.half_dim),
276
+ )
277
+ self.register_buffer("energy_clamp_min", torch.tensor(0.1))
278
+ self.register_buffer("energy_clamp_max", torch.tensor(10.0))
279
+
280
+ def _compute_positional_vortex(self, seq_len, device, dtype, start_pos=0):
281
+ positions = torch.arange(
282
+ start_pos, start_pos + seq_len, dtype=torch.float32, device=device
283
+ )
284
+ position_freqs = positions.unsqueeze(1) * self.base_freqs.unsqueeze(0)
285
+ base_phase = position_freqs + self.phase_shift[start_pos : start_pos + seq_len]
286
+ vortex_phase = self.vortex_strength[start_pos : start_pos + seq_len].unsqueeze(
287
+ 1
288
+ ) * torch.sin(base_phase + position_freqs * 0.1)
289
+ final_phase = base_phase + vortex_phase
290
+ rotation = torch.complex(torch.cos(final_phase), torch.sin(final_phase))
291
+ radial_factor = torch.sqrt(position_freqs + 1e-8)
292
+ energy = 1.0 + self.energy_amplification * torch.sin(
293
+ self.radial_warp[start_pos : start_pos + seq_len] * radial_factor
294
+ + vortex_phase.abs()
295
+ )
296
+ energy = energy.clamp(self.energy_clamp_min, self.energy_clamp_max)
297
+ return rotation, energy.to(dtype=dtype)
298
+
299
+ def apply_discrepancy_modulation(self, x, symplectic_state):
300
+ if not self.discrepancy_modulation or not symplectic_state:
301
+ return None
302
+ bsz, seq_len, _ = x.shape
303
+ total = torch.zeros(bsz, seq_len, 1, device=x.device, dtype=x.dtype)
304
+ count = 0
305
+ for theta, p in symplectic_state.values():
306
+ if theta is None or p is None:
307
+ continue
308
+ ev = theta.float().var(dim=-1, keepdim=True, correction=0) + p.float().var(
309
+ dim=-1, keepdim=True, correction=0
310
+ )
311
+ ev = (
312
+ ev.unsqueeze(1).expand(-1, seq_len, -1)
313
+ if theta.dim() == 2
314
+ else ev[:, :seq_len]
315
+ )
316
+ total = total + ev.to(dtype=x.dtype)
317
+ count += 1
318
+ if count == 0:
319
+ return None
320
+ lifted = F.pad(total / count, (0, self.dim - 1))
321
+ return self.discrepancy_projector(lifted)
322
+
323
+ def forward(self, x, symplectic_state=None, start_pos=0):
324
+ bsz, seq_len, dim = x.shape
325
+ if dim != self.dim or start_pos < 0 or start_pos + seq_len > self.max_seq_len:
326
+ raise ValueError(
327
+ "BlackHoleRoPE dimension/position exceeds configured bounds"
328
+ )
329
+ input_dtype = x.dtype
330
+ xp = x.reshape(bsz, seq_len, self.half_dim, 2)
331
+ xc = torch.complex(xp[..., 0].float(), xp[..., 1].float())
332
+ rotation, energy = self._compute_positional_vortex(
333
+ seq_len, x.device, torch.float32, start_pos
334
+ )
335
+ rotation = rotation.unsqueeze(0)
336
+ energy = energy.unsqueeze(0)
337
+ disc = self.apply_discrepancy_modulation(x, symplectic_state)
338
+ if disc is not None:
339
+ phase_scale = 1.0 + 0.1 * torch.tanh(disc.float())
340
+ base_phase = torch.angle(rotation)
341
+ rotation = torch.polar(
342
+ torch.ones_like(base_phase), base_phase * phase_scale
343
+ )
344
+ amp = xc * rotation * energy.float()
345
+ return (
346
+ torch.stack([amp.real, amp.imag], dim=-1)
347
+ .reshape(bsz, seq_len, dim)
348
+ .to(input_dtype)
349
+ )
350
+
351
+
352
+ class BlackHolePos(nn.Module):
353
+ """Adapter: folds heads into batch for BlackHoleRoPE and repeats the symplectic
354
+ state per head so discrepancy modulation keeps the right batch alignment."""
355
+
356
+ def __init__(self, bh: nn.Module):
357
+ super().__init__()
358
+ self.bh = bh
359
+
360
+ def forward(self, x, start_pos: int = 0, symplectic_state=None):
361
+ B, H, T, dh = x.shape
362
+ state = None
363
+ if symplectic_state:
364
+ state = {
365
+ n: (th.repeat_interleave(H, 0), p.repeat_interleave(H, 0))
366
+ for n, (th, p) in symplectic_state.items()
367
+ if th is not None and p is not None
368
+ }
369
+ y = self.bh(
370
+ x.reshape(B * H, T, dh), symplectic_state=state, start_pos=start_pos
371
+ )
372
+ return y.view(B, H, T, dh)
373
+
374
+
375
+ # FlexAttention compilation is lazy and shared by CUDA attention calls.
376
+ _COMPILED_FLEX = None
377
+
378
+
379
+ # =============================================================================
380
+ # Attention backends and visibility geometry
381
+ # =============================================================================
382
+
383
+
384
+ def _run_flex(q, k, v, block_mask):
385
+ global _COMPILED_FLEX
386
+ if q.is_cuda:
387
+ if min(q.shape[-1], k.shape[-1], v.shape[-1]) < 16:
388
+ raise ValueError(
389
+ "CUDA FlexAttention requires head_dim >= 16; use a supported GPU model configuration"
390
+ )
391
+ if _COMPILED_FLEX is None:
392
+ _COMPILED_FLEX = torch.compile(flex_attention, dynamic=False)
393
+ return _COMPILED_FLEX(q, k, v, block_mask=block_mask, enable_gqa=True)
394
+ return flex_attention(q, k, v, block_mask=block_mask, enable_gqa=True)
395
+
396
+
397
+ def geometry_allows(mech, q_idx, kv_idx, cfg: RoutedAttentionConfig, picked=None):
398
+ """Boolean key-visibility for a given mechanism id. Pure tensor algebra so the
399
+ same function serves FlexAttention's mask_mod and the dense reference mask.
400
+ `picked` is True where kv_idx lies in a graph-selected tile of q_idx's tile."""
401
+ d = q_idx - kv_idx
402
+ causal = d >= 0
403
+ local = d < cfg.local_window
404
+ tile_gap = torch.div(q_idx, cfg.tile, rounding_mode="floor") - torch.div(
405
+ kv_idx, cfg.tile, rounding_mode="floor"
406
+ )
407
+ strided = (d < cfg.strided_recent) | (
408
+ torch.remainder(tile_gap, cfg.strided_every) == 0
409
+ )
410
+ allowed = (
411
+ (mech == GLOBAL) | ((mech == LOCAL) & local) | ((mech == STRIDED) & strided)
412
+ )
413
+ if cfg.graph_top_r:
414
+ graph = d < cfg.graph_recent
415
+ if picked is not None:
416
+ graph = graph | picked
417
+ allowed = allowed | ((mech == GRAPH) & graph)
418
+ return causal & allowed
419
+
420
+
421
+ def geometry_block_mask(mech, length, cfg, mask_mod, picked=None):
422
+ """Exact block classification in O(B * H * (T/tile)^2), never O(B*H*T^2).
423
+
424
+ A partial block uses the original elementwise predicate. Fully allowed blocks
425
+ skip that predicate. No attention probabilities or causal rules are approximated.
426
+ `picked` is the tile-level graph selection [B, H, n, n] (query tile, kv tile).
427
+ """
428
+ c = cfg.tile
429
+ n = mech.shape[-1]
430
+ starts = torch.arange(n, device=mech.device) * c
431
+ ends = (starts + c - 1).clamp(max=length - 1)
432
+ dmin = starts[:, None] - ends[None, :]
433
+ dmax = ends[:, None] - starts[None, :]
434
+ gap = (
435
+ torch.arange(n, device=mech.device)[:, None]
436
+ - torch.arange(n, device=mech.device)[None, :]
437
+ )
438
+ periodic = gap.remainder(cfg.strided_every) == 0
439
+ mechanism = mech[..., None]
440
+ any_geometry = (
441
+ (mechanism == GLOBAL)
442
+ | ((mechanism == LOCAL) & (dmin < cfg.local_window))
443
+ | ((mechanism == STRIDED) & ((dmin < cfg.strided_recent) | periodic))
444
+ )
445
+ full_geometry = (
446
+ (mechanism == GLOBAL)
447
+ | ((mechanism == LOCAL) & (dmax < cfg.local_window))
448
+ | ((mechanism == STRIDED) & ((dmax < cfg.strided_recent) | periodic))
449
+ )
450
+ if cfg.graph_top_r:
451
+ g_any, g_full = dmin < cfg.graph_recent, dmax < cfg.graph_recent
452
+ if picked is not None:
453
+ # A picked tile is a strictly earlier, complete tile: every pair in the
454
+ # block is allowed, so it is safe for the full-block path.
455
+ g_any, g_full = g_any | picked, g_full | picked
456
+ any_geometry = any_geometry | ((mechanism == GRAPH) & g_any)
457
+ full_geometry = full_geometry | ((mechanism == GRAPH) & g_full)
458
+ any_block = (dmax >= 0) & any_geometry
459
+ # Out-of-range positions in tail tiles are masked. Such blocks must not
460
+ # enter the full-block path, which skips mask_mod in the CUDA kernel.
461
+ complete_tiles = starts + c <= length
462
+ full = (
463
+ (dmin >= 0) & full_geometry & complete_tiles[:, None] & complete_tiles[None, :]
464
+ )
465
+ partial = any_block & ~full
466
+
467
+ def encode(mask):
468
+ counts = mask.sum(-1).to(
469
+ dtype=torch.int32, memory_format=torch.contiguous_format
470
+ )
471
+ # Router permutes tile/head axes; argsort preserves that nonstandard
472
+ # layout. CUDA Flex flattens B/H using the H stride and requires
473
+ # contiguous metadata (the CPU eager path does not enforce this).
474
+ indices = (
475
+ mask.to(torch.int32)
476
+ .argsort(dim=-1, descending=True, stable=True)
477
+ .to(dtype=torch.int32, memory_format=torch.contiguous_format)
478
+ )
479
+ return counts, indices
480
+
481
+ pnum, pidx = encode(partial)
482
+ fnum, fidx = encode(full)
483
+ return BlockMask.from_kv_blocks(
484
+ pnum,
485
+ pidx,
486
+ fnum,
487
+ fidx,
488
+ BLOCK_SIZE=c,
489
+ mask_mod=mask_mod,
490
+ seq_lengths=(length, length),
491
+ )
492
+
493
+
494
+ # TAMELM routed sequence attention, v2.
495
+ #
496
+ # One shared GQA projection/cache; a tiny tile-level router picks, per (tile, q-head),
497
+ # top-k attention geometries from {global, local, strided, graph}; all geometries run
498
+ # through ONE attention call (FlexAttention block mask, or an equivalent dense mask).
499
+ #
500
+ # Invariants:
501
+ # * K/V are projected, position-encoded and cached BEFORE routing.
502
+ # * Tile routing is causal: tile c is routed, and its graph neighbours are chosen,
503
+ # from its first token only, so training == chunked prefill == token-by-token decode.
504
+ # * Top-k outputs are weighted by the ORIGINAL softmax probabilities (no
505
+ # renormalization), so top-1 still trains the router.
506
+ # * Graph geometry: a query tile sees its recent window plus the top-r COMPLETED
507
+ # past tiles ranked by (first-token query) . (tile-mean key). Ranking uses
508
+ # un-rotated q/k, so it is content similarity rather than position. The tile choice
509
+ # is hard (no gradient); the router weight on 'graph' is what trains. This is the
510
+ # causal, tile-level repair of the learned k-NN adjacency from the graph router.
511
+ #
512
+ # Positional transform is pluggable: RotaryPos (standard RoPE), WarpedRoPE (RoPE on a
513
+ # content-defined clock; relative in warped time), or BlackHolePos (legacy adapter;
514
+ # note it depends on absolute position through per-position phase/vortex/energy).
515
+
516
+
517
+ # =============================================================================
518
+ # Routed sequence attention
519
+ # =============================================================================
520
+
521
+
522
+ class RoutedSequenceAttention(nn.Module):
523
+
524
+ def __init__(self, cfg: RoutedAttentionConfig, pos: Optional[nn.Module] = None):
525
+ super().__init__()
526
+ H, Hkv, K = cfg.n_q_heads, cfg.n_kv_heads, cfg.top_k
527
+ dh = cfg.head_dim or cfg.d_model // H
528
+ if cfg.graph_top_r < 0:
529
+ raise ValueError("graph_top_r must be nonnegative")
530
+ self.M = 4 if cfg.graph_top_r else 3 # number of mechanisms
531
+ if (
532
+ min(
533
+ H,
534
+ Hkv,
535
+ dh,
536
+ cfg.tile,
537
+ cfg.local_window,
538
+ cfg.strided_recent,
539
+ cfg.strided_every,
540
+ cfg.graph_recent,
541
+ cfg.max_seq_len,
542
+ )
543
+ <= 0
544
+ ):
545
+ raise ValueError("Attention dimensions/windows must be positive")
546
+ if H % Hkv or dh % 2 or not 1 <= K <= self.M:
547
+ raise ValueError("Invalid GQA heads, odd rotary dimension, or top_k")
548
+ if cfg.backend not in {"flex", "dense"}:
549
+ raise ValueError("backend must be flex or dense")
550
+ self.cfg, self.H, self.Hkv, self.K, self.dh = cfg, H, Hkv, K, dh
551
+ self.graph = bool(cfg.graph_top_r)
552
+ self.n_max = -(-cfg.max_seq_len // cfg.tile) # tiles in a full window
553
+ self._force_flex = False # test hook: Flex on CPU (eager)
554
+
555
+ self.q_proj = nn.Linear(cfg.d_model, H * dh, bias=False)
556
+ self.k_proj = nn.Linear(cfg.d_model, Hkv * dh, bias=False)
557
+ self.v_proj = nn.Linear(cfg.d_model, Hkv * dh, bias=False)
558
+ self.o_proj = nn.Linear(H * dh, cfg.d_model, bias=False)
559
+
560
+ self.router = nn.Linear(cfg.d_model, H * self.M, bias=False)
561
+ nn.init.normal_(self.router.weight, std=cfg.router_init_std)
562
+ # [H, M] mask: this revision has no mandatory anchor heads.
563
+ # The run contract records actual constraints; anchored variants are separate experiments.
564
+ self.register_buffer("allowed", torch.ones(H, self.M, dtype=torch.bool))
565
+
566
+ self.pos = pos if pos is not None else RotaryPos(dh)
567
+
568
+ # --------------------------------------------------------------------- routing
569
+ def _route(self, x_starts): # [B, n, d]
570
+ B, n, _ = x_starts.shape
571
+ logits = router_logits_fp32(self.router, x_starts).view(B, n, self.H, self.M)
572
+ logits = logits.masked_fill(~self.allowed, float("-inf"))
573
+ P = logits.softmax(-1)
574
+ w, idx = P.topk(self.K, dim=-1) # un-renormalized weights
575
+ return P, w, idx
576
+
577
+ def _balance(self, P, idx):
578
+ M = self.M
579
+ f = F.one_hot(idx, M).float().sum(-2).reshape(-1, M).mean(0) / self.K
580
+ Pbar = P.reshape(-1, M).mean(0)
581
+ loss = self.cfg.balance_coef * M * (f * Pbar).sum()
582
+ ent = -(P.clamp_min(1e-9).log() * P).sum(-1).mean()
583
+ return loss, f.detach(), ent.detach()
584
+
585
+ @torch.no_grad()
586
+ def _select_tiles(self, q_first, k_raw, tiles):
587
+ """q_first [B,H,n_new,dh]: un-rotated query of each new tile's first token.
588
+ k_raw [B,Hkv,S,dh]: every un-rotated key known so far. tiles [n_new].
589
+ Returns [B, n_new, H, n_max] bool: past complete tiles picked per query tile."""
590
+ C, r = self.cfg.tile, self.cfg.graph_top_r
591
+ B, H, n_new, dh = q_first.shape
592
+ sel = torch.zeros(
593
+ B, n_new, H, self.n_max, dtype=torch.bool, device=q_first.device
594
+ )
595
+ n_full = min(k_raw.shape[2] // C, self.n_max)
596
+ if n_full == 0 or n_new == 0:
597
+ return sel
598
+ with torch.autocast(device_type=q_first.device.type, enabled=False):
599
+ kbar = k_raw[:, :, : n_full * C].float().unflatten(2, (n_full, C)).mean(3)
600
+ kbar = kbar.repeat_interleave(H // self.Hkv, dim=1) # [B,H,n_full,dh]
601
+ scores = torch.einsum("bhnd,bhtd->bnht", q_first.float(), kbar)
602
+ # Tile t is a candidate for query tile c iff t < c (strictly earlier, hence
603
+ # complete by the time tile c's first token exists).
604
+ past = torch.arange(n_full, device=sel.device)[None, :] < tiles[:, None]
605
+ scores = scores.masked_fill(~past[None, :, None, :], float("-inf"))
606
+ vals, idx = scores.topk(min(r, n_full), dim=-1)
607
+ part = torch.zeros(B, n_new, H, n_full, dtype=torch.bool, device=sel.device)
608
+ part.scatter_(-1, idx, torch.isfinite(vals))
609
+ sel[..., :n_full] = part
610
+ return sel
611
+
612
+ # --------------------------------------------------------------------- forward
613
+ def forward(
614
+ self, x, cache: Optional[Dict] = None, start_pos: int = 0, symplectic_state=None
615
+ ):
616
+ """x: [B, T, d]. With `cache` (a dict, mutated in place) supports chunked
617
+ prefill and decode; `start_pos` is the absolute position of x[:, 0]."""
618
+ cfg, H, Hkv, K, dh, C = (
619
+ self.cfg,
620
+ self.H,
621
+ self.Hkv,
622
+ self.K,
623
+ self.dh,
624
+ self.cfg.tile,
625
+ )
626
+ B, T, _ = x.shape
627
+ if T < 1 or start_pos < 0:
628
+ raise ValueError("Nonempty input and nonnegative start_pos required")
629
+ if cache is not None and torch.is_grad_enabled():
630
+ raise ValueError("KV caching is inference-only; use torch.no_grad()")
631
+ if cache is None and start_pos != 0:
632
+ raise ValueError("A nonzero start_pos requires the prior KV cache")
633
+ if cache is not None and "k" not in cache and start_pos != 0:
634
+ raise ValueError("An empty cache must begin at position zero")
635
+
636
+ q_raw = self.q_proj(x).view(B, T, H, dh).transpose(1, 2)
637
+ k_raw = self.k_proj(x).view(B, T, Hkv, dh).transpose(1, 2)
638
+ v = self.v_proj(x).view(B, T, Hkv, dh).transpose(1, 2)
639
+ if isinstance(self.pos, WarpedRoPE):
640
+ t0 = (
641
+ cache["clock"][:, -1]
642
+ if (cache is not None and "clock" in cache)
643
+ else None
644
+ )
645
+ times, clock = self.pos.clock(x, t0) # [B, T, Hkv]
646
+ if cache is not None:
647
+ cache["clock"] = (
648
+ torch.cat([cache["clock"], clock], 1) if "clock" in cache else clock
649
+ )
650
+ times = times.transpose(1, 2) # [B, Hkv, T]
651
+ q = self.pos.rotate(q_raw, times.repeat_interleave(H // Hkv, dim=1))
652
+ k = self.pos.rotate(k_raw, times)
653
+ else:
654
+ # Compute the positional transform once for the shared Q/K position range.
655
+ qk = self.pos(
656
+ torch.cat((q_raw, k_raw), dim=1),
657
+ start_pos=start_pos,
658
+ symplectic_state=symplectic_state,
659
+ )
660
+ q, k = qk.split((H, Hkv), dim=1)
661
+
662
+ # 1) write sequence memory before any routing decision
663
+ k_raw_all = k_raw
664
+ if cache is not None:
665
+ if "k" in cache:
666
+ assert (
667
+ cache["k"].shape[2] == start_pos
668
+ ), "cache length must equal start_pos"
669
+ k = torch.cat([cache["k"], k], 2)
670
+ v = torch.cat([cache["v"], v], 2)
671
+ if self.graph:
672
+ k_raw_all = torch.cat([cache["k_raw"], k_raw], 2)
673
+ cache["k"], cache["v"] = k, v
674
+ if self.graph:
675
+ cache["k_raw"] = k_raw_all
676
+
677
+ # 2) causal tile routing: route every tile whose FIRST token lies in this chunk
678
+ first_tile, last_tile = start_pos // C, (start_pos + T - 1) // C
679
+ n_known = cache["w"].shape[1] if (cache is not None and "w" in cache) else 0
680
+ new_tiles = torch.arange(n_known, last_tile + 1, device=x.device)
681
+ offsets = new_tiles * C - start_pos
682
+ if n_known <= last_tile and n_known * C < start_pos:
683
+ raise ValueError("A tile first token was skipped; routes out of sync")
684
+ aux = {}
685
+ sel_new = None
686
+ if len(new_tiles):
687
+ P_new, w_new, idx_new = self._route(x[:, offsets])
688
+ loss, f, ent = self._balance(P_new, idx_new)
689
+ aux = {"balance_loss": loss, "mech_freq": f, "router_entropy": ent}
690
+ with torch.no_grad():
691
+ selected = F.one_hot(idx_new, self.M).float()
692
+ selected_weight = torch.zeros_like(P_new).scatter_add_(
693
+ -1, idx_new, w_new
694
+ )
695
+ aux.update(
696
+ query_tile_indices=new_tiles.detach(),
697
+ mech_frequency_by_query_tile=selected.mean(dim=(0, 2, 3)),
698
+ mech_probability_by_query_tile=P_new.detach().mean(dim=(0, 2)),
699
+ mech_selected_weight_by_query_tile=selected_weight.mean(dim=(0, 2)),
700
+ )
701
+ if self.graph:
702
+ sel_new = self._select_tiles(q_raw[:, :, offsets], k_raw_all, new_tiles)
703
+ else:
704
+ w_new = x.new_zeros(B, 0, H, K, dtype=torch.float32)
705
+ idx_new = torch.zeros(B, 0, H, K, dtype=torch.long, device=x.device)
706
+ if self.graph and sel_new is None:
707
+ sel_new = torch.zeros(
708
+ B, 0, H, self.n_max, dtype=torch.bool, device=x.device
709
+ )
710
+ if cache is not None and n_known:
711
+ w_all = torch.cat([cache["w"], w_new], 1)
712
+ idx_all = torch.cat([cache["idx"], idx_new], 1)
713
+ sel_all = torch.cat([cache["sel"], sel_new], 1) if self.graph else None
714
+ else:
715
+ w_all, idx_all, sel_all = w_new, idx_new, sel_new
716
+ if cache is not None:
717
+ cache["w"], cache["idx"] = w_all.detach(), idx_all
718
+ if self.graph:
719
+ cache["sel"] = sel_all
720
+
721
+ # slot s = h*K + j (maps to kv head s // (H*K/Hkv) == h // (H/Hkv))
722
+ w_t = w_all[:, first_tile : last_tile + 1] # [B, n, H, K]
723
+ i_t = idx_all[:, first_tile : last_tile + 1]
724
+ n = w_t.shape[1]
725
+ mech = i_t.permute(0, 2, 3, 1).reshape(B, H * K, n) # [B, HK, n]
726
+ wslot = w_t.permute(0, 2, 3, 1).reshape(B, H * K, n)
727
+ picked_slots = None
728
+ if self.graph: # [B, HK, n, n_max]
729
+ picked_slots = (
730
+ sel_all[:, first_tile : last_tile + 1]
731
+ .permute(0, 2, 1, 3)
732
+ .repeat_interleave(K, dim=1)
733
+ )
734
+
735
+ q_slots = q.repeat_interleave(K, dim=1) # [B, HK, T, dh]
736
+ q_pos = torch.arange(start_pos, start_pos + T, device=x.device)
737
+ q_tile = q_pos // C - first_tile # [T]
738
+
739
+ if cfg.backend == "flex" and not HAS_FLEX:
740
+ raise RuntimeError(
741
+ "FlexAttention unavailable; install compatible torch or explicitly select dense"
742
+ )
743
+ use_flex = (
744
+ cfg.backend == "flex"
745
+ and (x.is_cuda or self._force_flex)
746
+ and cache is None
747
+ and start_pos == 0
748
+ )
749
+ if use_flex:
750
+ n_max = self.n_max
751
+ if picked_slots is not None:
752
+
753
+ def mask_mod(b, h, q_idx, kv_idx):
754
+ qt = (q_idx // C).clamp(max=n - 1)
755
+ picked = picked_slots[b, h, qt, (kv_idx // C).clamp(max=n_max - 1)]
756
+ return (
757
+ (q_idx < T)
758
+ & (kv_idx < T)
759
+ & geometry_allows(mech[b, h, qt], q_idx, kv_idx, cfg, picked)
760
+ )
761
+
762
+ else:
763
+
764
+ def mask_mod(b, h, q_idx, kv_idx):
765
+ return (
766
+ (q_idx < T)
767
+ & (kv_idx < T)
768
+ & geometry_allows(
769
+ mech[b, h, (q_idx // C).clamp(max=n - 1)],
770
+ q_idx,
771
+ kv_idx,
772
+ cfg,
773
+ )
774
+ )
775
+
776
+ if C == 128:
777
+ bm = geometry_block_mask(
778
+ mech,
779
+ T,
780
+ cfg,
781
+ mask_mod,
782
+ None if picked_slots is None else picked_slots[..., :n],
783
+ )
784
+ else:
785
+ bm = create_block_mask(
786
+ mask_mod,
787
+ B,
788
+ H * K,
789
+ T,
790
+ T,
791
+ device=x.device,
792
+ BLOCK_SIZE=C,
793
+ _compile=x.is_cuda,
794
+ )
795
+ out = _run_flex(q_slots, k, v, bm)
796
+ else:
797
+ S = k.shape[2]
798
+ kv_pos = torch.arange(S, device=x.device)
799
+ m = mech[:, :, q_tile] # [B, HK, T]
800
+ picked = None
801
+ if picked_slots is not None:
802
+ picked = picked_slots[:, :, q_tile][..., kv_pos // C] # [B, HK, T, S]
803
+ allow = geometry_allows(
804
+ m[..., None],
805
+ q_pos[None, None, :, None],
806
+ kv_pos[None, None, None, :],
807
+ cfg,
808
+ picked,
809
+ ) # [B, HK, T, S]
810
+ out = F.scaled_dot_product_attention(
811
+ q_slots, k, v, attn_mask=allow, enable_gqa=True
812
+ )
813
+
814
+ # 3) merge: o_h = sum_j P_{h,m_j} * A_{m_j}
815
+ out = out * wslot[:, :, q_tile, None].to(out.dtype)
816
+ out = out.view(B, H, K, T, dh).sum(2).transpose(1, 2).reshape(B, T, H * dh)
817
+ return self.o_proj(out), aux
818
+
819
+
820
+ # =============================================================================
821
+ # Grouped GEMM acceleration
822
+ # =============================================================================
823
+
824
+ # Ampere-compatible dropless grouped GEMM with explicit autograd.
825
+ #
826
+ # No Hopper TMA instructions, token dropping, padding capacity, or CPU count reads.
827
+ # A is [sum(tokens), K], W is [experts,K,N], offsets are cumulative token counts.
828
+ # Persistent forward/input-gradient kernels use tensor-core dot products; each
829
+ # weight-gradient tile has a unique writer and accumulates in FP32.
830
+
831
+ if HAS_TRITON:
832
+
833
+ @triton.jit
834
+ def _grouped_forward(
835
+ A,
836
+ W,
837
+ O,
838
+ Y,
839
+ K: tl.constexpr,
840
+ N: tl.constexpr,
841
+ AS0: tl.constexpr,
842
+ AS1: tl.constexpr,
843
+ WS0: tl.constexpr,
844
+ WS1: tl.constexpr,
845
+ WS2: tl.constexpr,
846
+ E: tl.constexpr,
847
+ GRID: tl.constexpr,
848
+ BM: tl.constexpr = 32,
849
+ BN: tl.constexpr = 64,
850
+ BK: tl.constexpr = 32,
851
+ ):
852
+ tile_id = tl.program_id(0)
853
+ prior_tiles = 0
854
+ start = 0
855
+ for expert in range(E):
856
+ end = tl.load(O + expert)
857
+ count = end - start
858
+ nt = tl.cdiv(N, BN)
859
+ tiles = tl.cdiv(count, BM) * nt
860
+ while tile_id >= prior_tiles and tile_id < prior_tiles + tiles:
861
+ local = tile_id - prior_tiles
862
+ mi = (local // nt) * BM + tl.arange(0, BM)
863
+ ni = (local % nt) * BN + tl.arange(0, BN)
864
+ ki = tl.arange(0, BK)
865
+ acc = tl.zeros((BM, BN), tl.float32)
866
+ for kk in range(tl.cdiv(K, BK)):
867
+ kval = kk * BK + ki
868
+ av = tl.load(
869
+ A + (start + mi[:, None]) * AS0 + kval[None, :] * AS1,
870
+ (mi[:, None] < count) & (kval[None, :] < K),
871
+ other=0,
872
+ )
873
+ wv = tl.load(
874
+ W + expert * WS0 + kval[:, None] * WS1 + ni[None, :] * WS2,
875
+ (kval[:, None] < K) & (ni[None, :] < N),
876
+ other=0,
877
+ )
878
+ acc += tl.dot(av, wv)
879
+ tl.store(
880
+ Y + (start + mi[:, None]) * N + ni[None, :],
881
+ acc,
882
+ (mi[:, None] < count) & (ni[None, :] < N),
883
+ )
884
+ tile_id += GRID
885
+ prior_tiles += tiles
886
+ start = end
887
+
888
+ @triton.jit
889
+ def _grouped_weight_grad(
890
+ A,
891
+ DY,
892
+ O,
893
+ DW,
894
+ K: tl.constexpr,
895
+ N: tl.constexpr,
896
+ AS0: tl.constexpr,
897
+ AS1: tl.constexpr,
898
+ DS0: tl.constexpr,
899
+ DS1: tl.constexpr,
900
+ BM: tl.constexpr = 32,
901
+ BN: tl.constexpr = 64,
902
+ BT: tl.constexpr = 32,
903
+ ):
904
+ expert = tl.program_id(0)
905
+ ki = tl.program_id(1) * BM + tl.arange(0, BM)
906
+ ni = tl.program_id(2) * BN + tl.arange(0, BN)
907
+ end = tl.load(O + expert)
908
+ start = 0
909
+ if expert > 0:
910
+ start = tl.load(O + expert - 1)
911
+ ti = tl.arange(0, BT)
912
+ acc = tl.zeros((BM, BN), tl.float32)
913
+ for block in range(tl.cdiv(end - start, BT)):
914
+ token = start + block * BT + ti
915
+ av = tl.load(
916
+ A + token[None, :] * AS0 + ki[:, None] * AS1,
917
+ (token[None, :] < end) & (ki[:, None] < K),
918
+ other=0,
919
+ )
920
+ dy = tl.load(
921
+ DY + token[:, None] * DS0 + ni[None, :] * DS1,
922
+ (token[:, None] < end) & (ni[None, :] < N),
923
+ other=0,
924
+ )
925
+ acc += tl.dot(av, dy)
926
+ tl.store(
927
+ DW + expert * K * N + ki[:, None] * N + ni[None, :],
928
+ acc,
929
+ (ki[:, None] < K) & (ni[None, :] < N),
930
+ )
931
+
932
+
933
+ def _forward(a, w, offsets):
934
+ if not HAS_TRITON or not a.is_cuda:
935
+ raise RuntimeError("Ampere grouped GEMM requires CUDA and Triton")
936
+ if a.dtype not in (torch.bfloat16, torch.float16) or w.dtype != a.dtype:
937
+ raise ValueError("Grouped GEMM expects matching BF16/FP16 operands")
938
+ if (
939
+ a.ndim != 2
940
+ or w.ndim != 3
941
+ or a.shape[1] != w.shape[1]
942
+ or offsets.numel() != w.shape[0]
943
+ ):
944
+ raise ValueError("Grouped GEMM shape mismatch")
945
+ out = torch.empty((a.shape[0], w.shape[2]), device=a.device, dtype=a.dtype)
946
+ grid = torch.cuda.get_device_properties(a.device).multi_processor_count
947
+ with torch.cuda.device(a.device):
948
+ _grouped_forward[(grid,)](
949
+ a,
950
+ w,
951
+ offsets,
952
+ out,
953
+ a.shape[1],
954
+ w.shape[2],
955
+ *a.stride(),
956
+ *w.stride(),
957
+ w.shape[0],
958
+ grid,
959
+ num_warps=4,
960
+ num_stages=3,
961
+ )
962
+ return out
963
+
964
+
965
+ class _GroupedGEMM(torch.autograd.Function):
966
+ @staticmethod
967
+ def forward(ctx, a, w, offsets):
968
+ ctx.save_for_backward(a, w, offsets)
969
+ return _forward(a, w, offsets)
970
+
971
+ @staticmethod
972
+ def backward(ctx, grad):
973
+ a, w, offsets = ctx.saved_tensors
974
+ da = (
975
+ _forward(grad, w.transpose(-1, -2), offsets)
976
+ if ctx.needs_input_grad[0]
977
+ else None
978
+ )
979
+ dw = None
980
+ if ctx.needs_input_grad[1]:
981
+ dw = torch.empty(w.shape, device=w.device, dtype=w.dtype)
982
+ grid = (
983
+ w.shape[0],
984
+ triton.cdiv(w.shape[1], 32),
985
+ triton.cdiv(w.shape[2], 64),
986
+ )
987
+ with torch.cuda.device(a.device):
988
+ _grouped_weight_grad[grid](
989
+ a,
990
+ grad,
991
+ offsets,
992
+ dw,
993
+ w.shape[1],
994
+ w.shape[2],
995
+ *a.stride(),
996
+ *grad.stride(),
997
+ num_warps=4,
998
+ num_stages=3,
999
+ )
1000
+ return da, dw, None
1001
+
1002
+
1003
+ def ampere_grouped_mm(a, w, offsets):
1004
+ return _GroupedGEMM.apply(a, w, offsets)
1005
+
1006
+
1007
+ def grouped_mm(a, b, offsets):
1008
+ return ampere_grouped_mm(a, b, offsets)
1009
+
1010
+
1011
+ # =============================================================================
1012
+ # Model building blocks
1013
+ # =============================================================================
1014
+
1015
+
1016
+ class RMSNorm(nn.Module):
1017
+ def __init__(self, dim, eps=1e-6):
1018
+ super().__init__()
1019
+ self.eps = eps
1020
+ self.weight = nn.Parameter(torch.ones(dim))
1021
+
1022
+ def forward(self, x):
1023
+ return (
1024
+ x.float() * torch.rsqrt(x.float().square().mean(-1, keepdim=True) + self.eps)
1025
+ ).to(x.dtype) * self.weight
1026
+
1027
+
1028
+ class DepthAttention(nn.Module):
1029
+ """Bank entries [B,T,H,d] preserve token index exactly; never reduce over T.
1030
+
1031
+ Keep the input embedding plus the most recent layer states. Keys and values
1032
+ are projected once when written, with autograd retained through the bank.
1033
+ """
1034
+
1035
+ def __init__(self, cfg):
1036
+ super().__init__()
1037
+ self.heads = cfg.depth_heads
1038
+ self.dim = cfg.depth_dim
1039
+ self.q_proj = nn.Linear(cfg.embed_dim, cfg.depth_dim, bias=False)
1040
+ self.out_proj = nn.Linear(cfg.depth_dim, cfg.embed_dim, bias=False)
1041
+ self.gate = nn.Parameter(torch.tensor(cfg.depth_gate_init))
1042
+
1043
+ def forward(self, x, bank):
1044
+ b, t, _ = x.shape
1045
+ q = self.q_proj(x).view(b, t, self.heads, self.dim // self.heads)
1046
+ keys = torch.stack([kv[0] for kv in bank], dim=-2)
1047
+ vals = torch.stack([kv[1] for kv in bank], dim=-2)
1048
+ scores = (q.unsqueeze(-2).float() * keys.float()).sum(-1) / math.sqrt(
1049
+ q.shape[-1]
1050
+ )
1051
+ weights = scores.softmax(-1).to(vals.dtype)
1052
+ y = (weights.unsqueeze(-1) * vals).sum(-2).reshape(b, t, self.dim)
1053
+ return self.gate.tanh() * self.out_proj(y)
1054
+
1055
+
1056
+ class DroplessMoE(nn.Module):
1057
+ def __init__(self, cfg):
1058
+ super().__init__()
1059
+ self.cfg = cfg
1060
+ self.router = nn.Linear(cfg.embed_dim, cfg.num_experts, bias=False)
1061
+ self.up = nn.Parameter(
1062
+ torch.empty(cfg.num_experts, cfg.embed_dim, cfg.expert_hidden_dim * 2)
1063
+ )
1064
+ self.down = nn.Parameter(
1065
+ torch.empty(cfg.num_experts, cfg.expert_hidden_dim, cfg.embed_dim)
1066
+ )
1067
+ nn.init.normal_(self.up, std=0.02)
1068
+ nn.init.normal_(self.down, std=0.02 / math.sqrt(2 * cfg.num_layers))
1069
+
1070
+ def forward(self, x):
1071
+ cfg = self.cfg
1072
+ flat = x.reshape(-1, x.shape[-1])
1073
+ prob = router_logits_fp32(self.router, flat).softmax(-1)
1074
+ weights, experts = prob.topk(cfg.expert_top_k, -1)
1075
+ weights = weights / weights.sum(-1, keepdim=True)
1076
+ order = experts.flatten().argsort(stable=True)
1077
+ token_ids = order // cfg.expert_top_k
1078
+ dispatched = flat[token_ids]
1079
+ counts = torch.zeros(cfg.num_experts, device=x.device, dtype=torch.int32)
1080
+ counts.scatter_add_(
1081
+ 0, experts.flatten(), torch.ones_like(experts.flatten(), dtype=torch.int32)
1082
+ )
1083
+ if cfg.expert_backend == "grouped" and x.is_cuda:
1084
+ # Explicit casts are differentiable and avoid ambiguous autocast support.
1085
+ offsets = counts.cumsum(0, dtype=torch.int32)
1086
+ h = grouped_mm(
1087
+ dispatched.to(torch.bfloat16), self.up.to(torch.bfloat16), offsets
1088
+ )
1089
+ gate, value = h.chunk(2, -1)
1090
+ h = (F.silu(gate) * value).contiguous()
1091
+ result = grouped_mm(h, self.down.to(torch.bfloat16), offsets)
1092
+ else:
1093
+ # One synchronization for all counts, instead of nonzero()+sync per expert.
1094
+ sizes = counts.cpu().tolist()
1095
+ chunks = dispatched.split(sizes, 0)
1096
+ result = torch.cat(
1097
+ [self._expert(chunk, i) for i, chunk in enumerate(chunks)], 0
1098
+ )
1099
+ # Autocast produces BF16 expert outputs while the residual may be FP32.
1100
+ # Both backends must mix in the residual dtype; index_add does not promote.
1101
+ result = result.to(flat.dtype)
1102
+ weighted = result * weights.flatten()[order, None].to(result.dtype)
1103
+ mixed = torch.zeros_like(flat).index_add(0, token_ids, weighted)
1104
+ frequency = counts.float() / experts.numel()
1105
+ balance = cfg.balance_coef * cfg.num_experts * (frequency * prob.mean(0)).sum()
1106
+ return mixed.view_as(x), balance, frequency.detach()
1107
+
1108
+ def _expert(self, x, i):
1109
+ gate, value = (x @ self.up[i]).chunk(2, -1)
1110
+ return (F.silu(gate) * value) @ self.down[i]
1111
+
1112
+
1113
+ class AttentionBlock(nn.Module):
1114
+ def __init__(self, cfg):
1115
+ super().__init__()
1116
+ self.hnorm, self.dnorm, self.mnorm = (RMSNorm(cfg.embed_dim, cfg.rms_norm_eps) for _ in range(3))
1117
+ ac = RoutedAttentionConfig(
1118
+ d_model=cfg.embed_dim,
1119
+ n_q_heads=cfg.n_q_heads,
1120
+ n_kv_heads=cfg.n_kv_heads,
1121
+ top_k=cfg.attention_top_k,
1122
+ tile=cfg.tile,
1123
+ local_window=cfg.local_window,
1124
+ strided_recent=cfg.strided_recent,
1125
+ strided_every=cfg.strided_every,
1126
+ graph_top_r=cfg.graph_top_r,
1127
+ graph_recent=cfg.graph_recent,
1128
+ max_seq_len=cfg.max_seq_len,
1129
+ router_init_std=cfg.router_init_std,
1130
+ balance_coef=cfg.balance_coef,
1131
+ backend=cfg.attention_backend,
1132
+ )
1133
+ head_dim = cfg.embed_dim // cfg.n_q_heads
1134
+ if cfg.rope_type == "warped":
1135
+ pos = WarpedRoPE(cfg.embed_dim, head_dim, cfg.n_q_heads, cfg.n_kv_heads)
1136
+ elif cfg.rope_type == "blackhole":
1137
+ pos = BlackHolePos(
1138
+ BlackHoleRoPE(head_dim, cfg.max_seq_len, discrepancy_modulation=False)
1139
+ )
1140
+ else:
1141
+ pos = None
1142
+ self.horizontal = RoutedSequenceAttention(ac, pos)
1143
+ self.vertical = DepthAttention(cfg)
1144
+ self.moe = DroplessMoE(cfg)
1145
+ self.depth_mode = cfg.depth_mode
1146
+
1147
+ def forward(self, x, bank, cache=None, start_pos=0):
1148
+ h, aux = self.horizontal(self.hnorm(x), cache=cache, start_pos=start_pos)
1149
+ x = x + h
1150
+ if self.depth_mode == "attention":
1151
+ x = x + self.vertical(self.dnorm(x), bank)
1152
+ m, mloss, freq = self.moe(self.mnorm(x))
1153
+ aux["expert_frequency"] = freq
1154
+ return x + m, aux.get("balance_loss", x.new_zeros(())) + mloss, aux
1155
+
1156
+
1157
+ # =============================================================================
1158
+ # Hugging Face cache and output interfaces
1159
+ # =============================================================================
1160
+
1161
+
1162
+ class TAMECache(Cache):
1163
+ """Per-sequence KV and tile-route state with HF beam and rollback operations.
1164
+
1165
+ Physical token count includes padding; each row's rotary position counts only
1166
+ real tokens. Depth attention is same-token across layers, so its bank is
1167
+ reconstructed for new tokens and needs no cross-token cache.
1168
+ """
1169
+
1170
+ def __init__(self, num_layers, tile):
1171
+ super().__init__(layers=[])
1172
+ self.num_layers, self.tile = num_layers, tile
1173
+ self.rows = []
1174
+ self.mask = None
1175
+
1176
+ def get_seq_length(self, layer_idx=0):
1177
+ return 0 if self.mask is None else self.mask.shape[1]
1178
+
1179
+ def get_max_cache_shape(self, layer_idx=0):
1180
+ return -1
1181
+
1182
+ def get_mask_sizes(self, query_length, layer_idx=0):
1183
+ return self.get_seq_length() + query_length, 0
1184
+
1185
+ @property
1186
+ def is_compileable(self):
1187
+ return False
1188
+
1189
+ @property
1190
+ def is_initialized(self):
1191
+ return bool(self.rows)
1192
+
1193
+ @property
1194
+ def is_sliding(self):
1195
+ return [False] * self.num_layers
1196
+
1197
+ @property
1198
+ def max_batch_size(self):
1199
+ return len(self.rows)
1200
+
1201
+ def reset(self):
1202
+ self.rows = []
1203
+ self.mask = None
1204
+
1205
+ def row_length(self, index):
1206
+ row = self.rows[index]
1207
+ return row[0]["k"].shape[2] if row and "k" in row[0] else 0
1208
+
1209
+ def reorder_cache(self, beam_idx):
1210
+ indices = beam_idx.tolist()
1211
+ self.rows = [[dict(layer) for layer in self.rows[i]] for i in indices]
1212
+ if self.mask is not None:
1213
+ self.mask = self.mask.index_select(0, beam_idx.to(self.mask.device))
1214
+
1215
+ def batch_select_indices(self, indices):
1216
+ self.reorder_cache(indices)
1217
+
1218
+ def batch_repeat_interleave(self, repeats):
1219
+ if self.mask is not None:
1220
+ indices = torch.arange(
1221
+ len(self.rows), device=self.mask.device
1222
+ ).repeat_interleave(repeats)
1223
+ self.reorder_cache(indices)
1224
+
1225
+ def crop(self, max_length):
1226
+ seen = self.get_seq_length()
1227
+ end = max(0, seen + max_length) if max_length < 0 else min(seen, max_length)
1228
+ if self.mask is None:
1229
+ return
1230
+ self.mask = self.mask[:, :end]
1231
+ for i, row in enumerate(self.rows):
1232
+ length = int(self.mask[i].sum())
1233
+ tiles = (length + self.tile - 1) // self.tile
1234
+ for layer in row:
1235
+ for key in ("k", "v", "k_raw"): # [B, Hkv, S, dh]
1236
+ if key in layer:
1237
+ layer[key] = layer[key][:, :, :length]
1238
+ if "clock" in layer: # [B, S, Hkv] warped time
1239
+ layer["clock"] = layer["clock"][:, :length]
1240
+ for key in ("w", "idx", "sel"): # [B, tiles, ...]
1241
+ if key in layer:
1242
+ layer[key] = layer[key][:, :tiles]
1243
+
1244
+
1245
+ @dataclass
1246
+ class TAMEOutput(ModelOutput):
1247
+ loss: torch.Tensor | None = None
1248
+ logits: torch.Tensor | None = None
1249
+ past_key_values: TAMECache | list | None = None
1250
+ aux_losses: dict | None = None
1251
+ attention_stats: list | None = None
1252
+
1253
+
1254
+ # =============================================================================
1255
+ # Complete language model configuration
1256
+ # =============================================================================
1257
+
1258
+ class TAMEConfig(PretrainedConfig):
1259
+ model_type = "tamelm_two_axis"
1260
+ attribute_map = {
1261
+ "hidden_size": "embed_dim",
1262
+ "num_hidden_layers": "num_layers",
1263
+ "num_attention_heads": "n_q_heads",
1264
+ "num_key_value_heads": "n_kv_heads",
1265
+ "max_position_embeddings": "max_seq_len",
1266
+ }
1267
+ # Keys introduced after the alpha checkpoint, with the value that reproduces
1268
+ # the older architecture when a saved config predates them.
1269
+ _LEGACY_DEFAULTS = {"graph_top_r": 0, "init_scheme": "pilot"}
1270
+
1271
+ def __init__(
1272
+ # Configuration instance; supplied automatically by Python.
1273
+ self,
1274
+ # Tokenizer vocabulary size; number of token IDs to embed/predict.
1275
+ vocab_size=44469,
1276
+ # Main token representation width.
1277
+ embed_dim=1024,
1278
+ # Number of stacked attention/depth/expert blocks.
1279
+ num_layers=14,
1280
+ # Available feed-forward experts per layer.
1281
+ num_experts=8,
1282
+ # Experts selected per token; their outputs are weighted together.
1283
+ expert_top_k=2,
1284
+ # Width of each expert gate/value branch before projecting back.
1285
+ expert_hidden_dim=4096,
1286
+ # Query heads; head width is embed_dim / n_q_heads (64 here).
1287
+ n_q_heads=16,
1288
+ # Shared key/value heads; eight query heads share each KV head here.
1289
+ n_kv_heads=2,
1290
+ # Attention geometries selected per query head and routing tile.
1291
+ attention_top_k=2,
1292
+ # Tokens per routing tile; first token sets routes. Also mask block size.
1293
+ tile=128,
1294
+ # Local visibility: current token plus up to 255 prior tokens here.
1295
+ local_window=256,
1296
+ # Dense recent window for strided attention, including current token.
1297
+ strided_recent=128,
1298
+ # Also expose past tiles at tile-distance multiples of this value.
1299
+ strided_every=4,
1300
+ # Content-selected completed past tiles; 0 disables graph geometry.
1301
+ graph_top_r=2,
1302
+ # Dense recent window added to graph-selected tiles.
1303
+ graph_recent=128,
1304
+ # "flex" or "dense"; cached decoding uses the dense path.
1305
+ attention_backend="flex",
1306
+ # "grouped" uses CUDA/Triton; "reference" is PyTorch. CPU uses reference.
1307
+ expert_backend="grouped",
1308
+ # Maximum real tokens per sequence, including generated continuation.
1309
+ max_seq_len=2048,
1310
+ # "attention" reads same-token earlier-layer states; "baseline" skips it.
1311
+ depth_mode="attention",
1312
+ # Depth bank capacity; here embedding entry plus recent layer entries.
1313
+ depth_slots=4,
1314
+ # Width of projected depth-attention representations.
1315
+ depth_dim=384,
1316
+ # Depth-attention heads; each has depth_dim / depth_heads dimensions.
1317
+ depth_heads=4,
1318
+ # Initial learned gate; tanh(gate) scales the depth contribution.
1319
+ depth_gate_init=0.01,
1320
+ # Auxiliary loss strength for balancing attention routes and experts.
1321
+ balance_coef=0.01,
1322
+ # "warped" learns token clock rates; alternatives: "standard", "blackhole".
1323
+ rope_type="warped",
1324
+ # Attention-router weight std under "v1"; not expert-router scale.
1325
+ router_init_std=0.01,
1326
+ # "v1" restores router/output scales; "pilot" retains older initialization.
1327
+ init_scheme="v1",
1328
+ # Token positions per vocabulary projection/CE chunk; saves memory.
1329
+ loss_chunk_size=256,
1330
+ # Tokenizer ID for padding; use attention_mask to mark padded positions.
1331
+ pad_token_id=0,
1332
+ # Tokenizer ID for beginning of sequence.
1333
+ bos_token_id=1,
1334
+ # Tokenizer ID for end of sequence; can stop generation.
1335
+ eos_token_id=2,
1336
+ # Generation cache default; direct forward() still defaults to False.
1337
+ use_cache=True,
1338
+ # RMS normalization stability epsilon; applied to every RMSNorm.
1339
+ rms_norm_eps=1e-6,
1340
+ # Additional Hugging Face configuration fields.
1341
+ **kwargs,
1342
+ ):
1343
+ kwargs.setdefault("tie_word_embeddings", True)
1344
+ super().__init__(
1345
+ pad_token_id=pad_token_id,
1346
+ bos_token_id=bos_token_id,
1347
+ eos_token_id=eos_token_id,
1348
+ **kwargs,
1349
+ )
1350
+ values = locals().copy()
1351
+ for key, value in values.items():
1352
+ if key not in {"self", "kwargs", "__class__", "values"}:
1353
+ setattr(self, key, value)
1354
+ if not math.isfinite(rms_norm_eps) or rms_norm_eps <= 0:
1355
+ raise ValueError("rms_norm_eps must be finite and positive")
1356
+ if embed_dim % n_q_heads or (embed_dim // n_q_heads) % 2:
1357
+ raise ValueError(
1358
+ "Width must divide query heads with even rotary head dimension"
1359
+ )
1360
+ if depth_dim % depth_heads or depth_mode not in {"attention", "baseline"}:
1361
+ raise ValueError("Invalid depth dimensions/mode")
1362
+ if (
1363
+ not 1 <= expert_top_k <= num_experts
1364
+ or min(num_layers, depth_slots, loss_chunk_size) < 1
1365
+ ):
1366
+ raise ValueError("Invalid expert/depth/loss dimensions")
1367
+ if expert_backend not in {"reference", "grouped"} or rope_type not in {
1368
+ "blackhole",
1369
+ "standard",
1370
+ "warped",
1371
+ }:
1372
+ raise ValueError("Unknown expert backend or position transform")
1373
+ if graph_top_r < 0 or init_scheme not in {"v1", "pilot"} or router_init_std < 0:
1374
+ raise ValueError("Invalid graph_top_r, init_scheme or router_init_std")
1375
+
1376
+ @classmethod
1377
+ def from_dict(cls, config_dict, **kwargs):
1378
+ config_dict = dict(config_dict)
1379
+ for key, legacy in cls._LEGACY_DEFAULTS.items():
1380
+ config_dict.setdefault(key, legacy)
1381
+ return super().from_dict(config_dict, **kwargs)
1382
+
1383
+
1384
+ # =============================================================================
1385
+ # Complete language model
1386
+ # =============================================================================
1387
+
1388
+
1389
+ class TAMELM(PreTrainedModel, GenerationMixin):
1390
+ config_class = TAMEConfig
1391
+ base_model_prefix = "tamelm"
1392
+ _tied_weights_keys = {"lm_head.weight": "token_embedding.weight"}
1393
+
1394
+ # Construction and checkpoint compatibility
1395
+
1396
+ def _init_weights(self, module):
1397
+ # Initialization below is the existing TAMELM scheme. HF post_init
1398
+ # records model metadata but must not draw extra random numbers or
1399
+ # change the seeded backbone used by the pilot and matched controls.
1400
+ return
1401
+
1402
+ def __init__(self, config):
1403
+ super().__init__(config)
1404
+ self.token_embedding = nn.Embedding(config.vocab_size, config.embed_dim)
1405
+ self.blocks = nn.ModuleList(
1406
+ [AttentionBlock(config) for _ in range(config.num_layers)]
1407
+ )
1408
+ self.final_norm = RMSNorm(config.embed_dim, config.rms_norm_eps)
1409
+ self.depth_norm = RMSNorm(config.embed_dim, config.rms_norm_eps)
1410
+ self.depth_kv = nn.Linear(config.embed_dim, 2 * config.depth_dim, bias=False)
1411
+ self.lm_head = nn.Linear(config.embed_dim, config.vocab_size, bias=False)
1412
+ self.lm_head.weight = self.token_embedding.weight
1413
+ self.post_init()
1414
+ nn.init.normal_(self.token_embedding.weight, std=0.02)
1415
+ for module in self.modules():
1416
+ if isinstance(module, nn.Linear) and module is not self.lm_head:
1417
+ nn.init.normal_(module.weight, std=0.02)
1418
+ # HF loads under no_init_weights(); torch.ones there may leave this
1419
+ # nonpersistent routing mask uninitialized. It is fixed config state.
1420
+ for block in self.blocks:
1421
+ block.horizontal.allowed.fill_(True)
1422
+ if config.init_scheme == "v1":
1423
+ # The generic Linear loop above overwrites module-specific schemes.
1424
+ # Restore them so the run config describes the network that trains.
1425
+ out_std = 0.02 / math.sqrt(2 * config.num_layers)
1426
+ for block in self.blocks:
1427
+ nn.init.normal_(
1428
+ block.horizontal.router.weight, std=config.router_init_std
1429
+ )
1430
+ nn.init.normal_(block.horizontal.o_proj.weight, std=out_std)
1431
+ nn.init.normal_(block.vertical.out_proj.weight, std=out_std)
1432
+ for block in self.blocks:
1433
+ if isinstance(block.horizontal.pos, WarpedRoPE):
1434
+ block.horizontal.pos.reset_parameters() # exactly standard RoPE at step 0
1435
+ self.tokenizer = None
1436
+
1437
+ # Embedding and tokenizer interfaces
1438
+
1439
+ def get_input_embeddings(self):
1440
+ return self.token_embedding
1441
+
1442
+ def load_state_dict(self, state_dict, strict=True, assign=False):
1443
+ # Pilot checkpoints predate serialization of this fixed routing mask.
1444
+ # Fill only missing masks; leave every learned tensor untouched.
1445
+ missing = [
1446
+ f"blocks.{i}.horizontal.allowed"
1447
+ for i in range(len(self.blocks))
1448
+ if f"blocks.{i}.horizontal.allowed" not in state_dict
1449
+ ]
1450
+ if missing:
1451
+ state_dict = dict(state_dict)
1452
+ for key in missing:
1453
+ state_dict[key] = torch.ones_like(self.get_buffer(key))
1454
+ return super().load_state_dict(state_dict, strict=strict, assign=assign)
1455
+
1456
+ def set_input_embeddings(self, value):
1457
+ self.token_embedding = value
1458
+ self.lm_head.weight = value.weight
1459
+
1460
+ def get_output_embeddings(self):
1461
+ return self.lm_head
1462
+
1463
+ def set_output_embeddings(self, value):
1464
+ self.lm_head = value
1465
+ self.token_embedding.weight = value.weight
1466
+
1467
+ def attach_tokenizer(self, tokenizer):
1468
+ import hashlib
1469
+ import json
1470
+
1471
+ if len(tokenizer) != self.config.vocab_size:
1472
+ raise ValueError("Tokenizer vocabulary does not match model")
1473
+ for name in ("pad", "bos", "eos"):
1474
+ if getattr(tokenizer, name + "_token_id") != getattr(
1475
+ self.config, name + "_token_id"
1476
+ ):
1477
+ raise ValueError(f"Tokenizer {name} ID does not match model")
1478
+ backend = json.loads(tokenizer.backend_tokenizer.to_str())
1479
+ backend.pop("truncation", None)
1480
+ backend.pop("padding", None)
1481
+ identity = {
1482
+ "backend": backend,
1483
+ "special_ids": {
1484
+ name: getattr(tokenizer, name + "_token_id")
1485
+ for name in ("pad", "bos", "eos")
1486
+ },
1487
+ }
1488
+ digest = hashlib.sha256(
1489
+ json.dumps(identity, sort_keys=True, separators=(",", ":")).encode()
1490
+ ).hexdigest()
1491
+ if getattr(self.config, "tokenizer_sha256", digest) != digest:
1492
+ raise ValueError(
1493
+ "Tokenizer identity differs from the checkpoint vocabulary/policy"
1494
+ )
1495
+ self.config.tokenizer_sha256 = digest
1496
+ self.tokenizer = tokenizer
1497
+
1498
+ # Depth state and packed forward path
1499
+
1500
+ def _write_depth(self, x):
1501
+ b, t, _ = x.shape
1502
+ k, v = self.depth_kv(self.depth_norm(x)).chunk(2, -1)
1503
+ shape = (
1504
+ b,
1505
+ t,
1506
+ self.config.depth_heads,
1507
+ self.config.depth_dim // self.config.depth_heads,
1508
+ )
1509
+ return k.reshape(shape), v.reshape(shape)
1510
+
1511
+ def _forward_unpadded(
1512
+ self,
1513
+ input_ids,
1514
+ labels=None,
1515
+ attention_mask=None,
1516
+ use_cache=False,
1517
+ past_key_values=None,
1518
+ start_pos=0,
1519
+ return_logits=None,
1520
+ **kwargs,
1521
+ ):
1522
+ if (
1523
+ input_ids.ndim != 2
1524
+ or not 0 < input_ids.shape[1] <= self.config.max_seq_len - start_pos
1525
+ ):
1526
+ raise ValueError("Input must be [batch, tokens] within max_seq_len")
1527
+ # Packed training has no padding. Do not accept a mask and silently ignore it.
1528
+ if attention_mask is not None:
1529
+ raise ValueError(
1530
+ "Use packed unpadded input_ids; padding masks are not supported"
1531
+ )
1532
+ if (use_cache or past_key_values is not None) and (
1533
+ labels is not None or torch.is_grad_enabled()
1534
+ ):
1535
+ raise ValueError(
1536
+ "KV cache requires inference without labels under torch.no_grad()"
1537
+ )
1538
+ caches = (
1539
+ past_key_values
1540
+ if past_key_values is not None
1541
+ else ([{} for _ in self.blocks] if use_cache else None)
1542
+ )
1543
+ if caches is not None and len(caches) != len(self.blocks):
1544
+ raise ValueError("One cache per layer required")
1545
+ x = self.token_embedding(input_ids)
1546
+ bank = [self._write_depth(x)] if self.config.depth_mode == "attention" else []
1547
+ losses, stats = [], []
1548
+ for i, block in enumerate(self.blocks):
1549
+ x, loss, info = block(
1550
+ x, bank, None if caches is None else caches[i], start_pos
1551
+ )
1552
+ losses.append(loss)
1553
+ stats.append({k: v.detach() for k, v in info.items()})
1554
+ if self.config.depth_mode == "attention" and i + 1 < len(self.blocks):
1555
+ bank.append(self._write_depth(x))
1556
+ if len(bank) > self.config.depth_slots:
1557
+ bank = (
1558
+ [bank[0]] + bank[-(self.config.depth_slots - 1) :]
1559
+ if self.config.depth_slots > 1
1560
+ else bank[-1:]
1561
+ )
1562
+ x = self.final_norm(x)
1563
+ aux = torch.stack(losses).mean()
1564
+ primary, total = None, None
1565
+ if labels is not None:
1566
+ # Checkpoint the vocabulary projection/CE chunks: no retained [B,T,V]
1567
+ # logits. Recompute only this head in backward, not the full model.
1568
+ from torch.utils.checkpoint import checkpoint
1569
+
1570
+ states, targets = x[:, :-1].reshape(-1, x.shape[-1]), labels[:, 1:].reshape(
1571
+ -1
1572
+ )
1573
+ if targets.numel() == 0:
1574
+ raise ValueError("Language-model loss requires at least two tokens")
1575
+ parts = []
1576
+
1577
+ def chunk_loss(h, y):
1578
+ return F.cross_entropy(
1579
+ F.linear(h, self.lm_head.weight).float(),
1580
+ y,
1581
+ reduction="sum",
1582
+ ignore_index=-100,
1583
+ )
1584
+
1585
+ for offset in range(0, targets.numel(), self.config.loss_chunk_size):
1586
+ h, y = (
1587
+ states[offset : offset + self.config.loss_chunk_size],
1588
+ targets[offset : offset + self.config.loss_chunk_size],
1589
+ )
1590
+ parts.append(
1591
+ checkpoint(chunk_loss, h, y, use_reentrant=False)
1592
+ if torch.is_grad_enabled()
1593
+ else chunk_loss(h, y)
1594
+ )
1595
+ primary = torch.stack(parts).sum() / (targets != -100).sum().clamp_min(1)
1596
+ total = primary + aux
1597
+ if return_logits is None:
1598
+ return_logits = labels is None
1599
+ logits = self.lm_head(x) if return_logits else None
1600
+ return TAMEOutput(
1601
+ loss=total,
1602
+ logits=logits,
1603
+ past_key_values=caches,
1604
+ aux_losses={
1605
+ "primary": None if primary is None else primary.detach(),
1606
+ "router_balance": aux.detach(),
1607
+ },
1608
+ attention_stats=stats,
1609
+ )
1610
+
1611
+ # Generation integration
1612
+
1613
+ @classmethod
1614
+ def _supports_default_dynamic_cache(cls):
1615
+ # A plain DynamicCache has no tile-routing state.
1616
+ return False
1617
+
1618
+ def _prepare_cache_for_generation(
1619
+ self, generation_config, model_kwargs, *args, **kwargs
1620
+ ):
1621
+ implementation = generation_config.cache_implementation
1622
+ if implementation not in (None, "dynamic"):
1623
+ raise ValueError(
1624
+ "TAMELM supports its dynamic KV/routing cache; static, offloaded and quantized caches are unsupported"
1625
+ )
1626
+ cache = model_kwargs.get("past_key_values")
1627
+ if cache is not None and not isinstance(cache, TAMECache):
1628
+ raise TypeError("TAMELM requires TAMECache to preserve tile-routing state")
1629
+
1630
+ def _validate_generated_length(
1631
+ self, generation_config, input_ids_length, has_default_max_length
1632
+ ):
1633
+ super()._validate_generated_length(
1634
+ generation_config, input_ids_length, has_default_max_length
1635
+ )
1636
+ if generation_config.max_length > self.config.max_seq_len:
1637
+ raise ValueError(
1638
+ f"Requested generation exceeds max_seq_len={self.config.max_seq_len}; shorten the prompt or max_new_tokens"
1639
+ )
1640
+
1641
+ def prepare_inputs_for_generation(
1642
+ self,
1643
+ input_ids,
1644
+ past_key_values=None,
1645
+ attention_mask=None,
1646
+ use_cache=True,
1647
+ next_sequence_length=None,
1648
+ position_ids=None,
1649
+ **kwargs,
1650
+ ):
1651
+ seen = past_key_values.get_seq_length() if past_key_values is not None else 0
1652
+ if seen:
1653
+ if input_ids.shape[1] > seen:
1654
+ input_ids = input_ids[:, seen:]
1655
+ elif next_sequence_length is not None:
1656
+ input_ids = input_ids[:, -next_sequence_length:]
1657
+ if position_ids is not None:
1658
+ position_ids = position_ids[:, -input_ids.shape[1] :]
1659
+ return {
1660
+ "input_ids": input_ids.contiguous(),
1661
+ "past_key_values": past_key_values,
1662
+ "attention_mask": attention_mask,
1663
+ "position_ids": position_ids,
1664
+ "use_cache": use_cache,
1665
+ "logits_to_keep": 1,
1666
+ }
1667
+
1668
+ # Public forward path with padding and cache handling
1669
+
1670
+ def forward(
1671
+ self,
1672
+ input_ids,
1673
+ labels=None,
1674
+ attention_mask=None,
1675
+ use_cache=False,
1676
+ past_key_values=None,
1677
+ start_pos=None,
1678
+ return_logits=None,
1679
+ return_dict=None,
1680
+ position_ids=None,
1681
+ logits_to_keep=0,
1682
+ token_type_ids=None,
1683
+ **kwargs,
1684
+ ):
1685
+ if input_ids.ndim != 2 or input_ids.shape[1] < 1:
1686
+ raise ValueError("input_ids must be a nonempty [batch, tokens] tensor")
1687
+ if token_type_ids is not None and bool(token_type_ids.any()):
1688
+ raise ValueError("TAMELM does not use token type embeddings")
1689
+ if kwargs.get("output_attentions") or kwargs.get("output_hidden_states"):
1690
+ raise NotImplementedError(
1691
+ "Attention/hidden-state collection is not implemented"
1692
+ )
1693
+ if (use_cache or past_key_values is not None) and (
1694
+ labels is not None or torch.is_grad_enabled()
1695
+ ):
1696
+ raise ValueError(
1697
+ "KV cache requires inference without labels under torch.no_grad()"
1698
+ )
1699
+ if past_key_values is not None and not isinstance(past_key_values, TAMECache):
1700
+ raise TypeError("past_key_values must be a TAMECache")
1701
+ if past_key_values is not None and not use_cache:
1702
+ raise ValueError("Set use_cache=True when providing past_key_values")
1703
+ cache = past_key_values
1704
+ if use_cache and cache is None:
1705
+ cache = TAMECache(len(self.blocks), self.config.tile)
1706
+ seen = cache.get_seq_length() if cache is not None else 0
1707
+ if start_pos is not None and start_pos != seen:
1708
+ raise ValueError("start_pos disagrees with the cache token count")
1709
+ batch, length = input_ids.shape
1710
+ if labels is not None and labels.shape != input_ids.shape:
1711
+ raise ValueError("labels must match input_ids")
1712
+ if attention_mask is None:
1713
+ current_mask = torch.ones_like(input_ids, dtype=torch.bool)
1714
+ mask = (
1715
+ torch.cat((cache.mask, current_mask), dim=1) if seen else current_mask
1716
+ )
1717
+ else:
1718
+ if attention_mask.shape != (batch, seen + length):
1719
+ raise ValueError("attention_mask must cover cached and new tokens")
1720
+ if not bool(((attention_mask == 0) | (attention_mask == 1)).all()):
1721
+ raise ValueError("attention_mask must contain only zeros and ones")
1722
+ mask = attention_mask.to(device=input_ids.device, dtype=torch.bool)
1723
+ if seen and not torch.equal(mask[:, :seen], cache.mask):
1724
+ raise ValueError("attention_mask changes already cached tokens")
1725
+ current_mask = mask[:, seen:]
1726
+ if cache is not None:
1727
+ if not cache.rows:
1728
+ cache.rows = [[{} for _ in self.blocks] for _ in range(batch)]
1729
+ if len(cache.rows) != batch:
1730
+ raise ValueError(
1731
+ "Cache batch size differs; reorder or expand it explicitly"
1732
+ )
1733
+ if (
1734
+ attention_mask is None
1735
+ and cache is None
1736
+ and length > self.config.max_seq_len
1737
+ ) or (
1738
+ (attention_mask is not None or cache is not None)
1739
+ and bool((mask.sum(1) > self.config.max_seq_len).any())
1740
+ ):
1741
+ raise ValueError("Real token count exceeds max_seq_len")
1742
+ if position_ids is not None:
1743
+ expected = mask.long().cumsum(-1)[:, seen:] - 1
1744
+ if position_ids.shape != input_ids.shape or not torch.equal(
1745
+ position_ids[current_mask], expected[current_mask]
1746
+ ):
1747
+ raise ValueError("position_ids must follow unpadded token positions")
1748
+ # Packed training and unpadded full forwards retain the vectorized path.
1749
+ if cache is None and (attention_mask is None or bool(current_mask.all())):
1750
+ out = self._forward_unpadded(
1751
+ input_ids, labels=labels, return_logits=return_logits
1752
+ )
1753
+ else:
1754
+ if return_logits is None:
1755
+ return_logits = labels is None
1756
+ logits = None
1757
+ weighted_loss, weighted_primary, weighted_aux, counts = [], [], [], []
1758
+ stats = []
1759
+ for i in range(batch):
1760
+ positions = current_mask[i].nonzero(as_tuple=True)[0]
1761
+ if not positions.numel():
1762
+ raise ValueError(
1763
+ "Each sequence must provide at least one real token"
1764
+ )
1765
+ ids = input_ids[i : i + 1].index_select(1, positions)
1766
+ target = (
1767
+ labels[i : i + 1].index_select(1, positions)
1768
+ if labels is not None
1769
+ else None
1770
+ )
1771
+ offset = cache.row_length(i) if cache is not None else 0
1772
+ native = self._forward_unpadded(
1773
+ ids,
1774
+ labels=target,
1775
+ use_cache=cache is not None,
1776
+ past_key_values=cache.rows[i] if cache is not None else None,
1777
+ start_pos=offset,
1778
+ return_logits=return_logits,
1779
+ )
1780
+ if native.logits is not None:
1781
+ if logits is None:
1782
+ logits = native.logits.new_zeros(
1783
+ batch, length, self.config.vocab_size
1784
+ )
1785
+ logits[i, positions] = native.logits[0]
1786
+ if labels is not None:
1787
+ count = (target[:, 1:] != -100).sum()
1788
+ counts.append(count)
1789
+ weighted_loss.append(native.loss * count)
1790
+ weighted_primary.append(native.aux_losses["primary"] * count)
1791
+ weighted_aux.append(native.aux_losses["router_balance"] * count)
1792
+ stats = native.attention_stats
1793
+ if cache is not None:
1794
+ cache.mask = mask.detach().clone()
1795
+ denom = torch.stack(counts).sum().clamp_min(1) if counts else None
1796
+ out = TAMEOutput(
1797
+ loss=torch.stack(weighted_loss).sum() / denom if counts else None,
1798
+ logits=logits,
1799
+ past_key_values=cache,
1800
+ aux_losses={
1801
+ "primary": (
1802
+ torch.stack(weighted_primary).sum() / denom if counts else None
1803
+ ),
1804
+ "router_balance": (
1805
+ torch.stack(weighted_aux).sum() / denom
1806
+ if counts
1807
+ else input_ids.new_zeros((), dtype=torch.float32)
1808
+ ),
1809
+ },
1810
+ attention_stats=None,
1811
+ ) # per-layer routing summaries are defined for packed batches
1812
+ if logits_to_keep and out.logits is not None:
1813
+ out.logits = out.logits[:, -logits_to_keep:]
1814
+ return out if return_dict is not False else out.to_tuple()
1815
+
1816
+ # Portable Hugging Face export
1817
+
1818
+ def export_hf_code(self, directory):
1819
+ from pathlib import Path
1820
+ import inspect
1821
+ import sys
1822
+
1823
+ directory = Path(directory)
1824
+ directory.mkdir(parents=True, exist_ok=True)
1825
+ source = _NOTEBOOK_SOURCES.get("model")
1826
+ if source is None:
1827
+ source = inspect.getsource(sys.modules[type(self).__module__])
1828
+ # Registration is notebook-local; portable loading uses auto_map.
1829
+ source = source.split("\nAutoConfig.register(TAMEConfig.model_type")[0]
1830
+ source = source.split("\n# Notebook-only source capture")[0]
1831
+ (directory / "modeling_tamelm.py").write_text(source, encoding="utf-8")
1832
+ self.config.auto_map = {
1833
+ "AutoConfig": "modeling_tamelm.TAMEConfig",
1834
+ "AutoModelForCausalLM": "modeling_tamelm.TAMELM",
1835
+ }
1836
+ self.config.architectures = ["TAMELM"]
1837
+ self.config.save_pretrained(directory)
1838
+ self.generation_config.save_pretrained(directory)
1839
+
1840
+ def save_pretrained(self, save_directory, *args, **kwargs):
1841
+ self.export_hf_code(save_directory)
1842
+ result = super().save_pretrained(save_directory, *args, **kwargs)
1843
+ if self.tokenizer is not None:
1844
+ self.tokenizer.save_pretrained(save_directory)
1845
+ return result
1846
+