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,284 @@
1
+ """
2
+ chronos/tuning/_base_tuner.py — Vendored subset of Auto_Fine_Tuning's AutoTuner.
3
+
4
+ Only the base class skeleton (event queue, start/stop/poll, Optuna loop) is kept.
5
+ The full probe-trial machinery from Auto_Fine_Tuning is intentionally omitted —
6
+ ChronosAutoTuner uses its own lightweight training loop instead.
7
+
8
+ This file is derived from Auto_Fine_Tuning/core/auto_tuner.py (Apache-2.0).
9
+ See THIRD_PARTY_NOTICES.md for full attribution.
10
+ """
11
+
12
+ import os
13
+ import gc
14
+ import time
15
+ import queue
16
+ import threading
17
+ from dataclasses import dataclass, field
18
+ from typing import List, Optional, Dict, Any
19
+
20
+ try:
21
+ import optuna
22
+ from optuna.samplers import TPESampler
23
+ from optuna.pruners import MedianPruner
24
+ optuna.logging.set_verbosity(optuna.logging.WARNING)
25
+ OPTUNA_AVAILABLE = True
26
+ except ImportError:
27
+ OPTUNA_AVAILABLE = False
28
+
29
+
30
+ @dataclass
31
+ class SearchSpaceConfig:
32
+ tune_lora_r: bool = True
33
+ lora_r_choices: List[int] = field(default_factory=lambda: [4, 8, 16, 32])
34
+ tune_lr: bool = True
35
+ lr_min: float = 5e-5
36
+ lr_max: float = 5e-4
37
+ tune_batch: bool = True
38
+ batch_size_choices: List[int] = field(default_factory=lambda: [1, 2, 4, 8])
39
+ tune_grad_accum: bool = False
40
+ grad_accum_choices: List[int] = field(default_factory=lambda: [1, 2, 4, 8])
41
+ tune_warmup: bool = True
42
+ warmup_min: float = 0.01
43
+ warmup_max: float = 0.15
44
+ tune_scheduler: bool = True
45
+ scheduler_choices: List[str] = field(default_factory=lambda: ["cosine", "linear", "constant"])
46
+ tune_lora_alpha: bool = True
47
+ alpha_multiplier_choices: List[int] = field(default_factory=lambda: [1, 2])
48
+
49
+
50
+ @dataclass
51
+ class TrialResult:
52
+ trial_number: int
53
+ params: Dict[str, Any]
54
+ train_loss: float
55
+ duration_s: float
56
+ status: str
57
+ error: str = ""
58
+
59
+
60
+ @dataclass
61
+ class AutoTuneResult:
62
+ best_params: Dict[str, Any]
63
+ best_loss: float
64
+ trials: List[TrialResult]
65
+ param_importances: Dict[str, float]
66
+ n_completed: int
67
+ n_pruned: int
68
+ n_failed: int
69
+ elapsed_s: float
70
+
71
+
72
+ class AutoTuner:
73
+ """
74
+ Base class: Optuna TPE loop + event queue.
75
+ Subclasses must implement _run_probe_trial().
76
+ """
77
+
78
+ def __init__(self):
79
+ self._event_queue: queue.Queue = queue.Queue(maxsize=5000)
80
+ self._stop_event = threading.Event()
81
+ self._thread: Optional[threading.Thread] = None
82
+ self.study = None
83
+ self.result: Optional[AutoTuneResult] = None
84
+ self.trials: List[TrialResult] = []
85
+ self.status: str = "idle"
86
+
87
+ def is_running(self) -> bool:
88
+ return self._thread is not None and self._thread.is_alive()
89
+
90
+ def start(
91
+ self,
92
+ model_id: str,
93
+ dataset_path: str,
94
+ train_ratio: float = 0.95,
95
+ prompt_template: str = "alpaca",
96
+ think_mode: str = "keep",
97
+ search_space: Optional[SearchSpaceConfig] = None,
98
+ n_trials: int = 20,
99
+ probe_steps: int = 80,
100
+ output_dir: str = "auto_tune_cache",
101
+ seed: int = 42,
102
+ **kwargs,
103
+ ) -> None:
104
+ if self.is_running():
105
+ raise RuntimeError("Auto-tune already running.")
106
+ if not OPTUNA_AVAILABLE:
107
+ raise RuntimeError("optuna not installed: pip install optuna")
108
+
109
+ self._stop_event.clear()
110
+ self.trials.clear()
111
+ self.result = None
112
+ self.status = "loading"
113
+
114
+ self._thread = threading.Thread(
115
+ target=self._loop,
116
+ kwargs=dict(
117
+ model_id=model_id,
118
+ dataset_path=dataset_path,
119
+ train_ratio=train_ratio,
120
+ search_space=search_space or SearchSpaceConfig(),
121
+ n_trials=n_trials,
122
+ probe_steps=probe_steps,
123
+ output_dir=output_dir,
124
+ seed=seed,
125
+ **kwargs,
126
+ ),
127
+ daemon=True,
128
+ name="chronos-auto-tune",
129
+ )
130
+ self._thread.start()
131
+
132
+ def stop(self) -> None:
133
+ self._stop_event.set()
134
+ self.status = "stopped"
135
+ self._put({"type": "status", "status": "stopped"})
136
+
137
+ def poll(self) -> List[Dict]:
138
+ events = []
139
+ while True:
140
+ try:
141
+ events.append(self._event_queue.get_nowait())
142
+ except queue.Empty:
143
+ break
144
+ return events
145
+
146
+ def _put(self, event: Dict) -> None:
147
+ try:
148
+ self._event_queue.put_nowait(event)
149
+ except queue.Full:
150
+ pass
151
+
152
+ def _loop(self, model_id, dataset_path, train_ratio, search_space,
153
+ n_trials, probe_steps, output_dir, seed, **kwargs):
154
+ self._put({"type": "log", "line": "=== Auto-tune started ==="})
155
+ t0 = time.time()
156
+ try:
157
+ sampler = TPESampler(seed=seed, n_startup_trials=max(5, n_trials // 5))
158
+ pruner = MedianPruner(n_startup_trials=5, n_warmup_steps=probe_steps // 4)
159
+ self.study = optuna.create_study(
160
+ direction="minimize", sampler=sampler, pruner=pruner,
161
+ study_name="chronos_auto_tune",
162
+ )
163
+ self.status = "running"
164
+ self._put({"type": "status", "status": "running"})
165
+
166
+ def objective(trial):
167
+ if self._stop_event.is_set():
168
+ raise optuna.exceptions.OptunaError("user stopped")
169
+ t_start = time.time()
170
+ params = self._sample_params(trial, search_space)
171
+ self._put({"type": "log",
172
+ "line": f"[Trial {trial.number+1}/{n_trials}] {_fmt(params)}"})
173
+ try:
174
+ loss = self._run_probe_trial(
175
+ trial, model_id, dataset_path, train_ratio,
176
+ probe_steps, output_dir, seed, params, **kwargs,
177
+ )
178
+ status = "complete"
179
+ except optuna.exceptions.TrialPruned:
180
+ loss = float("inf")
181
+ status = "pruned"
182
+ except Exception as e:
183
+ self._put({"type": "log", "line": f"[Trial {trial.number}] failed: {e}"})
184
+ loss = float("inf")
185
+ status = "failed"
186
+
187
+ self.trials.append(TrialResult(
188
+ trial_number=trial.number, params=params,
189
+ train_loss=loss, duration_s=round(time.time() - t_start, 1),
190
+ status=status,
191
+ ))
192
+ best = min((t.train_loss for t in self.trials if t.status == "complete"),
193
+ default=float("inf"))
194
+ self._put({"type": "log",
195
+ "line": f" → loss={loss:.4f} best={best:.4f} {status}"})
196
+ if status == "pruned":
197
+ raise optuna.exceptions.TrialPruned()
198
+ return loss
199
+
200
+ def _stop_cb(study, _trial):
201
+ if self._stop_event.is_set():
202
+ study.stop()
203
+
204
+ self.study.optimize(objective, n_trials=n_trials,
205
+ callbacks=[_stop_cb], gc_after_trial=True)
206
+
207
+ try:
208
+ importances = optuna.importance.get_param_importances(self.study)
209
+ except Exception:
210
+ importances = {}
211
+
212
+ completed = [t for t in self.trials if t.status == "complete"]
213
+ best_params = self.study.best_params if completed else {}
214
+ best_loss = self.study.best_value if completed else float("inf")
215
+
216
+ self.result = AutoTuneResult(
217
+ best_params=best_params, best_loss=best_loss,
218
+ trials=self.trials, param_importances=importances,
219
+ n_completed=len(completed),
220
+ n_pruned=len([t for t in self.trials if t.status == "pruned"]),
221
+ n_failed=len([t for t in self.trials if t.status == "failed"]),
222
+ elapsed_s=time.time() - t0,
223
+ )
224
+ self.status = "finished"
225
+ self._put({"type": "finished", "best_params": best_params, "best_loss": best_loss})
226
+ self._put({"type": "log",
227
+ "line": (f"=== Done === best_loss={best_loss:.4f} "
228
+ f"completed={self.result.n_completed} "
229
+ f"elapsed={self.result.elapsed_s:.0f}s")})
230
+ except Exception as e:
231
+ import traceback
232
+ self.status = "error"
233
+ self._put({"type": "status", "status": "error"})
234
+ self._put({"type": "log", "line": f"[ERROR] {e}\n{traceback.format_exc()}"})
235
+
236
+ def _sample_params(self, trial, ss: SearchSpaceConfig) -> dict:
237
+ params = {}
238
+ params["lora_r"] = (trial.suggest_categorical("lora_r", ss.lora_r_choices)
239
+ if ss.tune_lora_r else 16)
240
+ mult = (trial.suggest_categorical("alpha_mult", ss.alpha_multiplier_choices)
241
+ if ss.tune_lora_alpha else 2)
242
+ params["lora_alpha"] = params["lora_r"] * mult
243
+ params["learning_rate"] = (
244
+ trial.suggest_float("learning_rate", ss.lr_min, ss.lr_max, log=True)
245
+ if ss.tune_lr else 2e-4)
246
+ params["per_device_train_batch_size"] = (
247
+ trial.suggest_categorical("batch_size", ss.batch_size_choices)
248
+ if ss.tune_batch else 4)
249
+ params["gradient_accumulation_steps"] = (
250
+ trial.suggest_categorical("grad_accum", ss.grad_accum_choices)
251
+ if ss.tune_grad_accum else 4)
252
+ params["warmup_ratio"] = (
253
+ trial.suggest_float("warmup_ratio", ss.warmup_min, ss.warmup_max)
254
+ if ss.tune_warmup else 0.05)
255
+ params["lr_scheduler_type"] = (
256
+ trial.suggest_categorical("scheduler", ss.scheduler_choices)
257
+ if ss.tune_scheduler else "cosine")
258
+ return params
259
+
260
+ def _run_probe_trial(self, trial, model_id, dataset_path, train_ratio,
261
+ probe_steps, output_dir, seed, params, **kwargs) -> float:
262
+ raise NotImplementedError
263
+
264
+ def get_best_config_patch(self) -> Dict[str, Any]:
265
+ if not self.result or not self.result.best_params:
266
+ return {}
267
+ return dict(self.result.best_params)
268
+
269
+
270
+ def _fmt(p: dict) -> str:
271
+ return ", ".join(
272
+ f"{k}={v:.2e}" if isinstance(v, float) else f"{k}={v}"
273
+ for k, v in p.items()
274
+ )
275
+
276
+
277
+ def _gc_collect():
278
+ gc.collect()
279
+ try:
280
+ import torch
281
+ if torch.cuda.is_available():
282
+ torch.cuda.empty_cache()
283
+ except Exception:
284
+ pass
@@ -0,0 +1,203 @@
1
+ """
2
+ chronos/tuning/chronos_auto_tuner.py — ChronosAutoTuner: λ1/λ2/lookahead Optuna search.
3
+
4
+ Extends the vendored AutoTuner base with Chronos-specific hyperparameters.
5
+ Uses a lightweight training loop (no external unsloth/trl dependency).
6
+ """
7
+ import os
8
+ import time
9
+ import dataclasses
10
+ from dataclasses import dataclass, field
11
+ from typing import List
12
+
13
+ import chronos.deps # ensure minimind on sys.path
14
+
15
+ from chronos.tuning._base_tuner import (
16
+ AutoTuner, SearchSpaceConfig, AutoTuneResult, _gc_collect,
17
+ )
18
+
19
+ try:
20
+ import optuna
21
+ OPTUNA_AVAILABLE = True
22
+ except ImportError:
23
+ OPTUNA_AVAILABLE = False
24
+
25
+
26
+ @dataclass
27
+ class ChronosSearchSpaceConfig(SearchSpaceConfig):
28
+ tune_lambda_balance: bool = True
29
+ lambda_balance_min: float = 1e-4
30
+ lambda_balance_max: float = 1e-2
31
+
32
+ tune_lambda_temporal: bool = True
33
+ lambda_temporal_min: float = 1e-4
34
+ lambda_temporal_max: float = 1e-2
35
+
36
+ tune_lookahead_steps: bool = False
37
+ lookahead_steps_choices: List[int] = field(default_factory=lambda: [1, 2])
38
+
39
+ tune_lr: bool = True
40
+
41
+ # Extended (UI fix 6): structural knobs that can also be searched.
42
+ tune_hidden_size: bool = False
43
+ hidden_size_choices: List[int] = field(default_factory=lambda: [128, 256, 384, 512])
44
+
45
+ tune_num_experts: bool = False
46
+ num_experts_choices: List[int] = field(default_factory=lambda: [2, 4, 6, 8])
47
+
48
+ tune_num_shared_experts: bool = False
49
+ num_shared_experts_choices: List[int] = field(default_factory=lambda: [0, 1, 2])
50
+
51
+ tune_kv_latent_dim: bool = False
52
+ kv_latent_dim_choices: List[int] = field(default_factory=lambda: [32, 64, 96, 128])
53
+
54
+ tune_lambda_lookahead: bool = False
55
+ lambda_lookahead_min: float = 1e-3
56
+ lambda_lookahead_max: float = 1.0
57
+
58
+
59
+ class ChronosAutoTuner(AutoTuner):
60
+ """
61
+ Extends AutoTuner to search over Chronos-specific hyperparameters
62
+ (λ1, λ2, lookahead_steps) using a lightweight native training loop
63
+ (no trl/unsloth dependency required).
64
+ """
65
+
66
+ def _sample_params(self, trial, ss) -> dict:
67
+ params = {}
68
+
69
+ if ss.tune_lr:
70
+ params["learning_rate"] = trial.suggest_float(
71
+ "learning_rate", ss.lr_min, ss.lr_max, log=True
72
+ )
73
+ else:
74
+ params["learning_rate"] = 2e-4
75
+
76
+ if isinstance(ss, ChronosSearchSpaceConfig):
77
+ if ss.tune_lambda_balance:
78
+ params["lambda_balance"] = trial.suggest_float(
79
+ "lambda_balance", ss.lambda_balance_min, ss.lambda_balance_max, log=True
80
+ )
81
+ else:
82
+ params["lambda_balance"] = 5e-4
83
+
84
+ if ss.tune_lambda_temporal:
85
+ params["lambda_temporal"] = trial.suggest_float(
86
+ "lambda_temporal", ss.lambda_temporal_min, ss.lambda_temporal_max, log=True
87
+ )
88
+ else:
89
+ params["lambda_temporal"] = 1e-3
90
+
91
+ if ss.tune_lookahead_steps:
92
+ params["lookahead_steps"] = trial.suggest_categorical(
93
+ "lookahead_steps", ss.lookahead_steps_choices
94
+ )
95
+ else:
96
+ params["lookahead_steps"] = 2
97
+
98
+ # Extended (UI fix 6) — categorical structural knobs
99
+ if ss.tune_hidden_size:
100
+ params["hidden_size"] = trial.suggest_categorical(
101
+ "hidden_size", ss.hidden_size_choices)
102
+ if ss.tune_num_experts:
103
+ params["num_experts"] = trial.suggest_categorical(
104
+ "num_experts", ss.num_experts_choices)
105
+ if ss.tune_num_shared_experts:
106
+ params["num_shared_experts"] = trial.suggest_categorical(
107
+ "num_shared_experts", ss.num_shared_experts_choices)
108
+ if ss.tune_kv_latent_dim:
109
+ params["kv_latent_dim"] = trial.suggest_categorical(
110
+ "kv_latent_dim", ss.kv_latent_dim_choices)
111
+ if ss.tune_lambda_lookahead:
112
+ params["lambda_lookahead"] = trial.suggest_float(
113
+ "lambda_lookahead", ss.lambda_lookahead_min, ss.lambda_lookahead_max, log=True)
114
+ else:
115
+ params["lambda_balance"] = 5e-4
116
+ params["lambda_temporal"] = 1e-3
117
+ params["lookahead_steps"] = 2
118
+
119
+ return params
120
+
121
+ def _run_probe_trial(
122
+ self, trial, model_id, dataset_path, train_ratio,
123
+ probe_steps, output_dir, seed, params, **kwargs
124
+ ) -> float:
125
+ import torch
126
+ from chronos.backend import resolve_training_device
127
+ from chronos.model.config import ChronosConfig
128
+ from chronos.model.model_chronos import ChronosForCausalLM
129
+ from chronos.model.temporal_loss import total_loss
130
+ from chronos.model.moe_chronos import ChronosMOEFeedForward
131
+
132
+ _, device = resolve_training_device("auto")
133
+ lr = params.get("learning_rate", 2e-4)
134
+ lb = params.get("lambda_balance", 5e-4)
135
+ lt = params.get("lambda_temporal", 1e-3)
136
+ la = params.get("lookahead_steps", 2)
137
+ hs = int(params.get("hidden_size", 256))
138
+ ne = int(params.get("num_experts", 4))
139
+ ns = int(params.get("num_shared_experts", 1))
140
+ kv = int(params.get("kv_latent_dim", 64))
141
+
142
+ cfg = ChronosConfig(
143
+ hidden_size=hs,
144
+ num_hidden_layers=4,
145
+ num_experts=ne,
146
+ num_shared_experts=ns,
147
+ kv_latent_dim=kv,
148
+ lookahead_steps=la,
149
+ lambda_balance=lb,
150
+ lambda_temporal=lt,
151
+ use_hybrid_attention=True,
152
+ use_moe=True,
153
+ )
154
+ model = ChronosForCausalLM(cfg).to(device)
155
+ if os.path.exists(model_id):
156
+ try:
157
+ w = torch.load(model_id, map_location="cpu")
158
+ model.load_state_dict(w, strict=False)
159
+ except Exception:
160
+ pass
161
+
162
+ optimizer = torch.optim.AdamW(model.parameters(), lr=lr)
163
+ vocab_size = cfg.vocab_size
164
+ seq_len = 64
165
+ batch = 2
166
+ model.train()
167
+ losses = []
168
+ for step in range(probe_steps):
169
+ ids = torch.randint(0, vocab_size, (batch, seq_len), device=device)
170
+ out, _ = model(ids, labels=ids)
171
+ moe_layers = [l.mlp for l in model.model.layers
172
+ if isinstance(l.mlp, ChronosMOEFeedForward)]
173
+ if moe_layers and moe_layers[0].last_router_probs is not None:
174
+ probs = torch.stack(
175
+ [l.last_router_probs for l in moe_layers], dim=2
176
+ ).mean(dim=2)
177
+ loss = total_loss(out.loss, out.aux_loss, probs, lb, lt)
178
+ else:
179
+ loss = out.loss + out.aux_loss
180
+
181
+ loss.backward()
182
+ optimizer.step()
183
+ optimizer.zero_grad(set_to_none=True)
184
+ losses.append(loss.item())
185
+ trial.report(loss.item(), step=step)
186
+ if trial.should_prune():
187
+ del model
188
+ _gc_collect()
189
+ raise optuna.exceptions.TrialPruned()
190
+
191
+ final_loss = sum(losses[-max(1, probe_steps // 5):]) / max(1, probe_steps // 5)
192
+ del model
193
+ _gc_collect()
194
+ return final_loss
195
+
196
+ def get_best_chronos_config_patch(self) -> dict:
197
+ patch = self.get_best_config_patch()
198
+ if self.result and self.result.best_params:
199
+ p = self.result.best_params
200
+ for key in ("lambda_balance", "lambda_temporal", "lookahead_steps"):
201
+ if key in p:
202
+ patch[key] = p[key]
203
+ return patch