29858.diff 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. # Backport of sgl-project/sglang#29858 (head 0314d9de3a), ported to the
  2. # current update_sliding_window_buffer(window_kv_indptr, translator,
  3. # req_pool_indices, window, seq_lens, bs, ...) signature on 06008c17.
  4. diff --git a/python/sglang/srt/layers/attention/triton_backend.py b/python/sglang/srt/layers/attention/triton_backend.py
  5. index c378c80ab0f8..c5839ba47e10 100644
  6. --- a/python/sglang/srt/layers/attention/triton_backend.py
  7. +++ b/python/sglang/srt/layers/attention/triton_backend.py
  8. @@ -567,6 +567,25 @@ def _update_draft_extend_buffers(
  9. kv_indptr = self._fill_kv_indptr_and_indices(
  10. bs, kv_lens, req_pool_indices, self.cuda_graph_kv_indices
  11. )
  12. + if self.sliding_window_size is not None and self.sliding_window_size > 0:
  13. + # Fill the swa-pool window buffers like target_verify does, so the
  14. + # SWA layers' draft-extend reads the smaller swa K/V buffer with
  15. + # window indices instead of falling back to the full-pool
  16. + # kv_indices (which overruns the swa buffer -> OOB read).
  17. + window_kv_indices = self.cuda_graph_window_kv_indices
  18. + window_kv_offsets = self.cuda_graph_window_kv_offsets
  19. + _, window_kv_indices, _, window_kv_offsets[:bs] = (
  20. + update_sliding_window_buffer(
  21. + self.window_kv_indptr,
  22. + self.kv_index_translator,
  23. + req_pool_indices,
  24. + self.sliding_window_size,
  25. + kv_lens[:bs],
  26. + bs,
  27. + token_to_kv_pool=self.token_to_kv_pool,
  28. + window_kv_indices=window_kv_indices,
  29. + )
  30. + )
  31. return qo_indptr, kv_indptr, num_tokens_per_req
  32. def init_forward_metadata_out_graph(
  33. @@ -1153,10 +1172,12 @@ def _build_cuda_graph_forward_metadata(
  34. qo_indptr=self.qo_indptr[: bs + 1],
  35. custom_mask=None,
  36. mask_indptr=None,
  37. - window_kv_indptr=self.window_kv_indptr,
  38. - window_kv_indices=None,
  39. - window_num_kv_splits=None,
  40. - window_kv_offsets=None,
  41. + window_kv_indptr=self.window_kv_indptr[: bs + 1] if swa else None,
  42. + window_kv_indices=self.cuda_graph_window_kv_indices if swa else None,
  43. + window_num_kv_splits=(
  44. + self.cuda_graph_window_num_kv_splits if swa else None
  45. + ),
  46. + window_kv_offsets=self.cuda_graph_window_kv_offsets if swa else None,
  47. swa_out_cache_loc=swa_out_cache_loc,
  48. out_cache_loc_full_physical=out_cache_loc_full_physical,
  49. )
  50. diff --git a/test/registered/unit/spec/test_triton_draft_extend_cudagraph_swa_window.py b/test/registered/unit/spec/test_triton_draft_extend_cudagraph_swa_window.py
  51. new file mode 100644
  52. index 000000000000..747f490a21be
  53. --- /dev/null
  54. +++ b/test/registered/unit/spec/test_triton_draft_extend_cudagraph_swa_window.py
  55. @@ -0,0 +1,73 @@
  56. +"""Regression test for the EAGLE draft-extend cuda-graph SWA window buffers.
  57. +
  58. +White-box unit test: it builds a bare ``TritonAttnBackend`` via ``__new__`` and
  59. +stubs only the cuda-graph buffers that ``_build_cuda_graph_forward_metadata``
  60. +reads, so the draft-extend metadata can be exercised on CPU without a captured
  61. +graph or a real model. It is coupled to that method's internals on purpose,
  62. +which is the price of testing the metadata shape in isolation.
  63. +
  64. +The contract under test: on a hybrid sliding-window model the draft-extend
  65. +cuda-graph metadata must expose the swa-pool window buffers (a non-None
  66. +``window_kv_indices``) exactly like target_verify does. When it leaves them
  67. +None the SWA layers fall back to the full-pool kv_indices and read out of
  68. +bounds of the smaller swa K/V buffer. Non-SWA models must keep the window
  69. +fields None.
  70. +"""
  71. +
  72. +import unittest
  73. +
  74. +import torch
  75. +
  76. +from sglang.srt.layers.attention.triton_backend import TritonAttnBackend
  77. +from sglang.srt.model_executor.forward_batch_info import ForwardMode
  78. +from sglang.test.ci.ci_register import register_cpu_ci
  79. +from sglang.test.test_utils import CustomTestCase
  80. +
  81. +register_cpu_ci(est_time=5, suite="base-a-test-cpu")
  82. +
  83. +BS = 3
  84. +
  85. +
  86. +class TestTritonDraftExtendCudaGraphSWAWindow(CustomTestCase):
  87. + def _build_backend(self, sliding_window_size):
  88. + backend = TritonAttnBackend.__new__(TritonAttnBackend)
  89. + backend.sliding_window_size = sliding_window_size
  90. + backend.num_draft_tokens = 4
  91. + backend.speculative_num_steps = 3
  92. + backend.kv_indptr = torch.zeros(BS + 1, dtype=torch.int32)
  93. + backend.qo_indptr = torch.zeros(BS + 1, dtype=torch.int32)
  94. + backend.window_kv_indptr = torch.zeros(BS + 1, dtype=torch.int32)
  95. + backend.cuda_graph_kv_indices = torch.zeros(16, dtype=torch.int64)
  96. + backend.cuda_graph_window_kv_indices = torch.zeros(16, dtype=torch.int64)
  97. + backend.cuda_graph_window_num_kv_splits = torch.zeros(BS, dtype=torch.int32)
  98. + backend.cuda_graph_window_kv_offsets = torch.zeros(BS, dtype=torch.int32)
  99. + return backend
  100. +
  101. + def test_draft_extend_swa_exposes_window_buffers(self):
  102. + backend = self._build_backend(sliding_window_size=128)
  103. + md = backend._build_cuda_graph_forward_metadata(
  104. + BS, ForwardMode.DRAFT_EXTEND_V2, spec_info=None, swa_out_cache_loc=None
  105. + )
  106. + # The draft step must be handed the swa-pool window buffers instead of
  107. + # None, so the SWA layers stay on the swa buffer rather than indexing it
  108. + # with full-pool locations.
  109. + self.assertIsNotNone(md.window_kv_indices)
  110. + self.assertIs(md.window_kv_indices, backend.cuda_graph_window_kv_indices)
  111. + self.assertIsNotNone(md.window_kv_indptr)
  112. + self.assertIs(md.window_kv_offsets, backend.cuda_graph_window_kv_offsets)
  113. + self.assertIsNotNone(md.window_num_kv_splits)
  114. +
  115. + def test_draft_extend_without_swa_keeps_window_none(self):
  116. + for size in (None, 0):
  117. + backend = self._build_backend(sliding_window_size=size)
  118. + md = backend._build_cuda_graph_forward_metadata(
  119. + BS, ForwardMode.DRAFT_EXTEND_V2, spec_info=None, swa_out_cache_loc=None
  120. + )
  121. + self.assertIsNone(md.window_kv_indices, msg=f"size={size}")
  122. + self.assertIsNone(md.window_kv_indptr, msg=f"size={size}")
  123. + self.assertIsNone(md.window_kv_offsets, msg=f"size={size}")
  124. + self.assertIsNone(md.window_num_kv_splits, msg=f"size={size}")
  125. +
  126. +
  127. +if __name__ == "__main__":
  128. + unittest.main()