[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:
Teng Ma
2026-09-17 15:51:21 +08:00
committed by GitHub
co-authored by Teng Ma
parent aebae58b8c
commit 15b256bdb0
3 changed files with 148 additions and 17 deletions
@@ -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. 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. 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** **Using extra-config of sglang arguments to configure Mooncake**
```bash ```bash
@@ -106,6 +106,25 @@ class MooncakeStoreConfig:
ssd_offload_path: Optional[str] = None ssd_offload_path: Optional[str] = None
tenant_id: str = DEFAULT_TENANT_ID 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 @staticmethod
def from_file() -> "MooncakeStoreConfig": def from_file() -> "MooncakeStoreConfig":
"""Load the config from a JSON file.""" """Load the config from a JSON file."""
@@ -129,9 +148,7 @@ class MooncakeStoreConfig:
) )
return MooncakeStoreConfig( return MooncakeStoreConfig(
local_hostname=config.get( local_hostname=MooncakeStoreConfig._resolve_local_hostname(config),
"local_hostname", envs.MOONCAKE_LOCAL_HOSTNAME.default
),
metadata_server=config.get( metadata_server=config.get(
"metadata_server", envs.MOONCAKE_TE_META_DATA_SERVER.default "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." "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( return MooncakeStoreConfig(
local_hostname=local_hostname, local_hostname=MooncakeStoreConfig._resolve_local_hostname(),
metadata_server=envs.MOONCAKE_TE_META_DATA_SERVER.get(), metadata_server=envs.MOONCAKE_TE_META_DATA_SERVER.get(),
global_segment_size=_parse_global_segment_size( global_segment_size=_parse_global_segment_size(
envs.MOONCAKE_GLOBAL_SEGMENT_SIZE.get() envs.MOONCAKE_GLOBAL_SEGMENT_SIZE.get()
@@ -220,9 +227,7 @@ class MooncakeStoreConfig:
) )
return MooncakeStoreConfig( return MooncakeStoreConfig(
local_hostname=extra_config.get( local_hostname=MooncakeStoreConfig._resolve_local_hostname(extra_config),
"local_hostname", envs.MOONCAKE_LOCAL_HOSTNAME.default
),
metadata_server=extra_config.get( metadata_server=extra_config.get(
"metadata_server", envs.MOONCAKE_TE_META_DATA_SERVER.default "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()