Skip to content

Commit 594e057

Browse files
committed
Merge latest Puzzletron v2 target
Signed-off-by: Johannes Rausch <jrausch@nvidia.com>
2 parents c436ef3 + e58dde6 commit 594e057

10 files changed

Lines changed: 204 additions & 103 deletions

File tree

examples/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. 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+
"""ModelOpt examples."""

examples/puzzletron/README.md

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ import os
284284
from packaging.version import Version
285285
286286
import aiperf
287+
import lmms_eval
287288
import modelopt
288289
import nemo_automodel
289290
import torch
@@ -298,6 +299,7 @@ for package in (
298299
"vllm",
299300
"nemo-automodel",
300301
"aiperf",
302+
"lmms-eval",
301303
"nvidia-modelopt",
302304
):
303305
print(package, metadata.version(package))
@@ -312,6 +314,7 @@ assert Version(metadata.version("torchvision")).release == Version(
312314
ci_environment["torchvision"]
313315
).release
314316
assert transformers.__version__ == ci_environment["transformers"]
317+
assert metadata.version("lmms-eval") == ci_environment["lmms_eval"]
315318
assert Version(metadata.version("nemo-automodel")).base_version == (
316319
ci_environment["nemo_automodel"]["base_version"]
317320
)
@@ -612,25 +615,12 @@ After `mip`, prepare one deduplicated online-evaluation plan. Repeat
612615
`--profile-id` for every configured profile; aliases ensure that an identical
613616
architecture is evaluated once while remaining visible in every profile.
614617

615-
Downstream `lmms-eval` nodes use a separate evaluator Python. The reproducible
616-
example path is pinned to `lmms-eval==0.7.2` by
617-
`examples/puzzletron/requirements-lmms-eval.txt` and recorded in
618-
`ci_environment.json`. Keep this separate from the Puzzletron runtime
619-
environment because `lmms-eval==0.7.2` pins `wandb==0.25.0`, while the pinned
620-
AutoModel build requires a newer `wandb`.
621-
622-
```bash
623-
python3 -m venv /workspace/.venv-lmms-eval
624-
source /workspace/.venv-lmms-eval/bin/activate
625-
python -m pip install --upgrade pip "setuptools>=80,<81" wheel packaging
626-
VLLM_USE_PRECOMPILED=1 VLLM_PRECOMPILED_WHEEL_VARIANT=cu129 \
627-
python -m pip install --no-build-isolation -e "${VLLM_ROOT}"
628-
python -m pip install -r "${MODEL_OPT_ROOT}/examples/puzzletron/requirements-lmms-eval.txt"
629-
python -c 'import importlib.metadata as m; assert m.version("lmms-eval") == "0.7.2"'
630-
deactivate
631-
632-
export PUZZLETRON_LMMS_EVAL_PYTHON=/workspace/.venv-lmms-eval/bin/python
633-
```
618+
Downstream `lmms-eval` nodes run in the standard Puzzletron worker environment.
619+
The reproducible example path is pinned by
620+
`examples/puzzletron/requirements.txt` to an upstream 0.7.0 snapshot that
621+
packages the task definitions and accepts the newer `wandb` required by the
622+
pinned AutoModel build. The expected package version is recorded in
623+
`ci_environment.json`.
634624

635625
```bash
636626
python examples/puzzletron/run_profile_online_evaluation.py \

examples/puzzletron/ci_environment.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"torch": "2.11.0",
66
"torchvision": "0.26.0",
77
"transformers": "5.8.1",
8-
"lmms_eval": "0.7.2",
8+
"lmms_eval": "0.7.0",
99
"nemo_automodel": {
1010
"base_version": "0.5.0",
1111
"repository": "https://github.com/Separius/Automodel.git",

examples/puzzletron/configs/families/nemotron3/nano_30b_a3b_bf16/runs/lmms_eval.yaml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ defaults:
55
- _self_
66

77
# Opt-in downstream lmms-eval workflow for the realized runtime-075 candidate.
8-
# Use PUZZLETRON_LMMS_EVAL_PYTHON to point at the isolated pinned evaluator env.
8+
# Install examples/puzzletron/requirements.txt to use the pinned lmms-eval version.
99
# Non-empty post_mip.flows replaces the legacy post-MIP tail in the v2 orchestrator.
1010
zero_shot_evaluation:
1111
enabled: false
@@ -39,10 +39,6 @@ post_mip:
3939
type: downstream_evaluation
4040
input: materialized
4141
config:
42-
command_prefix:
43-
- ${oc.env:PUZZLETRON_LMMS_EVAL_PYTHON}
44-
- -m
45-
- lmms_eval
4642
model: vllm
4743
checkpoint_arg: model
4844
tasks:

examples/puzzletron/docs/post_mip_pipeline.md

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,10 @@ post_mip:
9191
max_steps: 2048
9292
```
9393
94-
Evaluation nodes publish every finite metric they produce. They do not declare
95-
metric lists or cases. Later filters reference metrics as `mip.<metric>` or
96-
`<node_id>.<metric>`.
94+
Evaluation nodes publish every finite metric they produce. Downstream lmms-eval
95+
nodes publish results only after every configured task, or every leaf of a
96+
configured task group, has numeric metrics and positive effective sample counts.
97+
Later filters reference metrics as `mip.<metric>` or `<node_id>.<metric>`.
9798

