[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.
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()