diff --git a/python/sglang/test/kv_canary/pd_fixture.py b/python/sglang/test/kv_canary/pd_fixture.py index ee369aaa8..53eb090f9 100644 --- a/python/sglang/test/kv_canary/pd_fixture.py +++ b/python/sglang/test/kv_canary/pd_fixture.py @@ -1,5 +1,6 @@ from __future__ import annotations +import uuid from typing import ClassVar, Literal, Optional from sglang.srt.kv_canary.config import CanaryMode @@ -61,10 +62,19 @@ class CanaryPDFixture(CanaryViolationAssertMixin, PDDisaggregationServerBase): assert_all_success: bool = True, max_new_tokens: int = 100, timeout: float = 60.0, + distinct_prompts: bool = False, ) -> list[dict]: + if distinct_prompts: + # Diverge at the very first token (request index before the per-call + # nonce) so requests share no radix-dedupable prefix beyond a + # tokenizer-added BOS, and retries never hit earlier attempts' cache. + nonce = uuid.uuid4().hex[:8] + prompts = [f"{i} {nonce} {_SHORT_PROMPT_BODY}" for i in range(n)] + else: + prompts = [_SHORT_PROMPT_BODY] * n results = post_parallel_generate( url=self.lb_url + "/generate", - prompts=[_SHORT_PROMPT_BODY] * n, + prompts=prompts, max_new_tokens=max_new_tokens, timeout=timeout, ) diff --git a/test/registered/kv_canary/test_self_e2e_pd_perturb.py b/test/registered/kv_canary/test_self_e2e_pd_perturb.py index 2a7f68a6b..d455c1a6d 100644 --- a/test/registered/kv_canary/test_self_e2e_pd_perturb.py +++ b/test/registered/kv_canary/test_self_e2e_pd_perturb.py @@ -37,7 +37,14 @@ class _PDPerturbBase(CanaryPDFixture): ) -> None: # send_parallel_short_requests defaults to max_new_tokens=100 so D-side runs # decode forwards that exercise canary verify on the transferred prefix. - self.send_parallel_short_requests(n=4) + # + # distinct_prompts is required for the violation to surface reliably: + # with a shared prompt, P-side radix caching rewrites each request's + # req_to_token row to the first-inserted (canonical) copy's slots in + # cache_unfinished_req BEFORE send_kv_chunk snapshots the indices, so a + # flip on a deduped duplicate slot is freed untransferred and never + # re-verified on either side. + self.send_parallel_short_requests(n=4, distinct_prompts=True) # D-side: first decode forward re-verifies the transferred prefix slots, # so the flip MUST surface as real_kv_hash violation. self.assert_per_forward_violation_reported(