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.
Files changed (83) hide show
  1. chronos/__init__.py +16 -0
  2. chronos/_backend_legacy.py +64 -0
  3. chronos/app.py +122 -0
  4. chronos/backend/__init__.py +40 -0
  5. chronos/backend/dispatcher.py +347 -0
  6. chronos/backend/ext/__init__.py +2 -0
  7. chronos/backend/ext/opencl.py +22 -0
  8. chronos/cli.py +62 -0
  9. chronos/data/__init__.py +0 -0
  10. chronos/data/flexible_dataset.py +309 -0
  11. chronos/deps.py +72 -0
  12. chronos/eval/__init__.py +2 -0
  13. chronos/eval/benchmark.py +159 -0
  14. chronos/eval/io_profiler.py +232 -0
  15. chronos/io/__init__.py +13 -0
  16. chronos/io/async_prefetcher.py +220 -0
  17. chronos/io/cluster_layout.py +317 -0
  18. chronos/io/expert_store.py +559 -0
  19. chronos/io/io_simulator.py +40 -0
  20. chronos/io/storage.py +237 -0
  21. chronos/mlx/__init__.py +32 -0
  22. chronos/mlx/attention.py +230 -0
  23. chronos/mlx/expert_store.py +155 -0
  24. chronos/mlx/inference.py +159 -0
  25. chronos/mlx/model.py +181 -0
  26. chronos/mlx/moe.py +102 -0
  27. chronos/model/__init__.py +6 -0
  28. chronos/model/config.py +54 -0
  29. chronos/model/hf_io.py +149 -0
  30. chronos/model/hybrid_attention.py +237 -0
  31. chronos/model/lookahead_router.py +55 -0
  32. chronos/model/model_chronos.py +271 -0
  33. chronos/model/moe_chronos.py +189 -0
  34. chronos/model/temporal_loss.py +124 -0
  35. chronos/router/__init__.py +12 -0
  36. chronos/router/expert_predictor.py +163 -0
  37. chronos/router/intent_classifier.py +193 -0
  38. chronos/router/prefill_scheduler.py +248 -0
  39. chronos/runtime/__init__.py +2 -0
  40. chronos/runtime/cache_manager.py +117 -0
  41. chronos/runtime/inference_engine.py +295 -0
  42. chronos/runtime/metrics.py +75 -0
  43. chronos/serving/__init__.py +13 -0
  44. chronos/serving/vllm_adapter.py +85 -0
  45. chronos/trainer/__init__.py +6 -0
  46. chronos/trainer/chronos_trainer.py +151 -0
  47. chronos/trainer/distill_trainer.py +193 -0
  48. chronos/trainer/dpo_trainer.py +190 -0
  49. chronos/trainer/grpo_trainer.py +288 -0
  50. chronos/trainer/loss_mixin.py +165 -0
  51. chronos/trainer/optim_utils.py +101 -0
  52. chronos/trainer/orpo_trainer.py +189 -0
  53. chronos/trainer/reward.py +130 -0
  54. chronos/trainer/sft_trainer.py +189 -0
  55. chronos/tuning/__init__.py +1 -0
  56. chronos/tuning/_base_tuner.py +284 -0
  57. chronos/tuning/chronos_auto_tuner.py +203 -0
  58. project_chronos-0.1.1.dist-info/METADATA +670 -0
  59. project_chronos-0.1.1.dist-info/RECORD +83 -0
  60. project_chronos-0.1.1.dist-info/WHEEL +5 -0
  61. project_chronos-0.1.1.dist-info/entry_points.txt +7 -0
  62. project_chronos-0.1.1.dist-info/licenses/LICENSE +177 -0
  63. project_chronos-0.1.1.dist-info/top_level.txt +8 -0
  64. train_chronos.py +133 -0
  65. train_chronos_distill.py +95 -0
  66. train_chronos_dpo.py +82 -0
  67. train_chronos_grpo.py +88 -0
  68. train_chronos_orpo.py +81 -0
  69. train_chronos_sft.py +85 -0
  70. ui/__init__.py +3 -0
  71. ui/estimator.py +204 -0
  72. ui/gradio_compat.py +72 -0
  73. ui/i18n.py +540 -0
  74. ui/presets.py +194 -0
  75. ui/tabs/__init__.py +3 -0
  76. ui/tabs/autotune_tab.py +210 -0
  77. ui/tabs/benchmark_tab.py +196 -0
  78. ui/tabs/config_tab.py +318 -0
  79. ui/tabs/data_tab.py +148 -0
  80. ui/tabs/inference_tab.py +496 -0
  81. ui/tabs/iomon_tab.py +37 -0
  82. ui/tabs/pipeline_tab.py +134 -0
  83. 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