test_switchless_ring_integration.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. #!/usr/bin/env python3
  2. """Offline launcher tests: no Docker, SSH, sysfs or external model paths."""
  3. import importlib.util
  4. import os
  5. from pathlib import Path
  6. import re
  7. import shlex
  8. import subprocess
  9. import sys
  10. import tempfile
  11. from types import SimpleNamespace
  12. from unittest.mock import patch
  13. ROOT = Path(__file__).resolve().parents[1]
  14. def load_sitecustomize():
  15. spec = importlib.util.spec_from_file_location("dsv41_test_sitecustomize", ROOT / "adapter/sitecustomize.py")
  16. module = importlib.util.module_from_spec(spec)
  17. with patch.dict(os.environ, {"DSV41_SOURCE": ""}):
  18. spec.loader.exec_module(module)
  19. return module
  20. class FakeLoader:
  21. def create_module(self, spec):
  22. return None
  23. def exec_module(self, module):
  24. module.should_async_load = lambda weight: True
  25. def test_serial_weight_load_opt_in_and_default():
  26. sc = load_sitecustomize()
  27. before = list(sys.meta_path)
  28. for value, expected in (("1", False), ("0", True), (None, True)):
  29. module = SimpleNamespace(__name__="sglang.srt.model_loader.utils")
  30. with patch.dict(os.environ):
  31. os.environ.pop("DSV41_SERIAL_WEIGHT_LOAD", None)
  32. if value is not None:
  33. os.environ["DSV41_SERIAL_WEIGHT_LOAD"] = value
  34. sc.EngramLoader(FakeLoader()).exec_module(module)
  35. assert module.should_async_load(object()) is expected
  36. assert sys.meta_path == before
  37. def test_serial_finder_does_not_intercept_when_disabled():
  38. sc = load_sitecustomize()
  39. with patch.dict(os.environ, {"DSV41_SERIAL_WEIGHT_LOAD": "0"}), patch.object(sc.importlib.machinery.PathFinder, "find_spec") as finder:
  40. assert sc.EngramFinder().find_spec("sglang.srt.model_loader.utils") is None
  41. finder.assert_not_called()
  42. def test_serial_loader_rejects_incompatible_upstream():
  43. sc = load_sitecustomize()
  44. loader = SimpleNamespace(exec_module=lambda module: None)
  45. with patch.dict(os.environ, {"DSV41_SERIAL_WEIGHT_LOAD": "1"}):
  46. try:
  47. sc.EngramLoader(loader).exec_module(SimpleNamespace(__name__="sglang.srt.model_loader.utils"))
  48. except RuntimeError as exc:
  49. assert "should_async_load" in str(exc)
  50. else:
  51. raise AssertionError("incompatible loader accepted")
  52. def launcher_source():
  53. source = (ROOT / "start.sh").read_text()
  54. # Run real defaults and function definitions; omit env sourcing and dispatch.
  55. # Every filesystem operation in this setup targets the fixture below.
  56. source = source[source.index('HEAD_IP='):source.index('case "$CMD" in')]
  57. return "set -euo pipefail\n" + source
  58. def shell(code, directory, settings=None):
  59. env = {"PATH": os.environ["PATH"], "HOME": os.environ["HOME"], "ROOT": str(ROOT)}
  60. for name in ("MODEL_DIR", "COMMON_MODEL", "SSH_IDENTITY", "NCCL_HOST_DIR", "STATE_DIR", "LOG_DIR", "WORKER_DIR", "WORKER_ENGRAM_DIR", "ENGRAM_DIR"):
  61. env[name] = str(directory / name)
  62. Path(env["MODEL_DIR"]).mkdir(exist_ok=True)
  63. Path(env["MODEL_DIR"], "config.json").write_text("{}")
  64. env.update(settings or {})
  65. return subprocess.run(["bash", "-c", launcher_source() + "\n" + code], env=env, cwd=ROOT, text=True, capture_output=True, timeout=20)
  66. def check(result):
  67. assert result.returncode == 0, result.stdout + result.stderr
  68. return result.stdout
  69. def pairs(args, flag):
  70. return [args[i + 1] for i, item in enumerate(args[:-1]) if item == flag]
  71. def test_head_worker_env_defaults_and_quoted_opt_in():
  72. with tempfile.TemporaryDirectory(dir=ROOT / "tests") as tmp:
  73. d = Path(tmp)
  74. for ring in (False, True):
  75. settings = {"SERVED_MODEL_NAME": "model name; $(false) 'quoted'", "DSV41_SERIAL_WEIGHT_LOAD": "1" if ring else "0"}
  76. if ring:
  77. settings.update(NCCL_SWITCHLESS_RING_ONLY="1", NCCL_MIN_NCHANNELS="4", NCCL_MAX_NCHANNELS="4")
  78. for rank in (1, 2, 3):
  79. code = f'''
  80. head=()
  81. docker_common_args head "$HEAD_IP" 3
  82. printf '%s\\0' "${{head[@]}}"
  83. printf 'SPLIT\\0'
  84. capture() {{ printf '%s\\0' "$@"; }}
  85. eval "capture $(worker_env_lines 10.0.0.2 3 {rank})
  86. end-of-command"
  87. '''
  88. if ring:
  89. libdir = d / "NCCL_HOST_DIR"
  90. libdir.mkdir(exist_ok=True)
  91. (libdir / "libnccl.so.2").write_text("SWITCHLESS_RING_ONLY")
  92. out = check(shell(code, d, settings)).split("\0")
  93. split = out.index("SPLIT")
  94. head, worker = out[:split], out[split + 1:]
  95. he, we = pairs(head, "-e"), pairs(worker, "-e")
  96. for value in ("DSPARK_BLOCK_SIZE=3", "SGLANG_DSV41_REASONING_EFFORT=75", "DSV41_MAX_NEW_TOKENS=32768", "DSV41_LOOP_ABORT=1", "SERVED_MODEL_NAME=" + settings["SERVED_MODEL_NAME"]):
  97. assert value in he and value in we, value
  98. assert f"NODE_RANK={rank}" in we
  99. assert "end-of-command" in worker
  100. assert ("NCCL_SWITCHLESS_RING_ONLY=1" in he) == ring
  101. assert ("NCCL_SWITCHLESS_RING_ONLY=1" in we) == ring
  102. if ring:
  103. assert [x for x in he if x.startswith("NCCL_") and x != "NCCL_IB_GID_INDEX=3"] == [x for x in we if x.startswith("NCCL_") and x != "NCCL_IB_GID_INDEX=3"]
  104. assert not any(x.startswith("LD_LIBRARY_PATH=") for x in he)
  105. assert any("site-packages/nvidia/nccl" in x for x in pairs(head, "-v"))
  106. def test_nccl_library_selection_and_worker_path_quoting():
  107. with tempfile.TemporaryDirectory(dir=ROOT / "tests") as tmp:
  108. d = Path(tmp)
  109. libdir = d / "custom nccl ' $(false)"
  110. libdir.mkdir()
  111. (libdir / "libnccl.so.2").write_text("SWITCHLESS_RING_ONLY")
  112. for overlay in ("0", "1"):
  113. result = shell('''
  114. eval "$(nccl_worker_settings)"
  115. a=(); nccl_mount_args a
  116. printf '%s\\0' "${a[@]}"
  117. ''', d, {"NCCL_HOST_DIR": str(libdir), "NCCL_OVERLAY_PIP": overlay})
  118. args = check(result).split("\0")
  119. if overlay == "1":
  120. assert pairs(args, "-v") == [str(libdir / "libnccl.so.2") + ":/opt/sglang/lib/python3.12/site-packages/nvidia/nccl/lib/libnccl.so.2:ro"]
  121. assert not pairs(args, "-e")
  122. else:
  123. assert pairs(args, "-v") == [str(libdir) + ":/nccl:ro"]
  124. assert pairs(args, "-e") == ["LD_LIBRARY_PATH=/nccl"]
  125. (libdir / "libnccl.so.2.30.7").write_text("preferred")
  126. assert check(shell('nccl_library "$NCCL_HOST_DIR"', d, {"NCCL_HOST_DIR": str(libdir)})).strip().endswith("libnccl.so.2.30.7")
  127. assert shell('a=(); nccl_mount_args a', d, {"NCCL_OVERLAY_PIP": "1"}).returncode != 0
  128. output = check(shell('NCCL_HOST_DIR="$HOME/custom build"; nccl_worker_settings', d))
  129. assert 'NCCL_HOST_DIR="$HOME"/custom\\ build' in output
  130. output = check(shell('NCCL_WORKER_DIR="/custom worker"; nccl_worker_settings', d))
  131. assert 'NCCL_HOST_DIR=/custom\\ worker' in output
  132. def make_fabric(d):
  133. for name in ("rocep1s0f0", "rocep1s0f1"):
  134. port = d / name / "ports/1"
  135. (port / "gid_attrs/types").mkdir(parents=True)
  136. (port / "gids").mkdir()
  137. (port / "state").write_text("4: ACTIVE\n")
  138. (port / "gid_attrs/types/3").write_text("RoCE v2\n")
  139. (port / "gids/3").write_text("0000:0000:0000:0000:0000:ffff:0a0a:000a\n")
  140. return port
  141. def test_ring_probes_all_selected_ports_and_explicit_gid():
  142. with tempfile.TemporaryDirectory(dir=ROOT / "tests") as tmp:
  143. d = Path(tmp)
  144. port = make_fabric(d / "fabric")
  145. command = "ring_gid_index " + shlex.quote(str(d / "fabric"))
  146. assert check(shell(command, d)).strip() == "3"
  147. assert shell(command, d, {"NCCL_IB_GID_INDEX": "2"}).returncode != 0
  148. (port / "state").write_text("2: INIT\n")
  149. assert shell(command, d).returncode != 0
  150. (port / "state").write_text("4: ACTIVE\n")
  151. (port / "gid_attrs/types/3").write_text("RoCE v1\n")
  152. assert shell(command, d).returncode != 0
  153. def test_ring_config_requires_safe_overlay_and_ring_algorithm():
  154. with tempfile.TemporaryDirectory(dir=ROOT / "tests") as tmp:
  155. d = Path(tmp)
  156. settings = dict(NCCL_SWITCHLESS_RING_ONLY="1", TP_SIZE="4", EP_SIZE="4", WORKER_IPS="10.0.0.2 10.0.0.3 10.0.0.4")
  157. check(shell("nccl_validate_config", d, settings))
  158. for key, value in (("NCCL_OVERLAY_PIP", "0"), ("NCCL_ALGO", "Tree"), ("NCCL_MIN_NCHANNELS", "33"), ("NCCL_IB_DISABLE", "1"), ("DSV41_SERIAL_WEIGHT_LOAD", "yes")):
  159. assert shell("nccl_validate_config", d, {**settings, key: value}).returncode != 0
  160. for ep in ("1", "2"): # start-tp4.sh: EP groups the experts inside the TP ring
  161. check(shell("nccl_validate_config", d, {**settings, "EP_SIZE": ep, "DSV41_LAUNCHER": "tp4"}))
  162. assert shell("nccl_validate_config", d, {**settings, "EP_SIZE": ep}).returncode != 0
  163. for ep in ("3", "8", "0"):
  164. assert shell("nccl_validate_config", d, {**settings, "EP_SIZE": ep, "DSV41_LAUNCHER": "tp4"}).returncode != 0
  165. def test_preflight_rejects_stock_nccl_and_missing_pip_path():
  166. with tempfile.TemporaryDirectory(dir=ROOT / "tests") as tmp:
  167. d = Path(tmp)
  168. libdir = d / "NCCL_HOST_DIR"
  169. libdir.mkdir()
  170. library = libdir / "libnccl.so.2"
  171. library.write_text("stock")
  172. settings = {"NCCL_SWITCHLESS_RING_ONLY": "1"}
  173. code = 'docker() { return 0; }; ring_gid_index() { echo 3; }; nccl_preflight'
  174. assert shell(code, d, settings).returncode != 0
  175. library.write_text("SWITCHLESS_RING_ONLY")
  176. assert check(shell(code, d, settings)).strip() == "3"
  177. assert shell(code.replace('docker() { return 0;', 'docker() { return 1;'), d, settings).returncode != 0
  178. assert shell(code.replace('ring_gid_index() { echo 3;', 'ring_gid_index() { return 1;'), d, settings).returncode != 0
  179. def test_serve_fails_before_replacing_containers_on_last_rank_preflight():
  180. with tempfile.TemporaryDirectory(dir=ROOT / "tests") as tmp:
  181. d = Path(tmp)
  182. code = '''
  183. cmd_doctor() { :; }; _busy_gpu() { return 1; }; ln() { :; }
  184. docker() { [[ "$1 $2" == 'image inspect' ]] || { echo MUTATION; return 1; }; }
  185. remote_ok_on() { return 1; }
  186. cmd_build() { echo BUILD; }
  187. nccl_preflight() { echo 3; }
  188. remote_on() { echo "CHECK:$1" >&2; [[ "$1" != 10.0.0.4 ]] || return 1; echo 3; }
  189. cmd_share() { echo MUTATION; }
  190. cmd_serve
  191. '''
  192. result = shell(code, d, dict(NCCL_SWITCHLESS_RING_ONLY="1", TP_SIZE="4", EP_SIZE="4", WORKER_IPS="10.0.0.2 10.0.0.3 10.0.0.4"))
  193. assert result.returncode != 0
  194. assert "BUILD" in result.stdout
  195. assert "CHECK:10.0.0.4" in result.stderr
  196. assert "MUTATION" not in result.stdout
  197. def test_local_weights_never_trigger_nfs_or_teardown_when_missing():
  198. with tempfile.TemporaryDirectory(dir=ROOT / "tests") as tmp:
  199. d = Path(tmp)
  200. result = shell('''
  201. cmd_doctor() { :; }; _busy_gpu() { return 1; }; ln() { :; }
  202. docker() { [[ "$1 $2" == 'image inspect' ]] || { echo MUTATION; return 1; }; }
  203. remote_ok_on() { return 0; }; nfs_worker_has_model() { return 1; }
  204. local_model_has_weights() { return 0; }
  205. cmd_share() { echo MUTATION; }
  206. cmd_serve
  207. ''', d, {"NFS_SHARE": "0"})
  208. assert result.returncode != 0 and "local volume" in result.stderr
  209. assert "MUTATION" not in result.stdout
  210. check(shell('nfs_ensure_server() { return 99; }; cmd_share', d, {"NFS_SHARE": "0"}))
  211. def test_worker_launch_script_executes_in_default_and_ring_modes():
  212. with tempfile.TemporaryDirectory(dir=ROOT / "tests") as tmp:
  213. d = Path(tmp)
  214. libdir = d / "nccl space ' $(false)"
  215. libdir.mkdir()
  216. (libdir / "libnccl.so.2").write_text("SWITCHLESS_RING_ONLY")
  217. for ring in ("0", "1"):
  218. result = shell(r'''
  219. cmd_doctor() { :; }; _busy_gpu() { return 1; }; ln() { :; }
  220. preflight_all_nodes() { RING_GID_HEAD=3; RING_WORKER_GIDS=(3 3 3); }
  221. nfs_worker_has_model() { return 0; }; remote_ok_on() { return 0; }
  222. local_model_has_weights() { return 0; }
  223. gid_index_local() { echo 3; }; gid_index_remote() { echo 3; }
  224. push_spec_tables() { :; }
  225. docker_common_args() { exit 0; }
  226. docker() {
  227. if [[ "$1 $2" == 'run -d' ]]; then printf '%s\0' "$@";
  228. elif [[ "$1 $2" != 'image inspect' && "$1 $2" != 'volume inspect' && "$1 $2" != 'rm -f' ]]; then return 99; fi
  229. }
  230. test() { [[ "$*" == '-d /dev/infiniband' ]] || builtin test "$@"; }
  231. mkdir() { :; }
  232. remote_on() {
  233. if [[ "$2" == *'docker run -d'* ]]; then
  234. bash -c "set -euo pipefail
  235. $(declare -f docker test mkdir)
  236. $2"
  237. fi
  238. }
  239. cmd_serve
  240. ''', d, {"NFS_SHARE": "0", "NCCL_SWITCHLESS_RING_ONLY": ring, "TP_SIZE": "4", "EP_SIZE": "4", "WORKER_IPS": "10.0.0.2 10.0.0.3 10.0.0.4", "NCCL_HOST_DIR": str(libdir), "API_KEY": "fixture ' $(false)", "EXTRA_SGLANG_ARGS": "--name 'quoted ; $(false)'"})
  241. args = check(result).split("\0")
  242. for rank in (1, 2, 3):
  243. assert f"NODE_RANK={rank}" in args
  244. assert args.count("API_KEY=fixture ' $(false)") == 3
  245. assert args.count("EXTRA_SGLANG_ARGS=--name 'quoted ; $(false)'") == 3
  246. assert args.count("NCCL_SWITCHLESS_RING_ONLY=1") == (3 if ring == "1" else 0)
  247. assert args.count("LD_LIBRARY_PATH=/nccl") == (0 if ring == "1" else 3)
  248. def test_weight_status_probe_checks_contents_without_pull_or_creation():
  249. with tempfile.TemporaryDirectory(dir=ROOT / "tests") as tmp:
  250. d = Path(tmp)
  251. volume = d / "weights"
  252. volume.mkdir()
  253. code = r'''
  254. remote_on() { eval "${@: -1}"; }
  255. docker() {
  256. if [[ "$1 $2" == 'volume inspect' ]]; then [[ "$VOLUME_PRESENT" == 1 ]]; return; fi
  257. [[ "$1" == run ]] || return 90
  258. [[ " $* " == *' --pull=never '* && " $* " == *' --network none '* ]] || return 91
  259. while [[ "$1" != -c ]]; do shift; done
  260. local check="${2//\/m\//$FIXTURE_VOLUME/}"
  261. bash -c "$check" sh 2
  262. }
  263. nfs_worker_has_model worker
  264. '''
  265. settings = {"VOLUME_PRESENT": "0", "FIXTURE_VOLUME": str(volume)}
  266. assert shell(code, d, settings).returncode != 0
  267. settings["VOLUME_PRESENT"] = "1"
  268. assert shell(code, d, settings).returncode != 0
  269. (volume / "config.json").write_text("{}")
  270. (volume / "model-1-of-2.safetensors").write_text("fixture")
  271. assert shell(code, d, settings).returncode != 0
  272. (volume / "model-2-of-2.safetensors").write_text("fixture")
  273. check(shell(code, d, settings))
  274. (volume / "model-2-of-2.safetensors").write_text("")
  275. assert shell(code, d, settings).returncode != 0
  276. def test_local_head_requires_nonempty_complete_shards():
  277. with tempfile.TemporaryDirectory(dir=ROOT / "tests") as tmp:
  278. d = Path(tmp)
  279. assert shell("local_model_has_weights", d, {"EXPECTED_SHARDS": "1"}).returncode != 0
  280. shard = d / "MODEL_DIR/model-1-of-1.safetensors"
  281. shard.write_text("fixture")
  282. check(shell("local_model_has_weights", d, {"EXPECTED_SHARDS": "1"}))
  283. shard.write_text("")
  284. assert shell("local_model_has_weights", d, {"EXPECTED_SHARDS": "1"}).returncode != 0
  285. def test_nfs_root_and_reused_subdirectory_are_preserved():
  286. with tempfile.TemporaryDirectory(dir=ROOT / "tests") as tmp:
  287. d = Path(tmp)
  288. export = d / "export" / "dsv41-native"
  289. export.mkdir(parents=True)
  290. (export / "config.json").write_text("{}")
  291. (export / "model-1-of-1.safetensors").write_text("fixture")
  292. output = check(shell('''
  293. NFS_REUSE_EXPORT=0; nfs_publish_model; printf 'DEVICE=%s\\n' "$NFS_DEVICE"
  294. NFS_REUSE_EXPORT=1; nfs_publish_model; printf 'DEVICE=%s\\n' "$NFS_DEVICE"
  295. ''', d, {"HF_EXPORT_ROOT": str(d / "export"), "EXPECTED_SHARDS": "1"}))
  296. assert "DEVICE=:/\n" in output
  297. assert "DEVICE=:/dsv41-native\n" in output
  298. def test_tp4_profile_ring_flags_are_commented_and_base_defaults_preserved():
  299. text = (ROOT / ".env.tp4.example").read_text()
  300. values = dict(re.findall(r"(?m)^([A-Z][A-Z0-9_]*)=(.*)$", text))
  301. # The TP4 profile is the switched production line: the ring's serial loader ships
  302. # commented in the ring block, next to the ring flags.
  303. commented = set(re.findall(r"(?m)^#([A-Z][A-Z0-9_]*)=", text))
  304. for key in ("NCCL_SWITCHLESS_RING_ONLY", "NCCL_ALGO", "NCCL_MIN_NCHANNELS", "NCCL_P2P_LEVEL", "DSV41_SERIAL_WEIGHT_LOAD"):
  305. assert key not in values
  306. assert key in commented
  307. assert values["NCCL_MAX_NCHANNELS"] == "8"
  308. for key, value in {"DSPARK_BLOCK_SIZE": "5", "SGLANG_DSV41_REASONING_EFFORT": "75", "DSV41_MAX_NEW_TOKENS": "32768", "DSV41_LOOP_ABORT": "1"}.items():
  309. assert values[key] == value
  310. if __name__ == "__main__":
  311. tests = [v for k, v in list(globals().items()) if k.startswith("test_")]
  312. failed = 0
  313. for fn in tests:
  314. try:
  315. fn()
  316. print(f"[OK] {fn.__name__}")
  317. except Exception as exc:
  318. failed += 1
  319. print(f"[FAIL] {fn.__name__}: {exc}")
  320. print(f"{len(tests) - failed}/{len(tests)} passed")
  321. raise SystemExit(bool(failed))