nemo-evaluator-launcher 0.1.0rc6__py3-none-any.whl → 0.1.41__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.
- nemo_evaluator_launcher/__init__.py +15 -1
- nemo_evaluator_launcher/api/functional.py +188 -27
- nemo_evaluator_launcher/api/types.py +9 -0
- nemo_evaluator_launcher/cli/export.py +131 -12
- nemo_evaluator_launcher/cli/info.py +477 -82
- nemo_evaluator_launcher/cli/kill.py +5 -3
- nemo_evaluator_launcher/cli/logs.py +102 -0
- nemo_evaluator_launcher/cli/ls_runs.py +31 -10
- nemo_evaluator_launcher/cli/ls_tasks.py +105 -3
- nemo_evaluator_launcher/cli/main.py +101 -5
- nemo_evaluator_launcher/cli/run.py +153 -30
- nemo_evaluator_launcher/cli/status.py +49 -5
- nemo_evaluator_launcher/cli/version.py +26 -23
- nemo_evaluator_launcher/common/execdb.py +121 -27
- nemo_evaluator_launcher/common/helpers.py +213 -33
- nemo_evaluator_launcher/common/logging_utils.py +16 -5
- nemo_evaluator_launcher/common/printing_utils.py +100 -0
- nemo_evaluator_launcher/configs/deployment/generic.yaml +33 -0
- nemo_evaluator_launcher/configs/deployment/sglang.yaml +4 -2
- nemo_evaluator_launcher/configs/deployment/trtllm.yaml +23 -0
- nemo_evaluator_launcher/configs/deployment/vllm.yaml +2 -2
- nemo_evaluator_launcher/configs/execution/local.yaml +2 -0
- nemo_evaluator_launcher/configs/execution/slurm/default.yaml +19 -4
- nemo_evaluator_launcher/executors/base.py +54 -1
- nemo_evaluator_launcher/executors/lepton/deployment_helpers.py +60 -5
- nemo_evaluator_launcher/executors/lepton/executor.py +240 -101
- nemo_evaluator_launcher/executors/lepton/job_helpers.py +15 -11
- nemo_evaluator_launcher/executors/local/executor.py +492 -56
- nemo_evaluator_launcher/executors/local/run.template.sh +76 -9
- nemo_evaluator_launcher/executors/slurm/executor.py +571 -98
- nemo_evaluator_launcher/executors/slurm/proxy.cfg.template +26 -0
- nemo_evaluator_launcher/exporters/base.py +9 -0
- nemo_evaluator_launcher/exporters/gsheets.py +27 -9
- nemo_evaluator_launcher/exporters/local.py +30 -16
- nemo_evaluator_launcher/exporters/mlflow.py +245 -74
- nemo_evaluator_launcher/exporters/utils.py +139 -184
- nemo_evaluator_launcher/exporters/wandb.py +157 -43
- nemo_evaluator_launcher/package_info.py +6 -3
- nemo_evaluator_launcher/resources/mapping.toml +56 -15
- nemo_evaluator_launcher-0.1.41.dist-info/METADATA +494 -0
- nemo_evaluator_launcher-0.1.41.dist-info/RECORD +62 -0
- {nemo_evaluator_launcher-0.1.0rc6.dist-info → nemo_evaluator_launcher-0.1.41.dist-info}/entry_points.txt +1 -0
- nemo_evaluator_launcher-0.1.0rc6.dist-info/METADATA +0 -35
- nemo_evaluator_launcher-0.1.0rc6.dist-info/RECORD +0 -57
- {nemo_evaluator_launcher-0.1.0rc6.dist-info → nemo_evaluator_launcher-0.1.41.dist-info}/WHEEL +0 -0
- {nemo_evaluator_launcher-0.1.0rc6.dist-info → nemo_evaluator_launcher-0.1.41.dist-info}/licenses/LICENSE +0 -0
- {nemo_evaluator_launcher-0.1.0rc6.dist-info → nemo_evaluator_launcher-0.1.41.dist-info}/top_level.txt +0 -0
|
@@ -16,29 +16,90 @@
|
|
|
16
16
|
import base64
|
|
17
17
|
import copy
|
|
18
18
|
import datetime
|
|
19
|
+
from dataclasses import dataclass
|
|
19
20
|
from typing import Optional
|
|
20
21
|
|
|
21
22
|
import yaml
|
|
22
23
|
from omegaconf import DictConfig, OmegaConf
|
|
23
24
|
|
|
25
|
+
from nemo_evaluator_launcher.cli.version import get_versions
|
|
24
26
|
from nemo_evaluator_launcher.common.logging_utils import logger
|
|
25
27
|
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class CmdAndReadableComment:
|
|
31
|
+
"""See the comment to `_yaml_to_echo_command`."""
|
|
32
|
+
|
|
33
|
+
# Actual command. Might include hard-to-debug elements such as base64-encoded
|
|
34
|
+
# configs.
|
|
35
|
+
cmd: str
|
|
36
|
+
# A debuggale readable comment that can be passed along for accompanying
|
|
37
|
+
# the actual command
|
|
38
|
+
debug: str
|
|
39
|
+
# Whether the content might be potentially unsafe. This is a flag useful for
|
|
40
|
+
# downstream callers who want to raise exceptions e.g. when a script was
|
|
41
|
+
# saved that would execute this command.
|
|
42
|
+
is_potentially_unsafe: bool = False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _str_to_echo_command(str_to_save: str, filename: str) -> CmdAndReadableComment:
|
|
46
|
+
"""Create a safe (see below) echo command saving a string to file.
|
|
47
|
+
|
|
48
|
+
Safety in this context means the ability to pass such echo command through the
|
|
49
|
+
`bash -c '...'` boundaries for example.
|
|
50
|
+
|
|
51
|
+
Naturally, enconding with base64 creates debuggability issues. For that, the second
|
|
52
|
+
output of the function is the string with bash comment signs prepended.
|
|
53
|
+
"""
|
|
54
|
+
str_to_save_b64 = base64.b64encode(str_to_save.encode("utf-8")).decode("utf-8")
|
|
55
|
+
debug_str = "\n".join(
|
|
56
|
+
[f"# Contents of {filename}"] + ["# " + s for s in str_to_save.splitlines()]
|
|
57
|
+
)
|
|
58
|
+
return CmdAndReadableComment(
|
|
59
|
+
cmd=f'echo "{str_to_save_b64}" | base64 -d > {filename}', debug=debug_str
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _set_nested_optionally_overriding(
|
|
64
|
+
d: dict, keys: list[str], val: object, *, override_if_exists: bool = False
|
|
65
|
+
):
|
|
66
|
+
"""Sets d[...keys....] = value, creating keys all the way"""
|
|
67
|
+
temp = d
|
|
68
|
+
for key in keys[:-1]:
|
|
69
|
+
temp = temp.setdefault(key, {})
|
|
70
|
+
if override_if_exists or keys[-1] not in temp:
|
|
71
|
+
temp[keys[-1]] = val
|
|
30
72
|
|
|
31
73
|
|
|
32
74
|
def get_eval_factory_config(
|
|
33
|
-
cfg: DictConfig,
|
|
75
|
+
cfg: DictConfig,
|
|
76
|
+
user_task_config: DictConfig,
|
|
34
77
|
) -> dict:
|
|
35
78
|
"""Extract config fields for eval factory.
|
|
36
79
|
|
|
37
80
|
This function extracts the config field similar to how overrides are handled.
|
|
81
|
+
|
|
82
|
+
Overrides will start to be deprecated (or not, but at least a warning will be logged).
|
|
38
83
|
"""
|
|
84
|
+
|
|
85
|
+
if cfg.evaluation.get("overrides") or user_task_config.get("overrides"):
|
|
86
|
+
# TODO(agronskiy): start removing overrides, test `test_start_deprecating_overrides`
|
|
87
|
+
# will start failing soon.
|
|
88
|
+
logger.warning(
|
|
89
|
+
"We are deprecating using old-style dot-delimited overrides "
|
|
90
|
+
"in favour of `nemo_evaluator_config` field. Please check "
|
|
91
|
+
"the documentation."
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
logger.debug("Getting nemo evaluator merged config")
|
|
39
95
|
# Extract config fields similar to overrides - convert to basic Python types first
|
|
40
|
-
|
|
41
|
-
|
|
96
|
+
# Support both new and old format for backward compatibility
|
|
97
|
+
cfg_config = cfg.evaluation.get("nemo_evaluator_config") or cfg.evaluation.get(
|
|
98
|
+
"config", {}
|
|
99
|
+
)
|
|
100
|
+
user_config = user_task_config.get("nemo_evaluator_config") or user_task_config.get(
|
|
101
|
+
"config", {}
|
|
102
|
+
)
|
|
42
103
|
|
|
43
104
|
# Convert OmegaConf objects to basic Python types
|
|
44
105
|
if cfg_config:
|
|
@@ -47,17 +108,115 @@ def get_eval_factory_config(
|
|
|
47
108
|
user_config = OmegaConf.to_container(user_config, resolve=True)
|
|
48
109
|
|
|
49
110
|
# Merge the configs
|
|
50
|
-
|
|
51
|
-
|
|
111
|
+
merged_nemo_evaluator_config: dict = OmegaConf.to_container(
|
|
112
|
+
OmegaConf.merge(cfg_config, user_config)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
logger.debug(
|
|
116
|
+
"Merged nemo evaluator config, not final",
|
|
117
|
+
source_global_cfg=cfg_config,
|
|
118
|
+
source_task_config=user_config,
|
|
119
|
+
result=merged_nemo_evaluator_config,
|
|
120
|
+
)
|
|
52
121
|
|
|
53
|
-
return
|
|
122
|
+
return merged_nemo_evaluator_config
|
|
54
123
|
|
|
55
124
|
|
|
56
125
|
def get_eval_factory_command(
|
|
57
|
-
cfg: DictConfig,
|
|
58
|
-
|
|
59
|
-
|
|
126
|
+
cfg: DictConfig,
|
|
127
|
+
user_task_config: DictConfig,
|
|
128
|
+
task_definition: dict,
|
|
129
|
+
) -> CmdAndReadableComment:
|
|
130
|
+
# This gets the eval_factory_config merged from both top-level and task-level.
|
|
131
|
+
merged_nemo_evaluator_config = get_eval_factory_config(
|
|
132
|
+
cfg,
|
|
133
|
+
user_task_config,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# We now prepare the config to be passed to `nemo-evaluator` command.
|
|
137
|
+
_set_nested_optionally_overriding(
|
|
138
|
+
merged_nemo_evaluator_config,
|
|
139
|
+
["target", "api_endpoint", "url"],
|
|
140
|
+
get_endpoint_url(
|
|
141
|
+
cfg,
|
|
142
|
+
merged_nemo_evaluator_config=merged_nemo_evaluator_config,
|
|
143
|
+
endpoint_type=task_definition["endpoint_type"],
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
_set_nested_optionally_overriding(
|
|
147
|
+
merged_nemo_evaluator_config,
|
|
148
|
+
["target", "api_endpoint", "model_id"],
|
|
149
|
+
get_served_model_name(cfg),
|
|
150
|
+
)
|
|
151
|
+
_set_nested_optionally_overriding(
|
|
152
|
+
merged_nemo_evaluator_config,
|
|
153
|
+
["target", "api_endpoint", "type"],
|
|
154
|
+
task_definition["endpoint_type"],
|
|
155
|
+
)
|
|
156
|
+
_set_nested_optionally_overriding(
|
|
157
|
+
merged_nemo_evaluator_config,
|
|
158
|
+
["config", "type"],
|
|
159
|
+
task_definition["task"],
|
|
160
|
+
)
|
|
161
|
+
_set_nested_optionally_overriding(
|
|
162
|
+
merged_nemo_evaluator_config,
|
|
163
|
+
["config", "output_dir"],
|
|
164
|
+
"/results",
|
|
165
|
+
)
|
|
166
|
+
_set_nested_optionally_overriding(
|
|
167
|
+
merged_nemo_evaluator_config,
|
|
168
|
+
["target", "api_endpoint", "api_key"],
|
|
169
|
+
"API_KEY",
|
|
170
|
+
)
|
|
171
|
+
_set_nested_optionally_overriding(
|
|
172
|
+
merged_nemo_evaluator_config,
|
|
173
|
+
[
|
|
174
|
+
"metadata",
|
|
175
|
+
"launcher_resolved_config",
|
|
176
|
+
],
|
|
177
|
+
OmegaConf.to_container(cfg, resolve=True),
|
|
178
|
+
)
|
|
179
|
+
_set_nested_optionally_overriding(
|
|
180
|
+
merged_nemo_evaluator_config,
|
|
181
|
+
["metadata", "versioning"],
|
|
182
|
+
get_versions(),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Now get the pre_cmd either from `evaluation.pre_cmd` or task-level pre_cmd. Note the
|
|
186
|
+
# order -- task level wins.
|
|
187
|
+
pre_cmd: str = (
|
|
188
|
+
user_task_config.get("pre_cmd") or cfg.evaluation.get("pre_cmd") or ""
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
is_potentially_unsafe = False
|
|
192
|
+
if pre_cmd:
|
|
193
|
+
logger.warning(
|
|
194
|
+
"Found non-empty pre_cmd that might be a security risk if executed. "
|
|
195
|
+
"Setting `is_potentially_unsafe` to `True`",
|
|
196
|
+
pre_cmd=pre_cmd,
|
|
197
|
+
)
|
|
198
|
+
is_potentially_unsafe = True
|
|
199
|
+
_set_nested_optionally_overriding(
|
|
200
|
+
merged_nemo_evaluator_config,
|
|
201
|
+
["metadata", "pre_cmd"],
|
|
202
|
+
pre_cmd,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
create_pre_script_cmd = _str_to_echo_command(pre_cmd, filename="pre_cmd.sh")
|
|
206
|
+
|
|
207
|
+
create_yaml_cmd = _str_to_echo_command(
|
|
208
|
+
yaml.safe_dump(merged_nemo_evaluator_config), "config_ef.yaml"
|
|
209
|
+
)
|
|
60
210
|
|
|
211
|
+
# NOTE: we use `source` to allow tricks like exports etc (if needed) -- it runs in the same
|
|
212
|
+
# shell as the command.
|
|
213
|
+
eval_command = (
|
|
214
|
+
"cmd=$(command -v nemo-evaluator >/dev/null 2>&1 && echo nemo-evaluator || echo eval-factory) "
|
|
215
|
+
+ "&& source pre_cmd.sh "
|
|
216
|
+
+ "&& $cmd run_eval --run_config config_ef.yaml"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# NOTE: see note and test about deprecating that.
|
|
61
220
|
overrides = copy.deepcopy(dict(cfg.evaluation.get("overrides", {})))
|
|
62
221
|
overrides.update(dict(user_task_config.get("overrides", {})))
|
|
63
222
|
# NOTE(dfridman): Temporary fix to make sure that the overrides arg is not split into multiple lines.
|
|
@@ -66,32 +225,46 @@ def get_eval_factory_command(
|
|
|
66
225
|
k: (v.strip("\n") if isinstance(v, str) else v) for k, v in overrides.items()
|
|
67
226
|
}
|
|
68
227
|
overrides_str = ",".join([f"{k}={v}" for k, v in overrides.items()])
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
228
|
+
if overrides_str:
|
|
229
|
+
eval_command = f"{eval_command} --overrides {overrides_str}"
|
|
230
|
+
|
|
231
|
+
# We return both the command and the debugging base64-decoded strings, useful
|
|
232
|
+
# for exposing when building scripts.
|
|
233
|
+
return CmdAndReadableComment(
|
|
234
|
+
cmd=create_pre_script_cmd.cmd
|
|
235
|
+
+ " && "
|
|
236
|
+
+ create_yaml_cmd.cmd
|
|
237
|
+
+ " && "
|
|
238
|
+
+ eval_command,
|
|
239
|
+
debug=create_pre_script_cmd.debug + "\n\n" + create_yaml_cmd.debug,
|
|
240
|
+
is_potentially_unsafe=is_potentially_unsafe,
|
|
77
241
|
)
|
|
78
|
-
nv_eval_command = f"""nv_eval run_eval --model_id {model_id} --model_type {model_type} --eval_type {eval_type} --model_url {model_url} --api_key_name API_KEY --output_dir /results --run_config config_ef.yaml"""
|
|
79
|
-
|
|
80
|
-
if overrides:
|
|
81
|
-
nv_eval_command = f"{nv_eval_command} --overrides {overrides_str}"
|
|
82
|
-
|
|
83
|
-
return create_file_cmd + " && " + "cat config_ef.yaml && " + nv_eval_command
|
|
84
242
|
|
|
85
243
|
|
|
86
244
|
def get_endpoint_url(
|
|
87
|
-
cfg: DictConfig,
|
|
245
|
+
cfg: DictConfig,
|
|
246
|
+
merged_nemo_evaluator_config: dict,
|
|
247
|
+
endpoint_type: str,
|
|
88
248
|
) -> str:
|
|
89
249
|
def apply_url_override(url: str) -> str:
|
|
90
250
|
"""Apply user URL override if provided."""
|
|
91
|
-
|
|
92
|
-
"
|
|
251
|
+
nemo_evaluator_config_url = (
|
|
252
|
+
merged_nemo_evaluator_config.get("target", {})
|
|
253
|
+
.get("api_endpoint", {})
|
|
254
|
+
.get("url", None)
|
|
93
255
|
)
|
|
94
|
-
|
|
256
|
+
|
|
257
|
+
if nemo_evaluator_config_url:
|
|
258
|
+
return nemo_evaluator_config_url
|
|
259
|
+
|
|
260
|
+
# Being deprecated, see `get_eval_factory_config` message.
|
|
261
|
+
overrides_old_style_url = merged_nemo_evaluator_config.get("overrides", {}).get(
|
|
262
|
+
"target.api_endpoint.url", None
|
|
263
|
+
)
|
|
264
|
+
if overrides_old_style_url:
|
|
265
|
+
return overrides_old_style_url
|
|
266
|
+
|
|
267
|
+
return url
|
|
95
268
|
|
|
96
269
|
if cfg.deployment.type == "none":
|
|
97
270
|
# For deployment: none, use target URL regardless of executor type
|
|
@@ -113,9 +286,16 @@ def get_endpoint_url(
|
|
|
113
286
|
|
|
114
287
|
else:
|
|
115
288
|
# Local executor - use localhost
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
289
|
+
endpoint_uri = cfg.deployment.endpoints[endpoint_type]
|
|
290
|
+
|
|
291
|
+
# Use HAProxy port if multiple_instances is enabled
|
|
292
|
+
if cfg.deployment.get("multiple_instances", False):
|
|
293
|
+
proxy_config = cfg.execution.get("proxy", {}).get("config", {})
|
|
294
|
+
port = proxy_config.get("haproxy_port", 5009)
|
|
295
|
+
else:
|
|
296
|
+
port = cfg.deployment.port
|
|
297
|
+
|
|
298
|
+
endpoint_url = f"http://127.0.0.1:{port}{endpoint_uri}"
|
|
119
299
|
return endpoint_url
|
|
120
300
|
|
|
121
301
|
|
|
@@ -61,8 +61,9 @@ import structlog
|
|
|
61
61
|
# both are unset, default would be used.
|
|
62
62
|
_LOG_LEVEL_ENV_VAR = "NEMO_EVALUATOR_LOG_LEVEL"
|
|
63
63
|
_DEFAULT_LOG_LEVEL = "WARNING"
|
|
64
|
-
|
|
65
|
-
# Keep minimal, broad substrings
|
|
64
|
+
_SENSITIVE_KEY_SUBSTRINGS_NORMALIZED = {
|
|
65
|
+
# Keep minimal, broad substrings
|
|
66
|
+
# NOTE: normalized: lowercased, no spaces/_/-
|
|
66
67
|
"authorization", # covers proxy-authorization, etc.
|
|
67
68
|
"apikey", # covers api_key, api-key, x-api-key, nvidia_api_key, ...
|
|
68
69
|
"accesskey", # covers access_key / access-key
|
|
@@ -73,6 +74,10 @@ _SENSITIVE_KEY_SUBSTRINGS = {
|
|
|
73
74
|
"pwd", # common shorthand
|
|
74
75
|
"passwd", # common variant
|
|
75
76
|
}
|
|
77
|
+
_ALLOWLISTED_KEYS_SUBSTRINGS = {
|
|
78
|
+
# NOTE: non-normalized (for allowlisting we want more control)
|
|
79
|
+
"_tokens", # This likely would allow us to not redact useful stuff like `limit_tokens`, `max_new_tokens`
|
|
80
|
+
}
|
|
76
81
|
|
|
77
82
|
|
|
78
83
|
def _mask(val: object) -> str:
|
|
@@ -91,8 +96,11 @@ def _normalize(name: object) -> str:
|
|
|
91
96
|
|
|
92
97
|
|
|
93
98
|
def _is_sensitive_key(key: object) -> bool:
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
k_norm = _normalize(key)
|
|
100
|
+
k_non_norm = str(key)
|
|
101
|
+
return any(
|
|
102
|
+
substr in k_norm for substr in _SENSITIVE_KEY_SUBSTRINGS_NORMALIZED
|
|
103
|
+
) and not any(substr in k_non_norm for substr in _ALLOWLISTED_KEYS_SUBSTRINGS)
|
|
96
104
|
|
|
97
105
|
|
|
98
106
|
def _redact_mapping(m: dict) -> dict:
|
|
@@ -263,6 +271,9 @@ def _configure_structlog() -> None:
|
|
|
263
271
|
structlog.processors.UnicodeDecoder(),
|
|
264
272
|
]
|
|
265
273
|
|
|
274
|
+
# Check if stderr is a TTY to determine if colors should be enabled
|
|
275
|
+
colors_enabled = sys.stderr.isatty()
|
|
276
|
+
|
|
266
277
|
logging.config.dictConfig(
|
|
267
278
|
{
|
|
268
279
|
"version": 1,
|
|
@@ -273,7 +284,7 @@ def _configure_structlog() -> None:
|
|
|
273
284
|
"()": "structlog.stdlib.ProcessorFormatter",
|
|
274
285
|
"processors": [
|
|
275
286
|
*shared_processors,
|
|
276
|
-
MainConsoleRenderer(colors=
|
|
287
|
+
MainConsoleRenderer(colors=colors_enabled),
|
|
277
288
|
],
|
|
278
289
|
},
|
|
279
290
|
# Formatter for plain file output
|
|
@@ -0,0 +1,100 @@
|
|
|
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
|
+
"""Printing utils for more structured or visually appealing prints.
|
|
17
|
+
|
|
18
|
+
NOTE: use printing only for main application output that matters. For logging,
|
|
19
|
+
see `logging_utils.py`.
|
|
20
|
+
|
|
21
|
+
USAGE:
|
|
22
|
+
```
|
|
23
|
+
from nemo_evaluator_launcher.common.printing_utils import red, bold
|
|
24
|
+
print(bold(red("some red bold")))
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import os
|
|
31
|
+
import sys
|
|
32
|
+
|
|
33
|
+
# If this env var is set, it will override a more standard "LOG_LEVEL". If
|
|
34
|
+
# both are unset, default would be used.
|
|
35
|
+
_DISABLE_COLOR_ENV_VAR = "NEMO_EVALUATOR_DISABLE_COLOR"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _is_color_disabled():
|
|
39
|
+
# Check environment variable first
|
|
40
|
+
env_var = os.environ.get(_DISABLE_COLOR_ENV_VAR, "0").lower()
|
|
41
|
+
|
|
42
|
+
if "1" in env_var or "yes" in env_var or "y" in env_var or "true" in env_var:
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
# If not explicitly disabled, check if stdout is a TTY
|
|
46
|
+
# Colors are disabled if output is not a TTY
|
|
47
|
+
if not sys.stdout.isatty():
|
|
48
|
+
return True
|
|
49
|
+
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
_CODES: dict[str, str] = dict(
|
|
54
|
+
green="\033[32m",
|
|
55
|
+
red="\033[31m",
|
|
56
|
+
red_bg="\033[41m", # red background
|
|
57
|
+
cyan="\033[36m",
|
|
58
|
+
yellow="\033[33m",
|
|
59
|
+
magenta="\033[35m",
|
|
60
|
+
grey="\033[90m",
|
|
61
|
+
bold="\033[1m",
|
|
62
|
+
reset="\033[0m",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# If the colors are disabled, we null-out all the codes.
|
|
66
|
+
if _is_color_disabled():
|
|
67
|
+
for c in _CODES.keys():
|
|
68
|
+
_CODES[c] = ""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def green(s: str) -> str:
|
|
72
|
+
return _CODES["green"] + s + _CODES["reset"]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def red(s: str) -> str:
|
|
76
|
+
return _CODES["red"] + s + _CODES["reset"]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def red_bg(s: str) -> str:
|
|
80
|
+
return _CODES["red_bg"] + s + _CODES["reset"]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def cyan(s: str) -> str:
|
|
84
|
+
return _CODES["cyan"] + s + _CODES["reset"]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def yellow(s: str) -> str:
|
|
88
|
+
return _CODES["yellow"] + s + _CODES["reset"]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def magenta(s: str) -> str:
|
|
92
|
+
return _CODES["magenta"] + s + _CODES["reset"]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def grey(s: str) -> str:
|
|
96
|
+
return _CODES["grey"] + s + _CODES["reset"]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def bold(s: str) -> str:
|
|
100
|
+
return _CODES["bold"] + s + _CODES["reset"]
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
# Generic server deployment configuration template
|
|
17
|
+
#
|
|
18
|
+
type: generic
|
|
19
|
+
image: ??? # Docker image to use for deployment
|
|
20
|
+
command: ??? # Command to run the server
|
|
21
|
+
|
|
22
|
+
# Server configuration
|
|
23
|
+
port: 8000
|
|
24
|
+
served_model_name: ??? # Name of the served model (used in command templates and evaluation)
|
|
25
|
+
extra_args: "" # Additional command line arguments
|
|
26
|
+
env_vars: {} # Environment variables as {name: value} dict
|
|
27
|
+
checkpoint_path: null # Path to model checkpoint
|
|
28
|
+
|
|
29
|
+
# API endpoints (customize based on your server)
|
|
30
|
+
endpoints:
|
|
31
|
+
chat: /v1/chat/completions
|
|
32
|
+
completions: /v1/completions
|
|
33
|
+
health: /health
|
|
@@ -19,6 +19,7 @@ checkpoint_path: ???
|
|
|
19
19
|
served_model_name: ???
|
|
20
20
|
port: 8000
|
|
21
21
|
tensor_parallel_size: 8
|
|
22
|
+
pipeline_parallel_size: 1
|
|
22
23
|
data_parallel_size: 1
|
|
23
24
|
extra_args: ""
|
|
24
25
|
env_vars: {} # {name: value} dict
|
|
@@ -33,6 +34,7 @@ command: python3 -m sglang.launch_server
|
|
|
33
34
|
--host 0.0.0.0
|
|
34
35
|
--port ${deployment.port}
|
|
35
36
|
--served-model-name ${deployment.served_model_name}
|
|
36
|
-
--tp ${deployment.tensor_parallel_size}
|
|
37
|
-
--dp ${deployment.data_parallel_size}
|
|
37
|
+
--tp-size ${deployment.tensor_parallel_size}
|
|
38
|
+
--dp-size ${deployment.data_parallel_size}
|
|
39
|
+
--pp-size ${deployment.pipeline_parallel_size}
|
|
38
40
|
${deployment.extra_args}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
type: trtllm
|
|
2
|
+
image: nvcr.io/nvidia/tensorrt-llm/release:1.0.0
|
|
3
|
+
checkpoint_path: ???
|
|
4
|
+
served_model_name: ???
|
|
5
|
+
port: 8000
|
|
6
|
+
tensor_parallel_size: 8
|
|
7
|
+
pipeline_parallel_size: 1
|
|
8
|
+
extra_args: ""
|
|
9
|
+
|
|
10
|
+
endpoints:
|
|
11
|
+
chat: /v1/chat/completions
|
|
12
|
+
completions: /v1/completions
|
|
13
|
+
health: /health
|
|
14
|
+
|
|
15
|
+
command: mpirun --allow-run-as-root --oversubscribe
|
|
16
|
+
trtllm-serve serve /checkpoint
|
|
17
|
+
--tp_size=${deployment.tensor_parallel_size}
|
|
18
|
+
--pp_size=${deployment.pipeline_parallel_size}
|
|
19
|
+
--host 0.0.0.0
|
|
20
|
+
--port ${deployment.port}
|
|
21
|
+
--backend pytorch
|
|
22
|
+
--trust_remote_code
|
|
23
|
+
${deployment.extra_args}
|
|
@@ -21,6 +21,7 @@ port: 8000
|
|
|
21
21
|
tensor_parallel_size: 8
|
|
22
22
|
pipeline_parallel_size: 1
|
|
23
23
|
data_parallel_size: 1
|
|
24
|
+
gpu_memory_utilization: 0.95
|
|
24
25
|
extra_args: ""
|
|
25
26
|
env_vars: {} # {name: value} dict
|
|
26
27
|
|
|
@@ -36,6 +37,5 @@ command: vllm serve ${oc.select:deployment.hf_model_handle,/checkpoint}
|
|
|
36
37
|
--port ${deployment.port}
|
|
37
38
|
--trust-remote-code
|
|
38
39
|
--served-model-name ${deployment.served_model_name}
|
|
39
|
-
--
|
|
40
|
-
--gpu-memory-utilization 0.95
|
|
40
|
+
--gpu-memory-utilization ${deployment.gpu_memory_utilization}
|
|
41
41
|
${deployment.extra_args}
|
|
@@ -14,16 +14,23 @@
|
|
|
14
14
|
# limitations under the License.
|
|
15
15
|
#
|
|
16
16
|
# Each slurm cluster has its own flavour, below we provide some defaults that might meet one's needs.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
type: slurm # Executor is chosen based on this field
|
|
18
|
+
hostname: ??? # SLURM headnode (login) hostname (required)
|
|
19
|
+
username: ${oc.env:USER} # Defaults to $USER env var
|
|
20
|
+
account: ??? # SLURM account allocation (required)
|
|
21
|
+
output_dir: ??? # Absolute path accessible on compute nodes (required)
|
|
20
22
|
partition: batch
|
|
21
23
|
num_nodes: 1
|
|
22
24
|
ntasks_per_node: 1
|
|
23
25
|
gres: gpu:8
|
|
24
26
|
walltime: 01:00:00
|
|
25
27
|
subproject: nemo-evaluator-launcher
|
|
26
|
-
|
|
28
|
+
sbatch_comment: null # Optional comment for SLURM job (translates to #SBATCH --comment='...')
|
|
29
|
+
|
|
30
|
+
# Deployment-specific SLURM configuration
|
|
31
|
+
deployment:
|
|
32
|
+
n_tasks: 1 # Number of tasks for deployment srun (default: 1, for multi-instance set to num_nodes)
|
|
33
|
+
|
|
27
34
|
env_vars:
|
|
28
35
|
deployment: {}
|
|
29
36
|
evaluation: {}
|
|
@@ -31,3 +38,11 @@ mounts:
|
|
|
31
38
|
deployment: {}
|
|
32
39
|
evaluation: {}
|
|
33
40
|
mount_home: true
|
|
41
|
+
|
|
42
|
+
proxy:
|
|
43
|
+
type: haproxy
|
|
44
|
+
image: haproxy:latest
|
|
45
|
+
config:
|
|
46
|
+
haproxy_port: 5009
|
|
47
|
+
health_check_path: /health
|
|
48
|
+
health_check_status: 200
|
|
@@ -21,10 +21,12 @@ Defines the abstract interface for all executor implementations and common statu
|
|
|
21
21
|
from abc import ABC, abstractmethod
|
|
22
22
|
from dataclasses import dataclass
|
|
23
23
|
from enum import Enum
|
|
24
|
-
from typing import Any, Optional
|
|
24
|
+
from typing import Any, Iterator, Optional, Tuple
|
|
25
25
|
|
|
26
26
|
from omegaconf import DictConfig
|
|
27
27
|
|
|
28
|
+
from nemo_evaluator_launcher.common.logging_utils import logger
|
|
29
|
+
|
|
28
30
|
|
|
29
31
|
class ExecutionState(Enum):
|
|
30
32
|
"""Enumeration of possible execution states."""
|
|
@@ -95,3 +97,54 @@ class BaseExecutor(ABC):
|
|
|
95
97
|
NotImplementedError: If not implemented by a subclass.
|
|
96
98
|
"""
|
|
97
99
|
raise NotImplementedError("Subclasses must implement this method")
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def get_kill_failure_message(
|
|
103
|
+
job_id: str, container_or_id: str, status: Optional[ExecutionState] = None
|
|
104
|
+
) -> str:
|
|
105
|
+
"""Generate an informative error message when kill fails based on job status.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
job_id: The job ID that failed to kill.
|
|
109
|
+
container_or_id: Container name, SLURM job ID, or other identifier.
|
|
110
|
+
status: Optional execution state of the job.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
str: An informative error message with job status context.
|
|
114
|
+
"""
|
|
115
|
+
if status == ExecutionState.SUCCESS:
|
|
116
|
+
return f"Could not find or kill job {job_id} ({container_or_id}) - job already completed successfully"
|
|
117
|
+
elif status == ExecutionState.FAILED:
|
|
118
|
+
return f"Could not find or kill job {job_id} ({container_or_id}) - job already failed"
|
|
119
|
+
elif status == ExecutionState.KILLED:
|
|
120
|
+
return f"Could not find or kill job {job_id} ({container_or_id}) - job was already killed"
|
|
121
|
+
# Generic error message
|
|
122
|
+
return f"Could not find or kill job {job_id} ({container_or_id})"
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def stream_logs(
|
|
126
|
+
id: str, executor_name: Optional[str] = None
|
|
127
|
+
) -> Iterator[Tuple[str, str, str]]:
|
|
128
|
+
"""Stream logs from a job or invocation group.
|
|
129
|
+
|
|
130
|
+
This is an optional method that executors can implement to provide log streaming.
|
|
131
|
+
If not implemented, it will log a warning and raise NotImplementedError.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
id: Unique job identifier or invocation identifier.
|
|
135
|
+
executor_name: Optional executor name for warning messages. If not provided,
|
|
136
|
+
will attempt to infer from the calling context.
|
|
137
|
+
|
|
138
|
+
Yields:
|
|
139
|
+
Tuple[str, str, str]: Tuples of (job_id, task_name, log_line) for each log line.
|
|
140
|
+
Empty lines are yielded as empty strings.
|
|
141
|
+
|
|
142
|
+
Raises:
|
|
143
|
+
NotImplementedError: If the executor does not support log streaming.
|
|
144
|
+
"""
|
|
145
|
+
executor_display_name = executor_name or "this executor"
|
|
146
|
+
logger.warning(
|
|
147
|
+
f"Log streaming is not yet implemented for executor '{executor_display_name}'. "
|
|
148
|
+
"Only 'local' executor currently supports log streaming."
|
|
149
|
+
)
|
|
150
|
+
raise NotImplementedError("This executor does not support log streaming")
|