-
Notifications
You must be signed in to change notification settings - Fork 10
Bake SciCode sandbox image once; pre-build from launch host #159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: finbarr/scicode
Are you sure you want to change the base?
Changes from all commits
b2a2330
c9bb520
b1785f5
6696d6e
082fe30
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| """Shared Rich console singleton. | ||
|
|
||
| Lives in `common` (not `cli`) so non-CLI modules can import it without pulling in | ||
| the CLI package and triggering circular imports through `harness.presets`. | ||
| """ | ||
|
|
||
| from rich.console import Console | ||
|
|
||
| console = Console(force_terminal=True, width=120) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |
| from __future__ import annotations | ||
|
|
||
| import asyncio | ||
| import base64 | ||
| import logging | ||
| import time | ||
| from dataclasses import dataclass | ||
|
|
@@ -20,6 +21,8 @@ | |
| from olmo_eval.common.types import LMRequest, RequestType, SamplingParams | ||
| from olmo_eval.evals.external.base import ExternalEval | ||
| from olmo_eval.evals.external.result import ExternalEvalResult | ||
| from olmo_eval.harness.sandbox import SandboxManager | ||
| from olmo_eval.harness.sandbox.config import Capability, SandboxConfig, SandboxMode | ||
| from olmo_eval.inference.base import InferenceProvider | ||
|
|
||
| from . import loader as scicode_loader | ||
|
|
@@ -36,6 +39,23 @@ | |
| _SANDBOX_DEPS_CONTAINER_DIR = "/opt/scicode-deps" | ||
|
|
||
|
|
||
| def _scicode_dockerfile_extra() -> tuple[str, ...]: | ||
| """Bake SciCode sandbox deps into the swerex image at build time. | ||
|
|
||
| Embedding the lockfile contents (base64) makes the image hash sensitive to | ||
| lockfile changes (see image.py:55-57), so dep bumps invalidate the cache. | ||
| """ | ||
| pyproject_b64 = base64.b64encode((_SANDBOX_LOCK_DIR / "pyproject.toml").read_bytes()).decode() | ||
| uvlock_b64 = base64.b64encode((_SANDBOX_LOCK_DIR / "uv.lock").read_bytes()).decode() | ||
| deps_dir = _SANDBOX_DEPS_CONTAINER_DIR | ||
| return ( | ||
| f"RUN mkdir -p {deps_dir}", | ||
| f"RUN echo {pyproject_b64} | base64 -d > {deps_dir}/pyproject.toml", | ||
| f"RUN echo {uvlock_b64} | base64 -d > {deps_dir}/uv.lock", | ||
| f"RUN uv sync --active --frozen --no-dev --project {deps_dir}", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This Useful? React with 👍 / 👎. |
||
| ) | ||
|
|
||
|
|
||
| @dataclass | ||
| class SciCodeConfig: | ||
| """Arguments for SciCode external evaluation.""" | ||
|
|
@@ -77,6 +97,10 @@ def description(self) -> str: | |
| def timeout_seconds(self) -> float: | ||
| return 14400.0 | ||
|
|
||
| def prebuild_sandbox_specs(self) -> tuple[tuple[str, tuple[str, ...]], ...]: | ||
| base_image = SciCodeConfig.__dataclass_fields__["sandbox_image"].default | ||
| return ((base_image, _scicode_dockerfile_extra()),) | ||
|
|
||
| async def execute( | ||
| self, | ||
| provider: InferenceProvider, | ||
|
|
@@ -253,13 +277,6 @@ async def _verify( | |
| if not scorable_indices: | ||
| return [] | ||
|
|
||
| from olmo_eval.harness.sandbox import SandboxManager | ||
| from olmo_eval.harness.sandbox.config import ( | ||
| Capability, | ||
| SandboxConfig, | ||
| SandboxMode, | ||
| ) | ||
|
|
||
| sandbox_config = SandboxConfig( | ||
| image=sc_args.sandbox_image, | ||
| mode=SandboxMode.DOCKER, | ||
|
|
@@ -268,32 +285,14 @@ async def _verify( | |
| startup_timeout=sc_args.startup_timeout, | ||
| command_timeout=sc_args.command_timeout, | ||
| inject_swerex=True, | ||
| volumes=( | ||
| (sc_args.h5py_host_path, sc_args.h5py_container_path), | ||
| ( | ||
| str(_SANDBOX_LOCK_DIR / "pyproject.toml"), | ||
| f"{_SANDBOX_DEPS_CONTAINER_DIR}/pyproject.toml", | ||
| ), | ||
| ( | ||
| str(_SANDBOX_LOCK_DIR / "uv.lock"), | ||
| f"{_SANDBOX_DEPS_CONTAINER_DIR}/uv.lock", | ||
| ), | ||
| ), | ||
| dockerfile_extra=_scicode_dockerfile_extra(), | ||
| volumes=((sc_args.h5py_host_path, sc_args.h5py_container_path),), | ||
| ) | ||
| sandbox_manager = SandboxManager([sandbox_config], owner=f"scicode-{problem.problem_id}") | ||
|
|
||
| results: list[bool] = [] | ||
| try: | ||
| await sandbox_manager.start() | ||
| # --active installs into the venv that swerex's image bakes at /root/venv | ||
| # (exported via VIRTUAL_ENV); without it, uv would create a fresh project venv. | ||
| sync_result = await sandbox_manager.execute_code( | ||
| f"uv sync --active --frozen --no-dev --project {_SANDBOX_DEPS_CONTAINER_DIR}", | ||
| language="bash", | ||
| timeout=sc_args.startup_timeout, | ||
| ) | ||
| if not sync_result.success: | ||
| raise RuntimeError(f"SciCode sandbox dep install failed: {sync_result.output}") | ||
| for idx in scorable_indices: | ||
| step = problem.sub_steps[idx] | ||
| script = scicode_verifier.build_step_script( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| """Round-trip tests for eval-arg / provider-kwarg serialization. | ||
|
|
||
| The Beaker launcher serializes parsed args back into ``-a key=value`` strings | ||
| that the inner ``run-external`` CLI re-parses. ``json.dumps`` on the way out and | ||
| ``json.loads`` (via ``_coerce_value``) on the way in must agree on every shape | ||
| we care about: strings, bools, ints, floats, lists, dicts, None. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import json | ||
| import unittest | ||
| from typing import Any | ||
|
|
||
| from parameterized import parameterized | ||
|
|
||
| from olmo_eval.cli import utils as cli_utils | ||
|
|
||
|
|
||
| class TestArgRoundTrip(unittest.TestCase): | ||
| @parameterized.expand( | ||
| [ | ||
| ("string", "hello"), | ||
| ("string_with_spaces", "hello world"), | ||
| ("bool_true", True), | ||
| ("bool_false", False), | ||
| ("int", 123), | ||
| ("float", 3.14), | ||
| ("list_of_strings", ["2", "11"]), | ||
| ("list_of_ints", [1, 2, 3]), | ||
| ("dict", {"a": 1, "b": "two"}), | ||
| ("none", None), | ||
| ] | ||
| ) | ||
| def test_round_trip(self, _name: str, value: Any) -> None: | ||
| encoded = json.dumps(value) | ||
| parsed = cli_utils.parse_key_value_args((f"k={encoded}",), coerce_types=True) | ||
| self.assertEqual(parsed["k"], value) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling
get_swerex_image(..., require_registry=True)makes pre-bake a hard prerequisite for launch: if the launch host lacks push auth toSWEREX_REGISTRY, this raises and abortsbeaker launchbefore any job is submitted. That is a regression from prior behavior where the Beaker job could still build and run the derived image locally on the GPU node (withrequire_registry=False) even when registry push failed.Useful? React with 👍 / 👎.