Project-Chronos 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chronos/__init__.py +16 -0
- chronos/_backend_legacy.py +64 -0
- chronos/app.py +122 -0
- chronos/backend/__init__.py +40 -0
- chronos/backend/dispatcher.py +347 -0
- chronos/backend/ext/__init__.py +2 -0
- chronos/backend/ext/opencl.py +22 -0
- chronos/cli.py +62 -0
- chronos/data/__init__.py +0 -0
- chronos/data/flexible_dataset.py +309 -0
- chronos/deps.py +72 -0
- chronos/eval/__init__.py +2 -0
- chronos/eval/benchmark.py +159 -0
- chronos/eval/io_profiler.py +232 -0
- chronos/io/__init__.py +13 -0
- chronos/io/async_prefetcher.py +220 -0
- chronos/io/cluster_layout.py +317 -0
- chronos/io/expert_store.py +559 -0
- chronos/io/io_simulator.py +40 -0
- chronos/io/storage.py +237 -0
- chronos/mlx/__init__.py +32 -0
- chronos/mlx/attention.py +230 -0
- chronos/mlx/expert_store.py +155 -0
- chronos/mlx/inference.py +159 -0
- chronos/mlx/model.py +181 -0
- chronos/mlx/moe.py +102 -0
- chronos/model/__init__.py +6 -0
- chronos/model/config.py +54 -0
- chronos/model/hf_io.py +149 -0
- chronos/model/hybrid_attention.py +237 -0
- chronos/model/lookahead_router.py +55 -0
- chronos/model/model_chronos.py +271 -0
- chronos/model/moe_chronos.py +189 -0
- chronos/model/temporal_loss.py +124 -0
- chronos/router/__init__.py +12 -0
- chronos/router/expert_predictor.py +163 -0
- chronos/router/intent_classifier.py +193 -0
- chronos/router/prefill_scheduler.py +248 -0
- chronos/runtime/__init__.py +2 -0
- chronos/runtime/cache_manager.py +117 -0
- chronos/runtime/inference_engine.py +295 -0
- chronos/runtime/metrics.py +75 -0
- chronos/serving/__init__.py +13 -0
- chronos/serving/vllm_adapter.py +85 -0
- chronos/trainer/__init__.py +6 -0
- chronos/trainer/chronos_trainer.py +151 -0
- chronos/trainer/distill_trainer.py +193 -0
- chronos/trainer/dpo_trainer.py +190 -0
- chronos/trainer/grpo_trainer.py +288 -0
- chronos/trainer/loss_mixin.py +165 -0
- chronos/trainer/optim_utils.py +101 -0
- chronos/trainer/orpo_trainer.py +189 -0
- chronos/trainer/reward.py +130 -0
- chronos/trainer/sft_trainer.py +189 -0
- chronos/tuning/__init__.py +1 -0
- chronos/tuning/_base_tuner.py +284 -0
- chronos/tuning/chronos_auto_tuner.py +203 -0
- project_chronos-0.1.1.dist-info/METADATA +670 -0
- project_chronos-0.1.1.dist-info/RECORD +83 -0
- project_chronos-0.1.1.dist-info/WHEEL +5 -0
- project_chronos-0.1.1.dist-info/entry_points.txt +7 -0
- project_chronos-0.1.1.dist-info/licenses/LICENSE +177 -0
- project_chronos-0.1.1.dist-info/top_level.txt +8 -0
- train_chronos.py +133 -0
- train_chronos_distill.py +95 -0
- train_chronos_dpo.py +82 -0
- train_chronos_grpo.py +88 -0
- train_chronos_orpo.py +81 -0
- train_chronos_sft.py +85 -0
- ui/__init__.py +3 -0
- ui/estimator.py +204 -0
- ui/gradio_compat.py +72 -0
- ui/i18n.py +540 -0
- ui/presets.py +194 -0
- ui/tabs/__init__.py +3 -0
- ui/tabs/autotune_tab.py +210 -0
- ui/tabs/benchmark_tab.py +196 -0
- ui/tabs/config_tab.py +318 -0
- ui/tabs/data_tab.py +148 -0
- ui/tabs/inference_tab.py +496 -0
- ui/tabs/iomon_tab.py +37 -0
- ui/tabs/pipeline_tab.py +134 -0
- ui/tabs/train_tab.py +1688 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""
|
|
2
|
+
chronos/model/hybrid_attention.py
|
|
3
|
+
|
|
4
|
+
Hybrid attention for Project Chronos:
|
|
5
|
+
- MLAAttention: Multi-head Latent Attention (DeepSeek-style)
|
|
6
|
+
Compresses KV cache via low-rank projection: hidden_dim → kv_latent_dim
|
|
7
|
+
Cache stores latent vectors instead of full KV → ~8-16x smaller KV cache
|
|
8
|
+
|
|
9
|
+
- SlidingWindowAttention: local attention with fixed window
|
|
10
|
+
KV cache capped at window_size tokens → O(1) memory per step
|
|
11
|
+
|
|
12
|
+
- HybridAttentionBlock: alternates MLA (even layers) / SlidingWindow (odd layers)
|
|
13
|
+
Even layers: MLA for global context with compressed cache
|
|
14
|
+
Odd layers: SlidingWindow for local patterns with bounded cache
|
|
15
|
+
"""
|
|
16
|
+
import sys
|
|
17
|
+
import chronos.deps # ensure minimind on sys.path
|
|
18
|
+
|
|
19
|
+
import math
|
|
20
|
+
import torch
|
|
21
|
+
import torch.nn as nn
|
|
22
|
+
import torch.nn.functional as F
|
|
23
|
+
|
|
24
|
+
from model.model_minimind import RMSNorm, apply_rotary_pos_emb, repeat_kv
|
|
25
|
+
from .config import ChronosConfig
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ── MLA: Multi-head Latent Attention ─────────────────────────────
|
|
29
|
+
|
|
30
|
+
class MLAAttention(nn.Module):
|
|
31
|
+
"""
|
|
32
|
+
Multi-head Latent Attention (DeepSeek MLA style).
|
|
33
|
+
|
|
34
|
+
KV compression:
|
|
35
|
+
c_kv = W_kv_down(x) [B, S, kv_latent_dim] ← cached
|
|
36
|
+
k, v = W_kv_up(c_kv) [B, S, nH, dH] ← recomputed from cache
|
|
37
|
+
|
|
38
|
+
Query decoupled RoPE:
|
|
39
|
+
q_nope = W_q_nope(x) [B, S, nH, dH - rope_dim]
|
|
40
|
+
q_rope = W_q_rope(x) [B, S, nH, rope_dim] ← RoPE applied here
|
|
41
|
+
k_rope = W_k_rope(c_kv) [B, S, nH, rope_dim] ← RoPE applied here
|
|
42
|
+
|
|
43
|
+
Cache stores only c_kv (latent) + k_rope → much smaller than full KV.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self, config: ChronosConfig):
|
|
47
|
+
super().__init__()
|
|
48
|
+
self.n_heads = config.num_attention_heads
|
|
49
|
+
self.n_kv_heads = config.num_key_value_heads or config.num_attention_heads
|
|
50
|
+
self.head_dim = config.head_dim
|
|
51
|
+
self.n_rep = self.n_heads // self.n_kv_heads
|
|
52
|
+
self.dropout = config.dropout
|
|
53
|
+
|
|
54
|
+
# KV latent dimension: much smaller than n_kv_heads * head_dim
|
|
55
|
+
self.kv_latent_dim = getattr(config, 'kv_latent_dim',
|
|
56
|
+
max(64, self.n_kv_heads * self.head_dim // 8))
|
|
57
|
+
# RoPE dimension per head (subset of head_dim); must leave room for nope
|
|
58
|
+
self.rope_dim = min(getattr(config, 'rope_dim', min(64, self.head_dim)), self.head_dim - 1)
|
|
59
|
+
self.rope_dim = max(1, self.rope_dim) # at least 1
|
|
60
|
+
self.nope_dim = self.head_dim - self.rope_dim
|
|
61
|
+
|
|
62
|
+
H = config.hidden_size
|
|
63
|
+
|
|
64
|
+
# Query projections
|
|
65
|
+
self.q_nope_proj = nn.Linear(H, self.n_heads * self.nope_dim, bias=False)
|
|
66
|
+
self.q_rope_proj = nn.Linear(H, self.n_heads * self.rope_dim, bias=False)
|
|
67
|
+
self.q_nope_norm = RMSNorm(self.nope_dim, eps=config.rms_norm_eps)
|
|
68
|
+
self.q_rope_norm = RMSNorm(self.rope_dim, eps=config.rms_norm_eps)
|
|
69
|
+
|
|
70
|
+
# KV down-projection (what gets cached)
|
|
71
|
+
self.kv_down_proj = nn.Linear(H, self.kv_latent_dim, bias=False)
|
|
72
|
+
self.kv_down_norm = RMSNorm(self.kv_latent_dim, eps=config.rms_norm_eps)
|
|
73
|
+
|
|
74
|
+
# KV up-projections (recomputed from cache at inference)
|
|
75
|
+
self.k_nope_proj = nn.Linear(self.kv_latent_dim, self.n_kv_heads * self.nope_dim, bias=False)
|
|
76
|
+
self.k_rope_proj = nn.Linear(self.kv_latent_dim, self.n_kv_heads * self.rope_dim, bias=False)
|
|
77
|
+
self.v_proj = nn.Linear(self.kv_latent_dim, self.n_kv_heads * self.head_dim, bias=False)
|
|
78
|
+
|
|
79
|
+
self.o_proj = nn.Linear(self.n_heads * self.head_dim, H, bias=False)
|
|
80
|
+
self.attn_dropout = nn.Dropout(self.dropout)
|
|
81
|
+
self.resid_dropout = nn.Dropout(self.dropout)
|
|
82
|
+
self.flash = hasattr(F, 'scaled_dot_product_attention') and config.flash_attn
|
|
83
|
+
|
|
84
|
+
def forward(self, x, position_embeddings, past_key_value=None, use_cache=False, attention_mask=None):
|
|
85
|
+
B, S, _ = x.shape
|
|
86
|
+
cos, sin = position_embeddings
|
|
87
|
+
|
|
88
|
+
# Query
|
|
89
|
+
q_nope = self.q_nope_norm(
|
|
90
|
+
self.q_nope_proj(x).view(B, S, self.n_heads, self.nope_dim)
|
|
91
|
+
)
|
|
92
|
+
q_rope = self.q_rope_norm(
|
|
93
|
+
self.q_rope_proj(x).view(B, S, self.n_heads, self.rope_dim)
|
|
94
|
+
)
|
|
95
|
+
# apply_rotary_pos_emb expects [B, S, nH, d] (NOT transposed)
|
|
96
|
+
# cos/sin are full-length buffers; slice to current step positions
|
|
97
|
+
past_len = past_key_value[0].shape[1] if past_key_value is not None else 0
|
|
98
|
+
cos_r = cos[past_len:past_len + S, :self.rope_dim] # [S, rope_dim]
|
|
99
|
+
sin_r = sin[past_len:past_len + S, :self.rope_dim]
|
|
100
|
+
q_rope, _ = apply_rotary_pos_emb(q_rope, q_rope, cos_r, sin_r) # [B, S, nH, rope_dim]
|
|
101
|
+
q = torch.cat([q_nope, q_rope], dim=-1) # [B, S, nH, head_dim]
|
|
102
|
+
|
|
103
|
+
# KV latent (this is what we cache)
|
|
104
|
+
c_kv = self.kv_down_norm(self.kv_down_proj(x)) # [B, S, kv_latent_dim]
|
|
105
|
+
|
|
106
|
+
if past_key_value is not None:
|
|
107
|
+
past_c_kv = past_key_value[0]
|
|
108
|
+
c_kv_full = torch.cat([past_c_kv, c_kv], dim=1)
|
|
109
|
+
else:
|
|
110
|
+
c_kv_full = c_kv
|
|
111
|
+
|
|
112
|
+
full_len = c_kv_full.shape[1]
|
|
113
|
+
|
|
114
|
+
# Expand KV from latent
|
|
115
|
+
k_nope = self.k_nope_proj(c_kv_full).view(B, full_len, self.n_kv_heads, self.nope_dim)
|
|
116
|
+
k_rope_raw = self.k_rope_proj(c_kv_full).view(B, full_len, self.n_kv_heads, self.rope_dim)
|
|
117
|
+
v = self.v_proj(c_kv_full).view(B, full_len, self.n_kv_heads, self.head_dim)
|
|
118
|
+
|
|
119
|
+
# RoPE on keys over full cached sequence — [B, T, nKV, rope_dim]
|
|
120
|
+
cos_full = cos[:full_len, :self.rope_dim]
|
|
121
|
+
sin_full = sin[:full_len, :self.rope_dim]
|
|
122
|
+
k_rope, _ = apply_rotary_pos_emb(k_rope_raw, k_rope_raw, cos_full, sin_full)
|
|
123
|
+
k = torch.cat([k_nope, k_rope], dim=-1) # [B, T, nKV, head_dim]
|
|
124
|
+
|
|
125
|
+
past_kv = (c_kv_full,) if use_cache else None
|
|
126
|
+
|
|
127
|
+
# Attention
|
|
128
|
+
q = q.transpose(1, 2) # [B, nH, S, dH]
|
|
129
|
+
k = repeat_kv(k, self.n_rep).transpose(1, 2) # [B, nH, T, dH]
|
|
130
|
+
v = repeat_kv(v, self.n_rep).transpose(1, 2) # [B, nH, T, dH]
|
|
131
|
+
|
|
132
|
+
T = k.shape[2]
|
|
133
|
+
if self.flash and S > 1 and attention_mask is None:
|
|
134
|
+
out = F.scaled_dot_product_attention(
|
|
135
|
+
q, k, v,
|
|
136
|
+
dropout_p=self.dropout if self.training else 0.0,
|
|
137
|
+
is_causal=True,
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
scores = (q @ k.transpose(-2, -1)) / math.sqrt(self.head_dim)
|
|
141
|
+
scores[:, :, :, -S:] += torch.full((S, S), float('-inf'), device=scores.device).triu(1)
|
|
142
|
+
if attention_mask is not None:
|
|
143
|
+
scores += (1.0 - attention_mask.unsqueeze(1).unsqueeze(2)) * -1e9
|
|
144
|
+
out = self.attn_dropout(F.softmax(scores.float(), dim=-1).type_as(q)) @ v
|
|
145
|
+
|
|
146
|
+
out = out.transpose(1, 2).reshape(B, S, -1)
|
|
147
|
+
return self.resid_dropout(self.o_proj(out)), past_kv
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# ── Sliding Window Attention ──────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
class SlidingWindowAttention(nn.Module):
|
|
153
|
+
"""
|
|
154
|
+
Local sliding window attention. KV cache is capped at window_size tokens,
|
|
155
|
+
preventing unbounded growth in long-context generation.
|
|
156
|
+
|
|
157
|
+
During prefill: full causal attention within each window.
|
|
158
|
+
During decode: KV cache evicts oldest tokens beyond window_size.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
def __init__(self, config: ChronosConfig):
|
|
162
|
+
super().__init__()
|
|
163
|
+
self.n_heads = config.num_attention_heads
|
|
164
|
+
self.n_kv_heads = config.num_key_value_heads or config.num_attention_heads
|
|
165
|
+
self.head_dim = config.head_dim
|
|
166
|
+
self.n_rep = self.n_heads // self.n_kv_heads
|
|
167
|
+
self.dropout = config.dropout
|
|
168
|
+
self.window_size = getattr(config, 'sliding_window_size', 2048)
|
|
169
|
+
|
|
170
|
+
H = config.hidden_size
|
|
171
|
+
self.q_proj = nn.Linear(H, self.n_heads * self.head_dim, bias=False)
|
|
172
|
+
self.k_proj = nn.Linear(H, self.n_kv_heads * self.head_dim, bias=False)
|
|
173
|
+
self.v_proj = nn.Linear(H, self.n_kv_heads * self.head_dim, bias=False)
|
|
174
|
+
self.o_proj = nn.Linear(self.n_heads * self.head_dim, H, bias=False)
|
|
175
|
+
self.q_norm = RMSNorm(self.head_dim, eps=config.rms_norm_eps)
|
|
176
|
+
self.k_norm = RMSNorm(self.head_dim, eps=config.rms_norm_eps)
|
|
177
|
+
self.attn_dropout = nn.Dropout(self.dropout)
|
|
178
|
+
self.resid_dropout = nn.Dropout(self.dropout)
|
|
179
|
+
self.flash = hasattr(F, 'scaled_dot_product_attention') and config.flash_attn
|
|
180
|
+
|
|
181
|
+
def forward(self, x, position_embeddings, past_key_value=None, use_cache=False, attention_mask=None):
|
|
182
|
+
B, S, _ = x.shape
|
|
183
|
+
cos, sin = position_embeddings
|
|
184
|
+
# cos/sin are full-length buffers; slice to current positions
|
|
185
|
+
past_len = past_key_value[0].shape[1] if past_key_value is not None else 0
|
|
186
|
+
cos_s = cos[past_len:past_len + S]
|
|
187
|
+
sin_s = sin[past_len:past_len + S]
|
|
188
|
+
|
|
189
|
+
xq = self.q_norm(self.q_proj(x).view(B, S, self.n_heads, self.head_dim))
|
|
190
|
+
xk = self.k_norm(self.k_proj(x).view(B, S, self.n_kv_heads, self.head_dim))
|
|
191
|
+
xv = self.v_proj(x).view(B, S, self.n_kv_heads, self.head_dim)
|
|
192
|
+
|
|
193
|
+
xq, xk = apply_rotary_pos_emb(xq, xk, cos_s, sin_s) # [B, S, nH/nKV, d]
|
|
194
|
+
|
|
195
|
+
if past_key_value is not None:
|
|
196
|
+
xk = torch.cat([past_key_value[0], xk], dim=1)
|
|
197
|
+
xv = torch.cat([past_key_value[1], xv], dim=1)
|
|
198
|
+
|
|
199
|
+
# Evict tokens beyond window_size (sliding window)
|
|
200
|
+
if xk.shape[1] > self.window_size:
|
|
201
|
+
xk = xk[:, -self.window_size:, :, :]
|
|
202
|
+
xv = xv[:, -self.window_size:, :, :]
|
|
203
|
+
|
|
204
|
+
past_kv = (xk, xv) if use_cache else None
|
|
205
|
+
|
|
206
|
+
xq = xq.transpose(1, 2)
|
|
207
|
+
xk = repeat_kv(xk, self.n_rep).transpose(1, 2)
|
|
208
|
+
xv = repeat_kv(xv, self.n_rep).transpose(1, 2)
|
|
209
|
+
|
|
210
|
+
T = xk.shape[2]
|
|
211
|
+
if self.flash and S > 1 and attention_mask is None:
|
|
212
|
+
out = F.scaled_dot_product_attention(
|
|
213
|
+
xq, xk, xv,
|
|
214
|
+
dropout_p=self.dropout if self.training else 0.0,
|
|
215
|
+
is_causal=(T == S),
|
|
216
|
+
)
|
|
217
|
+
else:
|
|
218
|
+
scores = (xq @ xk.transpose(-2, -1)) / math.sqrt(self.head_dim)
|
|
219
|
+
if T == S:
|
|
220
|
+
scores[:, :, :, -S:] += torch.full((S, S), float('-inf'), device=scores.device).triu(1)
|
|
221
|
+
if attention_mask is not None:
|
|
222
|
+
scores += (1.0 - attention_mask[:, :, :, -T:].unsqueeze(1)) * -1e9
|
|
223
|
+
out = self.attn_dropout(F.softmax(scores.float(), dim=-1).type_as(xq)) @ xv
|
|
224
|
+
|
|
225
|
+
out = out.transpose(1, 2).reshape(B, S, -1)
|
|
226
|
+
return self.resid_dropout(self.o_proj(out)), past_kv
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def make_attention(layer_id: int, config: ChronosConfig) -> nn.Module:
|
|
230
|
+
"""
|
|
231
|
+
Layer assignment:
|
|
232
|
+
even layers → MLAAttention (global context, compressed KV cache)
|
|
233
|
+
odd layers → SlidingWindowAttention (local patterns, bounded KV cache)
|
|
234
|
+
"""
|
|
235
|
+
if layer_id % 2 == 0:
|
|
236
|
+
return MLAAttention(config)
|
|
237
|
+
return SlidingWindowAttention(config)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.nn as nn
|
|
3
|
+
import torch.nn.functional as F
|
|
4
|
+
|
|
5
|
+
from .config import ChronosConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LookaheadRouter(nn.Module):
|
|
9
|
+
"""
|
|
10
|
+
Global lookahead routing predictor inserted after the first transformer block.
|
|
11
|
+
|
|
12
|
+
Input: hidden_states [B, S, H] (output of block 0)
|
|
13
|
+
Output: expert_probs [B, S, lookahead_steps+1, num_experts]
|
|
14
|
+
dim-2 index 0 = current step t
|
|
15
|
+
dim-2 index 1 = predicted t+1
|
|
16
|
+
dim-2 index 2 = predicted t+2 (if lookahead_steps >= 2)
|
|
17
|
+
|
|
18
|
+
During training: output drives temporal_locality_loss and lookahead accuracy.
|
|
19
|
+
During inference: output drives async prefetch for future expert weights.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, config: ChronosConfig):
|
|
23
|
+
super().__init__()
|
|
24
|
+
self.lookahead_steps = config.lookahead_steps
|
|
25
|
+
self.num_experts = config.num_experts
|
|
26
|
+
H = config.hidden_size
|
|
27
|
+
K = config.lookahead_steps + 1 # current + future steps
|
|
28
|
+
|
|
29
|
+
# Lightweight 2-layer MLP: H -> H//4 -> E*K
|
|
30
|
+
self.proj = nn.Sequential(
|
|
31
|
+
nn.Linear(H, H // 4, bias=False),
|
|
32
|
+
nn.SiLU(),
|
|
33
|
+
nn.Linear(H // 4, self.num_experts * K, bias=False),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
|
37
|
+
# hidden_states: [B, S, H]
|
|
38
|
+
B, S, H = hidden_states.shape
|
|
39
|
+
K = self.lookahead_steps + 1
|
|
40
|
+
logits = self.proj(hidden_states) # [B, S, E*K]
|
|
41
|
+
logits = logits.view(B, S, K, self.num_experts) # [B, S, K, E]
|
|
42
|
+
return F.softmax(logits, dim=-1) # [B, S, K, E]
|
|
43
|
+
|
|
44
|
+
def predict_next_experts(
|
|
45
|
+
self,
|
|
46
|
+
hidden_states: torch.Tensor,
|
|
47
|
+
top_k: int = 1,
|
|
48
|
+
) -> torch.Tensor:
|
|
49
|
+
"""
|
|
50
|
+
Inference helper: returns top-k expert indices for each future step.
|
|
51
|
+
Returns: [B, S, lookahead_steps, top_k]
|
|
52
|
+
"""
|
|
53
|
+
probs = self.forward(hidden_states) # [B, S, K, E]
|
|
54
|
+
future = probs[:, :, 1:, :] # [B, S, lookahead_steps, E]
|
|
55
|
+
return torch.topk(future, k=top_k, dim=-1).indices
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import chronos.deps # ensure minimind on sys.path
|
|
3
|
+
|
|
4
|
+
import math
|
|
5
|
+
import torch
|
|
6
|
+
import torch.nn as nn
|
|
7
|
+
import torch.nn.functional as F
|
|
8
|
+
from transformers import PreTrainedModel, GenerationMixin
|
|
9
|
+
from transformers.modeling_outputs import MoeCausalLMOutputWithPast
|
|
10
|
+
|
|
11
|
+
from model.model_minimind import (
|
|
12
|
+
RMSNorm, precompute_freqs_cis, Attention, FeedForward,
|
|
13
|
+
)
|
|
14
|
+
from .config import ChronosConfig
|
|
15
|
+
from .lookahead_router import LookaheadRouter
|
|
16
|
+
from .moe_chronos import ChronosMOEFeedForward
|
|
17
|
+
from .hybrid_attention import make_attention
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ChronosBlock(nn.Module):
|
|
21
|
+
"""
|
|
22
|
+
Transformer block with hybrid attention:
|
|
23
|
+
even layers → MLAAttention (compressed KV cache, global context)
|
|
24
|
+
odd layers → SlidingWindowAttention (bounded KV cache, local context)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, layer_id: int, config: ChronosConfig):
|
|
28
|
+
super().__init__()
|
|
29
|
+
self.layer_id = layer_id
|
|
30
|
+
if getattr(config, 'use_hybrid_attention', True):
|
|
31
|
+
self.self_attn = make_attention(layer_id, config)
|
|
32
|
+
else:
|
|
33
|
+
self.self_attn = Attention(config)
|
|
34
|
+
self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
|
35
|
+
self.post_attention_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
|
36
|
+
self.mlp = ChronosMOEFeedForward(config) if config.use_moe else FeedForward(config)
|
|
37
|
+
|
|
38
|
+
def forward(
|
|
39
|
+
self,
|
|
40
|
+
hidden_states,
|
|
41
|
+
position_embeddings,
|
|
42
|
+
past_key_value=None,
|
|
43
|
+
use_cache=False,
|
|
44
|
+
attention_mask=None,
|
|
45
|
+
available_expert_mask=None,
|
|
46
|
+
):
|
|
47
|
+
residual = hidden_states
|
|
48
|
+
hidden_states, present_key_value = self.self_attn(
|
|
49
|
+
self.input_layernorm(hidden_states),
|
|
50
|
+
position_embeddings,
|
|
51
|
+
past_key_value,
|
|
52
|
+
use_cache,
|
|
53
|
+
attention_mask,
|
|
54
|
+
)
|
|
55
|
+
hidden_states = hidden_states + residual
|
|
56
|
+
mlp_in = self.post_attention_layernorm(hidden_states)
|
|
57
|
+
if isinstance(self.mlp, ChronosMOEFeedForward):
|
|
58
|
+
hidden_states = hidden_states + self.mlp(mlp_in, available_expert_mask)
|
|
59
|
+
else:
|
|
60
|
+
hidden_states = hidden_states + self.mlp(mlp_in)
|
|
61
|
+
return hidden_states, present_key_value
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ChronosModel(nn.Module):
|
|
65
|
+
"""
|
|
66
|
+
Backbone: identical to MiniMindModel but:
|
|
67
|
+
- Uses ChronosBlock (with ChronosMOEFeedForward)
|
|
68
|
+
- Inserts LookaheadRouter after block 0
|
|
69
|
+
- Exposes router_probs_seq for temporal loss computation
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self, config: ChronosConfig):
|
|
73
|
+
super().__init__()
|
|
74
|
+
self.config = config
|
|
75
|
+
self.vocab_size = config.vocab_size
|
|
76
|
+
self.num_hidden_layers = config.num_hidden_layers
|
|
77
|
+
self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size)
|
|
78
|
+
self.dropout = nn.Dropout(config.dropout)
|
|
79
|
+
self.layers = nn.ModuleList([
|
|
80
|
+
ChronosBlock(l, config) for l in range(self.num_hidden_layers)
|
|
81
|
+
])
|
|
82
|
+
self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
|
83
|
+
self.lookahead_router = LookaheadRouter(config)
|
|
84
|
+
freqs_cos, freqs_sin = precompute_freqs_cis(
|
|
85
|
+
dim=config.head_dim,
|
|
86
|
+
end=config.max_position_embeddings,
|
|
87
|
+
rope_base=config.rope_theta,
|
|
88
|
+
rope_scaling=config.rope_scaling,
|
|
89
|
+
)
|
|
90
|
+
self.register_buffer("freqs_cos", freqs_cos, persistent=False)
|
|
91
|
+
self.register_buffer("freqs_sin", freqs_sin, persistent=False)
|
|
92
|
+
|
|
93
|
+
def forward(
|
|
94
|
+
self,
|
|
95
|
+
input_ids,
|
|
96
|
+
attention_mask=None,
|
|
97
|
+
past_key_values=None,
|
|
98
|
+
use_cache=False,
|
|
99
|
+
available_expert_masks=None, # list[Tensor|None] per layer
|
|
100
|
+
**kwargs,
|
|
101
|
+
):
|
|
102
|
+
B, S = input_ids.shape
|
|
103
|
+
if hasattr(past_key_values, 'layers'):
|
|
104
|
+
past_key_values = None
|
|
105
|
+
past_key_values = past_key_values or [None] * len(self.layers)
|
|
106
|
+
# MLA caches only c_kv tuple; SlidingWindow caches (k, v) tuple
|
|
107
|
+
# Determine start_pos from first non-None past_kv
|
|
108
|
+
start_pos = 0
|
|
109
|
+
for pkv in past_key_values:
|
|
110
|
+
if pkv is not None:
|
|
111
|
+
start_pos = pkv[0].shape[1]
|
|
112
|
+
break
|
|
113
|
+
|
|
114
|
+
hidden_states = self.dropout(self.embed_tokens(input_ids))
|
|
115
|
+
# Pass full freqs so hybrid attention layers can slice as needed
|
|
116
|
+
position_embeddings = (
|
|
117
|
+
self.freqs_cos,
|
|
118
|
+
self.freqs_sin,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
presents = []
|
|
122
|
+
lookahead_probs = None # [B, S, K, E] from block 0
|
|
123
|
+
|
|
124
|
+
for i, (layer, past_kv) in enumerate(zip(self.layers, past_key_values)):
|
|
125
|
+
mask = available_expert_masks[i] if available_expert_masks else None
|
|
126
|
+
hidden_states, present = layer(
|
|
127
|
+
hidden_states, position_embeddings,
|
|
128
|
+
past_key_value=past_kv,
|
|
129
|
+
use_cache=use_cache,
|
|
130
|
+
attention_mask=attention_mask,
|
|
131
|
+
available_expert_mask=mask,
|
|
132
|
+
)
|
|
133
|
+
presents.append(present)
|
|
134
|
+
# Insert LookaheadRouter after block 0
|
|
135
|
+
if i == 0:
|
|
136
|
+
lookahead_probs = self.lookahead_router(hidden_states)
|
|
137
|
+
|
|
138
|
+
hidden_states = self.norm(hidden_states)
|
|
139
|
+
aux_loss = sum(
|
|
140
|
+
[l.mlp.aux_loss for l in self.layers if isinstance(l.mlp, ChronosMOEFeedForward)],
|
|
141
|
+
hidden_states.new_zeros(1).squeeze(),
|
|
142
|
+
)
|
|
143
|
+
return hidden_states, presents, aux_loss, lookahead_probs
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class ChronosForCausalLM(PreTrainedModel, GenerationMixin):
|
|
147
|
+
config_class = ChronosConfig
|
|
148
|
+
# Map of tied-weight keys (lm_head.weight ↔ model.embed_tokens.weight).
|
|
149
|
+
# Newer Transformers expects a dict; older versions accept a list. We
|
|
150
|
+
# use a dict to satisfy the newest API.
|
|
151
|
+
_tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"}
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def all_tied_weights_keys(self) -> dict:
|
|
155
|
+
"""Compat shim: newer Transformers expects this property to exist
|
|
156
|
+
on every PreTrainedModel; older base classes don't define it."""
|
|
157
|
+
v = self._tied_weights_keys
|
|
158
|
+
if isinstance(v, dict):
|
|
159
|
+
return v
|
|
160
|
+
if isinstance(v, (list, tuple, set)):
|
|
161
|
+
return {k: None for k in v}
|
|
162
|
+
return {}
|
|
163
|
+
|
|
164
|
+
def __init__(self, config: ChronosConfig = None):
|
|
165
|
+
self.config = config or ChronosConfig()
|
|
166
|
+
super().__init__(self.config)
|
|
167
|
+
self.model = ChronosModel(self.config)
|
|
168
|
+
self.lm_head = nn.Linear(self.config.hidden_size, self.config.vocab_size, bias=False)
|
|
169
|
+
if self.config.tie_word_embeddings:
|
|
170
|
+
self.model.embed_tokens.weight = self.lm_head.weight
|
|
171
|
+
|
|
172
|
+
def forward(
|
|
173
|
+
self,
|
|
174
|
+
input_ids,
|
|
175
|
+
attention_mask=None,
|
|
176
|
+
past_key_values=None,
|
|
177
|
+
use_cache=False,
|
|
178
|
+
logits_to_keep=0,
|
|
179
|
+
labels=None,
|
|
180
|
+
available_expert_masks=None,
|
|
181
|
+
**kwargs,
|
|
182
|
+
):
|
|
183
|
+
hidden_states, past_key_values, aux_loss, lookahead_probs = self.model(
|
|
184
|
+
input_ids, attention_mask, past_key_values, use_cache,
|
|
185
|
+
available_expert_masks=available_expert_masks,
|
|
186
|
+
)
|
|
187
|
+
slice_indices = slice(-logits_to_keep, None) if logits_to_keep > 0 else slice(None)
|
|
188
|
+
logits = self.lm_head(hidden_states[:, slice_indices, :])
|
|
189
|
+
|
|
190
|
+
loss = None
|
|
191
|
+
if labels is not None:
|
|
192
|
+
x = logits[..., :-1, :].contiguous()
|
|
193
|
+
y = labels[..., 1:].contiguous()
|
|
194
|
+
loss = F.cross_entropy(x.view(-1, x.size(-1)), y.view(-1), ignore_index=-100)
|
|
195
|
+
|
|
196
|
+
return MoeCausalLMOutputWithPast(
|
|
197
|
+
loss=loss,
|
|
198
|
+
aux_loss=aux_loss,
|
|
199
|
+
logits=logits,
|
|
200
|
+
past_key_values=past_key_values,
|
|
201
|
+
hidden_states=hidden_states,
|
|
202
|
+
), lookahead_probs
|
|
203
|
+
|
|
204
|
+
@torch.inference_mode()
|
|
205
|
+
def generate(
|
|
206
|
+
self,
|
|
207
|
+
inputs=None,
|
|
208
|
+
attention_mask=None,
|
|
209
|
+
max_new_tokens=512,
|
|
210
|
+
temperature=0.85,
|
|
211
|
+
top_p=0.85,
|
|
212
|
+
top_k=50,
|
|
213
|
+
eos_token_id=2,
|
|
214
|
+
streamer=None,
|
|
215
|
+
use_cache=True,
|
|
216
|
+
do_sample=True,
|
|
217
|
+
repetition_penalty=1.0,
|
|
218
|
+
available_expert_masks=None,
|
|
219
|
+
**kwargs,
|
|
220
|
+
):
|
|
221
|
+
input_ids = kwargs.pop("input_ids", inputs)
|
|
222
|
+
past_key_values = kwargs.pop("past_key_values", None)
|
|
223
|
+
finished = torch.zeros(input_ids.shape[0], dtype=torch.bool, device=input_ids.device)
|
|
224
|
+
if streamer:
|
|
225
|
+
streamer.put(input_ids.cpu())
|
|
226
|
+
|
|
227
|
+
for _ in range(max_new_tokens):
|
|
228
|
+
past_len = past_key_values[0][0].shape[1] if past_key_values else 0
|
|
229
|
+
outputs, _ = self.forward(
|
|
230
|
+
input_ids[:, past_len:], attention_mask, past_key_values,
|
|
231
|
+
use_cache=use_cache,
|
|
232
|
+
available_expert_masks=available_expert_masks,
|
|
233
|
+
)
|
|
234
|
+
if attention_mask is not None:
|
|
235
|
+
attention_mask = torch.cat(
|
|
236
|
+
[attention_mask, attention_mask.new_ones(attention_mask.shape[0], 1)], -1
|
|
237
|
+
)
|
|
238
|
+
logits = outputs.logits[:, -1, :] / temperature
|
|
239
|
+
if repetition_penalty != 1.0:
|
|
240
|
+
for i in range(input_ids.shape[0]):
|
|
241
|
+
logits[i, torch.unique(input_ids[i])] /= repetition_penalty
|
|
242
|
+
if top_k > 0:
|
|
243
|
+
logits[logits < torch.topk(logits, top_k)[0][..., -1, None]] = -float('inf')
|
|
244
|
+
if top_p < 1.0:
|
|
245
|
+
sorted_logits, sorted_indices = torch.sort(logits, descending=True)
|
|
246
|
+
mask = torch.cumsum(torch.softmax(sorted_logits, dim=-1), dim=-1) > top_p
|
|
247
|
+
mask[..., 1:] = mask[..., :-1].clone()
|
|
248
|
+
mask[..., 0] = 0
|
|
249
|
+
logits[mask.scatter(1, sorted_indices, mask)] = -float('inf')
|
|
250
|
+
next_token = (
|
|
251
|
+
torch.multinomial(torch.softmax(logits, dim=-1), num_samples=1)
|
|
252
|
+
if do_sample else torch.argmax(logits, dim=-1, keepdim=True)
|
|
253
|
+
)
|
|
254
|
+
if eos_token_id is not None:
|
|
255
|
+
next_token = torch.where(
|
|
256
|
+
finished.unsqueeze(-1),
|
|
257
|
+
next_token.new_full((next_token.shape[0], 1), eos_token_id),
|
|
258
|
+
next_token,
|
|
259
|
+
)
|
|
260
|
+
input_ids = torch.cat([input_ids, next_token], dim=-1)
|
|
261
|
+
past_key_values = outputs.past_key_values if use_cache else None
|
|
262
|
+
if streamer:
|
|
263
|
+
streamer.put(next_token.cpu())
|
|
264
|
+
if eos_token_id is not None:
|
|
265
|
+
finished |= next_token.squeeze(-1).eq(eos_token_id)
|
|
266
|
+
if finished.all():
|
|
267
|
+
break
|
|
268
|
+
|
|
269
|
+
if streamer:
|
|
270
|
+
streamer.end()
|
|
271
|
+
return input_ids
|