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
chronos/__init__.py ADDED
@@ -0,0 +1,16 @@
1
+ # Project Chronos: On-device low-latency lookahead dual-layer MoE inference
2
+ __version__ = "0.1.0"
3
+
4
+ # Bootstrap minimind dependency before any submodule imports
5
+ from chronos.deps import ensure_minimind as _ensure_minimind
6
+ _ensure_minimind()
7
+
8
+ # Register with HuggingFace AutoXXX factories so
9
+ # AutoModelForCausalLM.from_pretrained("path/to/chronos") works out of the box.
10
+ try:
11
+ from chronos.model import hf_io as _hf_io # noqa: F401
12
+ except Exception as _e:
13
+ # Non-fatal: HF registration is a convenience, not a hard requirement.
14
+ import warnings
15
+ warnings.warn(f"Chronos HF registration skipped: {_e}")
16
+
@@ -0,0 +1,64 @@
1
+ """
2
+ chronos/backend.py — Unified backend selector.
3
+
4
+ Usage:
5
+ from chronos.backend import build_model, get_backend
6
+
7
+ backend = get_backend() # "mlx" | "cuda" | "cpu"
8
+ model, engine = build_model(config, backend=backend)
9
+ """
10
+ from __future__ import annotations
11
+ import importlib
12
+ from typing import Literal
13
+
14
+ BackendType = Literal["mlx", "cuda", "mps", "cpu"]
15
+
16
+
17
+ def get_backend() -> BackendType:
18
+ """
19
+ Auto-detect best available backend.
20
+ Priority: MLX (Apple Silicon) > CUDA > MPS > CPU
21
+ """
22
+ # Try MLX first (Apple Silicon unified memory)
23
+ if importlib.util.find_spec("mlx") is not None:
24
+ try:
25
+ import mlx.core as mx
26
+ if mx.metal.is_available():
27
+ return "mlx"
28
+ except Exception:
29
+ pass
30
+
31
+ import torch
32
+ if torch.cuda.is_available():
33
+ return "cuda"
34
+ try:
35
+ if torch.backends.mps.is_available():
36
+ return "mps"
37
+ except Exception:
38
+ pass
39
+ return "cpu"
40
+
41
+
42
+ def build_model(config, backend: BackendType = None, ssd_dir: str = "./expert_cache"):
43
+ """
44
+ Build ChronosModel + InferenceEngine for the given backend.
45
+
46
+ Returns (model, inference_engine).
47
+ """
48
+ if backend is None:
49
+ backend = get_backend()
50
+
51
+ if backend == "mlx":
52
+ from chronos.mlx.model import ChronosMLXModel
53
+ from chronos.mlx.inference import ChronosMLXInferenceEngine
54
+ model = ChronosMLXModel(config)
55
+ engine = ChronosMLXInferenceEngine(model, config, ssd_dir=ssd_dir)
56
+ return model, engine
57
+ else:
58
+ import torch
59
+ from chronos.model.model_chronos import ChronosForCausalLM
60
+ from chronos.runtime.inference_engine import ChronosInferenceEngine
61
+ device_map = {"cuda": "cuda", "mps": "mps", "cpu": "cpu"}[backend]
62
+ model = ChronosForCausalLM(config).to(device_map)
63
+ engine = ChronosInferenceEngine(model, config, ssd_dir=ssd_dir)
64
+ return model, engine
chronos/app.py ADDED
@@ -0,0 +1,122 @@
1
+ """
2
+ chronos/app.py — Project Chronos Web UI entry point (package-installable).
3
+
4
+ Exposed as `chronos-ui` CLI entry point via pyproject.toml.
5
+
6
+ Usage:
7
+ chronos-ui
8
+ chronos-ui --port 7861 --share
9
+ python -m chronos.app
10
+ """
11
+ import sys
12
+ import os
13
+ import argparse
14
+
15
+ # When run from the installed package, Project_Chronos root may not be on
16
+ # sys.path. We add it so that ui.* imports resolve correctly.
17
+ _pkg_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18
+ if _pkg_root not in sys.path:
19
+ sys.path.insert(0, _pkg_root)
20
+
21
+ from ui.gradio_compat import gr
22
+
23
+ import chronos.deps # auto-bootstrap minimind
24
+ from ui.i18n import (
25
+ LANGUAGE_CHOICES, DEFAULT_LANGUAGE,
26
+ t, set_current_lang, build_language_update, get_registered_components,
27
+ )
28
+ from ui.tabs.config_tab import build_config_tab
29
+ from ui.tabs.train_tab import build_train_tab
30
+ from ui.tabs.inference_tab import build_inference_tab
31
+ from ui.tabs.benchmark_tab import build_benchmark_tab
32
+ from ui.tabs.autotune_tab import build_autotune_tab
33
+ from ui.tabs.iomon_tab import build_iomon_tab
34
+ from ui.tabs.pipeline_tab import build_pipeline_tab
35
+ from ui.tabs.data_tab import build_data_tab
36
+
37
+
38
+ APP_CSS = """
39
+ .app-header { padding: 24px 0 16px; border-bottom: 1px solid var(--border-color-primary); margin-bottom: 16px; }
40
+ .app-header-title { font-size: 2rem; font-weight: 700; margin: 4px 0; }
41
+ .app-header-subtitle { color: var(--body-text-color-subdued); margin: 0; }
42
+ .app-meta-line { display: flex; gap: 12px; margin-top: 10px; flex-wrap: wrap; }
43
+ .app-meta-chip { background: var(--background-fill-secondary); border-radius: 6px; padding: 3px 10px; font-size: 12px; }
44
+ .toolbar-strip { margin-bottom: 8px; }
45
+ """
46
+
47
+
48
+ def _hero_html() -> str:
49
+ return (
50
+ "<div class='app-header'>"
51
+ "<div style='font-size:12px;color:var(--body-text-color-subdued);letter-spacing:.08em;text-transform:uppercase;'>On-Device MoE Inference Workbench</div>"
52
+ "<h1 class='app-header-title'>Project Chronos</h1>"
53
+ "<p class='app-header-subtitle'>"
54
+ "Lookahead Routing · MLA + Sliding Window Attention · Async SSD Prefetch · "
55
+ "Pretrain / SFT / RL / ORPO · Optuna Auto-Tune"
56
+ "</p>"
57
+ "<div class='app-meta-line'>"
58
+ "<span class='app-meta-chip'>Apache-2.0</span>"
59
+ "<span class='app-meta-chip'>Python 3.10+</span>"
60
+ "<span class='app-meta-chip'>PyTorch 2.1+</span>"
61
+ "<span class='app-meta-chip'><a href='https://github.com/jingyaogong/minimind' target='_blank'>MoE kernel: minimind</a></span>"
62
+ "</div>"
63
+ "</div>"
64
+ )
65
+
66
+
67
+ def build_app() -> gr.Blocks:
68
+ with gr.Blocks(
69
+ title="Project Chronos",
70
+ css=APP_CSS,
71
+ fill_width=True,
72
+ ) as app:
73
+ gr.HTML(_hero_html())
74
+
75
+ with gr.Row(elem_classes="toolbar-strip"):
76
+ language = gr.Dropdown(
77
+ choices=LANGUAGE_CHOICES,
78
+ value=DEFAULT_LANGUAGE,
79
+ label="Language / 语言 / 言語",
80
+ scale=1,
81
+ )
82
+
83
+ config_state, config_inputs, cfg_save_dir = build_config_tab()
84
+ build_data_tab()
85
+ build_train_tab(config_state, cfg_save_dir)
86
+ build_pipeline_tab()
87
+ build_inference_tab(config_state)
88
+ build_benchmark_tab()
89
+ build_autotune_tab(config_state, config_inputs)
90
+ build_iomon_tab()
91
+
92
+ def on_language_change(lang):
93
+ set_current_lang(lang)
94
+ return build_language_update(lang)
95
+
96
+ language.change(
97
+ fn=on_language_change,
98
+ inputs=[language],
99
+ outputs=get_registered_components(),
100
+ )
101
+
102
+ return app
103
+
104
+
105
+ def main():
106
+ parser = argparse.ArgumentParser(description="Project Chronos Web UI")
107
+ parser.add_argument("--port", type=int, default=7860)
108
+ parser.add_argument("--host", type=str, default="127.0.0.1")
109
+ parser.add_argument("--share", action="store_true")
110
+ args = parser.parse_args()
111
+
112
+ app = build_app()
113
+ app.launch(
114
+ server_name=args.host,
115
+ server_port=args.port,
116
+ share=args.share,
117
+ show_error=True,
118
+ )
119
+
120
+
121
+ if __name__ == "__main__":
122
+ main()
@@ -0,0 +1,40 @@
1
+ """
2
+ chronos/backend/__init__.py
3
+
4
+ Public re-exports for the backend dispatcher. Also preserves the legacy
5
+ ``get_backend()`` / ``build_model()`` API from the pre-M5 single-file
6
+ ``chronos/backend.py`` so existing UI and CLI code keeps working.
7
+ """
8
+ from chronos.backend.dispatcher import (
9
+ BackendDispatcher,
10
+ BackendInfo,
11
+ available,
12
+ select,
13
+ training_available,
14
+ select_training,
15
+ device_str,
16
+ training_device_str,
17
+ resolve_training_device,
18
+ describe,
19
+ describe_training,
20
+ AUTO_PRIORITY,
21
+ TRAINING_AUTO_PRIORITY,
22
+ )
23
+
24
+ # Back-compat shim for the pre-M5 API that code elsewhere still calls.
25
+ from chronos._backend_legacy import build_model, BackendType # type: ignore
26
+
27
+
28
+ def get_backend():
29
+ """Back-compat alias for ``select()`` (auto-detected default backend)."""
30
+ return select()
31
+
32
+
33
+ __all__ = [
34
+ "BackendDispatcher", "BackendInfo",
35
+ "available", "select", "training_available", "select_training",
36
+ "device_str", "training_device_str", "resolve_training_device",
37
+ "describe", "describe_training",
38
+ "AUTO_PRIORITY", "TRAINING_AUTO_PRIORITY",
39
+ "get_backend", "build_model", "BackendType",
40
+ ]
@@ -0,0 +1,347 @@
1
+ """
2
+ chronos/backend/dispatcher.py
3
+
4
+ Unified compute-backend dispatcher.
5
+
6
+ Chronos dispatches across these backend names:
7
+
8
+ cpu — PyTorch CPU (always available)
9
+ cuda — PyTorch CUDA (NVIDIA GPU)
10
+ mps — PyTorch Metal Performance Shaders (Apple Silicon via torch)
11
+ mlx — Apple MLX (Apple Silicon, non-torch inference path in this repo)
12
+ xpu — PyTorch Intel XPU
13
+ vulkan — PyTorch Vulkan (only if torch was built with USE_VULKAN=ON)
14
+ opencl — third-party extension hook (no upstream backend; plug-in only)
15
+
16
+ Training support in the current repo: cpu, cuda, mps, xpu
17
+ Inference support (stock paths): cpu, cuda, mps, mlx, xpu, (vulkan if built-in)
18
+ Inference via ext plugin: opencl
19
+
20
+ Important: although MLX is available as an inference backend, this repository
21
+ does not currently implement a full MLX-native training stack comparable to
22
+ ``chronos.trainer.*``. Training resolvers therefore exclude ``mlx`` until a
23
+ real MLX trainer exists.
24
+ """
25
+ from __future__ import annotations
26
+
27
+ import importlib
28
+ import importlib.util
29
+ import os
30
+ from dataclasses import dataclass
31
+ from typing import List, Optional
32
+
33
+
34
+ BACKENDS = ("cuda", "mlx", "mps", "xpu", "vulkan", "opencl", "cpu")
35
+
36
+ # General runtime auto-detect priority. This covers inference and legacy
37
+ # backend selection, where MLX is meaningful on Apple Silicon.
38
+ AUTO_PRIORITY = ("mlx", "cuda", "xpu", "mps", "vulkan", "opencl", "cpu")
39
+
40
+ # Training must only pick backends with an actual training implementation in
41
+ # this repository. Keep this separate from AUTO_PRIORITY so inference can
42
+ # still prefer MLX while training stays honest.
43
+ TRAINING_AUTO_PRIORITY = ("cuda", "xpu", "mps", "cpu")
44
+ TRAINING_BACKENDS = ("cuda", "xpu", "mps", "cpu")
45
+
46
+
47
+ @dataclass
48
+ class BackendInfo:
49
+ name: str
50
+ available: bool
51
+ supports_training: bool
52
+ supports_amp: bool
53
+ torch_device: Optional[str]
54
+ notes: str = ""
55
+
56
+
57
+ def _probe_cuda() -> BackendInfo:
58
+ try:
59
+ import torch
60
+ avail = bool(torch.cuda.is_available())
61
+ except Exception:
62
+ avail = False
63
+ return BackendInfo(
64
+ name="cuda", available=avail, supports_training=avail,
65
+ supports_amp=avail, torch_device="cuda" if avail else None,
66
+ )
67
+
68
+
69
+ def _probe_mps() -> BackendInfo:
70
+ try:
71
+ import torch
72
+ avail = bool(torch.backends.mps.is_available())
73
+ except Exception:
74
+ avail = False
75
+ return BackendInfo(
76
+ name="mps", available=avail, supports_training=avail,
77
+ # fp16 autocast on MPS is still patchy across torch versions; bf16
78
+ # generally OK. We report True but document the caveat.
79
+ supports_amp=avail,
80
+ torch_device="mps" if avail else None,
81
+ notes="bf16 autocast preferred over fp16 on MPS" if avail else "",
82
+ )
83
+
84
+
85
+ def _probe_xpu() -> BackendInfo:
86
+ """Intel XPU (torch.xpu). Only present on torch with oneAPI."""
87
+ try:
88
+ import torch
89
+ avail = hasattr(torch, "xpu") and bool(torch.xpu.is_available())
90
+ except Exception:
91
+ avail = False
92
+ return BackendInfo(
93
+ name="xpu", available=avail, supports_training=avail,
94
+ supports_amp=avail, torch_device="xpu" if avail else None,
95
+ )
96
+
97
+
98
+ def _probe_mlx() -> BackendInfo:
99
+ spec = importlib.util.find_spec("mlx")
100
+ if spec is None:
101
+ return BackendInfo("mlx", False, False, False, None,
102
+ notes="mlx not installed")
103
+ try:
104
+ import mlx.core as mx
105
+ avail = bool(mx.metal.is_available())
106
+ except Exception:
107
+ avail = False
108
+ return BackendInfo(
109
+ name="mlx", available=avail, supports_training=False,
110
+ supports_amp=avail, torch_device=None,
111
+ notes="non-torch backend; Chronos uses chronos.mlx.* inference paths",
112
+ )
113
+
114
+
115
+ def _probe_vulkan() -> BackendInfo:
116
+ """PyTorch Vulkan is only present in custom builds (USE_VULKAN=ON).
117
+ The stock wheel does not include it. When present, training autograd
118
+ is NOT supported upstream — only inference forward."""
119
+ try:
120
+ import torch
121
+ check = getattr(torch, "is_vulkan_available", None)
122
+ avail = bool(check()) if check is not None else False
123
+ except Exception:
124
+ avail = False
125
+ return BackendInfo(
126
+ name="vulkan", available=avail,
127
+ supports_training=False, # upstream torch.vulkan has no autograd
128
+ supports_amp=False,
129
+ torch_device="vulkan" if avail else None,
130
+ notes="inference-only; requires custom torch build",
131
+ )
132
+
133
+
134
+ def _probe_opencl() -> BackendInfo:
135
+ """Extension hook. A third-party module can register an OpenCL backend by
136
+ placing ``chronos.backend.ext.opencl.PROBE()`` that returns a BackendInfo
137
+ with available=True. The bundled stub returns False."""
138
+ try:
139
+ from chronos.backend.ext import opencl as opencl_ext
140
+ info = opencl_ext.PROBE()
141
+ if not isinstance(info, BackendInfo):
142
+ info = BackendInfo("opencl", False, False, False, None,
143
+ notes="ext.opencl.PROBE() malformed")
144
+ return info
145
+ except Exception as e:
146
+ return BackendInfo("opencl", False, False, False, None,
147
+ notes=f"ext not available: {e}")
148
+
149
+
150
+ _PROBES = {
151
+ "cuda": _probe_cuda, "mps": _probe_mps, "xpu": _probe_xpu,
152
+ "mlx": _probe_mlx, "vulkan": _probe_vulkan, "opencl": _probe_opencl,
153
+ "cpu": lambda: BackendInfo("cpu", True, True, False, "cpu"),
154
+ }
155
+
156
+
157
+ class BackendDispatcher:
158
+ """Central entry point for backend selection across Chronos."""
159
+
160
+ def __init__(self):
161
+ self._cache: dict[str, BackendInfo] = {}
162
+
163
+ def info(self, name: str) -> BackendInfo:
164
+ if name not in self._cache:
165
+ probe = _PROBES.get(name)
166
+ if probe is None:
167
+ raise ValueError(f"unknown backend: {name}")
168
+ self._cache[name] = probe()
169
+ return self._cache[name]
170
+
171
+ def available(self) -> List[str]:
172
+ """Returns names in priority order, filtered to those actually usable."""
173
+ return [n for n in AUTO_PRIORITY if self.info(n).available]
174
+
175
+ def training_available(self) -> List[str]:
176
+ """Returns trainable backend names in training-priority order."""
177
+ return [
178
+ n for n in TRAINING_AUTO_PRIORITY
179
+ if self.info(n).available and self.info(n).supports_training
180
+ ]
181
+
182
+ def select(self, prefer: Optional[str] = None) -> str:
183
+ """Resolve a concrete backend name.
184
+
185
+ Resolution order:
186
+ 1. ``CHRONOS_BACKEND`` env var if set.
187
+ 2. ``prefer`` argument if usable.
188
+ 3. First available in auto-priority order.
189
+ 4. ``cpu`` (always available).
190
+ """
191
+ env = os.environ.get("CHRONOS_BACKEND")
192
+ if env:
193
+ if self.info(env).available:
194
+ return env
195
+ # Fall through with warning; don't crash.
196
+ print(f"[chronos.backend] CHRONOS_BACKEND={env} not available; auto-selecting.")
197
+ if prefer and self.info(prefer).available:
198
+ return prefer
199
+ for n in AUTO_PRIORITY:
200
+ if self.info(n).available:
201
+ return n
202
+ return "cpu"
203
+
204
+ def select_training(self, prefer: Optional[str] = None) -> str:
205
+ """Resolve a concrete training backend name.
206
+
207
+ Resolution order:
208
+ 1. ``CHRONOS_TRAIN_BACKEND`` env var if set.
209
+ 2. ``prefer`` argument when available and trainable.
210
+ 3. First available backend in training priority order.
211
+ 4. ``cpu``.
212
+
213
+ ``prefer`` may be ``None`` or ``"auto"`` to request automatic
214
+ selection. Non-trainable values such as ``mlx`` are ignored here.
215
+ """
216
+ env = os.environ.get("CHRONOS_TRAIN_BACKEND")
217
+ if env:
218
+ env = env.strip().lower()
219
+ if env == "auto":
220
+ env = ""
221
+ elif env in BACKENDS and self.info(env).available and self.info(env).supports_training:
222
+ return env
223
+ elif env:
224
+ print(f"[chronos.backend] CHRONOS_TRAIN_BACKEND={env} not available for training; auto-selecting.")
225
+
226
+ prefer = (prefer or "").strip().lower()
227
+ if prefer == "auto":
228
+ prefer = ""
229
+ if prefer and prefer in BACKENDS:
230
+ info = self.info(prefer)
231
+ if info.available and info.supports_training:
232
+ return prefer
233
+
234
+ for n in TRAINING_AUTO_PRIORITY:
235
+ info = self.info(n)
236
+ if info.available and info.supports_training:
237
+ return n
238
+ return "cpu"
239
+
240
+ def device_str(self, name: str) -> Optional[str]:
241
+ """PyTorch device string for a backend, or None for non-torch backends."""
242
+ return self.info(name).torch_device
243
+
244
+ def training_device_str(self, name: Optional[str] = None) -> str:
245
+ """PyTorch device string for a training backend."""
246
+ backend = self.select_training(name)
247
+ return self.info(backend).torch_device or "cpu"
248
+
249
+ def resolve_training_device(self, prefer: Optional[str] = None) -> tuple[str, str]:
250
+ """Resolve ``(backend, torch_device)`` for training.
251
+
252
+ Accepts backend-level requests such as ``auto`` / ``cuda`` / ``mps``
253
+ as well as explicit torch device strings like ``cuda:0``.
254
+ """
255
+ raw = (prefer or "").strip()
256
+ name = raw.lower()
257
+
258
+ explicit_map = {
259
+ "cuda:": "cuda",
260
+ "xpu:": "xpu",
261
+ }
262
+ for prefix, backend in explicit_map.items():
263
+ if name.startswith(prefix):
264
+ info = self.info(backend)
265
+ if info.available and info.supports_training:
266
+ return backend, raw
267
+ print(f"[chronos.backend] requested training device {raw} is not available; auto-selecting.")
268
+ chosen = self.select_training()
269
+ return chosen, self.info(chosen).torch_device or "cpu"
270
+
271
+ if name in {"cpu", "mps", "cuda", "xpu"}:
272
+ chosen = self.select_training(name)
273
+ if chosen == name:
274
+ return chosen, self.info(chosen).torch_device or "cpu"
275
+
276
+ chosen = self.select_training(name or None)
277
+ return chosen, self.info(chosen).torch_device or "cpu"
278
+
279
+ def supports_training(self, name: str) -> bool:
280
+ return self.info(name).supports_training
281
+
282
+ def supports_amp(self, name: str) -> bool:
283
+ return self.info(name).supports_amp
284
+
285
+ def notes(self, name: str) -> str:
286
+ return self.info(name).notes
287
+
288
+ def describe(self) -> str:
289
+ """Human-readable summary, useful for --help and the WebUI."""
290
+ lines = ["Chronos compute backends:"]
291
+ for n in AUTO_PRIORITY:
292
+ i = self.info(n)
293
+ marker = "✓" if i.available else "·"
294
+ tr = "train+inf" if i.supports_training else ("infer" if i.available else "n/a")
295
+ extra = f" — {i.notes}" if i.notes else ""
296
+ lines.append(f" {marker} {n:<8} {tr:<10} dev={i.torch_device or '-':<6}{extra}")
297
+ return "\n".join(lines)
298
+
299
+ def describe_training(self) -> str:
300
+ """Human-readable summary for trainable backends only."""
301
+ lines = ["Chronos training backends:"]
302
+ for n in TRAINING_AUTO_PRIORITY:
303
+ i = self.info(n)
304
+ marker = "✓" if (i.available and i.supports_training) else "·"
305
+ extra = f" — {i.notes}" if i.notes else ""
306
+ lines.append(f" {marker} {n:<8} train dev={i.torch_device or '-':<6}{extra}")
307
+ return "\n".join(lines)
308
+
309
+
310
+ # Module-level convenience singleton
311
+ _default = BackendDispatcher()
312
+
313
+
314
+ def available() -> List[str]:
315
+ return _default.available()
316
+
317
+
318
+ def select(prefer: Optional[str] = None) -> str:
319
+ return _default.select(prefer)
320
+
321
+
322
+ def training_available() -> List[str]:
323
+ return _default.training_available()
324
+
325
+
326
+ def select_training(prefer: Optional[str] = None) -> str:
327
+ return _default.select_training(prefer)
328
+
329
+
330
+ def device_str(name: Optional[str] = None) -> Optional[str]:
331
+ return _default.device_str(name or select())
332
+
333
+
334
+ def training_device_str(name: Optional[str] = None) -> str:
335
+ return _default.training_device_str(name)
336
+
337
+
338
+ def resolve_training_device(prefer: Optional[str] = None) -> tuple[str, str]:
339
+ return _default.resolve_training_device(prefer)
340
+
341
+
342
+ def describe() -> str:
343
+ return _default.describe()
344
+
345
+
346
+ def describe_training() -> str:
347
+ return _default.describe_training()
@@ -0,0 +1,2 @@
1
+ """Backend extension plugin namespace. Third parties can add modules here
2
+ (e.g. chronos/backend/ext/opencl.py with a PROBE() function)."""
@@ -0,0 +1,22 @@
1
+ """Opt-in OpenCL backend plugin stub.
2
+
3
+ A real implementation should define::
4
+
5
+ from chronos.backend.dispatcher import BackendInfo
6
+ def PROBE() -> BackendInfo: ...
7
+ def to_device(tensor, ...): ...
8
+
9
+ The bundled stub always reports 'unavailable'. Third parties can replace
10
+ this file (or monkey-patch the module) to plug in their own OpenCL backend
11
+ without modifying Chronos core.
12
+ """
13
+ from chronos.backend.dispatcher import BackendInfo
14
+
15
+
16
+ def PROBE() -> BackendInfo:
17
+ return BackendInfo(
18
+ name="opencl", available=False,
19
+ supports_training=False, supports_amp=False,
20
+ torch_device=None,
21
+ notes="stub; install chronos-opencl-plugin or provide a custom ext module",
22
+ )
chronos/cli.py ADDED
@@ -0,0 +1,62 @@
1
+ """
2
+ chronos/cli.py — Project Chronos unified CLI
3
+
4
+ Commands:
5
+ chronos train — pretrain / fine-tune a Chronos model
6
+ chronos eval — Phase 1 lookahead accuracy validation
7
+ chronos benchmark — Phase 3 end-to-end benchmark
8
+ chronos export — export expert weights to SSD cluster layout
9
+ """
10
+ import sys
11
+
12
+ import argparse
13
+
14
+
15
+ def cmd_train(args):
16
+ import train_chronos
17
+ sys.argv = ['train_chronos'] + args
18
+ train_chronos.main()
19
+
20
+
21
+ def cmd_eval(args):
22
+ from chronos.eval.io_profiler import main
23
+ sys.argv = ['io_profiler'] + args
24
+ main()
25
+
26
+
27
+ def cmd_benchmark(args):
28
+ from chronos.eval.benchmark import main
29
+ sys.argv = ['benchmark'] + args
30
+ main()
31
+
32
+
33
+ def cmd_export(args):
34
+ from chronos.io.cluster_layout import main
35
+ sys.argv = ['cluster_layout'] + args
36
+ main()
37
+
38
+
39
+ def main():
40
+ parser = argparse.ArgumentParser(
41
+ prog='chronos',
42
+ description='Project Chronos — On-device low-latency lookahead dual-layer MoE inference',
43
+ )
44
+ sub = parser.add_subparsers(dest='command', required=True)
45
+
46
+ sub.add_parser('train', help='Train a Chronos model', add_help=False)
47
+ sub.add_parser('eval', help='Phase 1 lookahead accuracy eval', add_help=False)
48
+ sub.add_parser('benchmark', help='Phase 3 end-to-end benchmark', add_help=False)
49
+ sub.add_parser('export', help='Export expert cluster layout', add_help=False)
50
+
51
+ known, rest = parser.parse_known_args()
52
+ dispatch = {
53
+ 'train': cmd_train,
54
+ 'eval': cmd_eval,
55
+ 'benchmark': cmd_benchmark,
56
+ 'export': cmd_export,
57
+ }
58
+ dispatch[known.command](rest)
59
+
60
+
61
+ if __name__ == '__main__':
62
+ main()
File without changes