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,232 @@
1
+ """
2
+ chronos/eval/io_profiler.py
3
+
4
+ Phase 1 validation tool: measures LookaheadRouter prediction accuracy
5
+ and temporal routing smoothness offline, without running full inference.
6
+
7
+ Usage:
8
+ python -m chronos.eval.io_profiler \
9
+ --model_path /path/to/chronos_checkpoint \
10
+ --data_path /path/to/eval.jsonl \
11
+ --device cuda
12
+
13
+ Outputs:
14
+ - t+1 Top-1 accuracy (target: >= 85%)
15
+ - t+2 Top-1 accuracy (target: >= 75%)
16
+ - Mean L2 routing shift (lower = more cache-friendly)
17
+ - Cache hit rate estimate given VRAM budget
18
+ """
19
+ import sys
20
+ import chronos.deps # ensure minimind on sys.path
21
+
22
+ import argparse
23
+ import json
24
+ import torch
25
+ import torch.nn.functional as F
26
+ from torch.utils.data import DataLoader
27
+ from transformers import AutoTokenizer
28
+
29
+ from chronos.model.config import ChronosConfig
30
+ from chronos.model.model_chronos import ChronosForCausalLM
31
+ from chronos.model.moe_chronos import ChronosMOEFeedForward
32
+
33
+
34
+ def collect_ground_truth_routing(model, input_ids, device):
35
+ """
36
+ Run a full forward pass and collect per-layer, per-step top-1 expert indices.
37
+ Returns: [B, T, num_layers] int tensor of selected expert indices.
38
+ """
39
+ model.eval()
40
+ input_ids = input_ids.to(device)
41
+ with torch.no_grad():
42
+ _, _ = model(input_ids, use_cache=False)
43
+
44
+ layers = [l for l in model.model.layers if isinstance(l.mlp, ChronosMOEFeedForward)]
45
+ if not layers:
46
+ return None
47
+
48
+ # last_router_probs: [B, S, E] per layer
49
+ probs_per_layer = [l.mlp.last_router_probs for l in layers] # list of [B, S, E]
50
+ stacked = torch.stack(probs_per_layer, dim=2) # [B, S, L, E]
51
+ top1 = stacked.argmax(dim=-1) # [B, S, L]
52
+ return top1
53
+
54
+
55
+ def evaluate_lookahead_accuracy(model, dataloader, device, lookahead_steps=2):
56
+ """
57
+ For each sequence, compare LookaheadRouter's predicted expert at t+k
58
+ against the ground-truth expert selected at t+k.
59
+
60
+ Returns dict with accuracy per step and mean L2 routing shift.
61
+ """
62
+ model.eval()
63
+ correct = {k: 0 for k in range(1, lookahead_steps + 1)}
64
+ total = 0
65
+ l2_shifts = []
66
+
67
+ for input_ids, _ in dataloader:
68
+ input_ids = input_ids.to(device)
69
+ B, T = input_ids.shape
70
+ if T < lookahead_steps + 2:
71
+ continue
72
+
73
+ # Ground truth routing for full sequence
74
+ gt_routing = collect_ground_truth_routing(model, input_ids, device)
75
+ if gt_routing is None:
76
+ continue
77
+ # gt_routing: [B, T, L] — use layer 0 as reference
78
+ gt_top1 = gt_routing[:, :, 0] # [B, T]
79
+
80
+ # LookaheadRouter predictions from block 0 hidden states
81
+ with torch.no_grad():
82
+ hidden_after_block0 = None
83
+
84
+ def _hook(module, inp, out):
85
+ nonlocal hidden_after_block0
86
+ hidden_after_block0 = out[0].detach()
87
+
88
+ handle = model.model.layers[0].register_forward_hook(_hook)
89
+ model(input_ids, use_cache=False)
90
+ handle.remove()
91
+
92
+ if hidden_after_block0 is None:
93
+ continue
94
+
95
+ lookahead_probs = model.model.lookahead_router(hidden_after_block0)
96
+ # lookahead_probs: [B, T, K+1, E], index 1..K = future predictions
97
+
98
+ for k in range(1, lookahead_steps + 1):
99
+ if T <= k:
100
+ continue
101
+ # Predicted expert at step t for future t+k
102
+ pred_top1 = lookahead_probs[:, :-k, k, :].argmax(dim=-1) # [B, T-k]
103
+ gt_at_tk = gt_top1[:, k:] # [B, T-k]
104
+ correct[k] += (pred_top1 == gt_at_tk).sum().item()
105
+ total_k = pred_top1.numel()
106
+ if k == 1:
107
+ total += total_k
108
+
109
+ # L2 routing shift between adjacent steps
110
+ # Use mean router probs across layers
111
+ from chronos.model.moe_chronos import ChronosMOEFeedForward
112
+ moe_layers = [l for l in model.model.layers if isinstance(l.mlp, ChronosMOEFeedForward)]
113
+ if moe_layers:
114
+ probs = torch.stack([l.mlp.last_router_probs for l in moe_layers], dim=2).mean(dim=2)
115
+ diff = probs[:, 1:, :] - probs[:, :-1, :]
116
+ l2_shifts.append((diff ** 2).sum(dim=-1).mean().item())
117
+
118
+ accuracies = {}
119
+ for k in range(1, lookahead_steps + 1):
120
+ denom = total if k == 1 else max(total, 1)
121
+ accuracies[f"top1_acc_t+{k}"] = correct[k] / max(denom, 1)
122
+
123
+ mean_l2 = sum(l2_shifts) / max(len(l2_shifts), 1)
124
+ return {**accuracies, "mean_l2_routing_shift": mean_l2}
125
+
126
+
127
+ def estimate_cache_hit_rate(model, dataloader, device, vram_budget_gb=4.0):
128
+ """
129
+ Simulate a VRAM LRU cache of expert weights and estimate hit rate
130
+ given the LookaheadRouter's prefetch predictions.
131
+ """
132
+ import collections
133
+
134
+ # Estimate single expert weight size in GB
135
+ moe_layers = [l for l in model.model.layers if isinstance(l.mlp, ChronosMOEFeedForward)]
136
+ if not moe_layers:
137
+ return {"cache_hit_rate": 1.0}
138
+
139
+ expert_params = sum(p.numel() for p in moe_layers[0].mlp.experts[0].parameters())
140
+ expert_size_gb = expert_params * 2 / (1024 ** 3) # fp16
141
+ num_experts = model.config.num_experts
142
+ cache_capacity = max(1, int(vram_budget_gb / expert_size_gb))
143
+
144
+ hits, total = 0, 0
145
+ for input_ids, _ in dataloader:
146
+ input_ids = input_ids.to(device)
147
+ gt_routing = collect_ground_truth_routing(model, input_ids, device)
148
+ if gt_routing is None:
149
+ continue
150
+ gt_top1 = gt_routing[:, :, 0] # [B, T]
151
+
152
+ lru = collections.OrderedDict()
153
+ for t in range(gt_top1.shape[1]):
154
+ for b in range(gt_top1.shape[0]):
155
+ eid = gt_top1[b, t].item()
156
+ if eid in lru:
157
+ lru.move_to_end(eid)
158
+ hits += 1
159
+ else:
160
+ if len(lru) >= cache_capacity:
161
+ lru.popitem(last=False)
162
+ lru[eid] = True
163
+ total += 1
164
+
165
+ return {
166
+ "cache_hit_rate": hits / max(total, 1),
167
+ "cache_capacity_experts": cache_capacity,
168
+ "expert_size_gb": round(expert_size_gb, 4),
169
+ }
170
+
171
+
172
+ def main():
173
+ parser = argparse.ArgumentParser(description="Chronos Phase 1 Validation")
174
+ parser.add_argument("--model_path", type=str, default=None)
175
+ parser.add_argument("--data_path", type=str, required=True)
176
+ parser.add_argument("--device", type=str, default="cuda" if torch.cuda.is_available() else "cpu")
177
+ parser.add_argument("--batch_size", type=int, default=4)
178
+ parser.add_argument("--max_seq_len", type=int, default=256)
179
+ parser.add_argument("--vram_budget_gb", type=float, default=4.0)
180
+ parser.add_argument("--hidden_size", type=int, default=512)
181
+ parser.add_argument("--num_hidden_layers", type=int, default=8)
182
+ parser.add_argument("--num_experts", type=int, default=4)
183
+ args = parser.parse_args()
184
+
185
+ config = ChronosConfig(
186
+ hidden_size=args.hidden_size,
187
+ num_hidden_layers=args.num_hidden_layers,
188
+ num_experts=args.num_experts,
189
+ use_moe=True,
190
+ vram_budget_gb=args.vram_budget_gb,
191
+ )
192
+ model = ChronosForCausalLM(config).to(args.device)
193
+
194
+ if args.model_path:
195
+ weights = torch.load(args.model_path, map_location=args.device)
196
+ model.load_state_dict(weights, strict=False)
197
+ print(f"Loaded weights from {args.model_path}")
198
+
199
+ tokenizer = AutoTokenizer.from_pretrained(
200
+ chronos.deps.get_tokenizer_path()
201
+ )
202
+
203
+ from dataset.lm_dataset import PretrainDataset
204
+ dataset = PretrainDataset(args.data_path, tokenizer, max_length=args.max_seq_len)
205
+ loader = DataLoader(dataset, batch_size=args.batch_size, shuffle=False)
206
+
207
+ print("\n=== Phase 1 Validation: LookaheadRouter Accuracy ===")
208
+ acc_results = evaluate_lookahead_accuracy(model, loader, args.device)
209
+ for k, v in acc_results.items():
210
+ status = ""
211
+ if "t+1" in k:
212
+ status = "✓ PASS" if v >= 0.85 else "✗ FAIL (target: ≥85%)"
213
+ elif "t+2" in k:
214
+ status = "✓ PASS" if v >= 0.75 else "✗ FAIL (target: ≥75%)"
215
+ print(f" {k}: {v:.4f} {status}")
216
+
217
+ print("\n=== Cache Hit Rate Estimate ===")
218
+ cache_results = estimate_cache_hit_rate(model, loader, args.device, args.vram_budget_gb)
219
+ for k, v in cache_results.items():
220
+ print(f" {k}: {v}")
221
+
222
+ print("\n=== Summary ===")
223
+ t1 = acc_results.get("top1_acc_t+1", 0)
224
+ t2 = acc_results.get("top1_acc_t+2", 0)
225
+ phase1_pass = t1 >= 0.85 and t2 >= 0.75
226
+ print(f" Phase 1 Gate: {'PASS ✓' if phase1_pass else 'FAIL ✗'}")
227
+ print(f" t+1 acc={t1:.1%} t+2 acc={t2:.1%} "
228
+ f"L2 shift={acc_results['mean_l2_routing_shift']:.4f}")
229
+
230
+
231
+ if __name__ == "__main__":
232
+ main()
chronos/io/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ from .expert_store import ExpertStore, LRUCache
2
+ from .async_prefetcher import AsyncPrefetcher, PrefetchScheduler
3
+ from .storage import ClusterStorage, ClusterManifest, MANIFEST_FILENAME
4
+ from .cluster_layout import (
5
+ collect_activation_log,
6
+ build_cooccurrence_matrix,
7
+ cluster_experts_greedy,
8
+ try_louvain_clustering,
9
+ build_cluster_layout,
10
+ repack_expert_weights,
11
+ repack_expert_weights_safetensors,
12
+ load_cluster_layout,
13
+ )
@@ -0,0 +1,220 @@
1
+ """
2
+ chronos/io/async_prefetcher.py
3
+
4
+ Asynchronous expert weight prefetcher driven by LookaheadRouter predictions.
5
+
6
+ Architecture:
7
+ - Background daemon thread runs a prefetch loop
8
+ - Main thread submits prefetch requests via a queue
9
+ - Prefetcher loads SSD → RAM (pinned) ahead of time
10
+ - When main thread needs an expert, it's already warm in RAM → fast H2D
11
+
12
+ The key insight: LookaheadRouter predicts t+1/t+2 expert needs,
13
+ giving us 1-2 decode steps (~10-50ms) of lead time to hide SSD latency.
14
+ """
15
+ import threading
16
+ import queue
17
+ import time
18
+ from typing import List, Optional, Callable
19
+
20
+ from chronos.io.expert_store import ExpertStore
21
+
22
+ try:
23
+ from chronos.runtime.metrics import safe_record as _metric
24
+ except Exception:
25
+ def _metric(name, value): # fallback
26
+ pass
27
+
28
+
29
+ class AsyncPrefetcher:
30
+ """
31
+ Background thread that prefetches expert weights from SSD → RAM
32
+ based on LookaheadRouter predictions.
33
+
34
+ Usage:
35
+ prefetcher = AsyncPrefetcher(expert_store)
36
+ prefetcher.start()
37
+
38
+ # After each decode step, submit next predictions:
39
+ prefetcher.submit([expert_id_t1, expert_id_t2])
40
+
41
+ # Before using expert at step t:
42
+ prefetcher.wait_for(expert_id_t) # usually already done
43
+
44
+ prefetcher.stop()
45
+ """
46
+
47
+ def __init__(
48
+ self,
49
+ expert_store: ExpertStore,
50
+ queue_depth: int = 8,
51
+ log_fn: Optional[Callable] = None,
52
+ ):
53
+ self.store = expert_store
54
+ self.log_fn = log_fn or (lambda m: None)
55
+ self._request_queue: queue.Queue = queue.Queue(maxsize=queue_depth)
56
+ self._done_set: set = set()
57
+ self._done_lock = threading.Lock()
58
+ self._stop_event = threading.Event()
59
+ self._thread: Optional[threading.Thread] = None
60
+ self._stats = {"prefetch_hits": 0, "prefetch_misses": 0, "total_requests": 0}
61
+
62
+ def start(self):
63
+ self._stop_event.clear()
64
+ self._thread = threading.Thread(
65
+ target=self._prefetch_loop,
66
+ daemon=True,
67
+ name="chronos-prefetcher",
68
+ )
69
+ self._thread.start()
70
+
71
+ def stop(self):
72
+ self._stop_event.set()
73
+ # Unblock the queue
74
+ try:
75
+ self._request_queue.put_nowait(None)
76
+ except queue.Full:
77
+ pass
78
+ if self._thread:
79
+ self._thread.join(timeout=2.0)
80
+
81
+ def submit(self, expert_ids: List[int]):
82
+ """
83
+ Submit a list of expert IDs to prefetch (non-blocking).
84
+ Called after each decode step with LookaheadRouter predictions.
85
+ """
86
+ if not expert_ids:
87
+ return
88
+ try:
89
+ self._request_queue.put_nowait(expert_ids)
90
+ except queue.Full:
91
+ # Drop oldest request to make room
92
+ try:
93
+ self._request_queue.get_nowait()
94
+ self._request_queue.put_nowait(expert_ids)
95
+ except (queue.Empty, queue.Full):
96
+ pass
97
+
98
+ def wait_for(self, expert_id: int, timeout: float = 0.05) -> bool:
99
+ """
100
+ Wait until expert_id is in RAM (or VRAM). Returns True if ready.
101
+ In practice this should return immediately if prefetch worked.
102
+ """
103
+ deadline = time.monotonic() + timeout
104
+ while time.monotonic() < deadline:
105
+ if (self.store.ram_lru.contains(expert_id) or
106
+ self.store.vram_lru.contains(expert_id)):
107
+ with self._done_lock:
108
+ self._stats["prefetch_hits"] += 1
109
+ return True
110
+ time.sleep(0.001)
111
+ with self._done_lock:
112
+ self._stats["prefetch_misses"] += 1
113
+ return False
114
+
115
+ def _prefetch_loop(self):
116
+ while not self._stop_event.is_set():
117
+ try:
118
+ expert_ids = self._request_queue.get(timeout=0.01)
119
+ except queue.Empty:
120
+ continue
121
+
122
+ if expert_ids is None: # stop sentinel
123
+ break
124
+
125
+ with self._done_lock:
126
+ self._stats["total_requests"] += len(expert_ids)
127
+
128
+ # Filter out already-warm experts
129
+ to_fetch = [
130
+ eid for eid in expert_ids
131
+ if not self.store.ram_lru.contains(eid)
132
+ and not self.store.vram_lru.contains(eid)
133
+ ]
134
+ if to_fetch:
135
+ try:
136
+ self.store.prefetch_to_ram(to_fetch)
137
+ except Exception as e:
138
+ self.log_fn(f"[prefetcher] error fetching {to_fetch}: {e}")
139
+
140
+ @property
141
+ def stats(self) -> dict:
142
+ with self._done_lock:
143
+ s = dict(self._stats)
144
+ total = s["prefetch_hits"] + s["prefetch_misses"]
145
+ s["hit_rate"] = s["prefetch_hits"] / max(total, 1)
146
+ _metric("prefetch_hit_rate", s["hit_rate"])
147
+ return s
148
+
149
+
150
+ class PrefetchScheduler:
151
+ """
152
+ Integrates LookaheadRouter predictions with AsyncPrefetcher.
153
+
154
+ Called once per decode step to:
155
+ 1. Extract predicted expert IDs from lookahead_probs
156
+ 2. Submit them to the prefetcher
157
+ 3. Promote the current-step expert from RAM → VRAM
158
+ """
159
+
160
+ def __init__(self, prefetcher: AsyncPrefetcher, expert_store: ExpertStore):
161
+ self.prefetcher = prefetcher
162
+ self.store = expert_store
163
+
164
+ def step(
165
+ self,
166
+ lookahead_probs, # [B, S, K+1, E] from LookaheadRouter
167
+ current_step_expert_ids: List[int],
168
+ ):
169
+ """
170
+ Legacy single-call API: promote current experts (blocking), then
171
+ submit future predictions. Kept for backward compatibility.
172
+ Prefer `promote_current` + `prefetch_only` for the M3 pipeline.
173
+ """
174
+ self.promote_current(current_step_expert_ids, blocking=True)
175
+ self.prefetch_only(lookahead_probs)
176
+
177
+ def promote_current(
178
+ self,
179
+ current_step_expert_ids: List[int],
180
+ blocking: bool = False,
181
+ ) -> List[int]:
182
+ """
183
+ Move the experts needed right now from RAM → VRAM.
184
+
185
+ With ``blocking=False`` (M3 default) the H2D copies are issued on
186
+ the dedicated stream and return immediately; the caller is
187
+ responsible for waiting via ``expert_store.wait_for_experts``.
188
+ Returns the list of experts that were actually newly promoted
189
+ (already-resident experts are skipped).
190
+ """
191
+ newly = []
192
+ for eid in current_step_expert_ids:
193
+ if not self.store.vram_lru.contains(eid):
194
+ if self.store.promote_to_vram(eid, blocking=blocking):
195
+ newly.append(eid)
196
+ return newly
197
+
198
+ def prefetch_only(self, lookahead_probs) -> List[int]:
199
+ """
200
+ Submit future expert predictions to the async prefetcher.
201
+ Does NOT touch VRAM. Safe to call *before* the forward pass so the
202
+ H2D pipeline can overlap with compute.
203
+
204
+ Returns the list of unique expert IDs submitted.
205
+ """
206
+ if lookahead_probs is None:
207
+ return []
208
+ import torch
209
+ # lookahead_probs: [B, S, K+1, E] — take last token, steps 1..K.
210
+ # We submit t+1 first (highest priority), then t+2, etc., so the
211
+ # FIFO queue naturally orders them by recency.
212
+ last_token_probs = lookahead_probs[:, -1, 1:, :] # [B, K, E]
213
+ K = last_token_probs.shape[1]
214
+ all_submitted: List[int] = []
215
+ for k in range(K):
216
+ ids_k = last_token_probs[:, k, :].argmax(dim=-1).unique().cpu().tolist()
217
+ if ids_k:
218
+ self.prefetcher.submit(ids_k)
219
+ all_submitted.extend(ids_k)
220
+ return list(dict.fromkeys(all_submitted)) # preserve order, dedup