Rate this Page

Source code for monarch.config

# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.

# pyre-unsafe

"""Configuration utilities for Monarch.

This module provides utilities for managing Monarch's runtime
configuration, particularly useful for testing and temporary
configuration overrides.
"""

import contextlib
import sys
from typing import Any, Callable, Dict, Iterator, TYPE_CHECKING, TypedDict

from monarch._rust_bindings.monarch_hyperactor.channel import ChannelTransport
from monarch._rust_bindings.monarch_hyperactor.config import (
    clear_runtime_config as _clear_runtime_config,
    configure as _configure,
    Encoding,
    get_global_config as _get_global_config,
    get_runtime_config as _get_runtime_config,
)


__all__ = [
    "clear_runtime_config",
    "configure",
    "configured",
    "Encoding",
    "get_global_config",
    "get_runtime_config",
    "parametrize_config",
    "parametrize_config_pointwise",
]

if TYPE_CHECKING:
    if sys.version_info >= (3, 12):
        from typing import NotRequired, Unpack

        class ConfigureArgs(TypedDict):
            default_transport: NotRequired[ChannelTransport | str]
            enable_log_forwarding: NotRequired[bool]
            enable_file_capture: NotRequired[bool]
            tail_log_lines: NotRequired[int]
            codec_max_frame_length: NotRequired[int]
            message_delivery_timeout: NotRequired[str]
            host_spawn_ready_timeout: NotRequired[str]
            mesh_proc_spawn_max_idle: NotRequired[str]
            process_exit_timeout: NotRequired[str]
            message_ack_time_interval: NotRequired[str]
            message_ack_every_n_messages: NotRequired[int]
            message_ttl_default: NotRequired[int]
            split_max_buffer_size: NotRequired[int]
            split_max_buffer_age: NotRequired[str]
            stop_actor_timeout: NotRequired[str]
            cleanup_timeout: NotRequired[str]
            default_encoding: NotRequired[Encoding]
            channel_net_rx_buffer_full_check_interval: NotRequired[str]
            message_latency_sampling_rate: NotRequired[float]
            enable_dest_actor_reordering_buffer: NotRequired[bool]
            mesh_bootstrap_enable_pdeathsig: NotRequired[bool]
            mesh_terminate_concurrency: NotRequired[int]
            mesh_terminate_timeout: NotRequired[str]
            small_write_threshold: NotRequired[int]
            max_cast_dimension_size: NotRequired[int]
            remote_alloc_bind_to_inaddr_any: NotRequired[bool]
            remote_alloc_bootstrap_addr: NotRequired[str]
            remote_alloc_allowed_port_range: NotRequired[slice]
            read_log_buffer: NotRequired[int]
            force_file_log: NotRequired[bool]
            prefix_with_rank: NotRequired[bool]
            actor_spawn_max_idle: NotRequired[str]
            get_actor_state_max_idle: NotRequired[str]
            supervision_watchdog_timeout: NotRequired[str]
            proc_stop_max_idle: NotRequired[str]
            get_proc_state_max_idle: NotRequired[str]
            actor_queue_dispatch: NotRequired[bool]
            mesh_admin_addr: NotRequired[str]
            mesh_attach_config_timeout: NotRequired[str]
            mesh_orphan_timeout: NotRequired[str]
            rdma_allow_tcp_fallback: NotRequired[bool]
            rdma_disable_ibverbs: NotRequired[bool]
            rdma_max_chunk_size_mb: NotRequired[int]

        # pyrefly: ignore [invalid-annotation]
        ConfigureKwargsType = Unpack[ConfigureArgs]
    else:
        ConfigureKwargsType = object


