ins-pricing 0.4.5__py3-none-any.whl → 0.5.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 (93) hide show
  1. ins_pricing/README.md +48 -22
  2. ins_pricing/__init__.py +142 -90
  3. ins_pricing/cli/BayesOpt_entry.py +58 -46
  4. ins_pricing/cli/BayesOpt_incremental.py +77 -110
  5. ins_pricing/cli/Explain_Run.py +42 -23
  6. ins_pricing/cli/Explain_entry.py +551 -577
  7. ins_pricing/cli/Pricing_Run.py +42 -23
  8. ins_pricing/cli/bayesopt_entry_runner.py +51 -16
  9. ins_pricing/cli/utils/bootstrap.py +23 -0
  10. ins_pricing/cli/utils/cli_common.py +256 -256
  11. ins_pricing/cli/utils/cli_config.py +379 -360
  12. ins_pricing/cli/utils/import_resolver.py +375 -358
  13. ins_pricing/cli/utils/notebook_utils.py +256 -242
  14. ins_pricing/cli/watchdog_run.py +216 -198
  15. ins_pricing/frontend/__init__.py +10 -10
  16. ins_pricing/frontend/app.py +132 -61
  17. ins_pricing/frontend/config_builder.py +33 -0
  18. ins_pricing/frontend/example_config.json +11 -0
  19. ins_pricing/frontend/example_workflows.py +1 -1
  20. ins_pricing/frontend/runner.py +340 -388
  21. ins_pricing/governance/__init__.py +20 -20
  22. ins_pricing/governance/release.py +159 -159
  23. ins_pricing/modelling/README.md +1 -1
  24. ins_pricing/modelling/__init__.py +147 -92
  25. ins_pricing/modelling/{core/bayesopt → bayesopt}/README.md +31 -13
  26. ins_pricing/modelling/{core/bayesopt → bayesopt}/__init__.py +64 -102
  27. ins_pricing/modelling/{core/bayesopt → bayesopt}/config_components.py +12 -0
  28. ins_pricing/modelling/{core/bayesopt → bayesopt}/config_preprocess.py +589 -552
  29. ins_pricing/modelling/{core/bayesopt → bayesopt}/core.py +987 -958
  30. ins_pricing/modelling/{core/bayesopt → bayesopt}/model_explain_mixin.py +296 -296
  31. ins_pricing/modelling/{core/bayesopt → bayesopt}/model_plotting_mixin.py +488 -548
  32. ins_pricing/modelling/{core/bayesopt → bayesopt}/models/__init__.py +27 -27
  33. ins_pricing/modelling/{core/bayesopt → bayesopt}/models/model_ft_components.py +349 -342
  34. ins_pricing/modelling/{core/bayesopt → bayesopt}/models/model_ft_trainer.py +921 -913
  35. ins_pricing/modelling/{core/bayesopt → bayesopt}/models/model_gnn.py +794 -785
  36. ins_pricing/modelling/{core/bayesopt → bayesopt}/models/model_resn.py +454 -446
  37. ins_pricing/modelling/bayesopt/trainers/__init__.py +19 -0
  38. ins_pricing/modelling/{core/bayesopt → bayesopt}/trainers/trainer_base.py +1294 -1282
  39. ins_pricing/modelling/{core/bayesopt → bayesopt}/trainers/trainer_ft.py +64 -56
  40. ins_pricing/modelling/{core/bayesopt → bayesopt}/trainers/trainer_glm.py +203 -198
  41. ins_pricing/modelling/{core/bayesopt → bayesopt}/trainers/trainer_gnn.py +333 -325
  42. ins_pricing/modelling/{core/bayesopt → bayesopt}/trainers/trainer_resn.py +279 -267
  43. ins_pricing/modelling/{core/bayesopt → bayesopt}/trainers/trainer_xgb.py +515 -313
  44. ins_pricing/modelling/bayesopt/utils/__init__.py +67 -0
  45. ins_pricing/modelling/bayesopt/utils/constants.py +21 -0
  46. ins_pricing/modelling/{core/bayesopt → bayesopt}/utils/distributed_utils.py +193 -186
  47. ins_pricing/modelling/bayesopt/utils/io_utils.py +7 -0
  48. ins_pricing/modelling/bayesopt/utils/losses.py +27 -0
  49. ins_pricing/modelling/bayesopt/utils/metrics_and_devices.py +17 -0
  50. ins_pricing/modelling/{core/bayesopt → bayesopt}/utils/torch_trainer_mixin.py +636 -623
  51. ins_pricing/modelling/{core/evaluation.py → evaluation.py} +113 -104
  52. ins_pricing/modelling/explain/__init__.py +55 -55
  53. ins_pricing/modelling/explain/metrics.py +27 -174
  54. ins_pricing/modelling/explain/permutation.py +237 -237
  55. ins_pricing/modelling/plotting/__init__.py +40 -36
  56. ins_pricing/modelling/plotting/compat.py +228 -0
  57. ins_pricing/modelling/plotting/curves.py +572 -572
  58. ins_pricing/modelling/plotting/diagnostics.py +163 -163
  59. ins_pricing/modelling/plotting/geo.py +362 -362
  60. ins_pricing/modelling/plotting/importance.py +121 -121
  61. ins_pricing/pricing/__init__.py +27 -27
  62. ins_pricing/pricing/factors.py +67 -56
  63. ins_pricing/production/__init__.py +35 -25
  64. ins_pricing/production/{predict.py → inference.py} +140 -57
  65. ins_pricing/production/monitoring.py +8 -21
  66. ins_pricing/reporting/__init__.py +11 -11
  67. ins_pricing/setup.py +1 -1
  68. ins_pricing/tests/production/test_inference.py +90 -0
  69. ins_pricing/utils/__init__.py +112 -78
  70. ins_pricing/utils/device.py +258 -237
  71. ins_pricing/utils/features.py +53 -0
  72. ins_pricing/utils/io.py +72 -0
  73. ins_pricing/utils/logging.py +34 -1
  74. ins_pricing/{modelling/core/bayesopt/utils → utils}/losses.py +125 -129
  75. ins_pricing/utils/metrics.py +158 -24
  76. ins_pricing/utils/numerics.py +76 -0
  77. ins_pricing/utils/paths.py +9 -1
  78. ins_pricing/utils/profiling.py +8 -4
  79. {ins_pricing-0.4.5.dist-info → ins_pricing-0.5.1.dist-info}/METADATA +1 -1
  80. ins_pricing-0.5.1.dist-info/RECORD +132 -0
  81. ins_pricing/modelling/core/BayesOpt.py +0 -146
  82. ins_pricing/modelling/core/__init__.py +0 -1
  83. ins_pricing/modelling/core/bayesopt/trainers/__init__.py +0 -19
  84. ins_pricing/modelling/core/bayesopt/utils/__init__.py +0 -86
  85. ins_pricing/modelling/core/bayesopt/utils/constants.py +0 -183
  86. ins_pricing/modelling/core/bayesopt/utils/io_utils.py +0 -126
  87. ins_pricing/modelling/core/bayesopt/utils/metrics_and_devices.py +0 -555
  88. ins_pricing/modelling/core/bayesopt/utils.py +0 -105
  89. ins_pricing/modelling/core/bayesopt/utils_backup.py +0 -1503
  90. ins_pricing/tests/production/test_predict.py +0 -233
  91. ins_pricing-0.4.5.dist-info/RECORD +0 -130
  92. {ins_pricing-0.4.5.dist-info → ins_pricing-0.5.1.dist-info}/WHEEL +0 -0
  93. {ins_pricing-0.4.5.dist-info → ins_pricing-0.5.1.dist-info}/top_level.txt +0 -0
