config: every handler declares its cuda-graph decisions (#36725)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
bd4bb1781a
commit
7c3b5a6732
@@ -920,5 +920,103 @@ class TestResolutionDeclarations(CustomTestCase):
|
||||
)
|
||||
|
||||
|
||||
class TestDeclaredValuesAreNotEditedLater(CustomTestCase):
|
||||
"""A declaration records a value, not a handle on one.
|
||||
|
||||
The stash keeps whatever object the declaring handler passed, so a handler
|
||||
that declares a mutable and then edits it in place rewrites an entry that
|
||||
already went into the log. The projection still answers with the end state,
|
||||
which is why nothing else notices: what is lost is *which* handler decided
|
||||
what, and `validate_declarations` never sees the later change at all.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
environment = dict(os.environ)
|
||||
|
||||
def restore():
|
||||
os.environ.clear()
|
||||
os.environ.update(environment)
|
||||
|
||||
self.addCleanup(restore)
|
||||
|
||||
def _resolve_recording_each_entry(self, **supplied):
|
||||
"""Resolve, deep-copying every stash entry the moment it is appended."""
|
||||
from sglang.srt.arg_groups import overrides
|
||||
|
||||
recorded = []
|
||||
|
||||
def watch(name):
|
||||
original = getattr(overrides, name)
|
||||
|
||||
def wrapper(server_args, *args, **kwargs):
|
||||
result = original(server_args, *args, **kwargs)
|
||||
stash = getattr(server_args, "_resolved_overrides", None) or []
|
||||
while len(recorded) < len(stash):
|
||||
index = len(recorded)
|
||||
recorded.append((index, copy.deepcopy(stash[index])))
|
||||
return result
|
||||
|
||||
return original, wrapper
|
||||
|
||||
# Every path that appends to the stash.
|
||||
patched = {}
|
||||
for name in (
|
||||
"declare_resolution",
|
||||
"declare_late_resolution",
|
||||
"declare_direct_writes",
|
||||
"run_post_process_pass",
|
||||
):
|
||||
original, wrapper = watch(name)
|
||||
patched[name] = original
|
||||
setattr(overrides, name, wrapper)
|
||||
try:
|
||||
path = tempfile.mkdtemp(prefix="declared_values_")
|
||||
self.addCleanup(shutil.rmtree, path, ignore_errors=True)
|
||||
with open(os.path.join(path, "config.json"), "w") as handle:
|
||||
json.dump(_MINI_CONFIG, handle)
|
||||
server_args = ServerArgs(
|
||||
model_path=path, device="cuda", random_seed=42, **supplied
|
||||
)
|
||||
server_args.resolve_once()
|
||||
finally:
|
||||
for name, original in patched.items():
|
||||
setattr(overrides, name, original)
|
||||
return server_args, recorded
|
||||
|
||||
def test_no_entry_changes_after_it_is_recorded(self):
|
||||
# One shape per family of handlers that decides a graph setting.
|
||||
for label, supplied in (
|
||||
("plain", {}),
|
||||
("cuda_graph_knobs", {"cuda_graph_max_bs_decode": 16}),
|
||||
("chunked_prefill", {"chunked_prefill_size": 1024}),
|
||||
("explicit_json", {"cuda_graph_config": {"decode": {"max_bs": 12}}}),
|
||||
("disaggregation", {"disaggregation_mode": "prefill"}),
|
||||
("deterministic", {"enable_deterministic_inference": True}),
|
||||
("speculative", {"speculative_algorithm": "EAGLE"}),
|
||||
("dp_attention", {"tp_size": 2, "dp_size": 2, "enable_dp_attention": True}),
|
||||
):
|
||||
with self.subTest(shape=label):
|
||||
server_args, recorded = self._resolve_recording_each_entry(**supplied)
|
||||
stash = server_args._resolved_overrides
|
||||
self.assertGreater(
|
||||
len(recorded),
|
||||
0,
|
||||
"nothing was recorded, so this case is not watching the "
|
||||
"declaration paths it thinks it is",
|
||||
)
|
||||
drifted = [
|
||||
(index, was, stash[index])
|
||||
for index, was in recorded
|
||||
if stash[index] != was
|
||||
]
|
||||
self.assertEqual(
|
||||
[],
|
||||
drifted,
|
||||
"these entries changed after they were declared, so the log "
|
||||
f"credits the wrong handler for the end state: {drifted}",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -2245,8 +2245,9 @@ class TestDeepEPv2Args(CustomTestCase):
|
||||
for mode in ("direct", "hybrid"):
|
||||
args = self._args(moe_runner_backend="deep_gemm", deepep_v2_mode=mode)
|
||||
args._handle_a2a_moe()
|
||||
self.assertEqual(args.cuda_graph_config.decode.backend, Backend.FULL)
|
||||
self.assertEqual(args.cuda_graph_config.prefill.backend, Backend.DISABLED)
|
||||
declared = resolution_result(args, "cuda_graph_config")
|
||||
self.assertEqual(declared.decode.backend, Backend.FULL)
|
||||
self.assertEqual(declared.prefill.backend, Backend.DISABLED)
|
||||
|
||||
def test_two_batch_overlap_rejected(self):
|
||||
args = self._args(moe_runner_backend="deep_gemm", enable_two_batch_overlap=True)
|
||||
|
||||
Reference in New Issue
Block a user