nemo-evaluator-launcher 0.1.21__tar.gz → 0.1.64__tar.gz
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.
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/PKG-INFO +4 -3
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/pyproject.toml +5 -2
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/api/functional.py +159 -5
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/api/types.py +21 -14
- nemo_evaluator_launcher-0.1.64/src/nemo_evaluator_launcher/cli/logs.py +102 -0
- nemo_evaluator_launcher-0.1.64/src/nemo_evaluator_launcher/cli/ls_task.py +280 -0
- nemo_evaluator_launcher-0.1.64/src/nemo_evaluator_launcher/cli/ls_tasks.py +289 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/cli/main.py +29 -2
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/cli/run.py +77 -28
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/cli/version.py +26 -23
- nemo_evaluator_launcher-0.1.64/src/nemo_evaluator_launcher/common/container_metadata/__init__.py +61 -0
- nemo_evaluator_launcher-0.1.64/src/nemo_evaluator_launcher/common/container_metadata/intermediate_repr.py +530 -0
- nemo_evaluator_launcher-0.1.64/src/nemo_evaluator_launcher/common/container_metadata/loading.py +1126 -0
- nemo_evaluator_launcher-0.1.64/src/nemo_evaluator_launcher/common/container_metadata/registries.py +824 -0
- nemo_evaluator_launcher-0.1.64/src/nemo_evaluator_launcher/common/container_metadata/utils.py +63 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/common/helpers.py +115 -37
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/common/logging_utils.py +4 -1
- nemo_evaluator_launcher-0.1.64/src/nemo_evaluator_launcher/common/mapping.py +284 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/common/printing_utils.py +25 -12
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/configs/deployment/nim.yaml +3 -1
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/configs/deployment/sglang.yaml +4 -2
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/configs/deployment/trtllm.yaml +2 -3
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/configs/deployment/vllm.yaml +0 -1
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/configs/execution/slurm/default.yaml +14 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/executors/base.py +31 -1
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/executors/lepton/deployment_helpers.py +36 -1
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/executors/lepton/executor.py +107 -9
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/executors/local/executor.py +383 -24
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/executors/local/run.template.sh +54 -2
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/executors/slurm/executor.py +680 -78
- nemo_evaluator_launcher-0.1.64/src/nemo_evaluator_launcher/executors/slurm/proxy.cfg.template +26 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/exporters/utils.py +32 -46
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/package_info.py +1 -1
- nemo_evaluator_launcher-0.1.64/src/nemo_evaluator_launcher/resources/all_tasks_irs.yaml +17016 -0
- nemo_evaluator_launcher-0.1.64/src/nemo_evaluator_launcher/resources/mapping.toml +93 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher.egg-info/PKG-INFO +4 -3
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher.egg-info/SOURCES.txt +9 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher.egg-info/entry_points.txt +1 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher.egg-info/requires.txt +2 -1
- nemo_evaluator_launcher-0.1.21/src/nemo_evaluator_launcher/cli/ls_tasks.py +0 -136
- nemo_evaluator_launcher-0.1.21/src/nemo_evaluator_launcher/common/mapping.py +0 -295
- nemo_evaluator_launcher-0.1.21/src/nemo_evaluator_launcher/resources/mapping.toml +0 -380
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/LICENSE +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/README.md +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/setup.cfg +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/__init__.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/api/__init__.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/api/utils.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/cli/__init__.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/cli/export.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/cli/info.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/cli/kill.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/cli/ls_runs.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/cli/status.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/common/__init__.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/common/execdb.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/configs/__init__.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/configs/default.yaml +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/configs/deployment/generic.yaml +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/configs/deployment/none.yaml +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/configs/execution/lepton/default.yaml +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/configs/execution/local.yaml +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/executors/__init__.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/executors/lepton/__init__.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/executors/lepton/job_helpers.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/executors/local/__init__.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/executors/registry.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/executors/slurm/__init__.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/exporters/__init__.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/exporters/base.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/exporters/gsheets.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/exporters/local.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/exporters/mlflow.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/exporters/registry.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher/exporters/wandb.py +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher.egg-info/dependency_links.txt +0 -0
- {nemo_evaluator_launcher-0.1.21 → nemo_evaluator_launcher-0.1.64}/src/nemo_evaluator_launcher.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nemo-evaluator-launcher
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.64
|
|
4
4
|
Summary: Launcher for the evaluations provided by NeMo Evaluator containers with different runtime backends
|
|
5
5
|
Author: NVIDIA
|
|
6
6
|
Author-email: nemo-toolkit@nvidia.com
|
|
@@ -458,7 +458,7 @@ License:
|
|
|
458
458
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
459
459
|
SOFTWARE.
|
|
460
460
|
|
|
461
|
-
Project-URL: homepage, https://github.com/NVIDIA-NeMo/
|
|
461
|
+
Project-URL: homepage, https://github.com/NVIDIA-NeMo/Evaluator
|
|
462
462
|
Project-URL: repository, https://github.com/NVIDIA-NeMo/Evaluator/packages/nemo-evaluator-launcher
|
|
463
463
|
Keywords: deep learning,evaluations,machine learning,gpu,NLP,pytorch,torch
|
|
464
464
|
Requires-Python: <3.14,>=3.10
|
|
@@ -467,6 +467,7 @@ License-File: LICENSE
|
|
|
467
467
|
Requires-Dist: hydra-core<2.0.0,>=1.3.2
|
|
468
468
|
Requires-Dist: jinja2<4.0.0,>=3.1.6
|
|
469
469
|
Requires-Dist: leptonai>=0.25.0
|
|
470
|
+
Requires-Dist: nemo-evaluator
|
|
470
471
|
Requires-Dist: pyyaml>=6.0.0
|
|
471
472
|
Requires-Dist: requests>=2.32.4
|
|
472
473
|
Requires-Dist: simple-parsing<0.2.0,>=0.1.7
|
|
@@ -478,7 +479,7 @@ Requires-Dist: mlflow>=2.8.0; extra == "mlflow"
|
|
|
478
479
|
Provides-Extra: wandb
|
|
479
480
|
Requires-Dist: wandb>=0.15.0; extra == "wandb"
|
|
480
481
|
Provides-Extra: gsheets
|
|
481
|
-
Requires-Dist:
|
|
482
|
+
Requires-Dist: gspread>=5.0.0; extra == "gsheets"
|
|
482
483
|
Provides-Extra: exporters
|
|
483
484
|
Requires-Dist: mlflow; extra == "exporters"
|
|
484
485
|
Requires-Dist: wandb; extra == "exporters"
|
|
@@ -4,6 +4,7 @@ dependencies = [
|
|
|
4
4
|
"hydra-core>=1.3.2,<2.0.0",
|
|
5
5
|
"jinja2>=3.1.6,<4.0.0",
|
|
6
6
|
"leptonai>=0.25.0",
|
|
7
|
+
"nemo-evaluator",
|
|
7
8
|
"pyyaml>=6.0.0",
|
|
8
9
|
"requests>=2.32.4",
|
|
9
10
|
"simple-parsing>=0.1.7,<0.2.0",
|
|
@@ -33,20 +34,21 @@ keywords = [
|
|
|
33
34
|
|
|
34
35
|
[project.urls]
|
|
35
36
|
# BEGIN(if-changed): check package_info.py
|
|
36
|
-
homepage = "https://github.com/NVIDIA-NeMo/
|
|
37
|
+
homepage = "https://github.com/NVIDIA-NeMo/Evaluator"
|
|
37
38
|
repository = "https://github.com/NVIDIA-NeMo/Evaluator/packages/nemo-evaluator-launcher"
|
|
38
39
|
# END(if-changed)
|
|
39
40
|
|
|
40
41
|
[project.optional-dependencies]
|
|
41
42
|
mlflow = ["mlflow>=2.8.0"]
|
|
42
43
|
wandb = ["wandb>=0.15.0"]
|
|
43
|
-
gsheets = ["
|
|
44
|
+
gsheets = ["gspread>=5.0.0"]
|
|
44
45
|
exporters = ["mlflow", "wandb", "gsheets"]
|
|
45
46
|
all = ["mlflow", "wandb", "gsheets"]
|
|
46
47
|
|
|
47
48
|
[project.scripts]
|
|
48
49
|
nemo-evaluator-launcher = "nemo_evaluator_launcher.cli.main:main"
|
|
49
50
|
nv-eval = "nemo_evaluator_launcher.cli.main:main"
|
|
51
|
+
nel = "nemo_evaluator_launcher.cli.main:main"
|
|
50
52
|
|
|
51
53
|
[dependency-groups]
|
|
52
54
|
dev = [
|
|
@@ -75,6 +77,7 @@ where = ["src"]
|
|
|
75
77
|
"resources/**/*",
|
|
76
78
|
"configs/**/*",
|
|
77
79
|
"executors/**/*.sh",
|
|
80
|
+
"executors/**/*.template",
|
|
78
81
|
]
|
|
79
82
|
|
|
80
83
|
[tool.setuptools.dynamic]
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
This module provides the main functional entry points for running evaluations, querying job status, and listing available tasks. These functions are intended to be used by CLI commands and external integrations.
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
+
import copy
|
|
21
22
|
from pathlib import Path
|
|
22
|
-
from typing import Any, List, Optional, Union
|
|
23
|
+
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
|
|
23
24
|
|
|
24
25
|
import yaml
|
|
25
26
|
from omegaconf import DictConfig, OmegaConf
|
|
@@ -35,7 +36,7 @@ def get_tasks_list() -> list[list[Any]]:
|
|
|
35
36
|
"""Get a list of available tasks from the mapping.
|
|
36
37
|
|
|
37
38
|
Returns:
|
|
38
|
-
list[list[Any]]: Each sublist contains task name, endpoint type, harness, and
|
|
39
|
+
list[list[Any]]: Each sublist contains task name, endpoint type, harness, container, arch, description, and type.
|
|
39
40
|
"""
|
|
40
41
|
mapping = load_tasks_mapping()
|
|
41
42
|
data = [
|
|
@@ -44,6 +45,9 @@ def get_tasks_list() -> list[list[Any]]:
|
|
|
44
45
|
task_data.get("endpoint_type"),
|
|
45
46
|
task_data.get("harness"),
|
|
46
47
|
task_data.get("container"),
|
|
48
|
+
task_data.get("arch", ""),
|
|
49
|
+
task_data.get("description", ""),
|
|
50
|
+
task_data.get("type", ""),
|
|
47
51
|
]
|
|
48
52
|
for task_data in mapping.values()
|
|
49
53
|
]
|
|
@@ -75,12 +79,54 @@ def _validate_no_missing_values(cfg: Any, path: str = "") -> None:
|
|
|
75
79
|
_validate_no_missing_values(value, current_path)
|
|
76
80
|
|
|
77
81
|
|
|
78
|
-
def
|
|
82
|
+
def filter_tasks(cfg: RunConfig, task_names: list[str]) -> RunConfig:
|
|
83
|
+
"""Filter evaluation tasks to only include specified task names.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
cfg: The configuration object for the evaluation run.
|
|
87
|
+
task_names: List of task names to include (e.g., ["ifeval", "gsm8k"]).
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
RunConfig: A new configuration with filtered tasks (input is not mutated).
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
ValueError: If any requested task is not found in config or no tasks defined.
|
|
94
|
+
"""
|
|
95
|
+
if not task_names:
|
|
96
|
+
return cfg
|
|
97
|
+
|
|
98
|
+
if not hasattr(cfg.evaluation, "tasks") or not cfg.evaluation.tasks:
|
|
99
|
+
raise ValueError("No tasks defined in config. Cannot filter tasks.")
|
|
100
|
+
|
|
101
|
+
requested_tasks = set(task_names)
|
|
102
|
+
original_tasks = cfg.evaluation.tasks
|
|
103
|
+
filtered_tasks = [task for task in original_tasks if task.name in requested_tasks]
|
|
104
|
+
|
|
105
|
+
# Fail if ANY requested tasks are not found
|
|
106
|
+
found_names = {task.name for task in filtered_tasks}
|
|
107
|
+
not_found = requested_tasks - found_names
|
|
108
|
+
if not_found:
|
|
109
|
+
available = [task.name for task in original_tasks]
|
|
110
|
+
raise ValueError(
|
|
111
|
+
f"Requested task(s) not found in config: {sorted(not_found)}. "
|
|
112
|
+
f"Available tasks: {available}"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Create a deep copy to preserve input immutability
|
|
116
|
+
result = copy.deepcopy(cfg)
|
|
117
|
+
result.evaluation.tasks = filtered_tasks
|
|
118
|
+
return result
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def run_eval(
|
|
122
|
+
cfg: RunConfig, dry_run: bool = False, tasks: Optional[list[str]] = None
|
|
123
|
+
) -> Optional[str]:
|
|
79
124
|
"""Run evaluation with specified config and overrides.
|
|
80
125
|
|
|
81
126
|
Args:
|
|
82
127
|
cfg: The configuration object for the evaluation run.
|
|
83
128
|
dry_run: If True, do not run the evaluation, just prepare scripts and save them.
|
|
129
|
+
tasks: Optional list of task names to run. If provided, only these tasks will be executed.
|
|
84
130
|
|
|
85
131
|
Returns:
|
|
86
132
|
Optional[str]: The invocation ID for the evaluation run.
|
|
@@ -89,6 +135,10 @@ def run_eval(cfg: RunConfig, dry_run: bool = False) -> Optional[str]:
|
|
|
89
135
|
ValueError: If configuration validation fails or MISSING values are found.
|
|
90
136
|
RuntimeError: If the executor fails to start the evaluation.
|
|
91
137
|
"""
|
|
138
|
+
# Filter tasks if specified
|
|
139
|
+
if tasks:
|
|
140
|
+
cfg = filter_tasks(cfg, tasks)
|
|
141
|
+
|
|
92
142
|
# Validate that no MISSING values exist in the configuration
|
|
93
143
|
_validate_no_missing_values(cfg)
|
|
94
144
|
|
|
@@ -116,6 +166,7 @@ def get_status(ids_or_prefixes: list[str]) -> list[dict[str, Any]]:
|
|
|
116
166
|
db = ExecutionDB()
|
|
117
167
|
results: List[dict[str, Any]] = []
|
|
118
168
|
|
|
169
|
+
# TODO(agronskiy): refactor the `.`-checking job in all the functions.
|
|
119
170
|
for id_or_prefix in ids_or_prefixes:
|
|
120
171
|
# If id looks like an invocation_id (no dot), get all jobs for it
|
|
121
172
|
if "." not in id_or_prefix:
|
|
@@ -259,6 +310,108 @@ def get_status(ids_or_prefixes: list[str]) -> list[dict[str, Any]]:
|
|
|
259
310
|
return results
|
|
260
311
|
|
|
261
312
|
|
|
313
|
+
def stream_logs(
|
|
314
|
+
ids_or_prefixes: Union[str, list[str]],
|
|
315
|
+
) -> Iterator[Tuple[str, str, str]]:
|
|
316
|
+
"""Stream logs from jobs or invocations by their IDs or invocation IDs.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
ids_or_prefixes: Single ID/prefix or list of job IDs or invocation IDs to stream logs from.
|
|
320
|
+
Short prefixes are allowed, we would try to match the full ones from
|
|
321
|
+
prefixes if no collisions are present.
|
|
322
|
+
|
|
323
|
+
Yields:
|
|
324
|
+
Tuple[str, str, str]: Tuples of (job_id, task_name, log_line) for each log line.
|
|
325
|
+
Empty lines are yielded as empty strings.
|
|
326
|
+
|
|
327
|
+
Raises:
|
|
328
|
+
ValueError: If the executor doesn't support log streaming.
|
|
329
|
+
"""
|
|
330
|
+
db = ExecutionDB()
|
|
331
|
+
|
|
332
|
+
# Normalize to list for consistent processing
|
|
333
|
+
if isinstance(ids_or_prefixes, str):
|
|
334
|
+
ids_or_prefixes = [ids_or_prefixes]
|
|
335
|
+
|
|
336
|
+
# Collect all jobs from all IDs, grouped by executor
|
|
337
|
+
executor_to_jobs: Dict[str, Dict[str, JobData]] = {}
|
|
338
|
+
executor_to_invocations: Dict[str, list[str]] = {}
|
|
339
|
+
|
|
340
|
+
# TODO(agronskiy): refactor the `.`-checking job in all the functions.
|
|
341
|
+
for id_or_prefix in ids_or_prefixes:
|
|
342
|
+
# Determine if this is a job ID or invocation ID
|
|
343
|
+
if "." in id_or_prefix:
|
|
344
|
+
# This is a job ID
|
|
345
|
+
job_data = db.get_job(id_or_prefix)
|
|
346
|
+
if job_data is None:
|
|
347
|
+
continue
|
|
348
|
+
|
|
349
|
+
executor = job_data.executor
|
|
350
|
+
if executor not in executor_to_jobs:
|
|
351
|
+
executor_to_jobs[executor] = {}
|
|
352
|
+
executor_to_jobs[executor][id_or_prefix] = job_data
|
|
353
|
+
else:
|
|
354
|
+
# This is an invocation ID
|
|
355
|
+
jobs = db.get_jobs(id_or_prefix)
|
|
356
|
+
if not jobs:
|
|
357
|
+
continue
|
|
358
|
+
|
|
359
|
+
# Get the executor class from the first job
|
|
360
|
+
first_job_data = next(iter(jobs.values()))
|
|
361
|
+
executor = first_job_data.executor
|
|
362
|
+
if executor not in executor_to_invocations:
|
|
363
|
+
executor_to_invocations[executor] = []
|
|
364
|
+
executor_to_invocations[executor].append(id_or_prefix)
|
|
365
|
+
|
|
366
|
+
# Stream logs from each executor simultaneously
|
|
367
|
+
# For each executor, collect all job IDs and stream them together
|
|
368
|
+
for executor, jobs_dict in executor_to_jobs.items():
|
|
369
|
+
try:
|
|
370
|
+
executor_cls = get_executor(executor)
|
|
371
|
+
except ValueError:
|
|
372
|
+
continue
|
|
373
|
+
|
|
374
|
+
# For local executor with multiple jobs, pass list to stream simultaneously
|
|
375
|
+
# For other executors or single jobs, pass individual job IDs
|
|
376
|
+
if executor == "local" and len(jobs_dict) > 1:
|
|
377
|
+
# Pass all job IDs as a list to stream simultaneously
|
|
378
|
+
try:
|
|
379
|
+
yield from executor_cls.stream_logs(
|
|
380
|
+
list(jobs_dict.keys()), executor_name=executor
|
|
381
|
+
)
|
|
382
|
+
except NotImplementedError:
|
|
383
|
+
raise ValueError(
|
|
384
|
+
f"Log streaming is not yet implemented for executor '{executor}'"
|
|
385
|
+
)
|
|
386
|
+
else:
|
|
387
|
+
# Single job or non-local executor
|
|
388
|
+
for job_id in jobs_dict.keys():
|
|
389
|
+
try:
|
|
390
|
+
yield from executor_cls.stream_logs(job_id, executor_name=executor)
|
|
391
|
+
except NotImplementedError:
|
|
392
|
+
raise ValueError(
|
|
393
|
+
f"Log streaming is not yet implemented for executor '{executor}'"
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
# Stream logs from invocation IDs
|
|
397
|
+
for executor, invocation_ids in executor_to_invocations.items():
|
|
398
|
+
try:
|
|
399
|
+
executor_cls = get_executor(executor)
|
|
400
|
+
except ValueError:
|
|
401
|
+
continue
|
|
402
|
+
|
|
403
|
+
# Stream each invocation (each invocation already handles multiple jobs internally)
|
|
404
|
+
for invocation_id in invocation_ids:
|
|
405
|
+
try:
|
|
406
|
+
yield from executor_cls.stream_logs(
|
|
407
|
+
invocation_id, executor_name=executor
|
|
408
|
+
)
|
|
409
|
+
except NotImplementedError:
|
|
410
|
+
raise ValueError(
|
|
411
|
+
f"Log streaming is not yet implemented for executor '{executor}'"
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
|
|
262
415
|
def list_all_invocations_summary() -> list[dict[str, Any]]:
|
|
263
416
|
"""Return a concise per-invocation summary from the exec DB.
|
|
264
417
|
|
|
@@ -378,6 +531,7 @@ def kill_job_or_invocation(id: str) -> list[dict[str, Any]]:
|
|
|
378
531
|
"data": {"error": f"Unexpected error: {str(e)}"},
|
|
379
532
|
}
|
|
380
533
|
|
|
534
|
+
# TODO(agronskiy): refactor the `.`-checking job in all the functions.
|
|
381
535
|
# Determine if this is a job ID or invocation ID
|
|
382
536
|
if "." in id:
|
|
383
537
|
# This is a job ID - kill single job
|
|
@@ -413,7 +567,7 @@ def kill_job_or_invocation(id: str) -> list[dict[str, Any]]:
|
|
|
413
567
|
|
|
414
568
|
|
|
415
569
|
def export_results(
|
|
416
|
-
invocation_ids: Union[str,
|
|
570
|
+
invocation_ids: Union[str, list[str]],
|
|
417
571
|
dest: str = "local",
|
|
418
572
|
config: dict[Any, Any] | None = None,
|
|
419
573
|
) -> dict:
|
|
@@ -442,7 +596,7 @@ def export_results(
|
|
|
442
596
|
if "." in single_id: # job_id
|
|
443
597
|
# Try reading config from artifacts working dir (auto-export on remote node)
|
|
444
598
|
cfg_file = None
|
|
445
|
-
for name in ("
|
|
599
|
+
for name in ("config.yml", "run_config.yml"):
|
|
446
600
|
p = Path(name)
|
|
447
601
|
if p.exists():
|
|
448
602
|
cfg_file = p
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
This module defines data structures and helpers for configuration and type safety in the API layer.
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
-
import
|
|
21
|
+
import pathlib
|
|
22
22
|
import warnings
|
|
23
23
|
from dataclasses import dataclass
|
|
24
24
|
from typing import cast
|
|
@@ -42,33 +42,40 @@ from nemo_evaluator_launcher.common.logging_utils import logger
|
|
|
42
42
|
class RunConfig(DictConfig):
|
|
43
43
|
@staticmethod
|
|
44
44
|
def from_hydra(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
dict_overrides: dict = {},
|
|
45
|
+
config: str | None = None,
|
|
46
|
+
hydra_overrides: list[str] | None = None,
|
|
47
|
+
dict_overrides: dict | None = None,
|
|
49
48
|
) -> "RunConfig":
|
|
50
49
|
"""Load configuration from Hydra and merge with dictionary overrides.
|
|
51
50
|
|
|
52
51
|
Args:
|
|
53
|
-
|
|
52
|
+
config: Optional full path to a config file (e.g. /path/to/my_config.yaml).
|
|
53
|
+
If omitted, loads the internal default config from
|
|
54
|
+
`nemo_evaluator_launcher.configs`.
|
|
54
55
|
hydra_overrides: List of Hydra command-line style overrides.
|
|
55
56
|
dict_overrides: Dictionary of configuration overrides to merge.
|
|
56
|
-
config_dir: Optional path to user config directory. If provided, Hydra will
|
|
57
|
-
search in this directory first, then fall back to internal configs.
|
|
58
57
|
|
|
59
58
|
Returns:
|
|
60
59
|
RunConfig: Merged configuration object.
|
|
61
60
|
"""
|
|
62
|
-
overrides = hydra_overrides
|
|
61
|
+
overrides = list(hydra_overrides or [])
|
|
62
|
+
dict_overrides = dict_overrides or {}
|
|
63
|
+
|
|
64
|
+
resolved_config_path: str | None = None
|
|
65
|
+
config_name = "default"
|
|
66
|
+
|
|
63
67
|
# Check if a GlobalHydra instance is already initialized and clear it
|
|
64
68
|
if GlobalHydra.instance().is_initialized():
|
|
65
69
|
GlobalHydra.instance().clear()
|
|
66
70
|
|
|
67
|
-
if
|
|
68
|
-
|
|
69
|
-
if not
|
|
70
|
-
|
|
71
|
+
if config:
|
|
72
|
+
config_path = pathlib.Path(config).expanduser()
|
|
73
|
+
if not config_path.is_absolute():
|
|
74
|
+
config_path = (pathlib.Path.cwd() / config_path).resolve()
|
|
75
|
+
resolved_config_path = str(config_path)
|
|
71
76
|
|
|
77
|
+
config_dir = str(config_path.parent)
|
|
78
|
+
config_name = str(config_path.stem)
|
|
72
79
|
hydra.initialize_config_dir(
|
|
73
80
|
config_dir=config_dir,
|
|
74
81
|
version_base=None,
|
|
@@ -90,7 +97,7 @@ class RunConfig(DictConfig):
|
|
|
90
97
|
logger.debug(
|
|
91
98
|
"Loaded run config from hydra",
|
|
92
99
|
config_name=config_name,
|
|
93
|
-
|
|
100
|
+
config=resolved_config_path,
|
|
94
101
|
overrides=hydra_overrides,
|
|
95
102
|
dict_overrides=dict_overrides,
|
|
96
103
|
result=cfg,
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
"""Logs command for streaming logs from evaluation jobs."""
|
|
17
|
+
|
|
18
|
+
import sys
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import Callable, Dict
|
|
21
|
+
|
|
22
|
+
from simple_parsing import field
|
|
23
|
+
|
|
24
|
+
import nemo_evaluator_launcher.common.printing_utils as pu
|
|
25
|
+
from nemo_evaluator_launcher.api.functional import stream_logs
|
|
26
|
+
from nemo_evaluator_launcher.common.execdb import ExecutionDB
|
|
27
|
+
from nemo_evaluator_launcher.common.logging_utils import logger
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class Cmd:
|
|
32
|
+
"""Logs command configuration."""
|
|
33
|
+
|
|
34
|
+
ids: list[str] = field(
|
|
35
|
+
default_factory=list,
|
|
36
|
+
positional=True,
|
|
37
|
+
help="Invocation IDs or job IDs (e.g., '15b9f667' or '15b9f667.0'). Multiple IDs can be provided.",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def execute(self) -> None:
|
|
41
|
+
"""Execute the logs command to stream logs from jobs."""
|
|
42
|
+
if not self.ids:
|
|
43
|
+
logger.error("At least one ID is required")
|
|
44
|
+
sys.exit(1)
|
|
45
|
+
|
|
46
|
+
db = ExecutionDB()
|
|
47
|
+
|
|
48
|
+
# Validate all IDs exist
|
|
49
|
+
all_job_ids = []
|
|
50
|
+
for id_or_prefix in self.ids:
|
|
51
|
+
if "." in id_or_prefix:
|
|
52
|
+
# This is a job ID - get single job
|
|
53
|
+
job_data = db.get_job(id_or_prefix)
|
|
54
|
+
if job_data is None:
|
|
55
|
+
logger.error(f"Job {id_or_prefix} not found")
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
all_job_ids.append(id_or_prefix)
|
|
58
|
+
else:
|
|
59
|
+
# This is an invocation ID - get all jobs
|
|
60
|
+
jobs = db.get_jobs(id_or_prefix)
|
|
61
|
+
if not jobs:
|
|
62
|
+
logger.error(f"Invocation {id_or_prefix} not found")
|
|
63
|
+
sys.exit(1)
|
|
64
|
+
all_job_ids.extend(jobs.keys())
|
|
65
|
+
|
|
66
|
+
# Build color mapping for job IDs
|
|
67
|
+
colors = [pu.red, pu.green, pu.yellow, pu.magenta, pu.cyan]
|
|
68
|
+
job_colors: Dict[str, Callable[[str], str]] = {}
|
|
69
|
+
color_index = 0
|
|
70
|
+
|
|
71
|
+
for job_id in all_job_ids:
|
|
72
|
+
job_colors[job_id] = colors[color_index % len(colors)]
|
|
73
|
+
color_index += 1
|
|
74
|
+
|
|
75
|
+
# Stream logs from executor
|
|
76
|
+
try:
|
|
77
|
+
log_stream = stream_logs(self.ids)
|
|
78
|
+
for job_id, task_name, log_line in log_stream:
|
|
79
|
+
# Extract short prefix: first 6 chars of invocation ID + job number
|
|
80
|
+
if "." in job_id:
|
|
81
|
+
inv_id, job_num = job_id.split(".", 1)
|
|
82
|
+
short_prefix = f"{inv_id[:6]}.{job_num}"
|
|
83
|
+
else:
|
|
84
|
+
short_prefix = job_id[:6]
|
|
85
|
+
prefix = f"{short_prefix}:"
|
|
86
|
+
color_func = job_colors.get(job_id, pu.grey)
|
|
87
|
+
if log_line:
|
|
88
|
+
print(f"{color_func(prefix)} {log_line}")
|
|
89
|
+
else:
|
|
90
|
+
# Print empty lines without prefix
|
|
91
|
+
print()
|
|
92
|
+
|
|
93
|
+
except ValueError:
|
|
94
|
+
# Handle case where executor doesn't support streaming
|
|
95
|
+
# Warning already logged by BaseExecutor.stream_logs
|
|
96
|
+
pass
|
|
97
|
+
except KeyboardInterrupt:
|
|
98
|
+
# Clean exit on Ctrl+C
|
|
99
|
+
pass
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logger.error(f"Error streaming logs: {e}")
|
|
102
|
+
sys.exit(1)
|