9899
## Node types
99100

@@ -114,6 +115,13 @@ metric lists or cases. Later filters reference metrics as `mip.<metric>` or
114115
Nodes that require checkpoints never materialize implicitly. Add a `materialize`
115116
node where the transition is needed.
116117

118+
## Add downstream evaluation to an existing campaign
119+
120+
Keep the campaign's `puzzle_dir` and add a `post_mip.flows` entry whose source
121+
selects the completed MIP run. See the
122+
[lmms-eval run configuration](../configs/families/nemotron3/nano_30b_a3b_bf16/runs/lmms_eval.yaml)
123+
for a complete filter, materialization, and downstream-evaluation flow.
124+
117125
## Lineage and model source
118126

119127
Architectures are deduplicated across MIP and homogeneous results, while all MIP
@@ -133,21 +141,15 @@ from the original candidate.
133141

134142
## Downstream evaluation
135143

136-
`downstream_evaluation` runs `python -m lmms_eval` as a subprocess through
137-
`command_prefix`. The runner passes an argument list directly and does not invoke
138-
a shell. Values in `command_prefix` and `extra_args` are arguments; shell syntax
139-
is not interpreted. Install the pinned evaluator into an isolated environment
140-
rather than the Puzzletron runtime environment, because `lmms-eval==0.7.2` pins
141-
`wandb==0.25.0` and the pinned AutoModel build requires a newer `wandb`:
144+
`downstream_evaluation` runs `python -m lmms_eval` as a subprocess from the
145+
Puzzletron worker environment. The runner passes an argument list directly and
146+
does not invoke a shell. Values in `command_prefix` and `extra_args` are arguments;
147+
shell syntax is not interpreted. The standard example requirements pin a snapshot
148+
compatible with the newer `wandb` required by the pinned AutoModel build:
142149

143150
```bash
144-
python3 -m venv /workspace/.venv-lmms-eval
145-
source /workspace/.venv-lmms-eval/bin/activate
146-
python -m pip install -r examples/puzzletron/requirements-lmms-eval.txt
147-
python -c 'import importlib.metadata as m; assert m.version("lmms-eval") == "0.7.2"'
148-
deactivate
149-
150-
export PUZZLETRON_LMMS_EVAL_PYTHON=/workspace/.venv-lmms-eval/bin/python
151+
python -m pip install -r examples/puzzletron/requirements.txt
152+
python -c 'import importlib.metadata as m; assert m.version("lmms-eval") == "0.7.0"'
151153
```
152154

153155
The runner derives the realized checkpoint path, vLLM topology arguments, task

examples/puzzletron/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
aiohttp>=3.9,<4
2+
lmms-eval @ git+https://github.com/EvolvingLMMs-Lab/lmms-eval.git@15c32bfec165df13c269ddd3cda03b2ed9137825
23
math-verify
34
ray
45
# Transformers comes from nvidia-modelopt[hf]; CPU CI overlays the exact