[docs]def configure(**kwargs: "ConfigureKwargsType") -> None: """Configure Hyperactor runtime defaults for this process. This updates the **Runtime** configuration layer from Python, setting transports, logging behavior, timeouts, and other runtime parameters. All duration parameters accept humantime strings like ``"30s"``, ``"5m"``, ``"2h"``, or ``"1h 30m"``. Args: Transport configuration: default_transport: Default channel transport for actor communication. Can be a ChannelTransport enum or explicit address string. Basic logging behavior: enable_log_forwarding: Forward child stdout/stderr through the mesh. enable_file_capture: Persist child stdout/stderr to per-host files. tail_log_lines: Number of log lines to retain in memory. Message encoding and delivery: codec_max_frame_length: Maximum serialized message size in bytes. message_delivery_timeout: Max delivery time (humantime). Core mesh timeouts: host_spawn_ready_timeout: Max host bootstrapping time (humantime). mesh_proc_spawn_max_idle: Max idle time while spawning procs (humantime). Hyperactor timeouts and message handling: process_exit_timeout: Timeout for process exit (humantime). message_ack_time_interval: Time interval for message acknowledgments (humantime). message_ack_every_n_messages: Acknowledge every N messages. message_ttl_default: Default message time-to-live. split_max_buffer_size: Maximum buffer size for message splitting (bytes). split_max_buffer_age: Maximum age for split message buffers (humantime). stop_actor_timeout: Timeout for stopping actors (humantime). cleanup_timeout: Timeout for cleanup operations (humantime). default_encoding: Default message encoding (Encoding.Bincode, Encoding.Json, or Encoding.Multipart). channel_net_rx_buffer_full_check_interval: Network receive buffer check interval (humantime). message_latency_sampling_rate: Sampling rate for message latency tracking (0.0 to 1.0). enable_dest_actor_reordering_buffer: Enable reordering buffer in dest actor. Mesh bootstrap configuration: mesh_bootstrap_enable_pdeathsig: Enable parent-death signal for spawned processes. mesh_terminate_concurrency: Maximum concurrent terminations during shutdown. mesh_terminate_timeout: Timeout per child during graceful termination (humantime). Runtime and buffering: small_write_threshold: Threshold below which writes are copied (bytes). Mesh configuration: max_cast_dimension_size: Maximum dimension size for cast operations. Remote allocation: remote_alloc_bind_to_inaddr_any: Bind remote allocators to INADDR_ANY. remote_alloc_bootstrap_addr: Bootstrap address for remote allocators. remote_alloc_allowed_port_range: Allowed port range as slice(start, stop). Logging configuration: read_log_buffer: Buffer size for reading logs (bytes). force_file_log: Force file-based logging regardless of environment. prefix_with_rank: Prefix log lines with rank information. Proc mesh timeouts: actor_spawn_max_idle: Maximum idle time while spawning actors (humantime). get_actor_state_max_idle: Maximum idle time for actor state queries (humantime). supervision_watchdog_timeout: Watchdog timeout for the actor-mesh supervision stream; prolonged silence is interpreted as the controller being unreachable (humantime). Host mesh timeouts: proc_stop_max_idle: Maximum idle time while stopping procs (humantime). get_proc_state_max_idle: Maximum idle time for proc state queries (humantime). Mesh admin: mesh_admin_addr: Default socket address for the mesh admin HTTP server (e.g. ``"[::]:1729"``, ``"0.0.0.0:8080"``). Mesh attach: mesh_attach_config_timeout: Timeout for the config-push barrier during ``attach_to_workers()`` (humantime, default ``"10s"``). Best-effort: if exceeded, a warning is logged and attach continues. RDMA configuration: rdma_allow_tcp_fallback: Allow TCP fallback when ibverbs RDMA is unavailable. When True, RDMA operations use a TCP-based backend instead of failing. rdma_disable_ibverbs: Force-disable ibverbs even when available, causing all RDMA operations to use the TCP fallback backend. rdma_max_chunk_size_mb: Maximum chunk size in megabytes for RDMA transfers. **kwargs: Reserved for future configuration keys exposed by Rust bindings. """ _configure(**kwargs)
[docs]def get_global_config() -> Dict[str, Any]: """Return a merged view of all configuration layers. The resulting dict includes defaults, environment overrides, file-based settings, and the current Runtime layer. Mutating the returned dict does *not* change the active configuration; use :func:`configure` instead. """ return _get_global_config()
[docs]def get_runtime_config() -> Dict[str, Any]: """Return a snapshot of just the Runtime layer configuration. Useful for snapshot/restore flows (see :func:`configured`) or for inspecting which keys were last set via Python. """ return _get_runtime_config()
[docs]def clear_runtime_config() -> None: """Remove every key from the Runtime configuration layer. Environment variables, config files, and defaults are untouched. This is typically paired with :func:`configure` to reset overrides in long-lived processes. """ _clear_runtime_config()
[docs]@contextlib.contextmanager def configured(**overrides: "ConfigureKwargsType") -> Iterator[Dict[str, Any]]: """Temporarily apply Python-side config overrides for this process. This context manager: * snapshots the current **Runtime** configuration layer (`get_runtime_config()`), * applies the given `overrides` via `configure(**overrides)`, and * yields the **merged** view of config (`get_global_config()`), including defaults, env, file, and Runtime. On exit it restores the previous Runtime layer by: * clearing all Runtime entries, and * re-applying the saved snapshot. `configured` alters the global configuration; thus other threads will be subject to the overridden configuration while the context manager is active. Thus: this is intended for tests, which run as single threads; per-test overrides do not leak into other tests. Args: **overrides: Configuration key-value pairs to override for the duration of the context. Yields: Dict[str, Any]: The merged global configuration including all layers (defaults, environment, file, and runtime). Example: >>> from monarch.config import configured >>> with configured(enable_log_forwarding=True, tail_log_lines=100): ... # Configuration is temporarily overridden ... assert get_global_config()["enable_log_forwarding"] is True >>> # Configuration is automatically restored after the context """ # Retrieve runtime prev = get_runtime_config() try: # Merge overrides into runtime configure(**overrides) # Snapshot of merged config (all layers) yield get_global_config() finally: # Restore previous runtime clear_runtime_config() configure(**prev)
def parametrize_config( **config_options: set[Any], ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: """Create a pytest parametrize decorator for configuration cross-products. This decorator runs the test function under every combination of the specified configuration values. Each test invocation wraps the test body in a `configured(...)` context manager with the corresponding settings. Args: **config_options: Configuration keys mapped to sets of values to test. Each key should be a valid argument to `configure()`. Returns: A decorator that parametrizes and wraps the test function. Example: >>> from monarch.config import parametrize_config >>> >>> @parametrize_config( ... actor_queue_dispatch={True, False}, ... prefix_with_rank={True, False}, ... ) ... async def test_actor_feature(): ... # Test runs 4 times: all combinations of the two bool options ... pass """ import asyncio import functools import inspect import itertools import pytest if not config_options: raise ValueError("parametrize_config requires at least one config option") keys = list(config_options.keys()) value_lists = [list(config_options[k]) for k in keys] combinations = list(itertools.product(*value_lists)) # Create parameter IDs for clearer test output param_ids = [ "-".join(f"{k}={v}" for k, v in zip(keys, combo)) for combo in combinations ] # Create the config dicts for each combination config_dicts = [dict(zip(keys, combo)) for combo in combinations] def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: # Get original function's signature and add _config_overrides as first param orig_sig = inspect.signature(fn) new_params = [ inspect.Parameter( "_config_overrides", inspect.Parameter.POSITIONAL_OR_KEYWORD ) ] + list(orig_sig.parameters.values()) new_sig = orig_sig.replace(parameters=new_params) if asyncio.iscoroutinefunction(fn): async def async_wrapper( _config_overrides: Dict[str, Any], *args: Any, **kwargs: Any ) -> Any: with configured(**_config_overrides): return await fn(*args, **kwargs) functools.update_wrapper(async_wrapper, fn) async_wrapper.__signature__ = new_sig # type: ignore[attr-defined] wrapped = async_wrapper else: def sync_wrapper( _config_overrides: Dict[str, Any], *args: Any, **kwargs: Any ) -> Any: with configured(**_config_overrides): return fn(*args, **kwargs) functools.update_wrapper(sync_wrapper, fn) sync_wrapper.__signature__ = new_sig # type: ignore[attr-defined] wrapped = sync_wrapper return pytest.mark.parametrize( "_config_overrides", config_dicts, ids=param_ids )(wrapped) return decorator def parametrize_config_pointwise( **config_options: Any, ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: """Create a pytest parametrize decorator for pointwise configuration sweeps. Like :func:`parametrize_config`, but iterates the input sequences in lockstep (``zip``) rather than computing their cartesian product. Every sequence must have the same length; the test is invoked once per index with one value drawn from each sequence at that position. Args: **config_options: Configuration keys mapped to ordered sequences of values. Each key should be a valid argument to ``configure()``. Returns: A decorator that parametrizes and wraps the test function. Example: >>> from monarch.config import parametrize_config_pointwise >>> >>> @parametrize_config_pointwise( ... actor_queue_dispatch=[True, False], ... prefix_with_rank=[True, False], ... ) ... async def test_actor_feature(): ... # Runs 2 times: ... # (actor_queue_dispatch=True, prefix_with_rank=True) ... # (actor_queue_dispatch=False, prefix_with_rank=False) ... pass """ import asyncio import functools import inspect import pytest if not config_options: raise ValueError( "parametrize_config_pointwise requires at least one config option" ) keys = list(config_options.keys()) value_lists = [list(config_options[k]) for k in keys] lengths = {len(v) for v in value_lists} if len(lengths) != 1: raise ValueError( "parametrize_config_pointwise requires every option to have the " f"same number of values; got lengths {dict(zip(keys, [len(v) for v in value_lists]))}" ) combinations = list(zip(*value_lists)) param_ids = [ "-".join(f"{k}={v}" for k, v in zip(keys, combo)) for combo in combinations ] config_dicts = [dict(zip(keys, combo)) for combo in combinations] def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: orig_sig = inspect.signature(fn) new_params = [ inspect.Parameter( "_config_overrides", inspect.Parameter.POSITIONAL_OR_KEYWORD ) ] + list(orig_sig.parameters.values()) new_sig = orig_sig.replace(parameters=new_params) if asyncio.iscoroutinefunction(fn): async def async_wrapper( _config_overrides: Dict[str, Any], *args: Any, **kwargs: Any ) -> Any: with configured(**_config_overrides): return await fn(*args, **kwargs) functools.update_wrapper(async_wrapper, fn) async_wrapper.__signature__ = new_sig # type: ignore[attr-defined] wrapped = async_wrapper else: def sync_wrapper( _config_overrides: Dict[str, Any], *args: Any, **kwargs: Any ) -> Any: with configured(**_config_overrides): return fn(*args, **kwargs) functools.update_wrapper(sync_wrapper, fn) sync_wrapper.__signature__ = new_sig # type: ignore[attr-defined] wrapped = sync_wrapper return pytest.mark.parametrize( "_config_overrides", config_dicts, ids=param_ids )(wrapped) return decorator