@@ -1,184 +1,184 @@
1
- from __future__ import annotations
2
-
3
- import argparse
4
- import json
5
- import subprocess
6
- import sys
7
- from pathlib import Path
8
- from typing import Iterable, List, Optional, Sequence, cast
9
-
10
- try:
11
- from .cli_config import add_config_json_arg, set_env # type: ignore
12
- except Exception: # pragma: no cover
13
- from cli_config import add_config_json_arg, set_env # type: ignore
14
-
15
-
16
- def _find_ins_pricing_dir(cwd: Optional[Path] = None) -> Path:
17
- cwd = (cwd or Path().resolve()).resolve()
18
- pkg_root = Path(__file__).resolve().parents[2]
19
- candidates = [pkg_root, cwd / "ins_pricing", cwd, cwd.parent / "ins_pricing"]
20
- for cand in candidates:
21
- cli_entry = cand / "cli" / "BayesOpt_entry.py"
22
- cli_watchdog = cand / "cli" / "watchdog_run.py"
23
- if cli_entry.exists() and cli_watchdog.exists():
24
- return cand
25
- raise FileNotFoundError(
26
- "Cannot locate ins_pricing directory (expected cli/BayesOpt_entry.py and "
27
- "cli/watchdog_run.py). "
28
- f"cwd={cwd}"
29
- )
30
-
31
-
32
- def _stringify_cmd(cmd: Sequence[object]) -> List[str]:
33
- return [str(x) for x in cmd]
34
-
35
-
36
- def build_bayesopt_entry_cmd(
37
- config_json: str | Path,
38
- model_keys: Sequence[str],
39
- *,
40
- nproc_per_node: int = 1,
41
- standalone: bool = True,
42
- entry_script: str | Path = "cli/BayesOpt_entry.py",
43
- extra_args: Optional[Sequence[str]] = None,
44
- ) -> List[str]:
45
- """Build a command to run cli/BayesOpt_entry.py (optional torchrun/DDP)."""
46
- pkg_dir = _find_ins_pricing_dir()
47
- entry_script_path = Path(entry_script)
48
- if entry_script_path.is_absolute():
49
- entry_path = entry_script_path.resolve()
50
- else:
51
- candidate = pkg_dir / entry_script_path
52
- legacy = pkg_dir / "modelling" / entry_script_path
53
- entry_path = (
54
- candidate.resolve()
55
- if candidate.exists()
56
- else legacy.resolve()
57
- if legacy.exists()
58
- else candidate.resolve()
59
- )
60
- config_path = Path(config_json)
61
- if not config_path.is_absolute():
62
- config_path = (pkg_dir / config_path).resolve() if (pkg_dir / config_path).exists() else config_path.resolve()
63
-
64
- cmd: List[object]
65
- if int(nproc_per_node) > 1:
66
- cmd = [
67
- sys.executable,
68
- "-m",
69
- "torch.distributed.run",
70
- *(["--standalone"] if standalone else []),
71
- f"--nproc_per_node={int(nproc_per_node)}",
72
- str(entry_path),
73
- ]
74
- else:
75
- cmd = [sys.executable, str(entry_path)]
76
-
77
- cmd += ["--config-json", str(config_path), "--model-keys", *list(model_keys)]
78
- if extra_args:
79
- cmd += list(extra_args)
80
- return _stringify_cmd(cmd)
81
-
82
-
83
- def build_incremental_cmd(
84
- config_json: str | Path,
85
- *,
86
- entry_script: str | Path = "cli/BayesOpt_incremental.py",
87
- extra_args: Optional[Sequence[str]] = None,
88
- ) -> List[str]:
89
- """Build a command to run cli/BayesOpt_incremental.py."""
90
- pkg_dir = _find_ins_pricing_dir()
91
- entry_script_path = Path(entry_script)
92
- if entry_script_path.is_absolute():
93
- entry_path = entry_script_path.resolve()
94
- else:
95
- candidate = pkg_dir / entry_script_path
96
- legacy = pkg_dir / "modelling" / entry_script_path
97
- entry_path = (
98
- candidate.resolve()
99
- if candidate.exists()
100
- else legacy.resolve()
101
- if legacy.exists()
102
- else candidate.resolve()
103
- )
104
- config_path = Path(config_json)
105
- if not config_path.is_absolute():
106
- config_path = (pkg_dir / config_path).resolve() if (pkg_dir / config_path).exists() else config_path.resolve()
107
-
108
- cmd: List[object] = [sys.executable, str(entry_path), "--config-json", str(config_path)]
109
- if extra_args:
110
- cmd += list(extra_args)
111
- return _stringify_cmd(cmd)
112
-
113
-
114
- def build_explain_cmd(
115
- config_json: str | Path,
116
- *,
117
- entry_script: str | Path = "cli/Explain_entry.py",
118
- extra_args: Optional[Sequence[str]] = None,
119
- ) -> List[str]:
120
- """Build a command to run cli/Explain_entry.py."""
121
- pkg_dir = _find_ins_pricing_dir()
122
- entry_script_path = Path(entry_script)
123
- if entry_script_path.is_absolute():
124
- entry_path = entry_script_path.resolve()
125
- else:
126
- candidate = pkg_dir / entry_script_path
127
- legacy = pkg_dir / "modelling" / entry_script_path
128
- entry_path = (
129
- candidate.resolve()
130
- if candidate.exists()
131
- else legacy.resolve()
132
- if legacy.exists()
133
- else candidate.resolve()
134
- )
135
- config_path = Path(config_json)
136
- if not config_path.is_absolute():
137
- config_path = (pkg_dir / config_path).resolve() if (pkg_dir / config_path).exists() else config_path.resolve()
138
-
139
- cmd: List[object] = [sys.executable, str(entry_path), "--config-json", str(config_path)]
140
- if extra_args:
141
- cmd += list(extra_args)
142
- return _stringify_cmd(cmd)
143
-
144
-
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import Iterable, List, Optional, Sequence, cast
9
+
10
+ try:
11
+ from ins_pricing.cli.utils.cli_config import add_config_json_arg, set_env # type: ignore
12
+ except Exception: # pragma: no cover
13
+ from cli_config import add_config_json_arg, set_env # type: ignore
14
+
15
+
16
+ def _find_ins_pricing_dir(cwd: Optional[Path] = None) -> Path:
17
+ cwd = (cwd or Path().resolve()).resolve()
18
+ pkg_root = Path(__file__).resolve().parents[2]
19
+ candidates = [pkg_root, cwd / "ins_pricing", cwd, cwd.parent / "ins_pricing"]
20
+ for cand in candidates:
21
+ cli_entry = cand / "cli" / "BayesOpt_entry.py"
22
+ cli_watchdog = cand / "cli" / "watchdog_run.py"
23
+ if cli_entry.exists() and cli_watchdog.exists():
24
+ return cand
25
+ raise FileNotFoundError(
26
+ "Cannot locate ins_pricing directory (expected cli/BayesOpt_entry.py and "
27
+ "cli/watchdog_run.py). "
28
+ f"cwd={cwd}"
29
+ )
30
+
31
+
32
+ def _stringify_cmd(cmd: Sequence[object]) -> List[str]:
33
+ return [str(x) for x in cmd]
34
+
35
+
36
+ def build_bayesopt_entry_cmd(
37
+ config_json: str | Path,
38
+ model_keys: Sequence[str],
39
+ *,
40
+ nproc_per_node: int = 1,
41
+ standalone: bool = True,
42
+ entry_script: str | Path = "cli/BayesOpt_entry.py",
43
+ extra_args: Optional[Sequence[str]] = None,
44
+ ) -> List[str]:
45
+ """Build a command to run cli/BayesOpt_entry.py (optional torchrun/DDP)."""
46
+ pkg_dir = _find_ins_pricing_dir()
47
+ entry_script_path = Path(entry_script)
48
+ if entry_script_path.is_absolute():
49
+ entry_path = entry_script_path.resolve()
50
+ else:
51
+ candidate = pkg_dir / entry_script_path
52
+ legacy = pkg_dir / "modelling" / entry_script_path
53
+ entry_path = (
54
+ candidate.resolve()
55
+ if candidate.exists()
56
+ else legacy.resolve()
57
+ if legacy.exists()
58
+ else candidate.resolve()
59
+ )
60
+ config_path = Path(config_json)
61
+ if not config_path.is_absolute():
62
+ config_path = (pkg_dir / config_path).resolve() if (pkg_dir / config_path).exists() else config_path.resolve()
63
+
64
+ cmd: List[object]
65
+ if int(nproc_per_node) > 1:
66
+ cmd = [
67
+ sys.executable,
68
+ "-m",
69
+ "torch.distributed.run",
70
+ *(["--standalone"] if standalone else []),
71
+ f"--nproc_per_node={int(nproc_per_node)}",
72
+ str(entry_path),
73
+ ]
74
+ else:
75
+ cmd = [sys.executable, str(entry_path)]
76
+
77
+ cmd += ["--config-json", str(config_path), "--model-keys", *list(model_keys)]
78
+ if extra_args:
79
+ cmd += list(extra_args)
80
+ return _stringify_cmd(cmd)
81
+
82
+
83
+ def build_incremental_cmd(
84
+ config_json: str | Path,
85
+ *,
86
+ entry_script: str | Path = "cli/BayesOpt_incremental.py",
87
+ extra_args: Optional[Sequence[str]] = None,
88
+ ) -> List[str]:
89
+ """Build a command to run cli/BayesOpt_incremental.py."""
90
+ pkg_dir = _find_ins_pricing_dir()
91
+ entry_script_path = Path(entry_script)
92
+ if entry_script_path.is_absolute():
93
+ entry_path = entry_script_path.resolve()
94
+ else:
95
+ candidate = pkg_dir / entry_script_path
96
+ legacy = pkg_dir / "modelling" / entry_script_path
97
+ entry_path = (
98
+ candidate.resolve()
99
+ if candidate.exists()
100
+ else legacy.resolve()
101
+ if legacy.exists()
102
+ else candidate.resolve()
103
+ )
104
+ config_path = Path(config_json)
105
+ if not config_path.is_absolute():
106
+ config_path = (pkg_dir / config_path).resolve() if (pkg_dir / config_path).exists() else config_path.resolve()
107
+
108
+ cmd: List[object] = [sys.executable, str(entry_path), "--config-json", str(config_path)]
109
+ if extra_args:
110
+ cmd += list(extra_args)
111
+ return _stringify_cmd(cmd)
112
+
113
+
114
+ def build_explain_cmd(
115
+ config_json: str | Path,
116
+ *,
117
+ entry_script: str | Path = "cli/Explain_entry.py",
118
+ extra_args: Optional[Sequence[str]] = None,
119
+ ) -> List[str]:
120
+ """Build a command to run cli/Explain_entry.py."""
121
+ pkg_dir = _find_ins_pricing_dir()
122
+ entry_script_path = Path(entry_script)
123
+ if entry_script_path.is_absolute():
124
+ entry_path = entry_script_path.resolve()
125
+ else:
126
+ candidate = pkg_dir / entry_script_path
127
+ legacy = pkg_dir / "modelling" / entry_script_path
128
+ entry_path = (
129
+ candidate.resolve()
130
+ if candidate.exists()
131
+ else legacy.resolve()
132
+ if legacy.exists()
133
+ else candidate.resolve()
134
+ )
135
+ config_path = Path(config_json)
136
+ if not config_path.is_absolute():
137
+ config_path = (pkg_dir / config_path).resolve() if (pkg_dir / config_path).exists() else config_path.resolve()
138
+
139
+ cmd: List[object] = [sys.executable, str(entry_path), "--config-json", str(config_path)]
140
+ if extra_args:
141
+ cmd += list(extra_args)
142
+ return _stringify_cmd(cmd)
143
+
144
+
145
145
  def wrap_with_watchdog(
146
146
  cmd: Sequence[str],
147
- *,
148
- idle_seconds: int = 7200,
149
- max_restarts: int = 50,
150
- restart_delay_seconds: int = 10,
151
- stop_on_nonzero_exit: bool = True,
152
- watchdog_script: str | Path = "cli/watchdog_run.py",
153
- ) -> List[str]:
154
- """Wrap a command with watchdog: restart when idle_seconds elapses with no output."""
155
- pkg_dir = _find_ins_pricing_dir()
156
- watchdog_script_path = Path(watchdog_script)
157
- if watchdog_script_path.is_absolute():
158
- watchdog_path = watchdog_script_path.resolve()
159
- else:
160
- candidate = pkg_dir / watchdog_script_path
161
- legacy = pkg_dir / "modelling" / watchdog_script_path
162
- watchdog_path = (
163
- candidate.resolve()
164
- if candidate.exists()
165
- else legacy.resolve()
166
- if legacy.exists()
167
- else candidate.resolve()
168
- )
169
- wd_cmd: List[object] = [
170
- sys.executable,
171
- str(watchdog_path),
172
- "--idle-seconds",
173
- str(int(idle_seconds)),
174
- "--max-restarts",
175
- str(int(max_restarts)),
176
- "--restart-delay-seconds",
177
- str(int(restart_delay_seconds)),
178
- ]
179
- if stop_on_nonzero_exit:
180
- wd_cmd.append("--stop-on-nonzero-exit")
181
- wd_cmd.append("--")
147
+ *,
148
+ idle_seconds: int = 7200,
149
+ max_restarts: int = 50,
150
+ restart_delay_seconds: int = 10,
151
+ stop_on_nonzero_exit: bool = True,
152
+ watchdog_script: str | Path = "cli/watchdog_run.py",
153
+ ) -> List[str]:
154
+ """Wrap a command with watchdog: restart when idle_seconds elapses with no output."""
155
+ pkg_dir = _find_ins_pricing_dir()
156
+ watchdog_script_path = Path(watchdog_script)
157
+ if watchdog_script_path.is_absolute():
158
+ watchdog_path = watchdog_script_path.resolve()
159
+ else:
160
+ candidate = pkg_dir / watchdog_script_path
161
+ legacy = pkg_dir / "modelling" / watchdog_script_path
162
+ watchdog_path = (
163
+ candidate.resolve()
164
+ if candidate.exists()
165
+ else legacy.resolve()
166
+ if legacy.exists()
167
+ else candidate.resolve()
168
+ )
169
+ wd_cmd: List[object] = [
170
+ sys.executable,
171
+ str(watchdog_path),
172
+ "--idle-seconds",
173
+ str(int(idle_seconds)),
174
+ "--max-restarts",
175
+ str(int(max_restarts)),
176
+ "--restart-delay-seconds",
177
+ str(int(restart_delay_seconds)),
178
+ ]
179
+ if stop_on_nonzero_exit:
180
+ wd_cmd.append("--stop-on-nonzero-exit")
181
+ wd_cmd.append("--")
182
182
  wd_cmd.extend(list(cmd))