modelopt/torch/puzzletron/post_mip/runner.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import signal
2929
import subprocess
3030
import sys
31+
import time
3132
import traceback
3233
import uuid
3334
from contextlib import contextmanager
@@ -561,6 +562,9 @@ def _aiperf(
561562
)
562563
_LMMS_EVAL_RESERVED_EXTRA_ARG_FLAGS = frozenset(
563564
{
565+
"--batch-size",
566+
"--batch_size",
567+
"--model",
564568
"--model_args",
565569
"--model-args",
566570
"--output_path",
@@ -570,6 +574,7 @@ def _aiperf(
570574
)
571575
_DEFAULT_LMMS_EVAL_TIMEOUT_SECONDS = 3600.0
572576
_LMMS_EVAL_PROCESS_CLEANUP_TIMEOUT_SECONDS = 10.0
577+
_LMMS_EVAL_PROCESS_GROUP_POLL_INTERVAL_SECONDS = 0.1
573578

574579

575580
def _as_cli_bool(value: bool) -> str:
@@ -688,6 +693,8 @@ def _model_arg_string(values: Mapping[str, Any]) -> str:
688693
def _merge_lmms_eval_model_args(settings: Mapping[str, Any], checkpoint: str) -> str:
689694
raw = settings.get("model_args")
690695
checkpoint_arg = str(settings.get("checkpoint_arg", "model"))
696+
if checkpoint_arg != "model":
697+
raise ValueError("downstream_evaluation.config.checkpoint_arg must be 'model'")
691698
topology = dict(settings.get("topology") or {})
692699
canonical_topology = normalize_vllm_topology(topology) if topology else {}
693700
reserved_fields = _lmms_eval_reserved_model_arg_fields(checkpoint_arg)
@@ -766,14 +773,17 @@ def _lmms_eval_command(
766773
*,
767774
checkpoint: str,
768775
output_path: Path,
769-
) -> tuple[list[str], dict[str, str], float | None]:
776+
) -> tuple[list[str], dict[str, str], float]:
770777
"""Build a deterministic lmms-eval CLI invocation for one realized checkpoint."""
771778

779+
model = str(settings.get("model", "vllm"))
780+
if model != "vllm":
781+
raise ValueError("downstream_evaluation.config.model must be 'vllm'")
772782
tasks = ",".join(_configured_lmms_eval_tasks(settings))
773783
argv = [
774784
*_command_prefix(settings),
775785
"--model",
776-
str(settings.get("model", "vllm")),
786+
model,
777787
"--model_args",
778788
_merge_lmms_eval_model_args(settings, checkpoint),
779789
"--tasks",
@@ -816,10 +826,15 @@ def _lmms_eval_command(
816826
env[str(key)] = str(value)
817827
if settings.get("cache_dir") is not None:
818828
env.setdefault("LMMS_EVAL_HOME", str(settings["cache_dir"]))
819-
timeout = settings.get("timeout_seconds", settings.get("timeout"))
829+
timeout = settings.get("timeout_seconds")
830+
if timeout is None:
831+
timeout = settings.get("timeout")
820832
if timeout is None:
821833
timeout = _DEFAULT_LMMS_EVAL_TIMEOUT_SECONDS
822-
return argv, env, float(timeout)
834+
timeout = float(timeout)
835+
if not math.isfinite(timeout) or timeout <= 0:
836+
raise ValueError("lmms-eval timeout must be a finite positive number")
837+
return argv, env, timeout
823838

824839

825840
def _metric_key(value: Any) -> str:
@@ -1002,12 +1017,22 @@ def _lmms_eval_process_group_exists(process: subprocess.Popen[str]) -> bool:
10021017
return True
10031018

10041019

1020+
def _wait_for_lmms_eval_process_group_exit(
1021+
process: subprocess.Popen[str], *, deadline: float
1022+
) -> None:
1023+
while _lmms_eval_process_group_exists(process):
1024+
remaining = deadline - time.monotonic()
1025+
if remaining <= 0:
1026+
return
1027+
time.sleep(min(_LMMS_EVAL_PROCESS_GROUP_POLL_INTERVAL_SECONDS, remaining))
1028+
1029+
10051030
def _run_lmms_eval_process(
10061031
argv: list[str],
10071032
*,
10081033
cwd: str,
10091034
env: Mapping[str, str],
1010-
timeout: float | None,
1035+
timeout: float,
10111036
) -> subprocess.CompletedProcess[str]:
10121037
process = subprocess.Popen(
10131038
argv,
@@ -1026,15 +1051,21 @@ def _run_lmms_eval_process(
10261051
stdout, stderr = process.communicate(timeout=_LMMS_EVAL_PROCESS_CLEANUP_TIMEOUT_SECONDS)
10271052
except subprocess.TimeoutExpired:
10281053
_signal_lmms_eval_process_group(process, signal.SIGKILL)
1054+
cleanup_deadline = time.monotonic() + _LMMS_EVAL_PROCESS_CLEANUP_TIMEOUT_SECONDS
10291055
try:
10301056
stdout, stderr = process.communicate(
10311057
timeout=_LMMS_EVAL_PROCESS_CLEANUP_TIMEOUT_SECONDS
10321058
)
10331059
except subprocess.TimeoutExpired as kill_error:
10341060
stdout, stderr = kill_error.output, kill_error.stderr
1061+
_wait_for_lmms_eval_process_group_exit(process, deadline=cleanup_deadline)
10351062
else:
10361063
if _lmms_eval_process_group_exists(process):
10371064
_signal_lmms_eval_process_group(process, signal.SIGKILL)
1065+
_wait_for_lmms_eval_process_group_exit(
1066+
process,
1067+
deadline=time.monotonic() + _LMMS_EVAL_PROCESS_CLEANUP_TIMEOUT_SECONDS,
1068+
)
10381069
raise subprocess.TimeoutExpired(
10391070
argv,
10401071
error.timeout,

noxfile.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ def puzzletron_v2(session):
110110
"torch": PUZZLETRON_V2_CI_ENVIRONMENT["torch"],
111111
"torchvision": PUZZLETRON_V2_CI_ENVIRONMENT["torchvision"],
112112
"transformers": PUZZLETRON_V2_CI_ENVIRONMENT["transformers"],
113+
"lmms-eval": PUZZLETRON_V2_CI_ENVIRONMENT["lmms_eval"],
113114
"nemo-automodel": PUZZLETRON_V2_AUTOMODEL_SOURCE["base_version"],
114115
}
115116
session.run(
@@ -125,6 +126,7 @@ def puzzletron_v2(session):
125126
"'torch': Version(version('torch')).base_version, "
126127
"'torchvision': Version(version('torchvision')).base_version, "
127128
"'transformers': Version(version('transformers')).base_version, "
129+
"'lmms-eval': Version(version('lmms-eval')).base_version, "
128130
"'nemo-automodel': Version(version('nemo-automodel')).base_version}; "
129131
"mismatches = {name: (actual[name], expected_version) "
130132
"for name, expected_version in expected.items() "

tests/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. 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+
"""ModelOpt test suite."""

0 commit comments

Comments
 (0)