Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion miles/ray/train/cell.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
StatePending,
StateStopped,
)
from miles.utils.ft_utils.control_server.models import CellStatus
from miles.utils.ft_utils.api_server.models import CellStatus
from miles.utils.ft_utils.health_checker import BaseHealthChecker
from miles.utils.ft_utils.indep_dp import IndepDPInfo
from miles.utils.tracking_utils.structured_log import log_structured
Expand Down
2 changes: 1 addition & 1 deletion miles/ray/train/cell_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
StatePending,
StateStopped,
)
from miles.utils.ft_utils.control_server.models import CellCondition, CellStatus, TriState
from miles.utils.ft_utils.api_server.models import CellCondition, CellStatus, TriState
from miles.utils.ft_utils.health_checker import SimpleHealthChecker, SimpleHealthCheckerConfig

if TYPE_CHECKING:
Expand Down
8 changes: 4 additions & 4 deletions miles/utils/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -992,10 +992,10 @@ def add_fault_tolerance_arguments(parser):
help="Initial grace period (in seconds) before starting health checks. This allows time for model compilation and initialization. Increase this value significantly when using deepgemm.",
)
parser.add_argument(
"--control-server-port",
"--api-server-port",
type=int,
default=0,
help="Port for HTTP control server. 0 = disabled.",
help="Port for HTTP api server. 0 = disabled.",
)
parser.add_argument(
"--mini-ft-controller-enable",
Expand Down Expand Up @@ -2901,8 +2901,8 @@ def miles_validate_args(args):
args.ft_components = _resolve_ft_components(args)
args.eval_datasets = _resolve_eval_datasets(args)

if args.mini_ft_controller_enable and args.control_server_port == 0:
raise ValueError("--mini-ft-controller-enable requires --control-server-port to be set (non-zero)")
if args.mini_ft_controller_enable and args.api_server_port == 0:
raise ValueError("--mini-ft-controller-enable requires --api-server-port to be set (non-zero)")

if "train" in args.ft_components:
args.indep_dp = True
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import abc

from miles.ray.train.group import RayTrainGroup
from miles.utils.ft_utils.control_server.models import Cell, CellCondition, CellMetadata, CellSpec, CellStatus
from miles.utils.ft_utils.api_server.models import Cell, CellCondition, CellMetadata, CellSpec, CellStatus
from miles.utils.test_utils.fault_injector import FailureMode


Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from miles.utils.ft_utils.control_server.handles import _CellHandle
from miles.utils.ft_utils.api_server.handles import _CellHandle


class _CellRegistry:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,17 @@
from starlette.responses import JSONResponse

from miles.ray.train.group import RayTrainGroup
from miles.utils.ft_utils.control_server.handles import _ActorCellHandle, _CellHandle, _RolloutCellHandle
from miles.utils.ft_utils.control_server.models import (
Cell,
CellList,
CellPatch,
FaultInjection,
K8sStatus,
_OkResponse,
)
from miles.utils.ft_utils.control_server.registry import _CellRegistry
from miles.utils.ft_utils.api_server.handles import _ActorCellHandle, _CellHandle, _RolloutCellHandle
from miles.utils.ft_utils.api_server.models import Cell, CellList, CellPatch, FaultInjection, K8sStatus, _OkResponse
from miles.utils.ft_utils.api_server.registry import _CellRegistry

logger = logging.getLogger(__name__)


# -------------------------- entrypoint ------------------------------


def start_control_server(
def start_api_server(
*,
actor_model: RayTrainGroup,
inference_controller: object,
Expand All @@ -49,24 +42,24 @@ def start_control_server(
)
)

_start_control_server_raw(registry=registry, port=port)
_start_api_server_raw(registry=registry, port=port)


def _start_control_server_raw(registry: _CellRegistry, port: int) -> None:
app = _create_control_app(registry)
def _start_api_server_raw(registry: _CellRegistry, port: int) -> None:
app = _create_api_app(registry)

def _run() -> None:
uvicorn.run(app, host="0.0.0.0", port=port)

thread = threading.Thread(target=_run, daemon=True)
thread.start()
logger.info("Control server started on port %d", port)
logger.info("Api server started on port %d", port)


# -------------------------- main app ------------------------------


def _create_control_app(registry: _CellRegistry) -> FastAPI:
def _create_api_app(registry: _CellRegistry) -> FastAPI:
app = FastAPI()

# -------------------------- exceptions ------------------------------
Expand Down
2 changes: 1 addition & 1 deletion miles/utils/ft_utils/health_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import Any

from miles.backends.sglang_utils.sglang_api_client import SGLangApiClient
from miles.utils.ft_utils.control_server.models import TriState
from miles.utils.ft_utils.api_server.models import TriState
from miles.utils.pydantic_utils import StrictBaseModel
from miles.utils.test_utils.clock import Clock, RealClock
from miles.utils.tracking_utils.structured_log import log_structured
Expand Down
8 changes: 4 additions & 4 deletions miles/utils/ft_utils/mini_ft_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import httpx

from miles.utils.ft_utils.control_server.models import Cell, CellList, CellPatch, CellPatchSpec, TriState
from miles.utils.ft_utils.api_server.models import Cell, CellList, CellPatch, CellPatchSpec, TriState
from miles.utils.pydantic_utils import StrictBaseModel
from miles.utils.tracking_utils.structured_log import log_structured

Expand All @@ -26,7 +26,7 @@ def maybe_start_mini_ft_controller(args: Any) -> None:
return

runner = _MiniFTControllerRunner(
control_server_url=f"http://127.0.0.1:{args.control_server_port}",
api_server_url=f"http://127.0.0.1:{args.api_server_port}",
poll_interval=args.mini_ft_controller_poll_interval,
resume_delay=args.mini_ft_controller_resume_delay,
)
Expand All @@ -46,11 +46,11 @@ class _MiniFTControllerRunner:
def __init__(
self,
*,
control_server_url: str,
api_server_url: str,
poll_interval: float,
resume_delay: float,
) -> None:
url = control_server_url.rstrip("/")
url = api_server_url.rstrip("/")
self._client = httpx.AsyncClient(base_url=url, timeout=30.0)
self._controller = _MiniFTController(
get_cells=self._get_cells,
Expand Down
6 changes: 3 additions & 3 deletions tests/e2e/ft/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,13 +239,13 @@ Type: non-comparison (no baseline, no compare)
Steps: 30 (default), configurable via --num-steps

Architecture (external fault injection, not inside training loop):
1. Start training with indep_dp + control server (port 18080) + mini FT controller
1. Start training with indep_dp + api server (port 18080) + mini FT controller
2. Start a background daemon thread that:
a. Sleeps a random interval (exponential, mean = 60s / crash_probability ≈ 120s at the default)
b. GET /api/v1/cells — read each cell's Healthy condition
c. Count the genuinely-alive cells — reported Healthy, minus cells we injected that have
not finished a down->up recovery (RecoveryGate) — and skip if injecting would leave
<=1 of them. The control server reports a just-killed cell Healthy for ~95s >> the
<=1 of them. The api server reports a just-killed cell Healthy for ~95s >> the
inject interval, so excluding still-recovering cells is what keeps >=1 live replica
(indep_dp cannot heal from zero survivors).
d. Otherwise POST /api/v1/cells/{name}/inject-fault with a random failure mode
Expand Down Expand Up @@ -273,7 +273,7 @@ Type: non-comparison (no baseline run; reference = the baseline test's wandb cur
Recipe: Qwen2.5-0.5B, GRPO, 250 rollouts; parallelism mirrors dp2_cp2_real_rollout
(2 cells x CP2 on 4 train GPUs + 4 rollout engines x 1 GPU, disaggregated)
Faults: same external random injection loop as scenario_ft_random
(train cells via control server)
(train cells via api server)

Assertion: --ci-metric-checker-key eval/gsm8k with a threshold that must stay
identical to the no-fault baseline's (0.55): fault recovery must not cost
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/ft/conftest_ft/execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def get_common_train_args(


def get_ft_args(mode: FTTestMode) -> str:
return "--use-fault-tolerance " "--ft-components train " "--control-server-port 0 "
return "--use-fault-tolerance " "--ft-components train " "--api-server-port 0 "


# Required for reproducibility (ref: https://github.com/THUDM/slime/pull/370)
Expand Down
8 changes: 4 additions & 4 deletions tests/e2e/ft/conftest_ft/fault_injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

logger = logging.getLogger(__name__)

CONTROL_SERVER_PORT: int = 18080
API_SERVER_PORT: int = 18080
MEAN_INTERVAL_SECONDS: float = 60.0
# Poll cell liveness this often so the gate tracks a crash->detect->heal cycle even when it
# happens entirely between two (much sparser) injections; injections still fire on the long
Expand All @@ -27,7 +27,7 @@ def cell_is_alive(cell: dict) -> bool:


class _CellState(enum.Enum):
INJECTED = enum.auto() # we crashed it; the control server may still report it Healthy
INJECTED = enum.auto() # we crashed it; the api server may still report it Healthy
RECOVERING = enum.auto() # observed unhealthy; awaiting its return to Healthy


Expand Down Expand Up @@ -76,7 +76,7 @@ def run_fault_injection_loop(
resp.raise_for_status()
cells = resp.json()["items"]
except Exception:
logger.info("Failed to list cells from control server", exc_info=True)
logger.info("Failed to list cells from api server", exc_info=True)
continue

# Track recovery on every poll so a crash->detect->heal cycle that completes between two
Expand Down Expand Up @@ -139,7 +139,7 @@ def _on_successful_injection(self) -> None:


def spawn_fault_injector(*, seed: int, mean_interval_seconds: float) -> FaultInjectorHandle:
base_url = f"http://localhost:{CONTROL_SERVER_PORT}"
base_url = f"http://localhost:{API_SERVER_PORT}"
handle = FaultInjectorHandle(base_url=base_url, seed=seed, mean_interval_seconds=mean_interval_seconds)
handle.start()
return handle
6 changes: 3 additions & 3 deletions tests/e2e/ft/conftest_ft/scenario_ft_random.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
prepare,
run_training,
)
from tests.e2e.ft.conftest_ft.fault_injection import CONTROL_SERVER_PORT, MEAN_INTERVAL_SECONDS, spawn_fault_injector
from tests.e2e.ft.conftest_ft.fault_injection import API_SERVER_PORT, MEAN_INTERVAL_SECONDS, spawn_fault_injector
from tests.e2e.ft.conftest_ft.modes import FTTestMode, resolve_mode

from miles.utils.test_utils.reconfigure_assertions import assert_soak_reconfigure_events
Expand All @@ -34,7 +34,7 @@ def run_ci(
"""Random failure soak test.

Starts a background thread that injects faults at random intervals via the
control server HTTP API. The mini FT controller auto-recovers; the test passes
api server HTTP API. The mini FT controller auto-recovers; the test passes
if training completes without hanging.

Doubles as the per-mode CI entry point: a CI file calls ``run_ci(mode)`` (defaults);
Expand All @@ -54,7 +54,7 @@ def run_ci(
ft_mode, dump_dir=dump_dir, num_steps=num_steps, debug_rollout_data_dir=debug_rollout_data_dir
)
+ get_ft_args(ft_mode)
+ f"--control-server-port {CONTROL_SERVER_PORT} "
+ f"--api-server-port {API_SERVER_PORT} "
+ "--mini-ft-controller-enable "
)

Expand Down
4 changes: 2 additions & 2 deletions tests/e2e/ft/conftest_ft/scenario_realistic_gsm8k.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import typer

from tests.e2e.ft.conftest_ft.app import resolve_dump_dir
from tests.e2e.ft.conftest_ft.fault_injection import CONTROL_SERVER_PORT, MEAN_INTERVAL_SECONDS, spawn_fault_injector
from tests.e2e.ft.conftest_ft.fault_injection import API_SERVER_PORT, MEAN_INTERVAL_SECONDS, spawn_fault_injector

import miles.utils.external_utils.command_utils as U

Expand Down Expand Up @@ -146,7 +146,7 @@ def _get_gsm8k_train_args(*, seed: int, num_rollout: int, metric_threshold: floa
fault_tolerance_args = (
"--use-fault-tolerance "
"--ft-components train "
f"--control-server-port {CONTROL_SERVER_PORT} "
f"--api-server-port {API_SERVER_PORT} "
"--mini-ft-controller-enable "
)

Expand Down
2 changes: 1 addition & 1 deletion tests/fast/e2e/ft/test_fault_injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def test_fresh_gate_counts_every_healthy_cell_as_alive() -> None:


def test_injected_cell_is_excluded_while_its_crash_is_still_undetected() -> None:
"""The control server's stale 'still healthy' view must not count a just-killed cell."""
"""The api server's stale 'still healthy' view must not count a just-killed cell."""
gate = RecoveryGate()
cells = [_cell("c0", healthy=True), _cell("c1", healthy=True)]
gate.note_injected("c0")
Expand Down
2 changes: 1 addition & 1 deletion tests/fast/ray/train/test_cell_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
StatePending,
StateStopped,
)
from miles.utils.ft_utils.control_server.models import TriState
from miles.utils.ft_utils.api_server.models import TriState
from miles.utils.ft_utils.health_checker import SimpleHealthCheckerConfig
from miles.utils.ft_utils.indep_dp import IndepDPInfo

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
import httpx
import pytest

from miles.utils.ft_utils.control_server.models import Cell, CellCondition, CellMetadata, CellSpec, CellStatus
from miles.utils.ft_utils.control_server.registry import _CellRegistry
from miles.utils.ft_utils.control_server.server import _create_control_app
from miles.utils.ft_utils.api_server.models import Cell, CellCondition, CellMetadata, CellSpec, CellStatus
from miles.utils.ft_utils.api_server.registry import _CellRegistry
from miles.utils.ft_utils.api_server.server import _create_api_app


class MockHandle:
Expand Down Expand Up @@ -146,7 +146,7 @@ def is_stopped(self) -> bool:
return self._is_stopped

def cell_status(self) -> CellStatus:
from miles.utils.ft_utils.control_server.models import CellCondition, CellStatus
from miles.utils.ft_utils.api_server.models import CellCondition, CellStatus

return CellStatus(
phase=self._phase,
Expand All @@ -171,6 +171,6 @@ def registry() -> _CellRegistry:

@pytest.fixture
def async_client(registry: _CellRegistry) -> httpx.AsyncClient:
app = _create_control_app(registry)
app = _create_api_app(registry)
transport = httpx.ASGITransport(app=app)
return httpx.AsyncClient(transport=transport, base_url="http://test")
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pytest

from miles.ray.train.group import RayTrainGroup
from miles.utils.ft_utils.control_server.handles import _ActorCellHandle, _CellHandle, _RolloutCellHandle
from miles.utils.ft_utils.api_server.handles import _ActorCellHandle, _CellHandle, _RolloutCellHandle
from miles.utils.test_utils.fault_injector import FailureMode

from .conftest import MockInferenceController, MockRayTrainCell, make_mock_group
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest

from miles.utils.ft_utils.control_server.registry import _CellRegistry
from miles.utils.ft_utils.api_server.registry import _CellRegistry

from .conftest import MockHandle

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import httpx
import pytest

from miles.utils.ft_utils.control_server.registry import _CellRegistry
from miles.utils.ft_utils.api_server.registry import _CellRegistry

from .conftest import MockHandle

Expand Down
2 changes: 1 addition & 1 deletion tests/fast/utils/test_health_checker.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import asyncio

from miles.utils.ft_utils.control_server.models import TriState
from miles.utils.ft_utils.api_server.models import TriState
from miles.utils.ft_utils.health_checker import NoopHealthChecker, SimpleHealthChecker
from miles.utils.test_utils.clock import FakeClock

Expand Down
Loading
Loading