Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions python/sglang/srt/disaggregation/decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -2202,11 +2202,11 @@ def _commit_transfer_to_req(self, decode_req: DecodeRequest):
# under the current weights and sampled a fresh handoff token, but when
# there is a remembered boundary token we are *replaying* an
# already-emitted token. Override the handoff with it, and skip
# re-committing a logprob for it -- it keeps its original behavior
# logprob from before the retract (we never re-score generated tokens
# under the new policy). A rebootstrap with no boundary token (retracted
# before emitting any output) falls through to the normal path so its
# first token and logprob are committed as usual.
# re-committing a logprob or sampling mask for it -- it keeps its original
# behavior logprob and sampling mask from before the retract (we never
# re-score generated tokens under the new policy). A rebootstrap with no
# boundary token (retracted before emitting any output) falls through to
# the normal path so its first token and logprob are committed as usual.
replayed_boundary = (
decode_req.is_rebootstrap
and decode_req.req.pd_rebootstrap_forced_output_id is not None
Expand Down Expand Up @@ -2275,7 +2275,7 @@ def _commit_transfer_to_req(self, decode_req: DecodeRequest):
: decode_req.req.logprob.top_logprobs_num
].tolist()
)
if decode_req.req.return_sampling_mask:
if decode_req.req.return_sampling_mask and not replayed_boundary:
assert output_token_sampling_mask_idx is not None, (
"sampling mask buffer disabled on decode side"
)
Expand Down
62 changes: 62 additions & 0 deletions test/registered/unit/disaggregation/test_disaggregation_wire.py
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,68 @@ def test_sampling_mask_row_reaches_decode_unchanged(self):
([[7, 8, 9]], expected),
)

def test_rebootstrap_replay_keeps_one_sampling_mask_row_per_token(self):
"""A PD rebootstrap that replays an already-emitted token keeps that token's
sampling-mask row instead of adding the prefill worker's fresh one."""
with envs.SGLANG_ENABLE_DISAGG_SAMPLING_MASK.override(True):
buffers = MetadataBuffers(
size=1,
hidden_size=2,
hidden_states_dtype=torch.float32,
max_sampling_mask_tokens=4,
)
buffers.set_buf(
self._make_req(
None,
sampling_mask=[7, 8, 9],
sampling_logprobs=[-1.25, -1.5, -2.0],
)
)
queue = DecodeTransferQueue.__new__(DecodeTransferQueue)
queue.scheduler = SimpleNamespace(
kv_checksum_computer=None,
batch_result_processor=SimpleNamespace(
_maybe_update_reasoning_tokens=lambda req, token_id: None
),
)
queue.spec_algorithm = SimpleNamespace(is_none=lambda: True)
queue.metadata_buffers = buffers
# Retracting popped token 6 from output_ids; its row is still queued.
sampling_mask_rows = SamplingMaskRows()
sampling_mask_rows.append(
np.array([5, 1], np.int32), np.array([-0.5, -2.25], np.float32)
)
sampling_mask_rows.append(
np.array([6, 2], np.int32), np.array([-0.25, -1.75], np.float32)
)
req = SimpleNamespace(
rid="r0",
bootstrap_host="127.0.0.1",
bootstrap_room=9,
output_ids=[5],
pd_rebootstrap_forced_output_id=6,
return_logprob=False,
return_sampling_mask=True,
sampling_logprobs_mode="support",
sampling_mask_rows=sampling_mask_rows,
time_stats=SimpleNamespace(set_wait_queue_entry_time=lambda: None),
)

queue._commit_transfer_to_req(
DecodeRequest(
req=req,
kv_receiver=SimpleNamespace(clear=lambda: None),
metadata_buffer_index=0,
is_rebootstrap=True,
)
)

self.assertEqual(req.output_ids, [5, 6])
self.assertEqual(
req.sampling_mask_rows.take().to_lists(support_logprobs=True),
([[5, 1], [6, 2]], [[-0.5, -2.25], [-0.25, -1.75]]),
)

def test_decode_input_requires_valid_seed_for_every_request(self):
seeds = (
torch.tensor([1, 2, 3], dtype=torch.int32),
Expand Down
Loading