[HiCache] fix: resolve Mooncake local_hostname per node for runtime attach (#29668)
Co-authored-by: Teng Ma <11641725+stmatengss@users.noreply.github.com>
This commit is contained in:
@@ -196,6 +196,10 @@ Mooncake loads configuration in the following priority order:
|
||||
2. If not, Mooncake checks whether the environment variable `DEFAULT_MOONCAKE_CONFIG_PATH_ENV` is set, and loads the JSON config file from that path.
|
||||
3. If neither of the above is provided, Mooncake falls back to environment variables.
|
||||
|
||||
For multi-node deployments that attach Mooncake at runtime via `PUT /hicache/storage-backend`, omit `local_hostname` from the attach payload and set `MOONCAKE_LOCAL_HOSTNAME` (or `LOCAL_HOSTNAME`) per node before launching SGLang. Each rank resolves `local_hostname` from its own process environment instead of a shared default.
|
||||
|
||||
When loading from a JSON config file, `local_hostname` follows the same per-process precedence: `MOONCAKE_LOCAL_HOSTNAME`, then `LOCAL_HOSTNAME`, then the value in the JSON file, then `"localhost"`.
|
||||
|
||||
**Using extra-config of sglang arguments to configure Mooncake**
|
||||
|
||||
```bash
|
||||
|
||||
@@ -106,6 +106,25 @@ class MooncakeStoreConfig:
|
||||
ssd_offload_path: Optional[str] = None
|
||||
tenant_id: str = DEFAULT_TENANT_ID
|
||||
|
||||
@staticmethod
|
||||
def _resolve_local_hostname(overrides: Optional[dict] = None) -> str:
|
||||
"""Resolve local_hostname for the current process.
|
||||
|
||||
Process environment takes precedence over config overrides so multi-node
|
||||
runtime attach can broadcast shared extra_config while each node uses its
|
||||
own MOONCAKE_LOCAL_HOSTNAME / LOCAL_HOSTNAME.
|
||||
"""
|
||||
if envs.MOONCAKE_LOCAL_HOSTNAME.is_set():
|
||||
return envs.MOONCAKE_LOCAL_HOSTNAME.get()
|
||||
local_hostname = os.getenv("LOCAL_HOSTNAME")
|
||||
if local_hostname:
|
||||
return local_hostname
|
||||
if overrides is not None:
|
||||
value = overrides.get("local_hostname")
|
||||
if value:
|
||||
return value
|
||||
return envs.MOONCAKE_LOCAL_HOSTNAME.default
|
||||
|
||||
@staticmethod
|
||||
def from_file() -> "MooncakeStoreConfig":
|
||||
"""Load the config from a JSON file."""
|
||||
@@ -129,9 +148,7 @@ class MooncakeStoreConfig:
|
||||
)
|
||||
|
||||
return MooncakeStoreConfig(
|
||||
local_hostname=config.get(
|
||||
"local_hostname", envs.MOONCAKE_LOCAL_HOSTNAME.default
|
||||
),
|
||||
local_hostname=MooncakeStoreConfig._resolve_local_hostname(config),
|
||||
metadata_server=config.get(
|
||||
"metadata_server", envs.MOONCAKE_TE_META_DATA_SERVER.default
|
||||
),
|
||||
@@ -180,18 +197,8 @@ class MooncakeStoreConfig:
|
||||
"Either the environment variable 'MOONCAKE_MASTER' or 'MOONCAKE_CLIENT' is not set."
|
||||
)
|
||||
|
||||
# Special handling for local_hostname: try MOONCAKE_LOCAL_HOSTNAME first,
|
||||
# then fall back to LOCAL_HOSTNAME if not set.
|
||||
# This is for forward compatibility with the legacy LOCAL_HOSTNAME environment variable.
|
||||
if envs.MOONCAKE_LOCAL_HOSTNAME.is_set():
|
||||
local_hostname = envs.MOONCAKE_LOCAL_HOSTNAME.get()
|
||||
else:
|
||||
local_hostname = os.getenv(
|
||||
"LOCAL_HOSTNAME", envs.MOONCAKE_LOCAL_HOSTNAME.default
|
||||
)
|
||||
|
||||
return MooncakeStoreConfig(
|
||||
local_hostname=local_hostname,
|
||||
local_hostname=MooncakeStoreConfig._resolve_local_hostname(),
|
||||
metadata_server=envs.MOONCAKE_TE_META_DATA_SERVER.get(),
|
||||
global_segment_size=_parse_global_segment_size(
|
||||
envs.MOONCAKE_GLOBAL_SEGMENT_SIZE.get()
|
||||
@@ -220,9 +227,7 @@ class MooncakeStoreConfig:
|
||||
)
|
||||
|
||||
return MooncakeStoreConfig(
|
||||
local_hostname=extra_config.get(
|
||||
"local_hostname", envs.MOONCAKE_LOCAL_HOSTNAME.default
|
||||
),
|
||||
local_hostname=MooncakeStoreConfig._resolve_local_hostname(extra_config),
|
||||
metadata_server=extra_config.get(
|
||||
"metadata_server", envs.MOONCAKE_TE_META_DATA_SERVER.default
|
||||
),
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
"""Unit tests for MooncakeStoreConfig local_hostname resolution.
|
||||
|
||||
Regression for sgl-project/sglang#23457: runtime attach must resolve
|
||||
per-node local_hostname from process environment instead of a shared default.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from sglang.srt.mem_cache.storage.mooncake_store.mooncake_store import (
|
||||
MooncakeStoreConfig,
|
||||
)
|
||||
from sglang.srt.utils.common import temp_set_env
|
||||
from sglang.test.ci.ci_register import register_cpu_ci
|
||||
from sglang.test.test_utils import CustomTestCase
|
||||
|
||||
register_cpu_ci(est_time=5, suite="base-a-test-cpu")
|
||||
|
||||
|
||||
class TestMooncakeStoreConfigLocalHostname(CustomTestCase):
|
||||
_BASE_EXTRA_CONFIG = {"master_server_address": "127.0.0.1:50051"}
|
||||
|
||||
def _load_from_extra_config(self, extra_config=None):
|
||||
config = {**self._BASE_EXTRA_CONFIG, **(extra_config or {})}
|
||||
return MooncakeStoreConfig.load_from_extra_config(config)
|
||||
|
||||
def test_load_from_extra_config_uses_mooncake_env_when_omitted(self):
|
||||
with temp_set_env(MOONCAKE_LOCAL_HOSTNAME="10.0.0.2"):
|
||||
cfg = self._load_from_extra_config()
|
||||
self.assertEqual(cfg.local_hostname, "10.0.0.2")
|
||||
|
||||
def test_load_from_extra_config_uses_local_hostname_env_when_omitted(self):
|
||||
with temp_set_env(LOCAL_HOSTNAME="10.0.0.3"):
|
||||
cfg = self._load_from_extra_config()
|
||||
self.assertEqual(cfg.local_hostname, "10.0.0.3")
|
||||
|
||||
def test_load_from_extra_config_prefers_mooncake_env_over_local_hostname(self):
|
||||
with temp_set_env(
|
||||
MOONCAKE_LOCAL_HOSTNAME="10.0.0.7", LOCAL_HOSTNAME="10.0.0.8"
|
||||
):
|
||||
cfg = self._load_from_extra_config()
|
||||
self.assertEqual(cfg.local_hostname, "10.0.0.7")
|
||||
|
||||
def test_load_from_extra_config_defaults_to_localhost_without_env(self):
|
||||
with temp_set_env(MOONCAKE_LOCAL_HOSTNAME=None, LOCAL_HOSTNAME=None):
|
||||
cfg = self._load_from_extra_config()
|
||||
self.assertEqual(cfg.local_hostname, "localhost")
|
||||
|
||||
def test_load_from_extra_config_uses_explicit_override_without_env(self):
|
||||
with temp_set_env(MOONCAKE_LOCAL_HOSTNAME=None, LOCAL_HOSTNAME=None):
|
||||
cfg = self._load_from_extra_config({"local_hostname": "10.0.0.9"})
|
||||
self.assertEqual(cfg.local_hostname, "10.0.0.9")
|
||||
|
||||
def test_load_from_extra_config_prefers_env_over_broadcast_override(self):
|
||||
with temp_set_env(MOONCAKE_LOCAL_HOSTNAME="10.0.0.4"):
|
||||
cfg = self._load_from_extra_config({"local_hostname": "10.0.0.1"})
|
||||
self.assertEqual(cfg.local_hostname, "10.0.0.4")
|
||||
|
||||
def test_load_from_env_uses_mooncake_env(self):
|
||||
with temp_set_env(
|
||||
MOONCAKE_LOCAL_HOSTNAME="10.0.0.5", MOONCAKE_MASTER="127.0.0.1:50051"
|
||||
):
|
||||
cfg = MooncakeStoreConfig.load_from_env()
|
||||
self.assertEqual(cfg.local_hostname, "10.0.0.5")
|
||||
|
||||
def test_load_from_env_uses_local_hostname_env(self):
|
||||
with temp_set_env(
|
||||
LOCAL_HOSTNAME="10.0.0.10", MOONCAKE_MASTER="127.0.0.1:50051"
|
||||
):
|
||||
cfg = MooncakeStoreConfig.load_from_env()
|
||||
self.assertEqual(cfg.local_hostname, "10.0.0.10")
|
||||
|
||||
def test_from_file_uses_process_env_when_local_hostname_omitted(self):
|
||||
with tempfile.NamedTemporaryFile("w", delete=False) as fin:
|
||||
json.dump(
|
||||
{
|
||||
"master_server_address": "127.0.0.1:50051",
|
||||
"metadata_server": "P2PHANDSHAKE",
|
||||
},
|
||||
fin,
|
||||
)
|
||||
config_path = fin.name
|
||||
|
||||
try:
|
||||
with temp_set_env(
|
||||
allow_sglang=True,
|
||||
SGLANG_HICACHE_MOONCAKE_CONFIG_PATH=config_path,
|
||||
MOONCAKE_LOCAL_HOSTNAME="10.0.0.6",
|
||||
):
|
||||
cfg = MooncakeStoreConfig.from_file()
|
||||
self.assertEqual(cfg.local_hostname, "10.0.0.6")
|
||||
finally:
|
||||
os.unlink(config_path)
|
||||
|
||||
def test_from_file_prefers_env_over_file_local_hostname(self):
|
||||
with tempfile.NamedTemporaryFile("w", delete=False) as fin:
|
||||
json.dump(
|
||||
{
|
||||
"master_server_address": "127.0.0.1:50051",
|
||||
"metadata_server": "P2PHANDSHAKE",
|
||||
"local_hostname": "10.0.0.1",
|
||||
},
|
||||
fin,
|
||||
)
|
||||
config_path = fin.name
|
||||
|
||||
try:
|
||||
with temp_set_env(
|
||||
allow_sglang=True,
|
||||
SGLANG_HICACHE_MOONCAKE_CONFIG_PATH=config_path,
|
||||
MOONCAKE_LOCAL_HOSTNAME="10.0.0.11",
|
||||
):
|
||||
cfg = MooncakeStoreConfig.from_file()
|
||||
self.assertEqual(cfg.local_hostname, "10.0.0.11")
|
||||
finally:
|
||||
os.unlink(config_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user