Source code for nemo_rl.utils.compile_cache

# Copyright (c) 2026, NVIDIA CORPORATION.  All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Persistent JIT compile caches for NeMo RL's vLLM and Megatron workers (opt-in).

Set ``NRL_COMPILE_CACHE_SHARED_DIR`` (kept between jobs, on a shared filesystem)
and ``NRL_COMPILE_CACHE_LOCAL_DIR`` (node-local). Each cache type (``vllm``,
``megatron``) lives in ``<shared>/<type>``, or in ``NRL_COMPILE_CACHE_<TYPE>_DIR``
if set; a node copies it once to ``<local>/<type>`` (``cache_sync.sh seed-type``)
and points that type's compilers there. Writing new entries back is the
launcher's job (``cache_sync.sh sidecar-types``). See
docs/guides/persistent-jit-caches.md.
"""

import logging
import os
import runpy
import shutil
import subprocess
import sys
from pathlib import Path

SHARED_VAR = "NRL_COMPILE_CACHE_SHARED_DIR"
LOCAL_VAR = "NRL_COMPILE_CACHE_LOCAL_DIR"
SCRIPT_VAR = "NRL_COMPILE_CACHE_SYNC_SCRIPT"
# Compiler cache variables and their folder under <local>/<type>.
COMPILER_DIRS = {
    "TORCHINDUCTOR_CACHE_DIR": "inductor",
    "TRITON_CACHE_DIR": "triton",
    "FLASHINFER_WORKSPACE_BASE": "flashinfer",
    "CUDA_CACHE_PATH": "cuda",
    "FLASH_ATTENTION_CUTE_DSL_CACHE_DIR": "fa4_cute",
}


[docs] def local_dir() -> str | None: """The node-local cache root, or None if persistent caches are off.""" shared, local = os.environ.get(SHARED_VAR), os.environ.get(LOCAL_VAR) if bool(shared) != bool(local): raise ValueError( f"Persistent compile caches need both {SHARED_VAR} and {LOCAL_VAR}" ) return local or None
[docs] def script() -> str: """The cache_sync.sh to run: the one next to this module unless overridden.""" path = os.environ.get(SCRIPT_VAR) or str(Path(__file__).with_name("cache_sync.sh")) if not os.path.isfile(path): raise FileNotFoundError(f"Persistent compile cache script not found: {path}") return path
[docs] def env_overrides(cache_type: str) -> dict[str, str]: """Point the compilers at ``<local>/<cache_type>``; {} if persistent caches are off.""" local = local_dir() if local is None: return {} env = {var: f"{local}/{cache_type}/{sub}" for var, sub in COMPILER_DIRS.items()} env["FLASH_ATTENTION_CUTE_DSL_CACHE_ENABLED"] = "1" return env
[docs] def seed(cache_type: str) -> None: """Copy ``cache_type``'s persistent cache onto this node once (no-op if off). Returns when the copy is done, also one another process on this node started (e.g. the launcher, to overlap Ray start-up). A failed copy only warns (in cache_sync.sh); a missing script or variable raises. """ if local_dir() is None: return subprocess.run(["bash", script(), "seed-type", cache_type], check=True)
[docs] def vllm_engine_dir(seed: int) -> str | None: """This engine's own VLLM_CACHE_ROOT under the local root, or None if off. Engines on one node must not share a folder (vLLM writes its cache index without locking), so each gets its own, filled from ``<local>/vllm/vllm``. """ local = local_dir() return None if local is None else f"{local}/vllm_engines/{seed}"
[docs] def _vllm_patches() -> dict: # By path, as tools/external_gym_vllm/serve_vllm_on_ray.py does: importing the # package would pull in NeMo RL's generation stack on every node. return runpy.run_path( str(Path(__file__).parents[1] / "models/generation/vllm/patches.py") )
[docs] def _prepare_vllm_engine_on_this_node(engine_dir: str) -> None: # The engine's GPU workers on this node load artifacts another engine wrote. _vllm_patches()["_patch_vllm_compile_cache_relative_artifacts"]( logging.getLogger(__name__) ) seed("vllm") shared = Path(os.environ[LOCAL_VAR]) / "vllm" / "vllm" if shared.is_dir() and not os.path.exists(engine_dir): tmp = f"{engine_dir}.tmp{os.getpid()}" shutil.copytree(shared, tmp, symlinks=True) # copy2 keeps mtimes os.rename(tmp, engine_dir) os.makedirs(engine_dir, exist_ok=True)
[docs] def prepare_vllm_engine(bundle_indices: list[int] | None) -> None: """Before an engine compiles: seed every node it runs on and fill its folder. Runs one task per node of the engine's placement-group bundles (the vLLM process's own node included) and waits for all of them. """ engine_dir = os.environ.get("VLLM_CACHE_ROOT") if local_dir() is None or not engine_dir: return import ray from ray.util.scheduling_strategies import NodeAffinitySchedulingStrategy node_ids = {ray.get_runtime_context().get_node_id()} pg = ray.util.get_current_placement_group() if pg is not None and bundle_indices: bundle_nodes = ray.util.placement_group_table(pg)["bundles_to_node_id"] node_ids.update(bundle_nodes[i] for i in bundle_indices) task = ray.remote(_prepare_vllm_engine_on_this_node) ray.get( [ task.options( num_cpus=0, scheduling_strategy=NodeAffinitySchedulingStrategy(node_id, soft=False), runtime_env={"py_executable": sys.executable}, ).remote(engine_dir) for node_id in sorted(node_ids) ] )