|
14 | 14 | import asyncio |
15 | 15 | import json |
16 | 16 | import shlex |
| 17 | +import subprocess |
17 | 18 | import tomllib |
18 | 19 | from glob import glob |
19 | 20 | from os import environ, makedirs |
|
27 | 28 | import rich |
28 | 29 | import uvicorn |
29 | 30 | from devtools import pprint |
30 | | -from omegaconf import DictConfig, OmegaConf |
| 31 | +from omegaconf import DictConfig, OmegaConf, open_dict |
31 | 32 | from pydantic import BaseModel, Field |
32 | 33 | from tqdm.auto import tqdm |
33 | 34 |
|
34 | 35 | from nemo_gym import PARENT_DIR |
35 | 36 | from nemo_gym.config_types import BaseNeMoGymCLIConfig |
36 | 37 | from nemo_gym.global_config import ( |
| 38 | + HEAD_SERVER_DEPS_KEY_NAME, |
37 | 39 | NEMO_GYM_CONFIG_DICT_ENV_VAR_NAME, |
38 | 40 | NEMO_GYM_CONFIG_PATH_ENV_VAR_NAME, |
39 | 41 | NEMO_GYM_RESERVED_TOP_LEVEL_KEYS, |
|
45 | 47 | HeadServer, |
46 | 48 | ServerClient, |
47 | 49 | ServerStatus, |
| 50 | + initialize_ray, |
48 | 51 | ) |
49 | 52 |
|
50 | 53 |
|
51 | | -def _setup_env_command(dir_path: Path) -> str: # pragma: no cover |
| 54 | +def _capture_head_server_dependencies(global_config_dict: DictConfig) -> None: # pragma: no cover |
| 55 | + """ |
| 56 | + Capture head server dependencies and store it in the global config dict. |
| 57 | + These dependencies are used as constraints to ensure that other servers use the same dependency versions as the head server. |
| 58 | + Note: This function will modify the global config dict - update `head_server_deps` |
| 59 | + """ |
| 60 | + |
| 61 | + try: |
| 62 | + result = subprocess.run( |
| 63 | + ["uv", "pip", "freeze", "--exclude-editable"], |
| 64 | + capture_output=True, |
| 65 | + text=True, |
| 66 | + check=True, |
| 67 | + ) |
| 68 | + head_server_deps = result.stdout |
| 69 | + except Exception as e: |
| 70 | + print(f"Warning: Could not capture head server dependencies: {e}") |
| 71 | + head_server_deps = None |
| 72 | + |
| 73 | + with open_dict(global_config_dict): |
| 74 | + global_config_dict[HEAD_SERVER_DEPS_KEY_NAME] = head_server_deps |
| 75 | + |
| 76 | + |
| 77 | +def _setup_env_command(dir_path: Path, head_server_deps: Optional[str] = None) -> str: # pragma: no cover |
| 78 | + install_cmd = "uv pip install -r requirements.txt" |
| 79 | + if head_server_deps: |
| 80 | + install_cmd += f" --constraint <(cat << 'EOF'\n{head_server_deps}\nEOF\n)" |
| 81 | + |
52 | 82 | return f"""cd {dir_path} \\ |
53 | 83 | && uv venv --allow-existing \\ |
54 | 84 | && source .venv/bin/activate \\ |
55 | | - && uv pip install -r requirements.txt \\ |
| 85 | + && {install_cmd} \\ |
56 | 86 | """ |
57 | 87 |
|
58 | 88 |
|
59 | 89 | def _run_command(command: str, working_directory: Path) -> Popen: # pragma: no cover |
60 | 90 | custom_env = environ.copy() |
61 | 91 | custom_env["PYTHONPATH"] = f"{working_directory.absolute()}:{custom_env.get('PYTHONPATH', '')}" |
62 | | - print(f"Executing command:\n{command}\n") |
63 | 92 | return Popen(command, executable="/bin/bash", shell=True, env=custom_env) |
64 | 93 |
|
65 | 94 |
|
@@ -114,6 +143,14 @@ class RunHelper: # pragma: no cover |
114 | 143 | def start(self, global_config_dict_parser_config: GlobalConfigDictParserConfig) -> None: |
115 | 144 | global_config_dict = get_global_config_dict(global_config_dict_parser_config=global_config_dict_parser_config) |
116 | 145 |
|
| 146 | + # Capture head server dependencies and store in global config dict |
| 147 | + # Note: This function will modify the global config dict - update `head_server_deps` |
| 148 | + _capture_head_server_dependencies(global_config_dict) |
| 149 | + |
| 150 | + # Initialize Ray cluster in the main process |
| 151 | + # Note: This function will modify the global config dict - update `ray_head_node_address` |
| 152 | + initialize_ray() |
| 153 | + |
117 | 154 | # Assume Nemo Gym Run is for a single agent. |
118 | 155 | escaped_config_dict_yaml_str = shlex.quote(OmegaConf.to_yaml(global_config_dict)) |
119 | 156 |
|
@@ -149,7 +186,9 @@ def start(self, global_config_dict_parser_config: GlobalConfigDictParserConfig) |
149 | 186 |
|
150 | 187 | dir_path = PARENT_DIR / Path(first_key, second_key) |
151 | 188 |
|
152 | | - command = f"""{_setup_env_command(dir_path)} \\ |
| 189 | + head_server_deps = global_config_dict.get(HEAD_SERVER_DEPS_KEY_NAME) |
| 190 | + |
| 191 | + command = f"""{_setup_env_command(dir_path, head_server_deps)} \\ |
153 | 192 | && {NEMO_GYM_CONFIG_DICT_ENV_VAR_NAME}={escaped_config_dict_yaml_str} \\ |
154 | 193 | {NEMO_GYM_CONFIG_PATH_ENV_VAR_NAME}={shlex.quote(top_level_path)} \\ |
155 | 194 | python {str(entrypoint_fpath)}""" |
|
0 commit comments