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
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,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()
|
chronos/data/__init__.py
ADDED
|
File without changes
|