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.
Files changed (47) hide show
  1. nemo_evaluator_launcher/__init__.py +15 -1
  2. nemo_evaluator_launcher/api/functional.py +188 -27
  3. nemo_evaluator_launcher/api/types.py +9 -0
  4. nemo_evaluator_launcher/cli/export.py +131 -12
  5. nemo_evaluator_launcher/cli/info.py +477 -82
  6. nemo_evaluator_launcher/cli/kill.py +5 -3
  7. nemo_evaluator_launcher/cli/logs.py +102 -0
  8. nemo_evaluator_launcher/cli/ls_runs.py +31 -10
  9. nemo_evaluator_launcher/cli/ls_tasks.py +105 -3
  10. nemo_evaluator_launcher/cli/main.py +101 -5
  11. nemo_evaluator_launcher/cli/run.py +153 -30
  12. nemo_evaluator_launcher/cli/status.py +49 -5
  13. nemo_evaluator_launcher/cli/version.py +26 -23
  14. nemo_evaluator_launcher/common/execdb.py +121 -27
  15. nemo_evaluator_launcher/common/helpers.py +213 -33
  16. nemo_evaluator_launcher/common/logging_utils.py +16 -5
  17. nemo_evaluator_launcher/common/printing_utils.py +100 -0
  18. nemo_evaluator_launcher/configs/deployment/generic.yaml +33 -0
  19. nemo_evaluator_launcher/configs/deployment/sglang.yaml +4 -2
  20. nemo_evaluator_launcher/configs/deployment/trtllm.yaml +23 -0
  21. nemo_evaluator_launcher/configs/deployment/vllm.yaml +2 -2
  22. nemo_evaluator_launcher/configs/execution/local.yaml +2 -0
  23. nemo_evaluator_launcher/configs/execution/slurm/default.yaml +19 -4
  24. nemo_evaluator_launcher/executors/base.py +54 -1
  25. nemo_evaluator_launcher/executors/lepton/deployment_helpers.py +60 -5
  26. nemo_evaluator_launcher/executors/lepton/executor.py +240 -101
  27. nemo_evaluator_launcher/executors/lepton/job_helpers.py +15 -11
  28. nemo_evaluator_launcher/executors/local/executor.py +492 -56
  29. nemo_evaluator_launcher/executors/local/run.template.sh +76 -9
  30. nemo_evaluator_launcher/executors/slurm/executor.py +571 -98
  31. nemo_evaluator_launcher/executors/slurm/proxy.cfg.template +26 -0
  32. nemo_evaluator_launcher/exporters/base.py +9 -0
  33. nemo_evaluator_launcher/exporters/gsheets.py +27 -9
  34. nemo_evaluator_launcher/exporters/local.py +30 -16
  35. nemo_evaluator_launcher/exporters/mlflow.py +245 -74
  36. nemo_evaluator_launcher/exporters/utils.py +139 -184
  37. nemo_evaluator_launcher/exporters/wandb.py +157 -43
  38. nemo_evaluator_launcher/package_info.py +6 -3
  39. nemo_evaluator_launcher/resources/mapping.toml +56 -15
  40. nemo_evaluator_launcher-0.1.41.dist-info/METADATA +494 -0
  41. nemo_evaluator_launcher-0.1.41.dist-info/RECORD +62 -0
  42. {nemo_evaluator_launcher-0.1.0rc6.dist-info → nemo_evaluator_launcher-0.1.41.dist-info}/entry_points.txt +1 -0
  43. nemo_evaluator_launcher-0.1.0rc6.dist-info/METADATA +0 -35
  44. nemo_evaluator_launcher-0.1.0rc6.dist-info/RECORD +0 -57
  45. {nemo_evaluator_launcher-0.1.0rc6.dist-info → nemo_evaluator_launcher-0.1.41.dist-info}/WHEEL +0 -0
  46. {nemo_evaluator_launcher-0.1.0rc6.dist-info → nemo_evaluator_launcher-0.1.41.dist-info}/licenses/LICENSE +0 -0
  47. {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
- def _yaml_to_echo_command(yaml_str: str, filename: str = "config_ef.yaml") -> str:
28
- yaml_str_b64 = base64.b64encode(yaml_str.encode("utf-8")).decode("utf-8")
29
- return f'echo "{yaml_str_b64}" | base64 -d > {filename}'
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, user_task_config: DictConfig, task_definition: dict
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
- cfg_config = cfg.evaluation.get("config", {})
41
- user_config = user_task_config.get("config", {})
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
- config_fields = copy.deepcopy(cfg_config or {})
51
- config_fields.update(user_config or {})
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 config_fields
122
+ return merged_nemo_evaluator_config
54
123
 
55
124
 
56
125
  def get_eval_factory_command(
57
- cfg: DictConfig, user_task_config: DictConfig, task_definition: dict
58
- ) -> str:
59
- config_fields = get_eval_factory_config(cfg, user_task_config, task_definition)
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
- model_url = get_endpoint_url(cfg, user_task_config, task_definition)
70
-
71
- model_id = get_served_model_name(cfg)
72
- model_type = task_definition["endpoint_type"]
73
- eval_type = task_definition["task"]
74
-
75
- create_file_cmd = _yaml_to_echo_command(
76
- yaml.safe_dump(config_fields), "config_ef.yaml"
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, user_task_config: DictConfig, task_definition: dict
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
- override_url = user_task_config.get("overrides", {}).get(
92
- "config.target.api_endpoint.url"
251
+ nemo_evaluator_config_url = (
252
+ merged_nemo_evaluator_config.get("target", {})
253
+ .get("api_endpoint", {})
254
+ .get("url", None)
93
255
  )
94
- return override_url if override_url is not None else url
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
- task_endpoint_type = task_definition["endpoint_type"]
117
- endpoint_uri = cfg.deployment.endpoints[task_endpoint_type]
118
- endpoint_url = f"http://127.0.0.1:{cfg.deployment.port}{endpoint_uri}"
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
- _SENSITIVE_KEY_SUBSTRINGS = {
65
- # Keep minimal, broad substrings (normalized: lowercased, no spaces/_/-)
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
- k = _normalize(key)
95
- return any(substr in k for substr in _SENSITIVE_KEY_SUBSTRINGS)
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=True),
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
- --enforce-eager
40
- --gpu-memory-utilization 0.95
40
+ --gpu-memory-utilization ${deployment.gpu_memory_utilization}
41
41
  ${deployment.extra_args}
@@ -15,3 +15,5 @@
15
15
  #
16
16
  type: local
17
17
  output_dir: ???
18
+ extra_docker_args: ""
19
+ mode: sequential
@@ -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
- hostname: ???
18
- username: ${oc.env:USER}
19
- account: ???
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
- output_dir: ???
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")