183
183
  return _stringify_cmd(wd_cmd)
184
184
 
@@ -189,72 +189,19 @@ def run(cmd: Sequence[str], *, check: bool = True) -> subprocess.CompletedProces
189
189
 
190
190
 
191
191
  def _build_config_parser(description: str) -> argparse.ArgumentParser:
192
- parser = argparse.ArgumentParser(description=description)
193
- add_config_json_arg(
194
- parser,
195
- help_text="Path to config.json (relative paths are resolved from ins_pricing/ when possible).",
192
+ parser = argparse.ArgumentParser(description=description)
193
+ add_config_json_arg(
194
+ parser,
195
+ help_text="Path to config.json (relative paths are resolved from ins_pricing/ when possible).",
196
196
  )
197
197
  return parser
198
198
 
199
199
 
200
- def run_from_config_cli(
201
- description: str,
202
- argv: Optional[Sequence[str]] = None,
203
- ) -> subprocess.CompletedProcess:
204
- parser = _build_config_parser(description)
205
- args = parser.parse_args(argv)
206
- return run_from_config(args.config_json)
207
-
200
+ def build_cmd_from_config(config_json: str | Path) -> tuple[List[str], str]:
201
+ """Build a command list from config.json runner settings.
208
202
 
209
- def run_bayesopt_entry(
210
- *,
211
- config_json: str | Path,
212
- model_keys: Sequence[str],
213
- max_evals: int = 50,
214
- plot_curves: bool = True,
215
- ft_role: Optional[str] = None,
216
- nproc_per_node: int = 1,
217
- use_watchdog: bool = False,
218
- idle_seconds: int = 7200,
219
- max_restarts: int = 50,
220
- restart_delay_seconds: int = 10,
221
- extra_args: Optional[Sequence[str]] = None,
222
- ) -> subprocess.CompletedProcess:
223
- """Convenience wrapper: build and run BayesOpt_entry (optional torchrun + watchdog)."""
224
- args: List[str] = ["--max-evals", str(int(max_evals))]
225
- if plot_curves:
226
- args.append("--plot-curves")
227
- if ft_role:
228
- args += ["--ft-role", str(ft_role)]
229
- if extra_args:
230
- args += list(extra_args)
231
-
232
- cmd = build_bayesopt_entry_cmd(
233
- config_json=config_json,
234
- model_keys=model_keys,
235
- nproc_per_node=nproc_per_node,
236
- extra_args=args,
237
- )
238
- if use_watchdog:
239
- cmd = wrap_with_watchdog(
240
- cmd,
241
- idle_seconds=idle_seconds,
242
- max_restarts=max_restarts,
243
- restart_delay_seconds=restart_delay_seconds,
244
- )
245
- return run(cmd, check=True)
246
-
247
-
248
- def run_from_config(config_json: str | Path) -> subprocess.CompletedProcess:
249
- """Notebook entry point: switch execution modes by editing config.json.
250
-
251
- Convention: config.json may include a `runner` section for notebook control:
252
- - runner.mode: "entry" (default), "incremental", or "explain"
253
- - runner.nproc_per_node: >1 enables torchrun/DDP (entry only)
254
- - runner.model_keys: list of models to run (entry only)
255
- - runner.max_evals / runner.plot_curves / runner.ft_role (entry only; override config fields)
256
- - runner.use_watchdog / runner.idle_seconds / runner.max_restarts / runner.restart_delay_seconds
257
- - runner.incremental_args: List[str] (incremental only; extra args for cli/BayesOpt_incremental.py)
203
+ Returns:
204
+ (cmd, mode) where mode is one of: entry, incremental, explain.
258
205
  """
259
206
  pkg_dir = _find_ins_pricing_dir()
260
207
  config_path = Path(config_json)
@@ -266,6 +213,10 @@ def run_from_config(config_json: str | Path) -> subprocess.CompletedProcess:
266
213
 
267
214
  mode = str(runner.get("mode") or "entry").strip().lower()
268
215
  use_watchdog = bool(runner.get("use_watchdog", False))
216
+ if mode == "watchdog":
217
+ use_watchdog = True
218
+ mode = "entry"
219
+
269
220
  idle_seconds = int(runner.get("idle_seconds", 7200))
270
221
  max_restarts = int(runner.get("max_restarts", 50))
271
222
  restart_delay_seconds = int(runner.get("restart_delay_seconds", 10))
@@ -282,7 +233,7 @@ def run_from_config(config_json: str | Path) -> subprocess.CompletedProcess:
282
233
  max_restarts=max_restarts,
283
234
  restart_delay_seconds=restart_delay_seconds,
284
235
  )
285
- return run(cmd, check=True)
236
+ return cmd, "incremental"
286
237
 
287
238
  if mode == "explain":
288
239
  exp_args = runner.get("explain_args") or []
@@ -296,7 +247,7 @@ def run_from_config(config_json: str | Path) -> subprocess.CompletedProcess:
296
247
  max_restarts=max_restarts,
297
248
  restart_delay_seconds=restart_delay_seconds,
298
249
  )
299
- return run(cmd, check=True)
250
+ return cmd, "explain"
300
251
 
301
252
  if mode != "entry":
302
253
  raise ValueError(
@@ -337,4 +288,67 @@ def run_from_config(config_json: str | Path) -> subprocess.CompletedProcess:
337
288
  max_restarts=max_restarts,
338
289
  restart_delay_seconds=restart_delay_seconds,
339
290
  )
291
+ return cmd, "entry"
292
+
293
+
294
+ def run_from_config_cli(
295
+ description: str,
296
+ argv: Optional[Sequence[str]] = None,
297
+ ) -> subprocess.CompletedProcess:
298
+ parser = _build_config_parser(description)
299
+ args = parser.parse_args(argv)
300
+ return run_from_config(args.config_json)
301
+
302
+
303
+ def run_bayesopt_entry(
304
+ *,
305
+ config_json: str | Path,
306
+ model_keys: Sequence[str],
307
+ max_evals: int = 50,
308
+ plot_curves: bool = True,
309
+ ft_role: Optional[str] = None,
310
+ nproc_per_node: int = 1,
311
+ use_watchdog: bool = False,
312
+ idle_seconds: int = 7200,
313
+ max_restarts: int = 50,
314
+ restart_delay_seconds: int = 10,
315
+ extra_args: Optional[Sequence[str]] = None,
316
+ ) -> subprocess.CompletedProcess:
317
+ """Convenience wrapper: build and run BayesOpt_entry (optional torchrun + watchdog)."""
318
+ args: List[str] = ["--max-evals", str(int(max_evals))]
319
+ if plot_curves:
320
+ args.append("--plot-curves")
321
+ if ft_role:
322
+ args += ["--ft-role", str(ft_role)]
323
+ if extra_args:
324
+ args += list(extra_args)
325
+
326
+ cmd = build_bayesopt_entry_cmd(
327
+ config_json=config_json,
328
+ model_keys=model_keys,
329
+ nproc_per_node=nproc_per_node,
330
+ extra_args=args,
331
+ )
332
+ if use_watchdog:
333
+ cmd = wrap_with_watchdog(
334
+ cmd,
335
+ idle_seconds=idle_seconds,
336
+ max_restarts=max_restarts,
337
+ restart_delay_seconds=restart_delay_seconds,
338
+ )
339
+ return run(cmd, check=True)
340
+
341
+
342
+ def run_from_config(config_json: str | Path) -> subprocess.CompletedProcess:
343
+ """Notebook entry point: switch execution modes by editing config.json.
344
+
345
+ Convention: config.json may include a `runner` section for notebook control:
346
+ - runner.mode: "entry" (default), "incremental", or "explain"
347
+ - runner.nproc_per_node: >1 enables torchrun/DDP (entry only)
348
+ - runner.model_keys: list of models to run (entry only)
349
+ - runner.max_evals / runner.plot_curves / runner.ft_role (entry only; override config fields)
350
+ - runner.use_watchdog / runner.idle_seconds / runner.max_restarts / runner.restart_delay_seconds
351
+ - runner.incremental_args: List[str] (incremental only; extra args for cli/BayesOpt_incremental.py)
352
+ """
353
+ cmd, _mode = build_cmd_from_config(config_json)
340
354
  return run(cmd, check=True)