patch_dspark_uniform.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. #!/usr/bin/env python3
  2. """Patch dspark_proposer.py so a DSpark batch with mixed per-request context
  3. lengths does not kill the engine (observed under `tool-eval-bench --parallel 4`).
  4. Stock behaviour: `_trim_rejected_target_context` + `_view_by_request` require
  5. every request in a DSpark batch to have the SAME flattened length. When
  6. requests have divergent context lengths (e.g. [1, 2052]) this raises ValueError
  7. in the worker -> EngineCore dies -> EngineDeadError on every later request.
  8. There are two separate uniform checks that both crash:
  9. 1. `_trim_rejected_target_context` requires uniform *effective* lengths after
  10. rejection-trimming (raises only when any token was rejected).
  11. 2. `_view_by_request` requires the flattened input to be a multiple of
  12. batch_size (crashes when all drafts are ACCEPTED and contexts differ,
  13. because the raw per-request lengths are returned unpadded).
  14. Fix: restructure `_trim_rejected_target_context` to ALWAYS split the flattened
  15. per-request chunks via `query_start_loc`, trim rejected tokens (when provided),
  16. and pad every shorter request to the batch-max effective length by repeating its
  17. last valid token (hidden state + position). This keeps the batch rectangular /
  18. uniform for `_view_by_request` and `prefill_main` while the draft continuation
  19. point `[:, -1]` stays the real last token. Only falls back to returning the raw
  20. tensor if per-request boundaries are unavailable.
  21. """
  22. import pathlib
  23. P = pathlib.Path(
  24. "/opt/env/lib/python3.12/site-packages/vllm/v1/spec_decode/dspark_proposer.py"
  25. )
  26. src = P.read_text()
  27. OLD = """ if num_rejected_tokens_gpu is None or common_attn_metadata is None:
  28. return target_hidden_states, target_positions
  29. rejected = num_rejected_tokens_gpu.detach().cpu().tolist()
  30. if not any(int(value) for value in rejected):
  31. return target_hidden_states, target_positions
  32. query_start_loc_cpu = common_attn_metadata.query_start_loc_cpu
  33. if query_start_loc_cpu is None:
  34. query_start_loc_cpu = common_attn_metadata.query_start_loc.detach().cpu()
  35. query_starts = query_start_loc_cpu.tolist()
  36. if len(query_starts) != len(rejected) + 1:
  37. raise ValueError(
  38. "DSpark rejected-context trimming requires query_start_loc "
  39. "to have batch_size + 1 entries; got "
  40. f"{len(query_starts)} starts for {len(rejected)} requests."
  41. )
  42. hidden_chunks: list[torch.Tensor] = []
  43. position_chunks: list[torch.Tensor] = []
  44. effective_lengths: list[int] = []
  45. flat_positions = target_positions.reshape(-1)
  46. for req_index, num_rejected in enumerate(rejected):
  47. start = int(query_starts[req_index])
  48. end = int(query_starts[req_index + 1]) - int(num_rejected)
  49. if end <= start:
  50. raise ValueError(
  51. "DSpark rejected-context trimming removed every token for "
  52. f"request {req_index}: start={start}, end={end}."
  53. )
  54. hidden_chunks.append(target_hidden_states[start:end])
  55. position_chunks.append(flat_positions[start:end])
  56. effective_lengths.append(end - start)
  57. if len(set(effective_lengths)) != 1:
  58. raise ValueError(
  59. "DSpark currently requires uniform effective per-request "
  60. "target context lengths after rejection trimming; got "
  61. f"{effective_lengths}."
  62. )
  63. return torch.cat(hidden_chunks, dim=0), torch.cat(position_chunks, dim=0)"""
  64. NEW = """ # sm121-dspark38: if we cannot determine per-request boundaries, fall
  65. # back to returning the tensors untouched.
  66. qsl_cpu = getattr(common_attn_metadata, "query_start_loc_cpu", None)
  67. if common_attn_metadata is None or (
  68. qsl_cpu is None
  69. and getattr(common_attn_metadata, "query_start_loc", None) is None
  70. ):
  71. return target_hidden_states, target_positions
  72. if qsl_cpu is None:
  73. qsl_cpu = common_attn_metadata.query_start_loc.detach().cpu()
  74. query_starts = qsl_cpu.tolist()
  75. nreq = len(query_starts) - 1
  76. if nreq < 1:
  77. return target_hidden_states, target_positions
  78. rejected = (
  79. num_rejected_tokens_gpu.detach().cpu().tolist()
  80. if num_rejected_tokens_gpu is not None
  81. else [0] * nreq
  82. )
  83. if len(query_starts) != len(rejected) + 1:
  84. raise ValueError(
  85. "DSpark rejected-context trimming requires query_start_loc "
  86. "to have batch_size + 1 entries; got "
  87. f"{len(query_starts)} starts for {len(rejected)} requests."
  88. )
  89. hidden_chunks: list[torch.Tensor] = []
  90. position_chunks: list[torch.Tensor] = []
  91. effective_lengths: list[int] = []
  92. flat_positions = target_positions.reshape(-1)
  93. for req_index, num_rejected in enumerate(rejected):
  94. start = int(query_starts[req_index])
  95. end = int(query_starts[req_index + 1]) - int(num_rejected)
  96. if end <= start:
  97. raise ValueError(
  98. "DSpark rejected-context trimming removed every token for "
  99. f"request {req_index}: start={start}, end={end}."
  100. )
  101. hidden_chunks.append(target_hidden_states[start:end])
  102. position_chunks.append(flat_positions[start:end])
  103. effective_lengths.append(end - start)
  104. # sm121-dspark38: pad every shorter request to the batch-max effective
  105. # length by repeating its last valid token (hidden state + position),
  106. # so the batch stays rectangular/uniform for _view_by_request and
  107. # prefill_main while the draft continuation point ([:, -1]) remains the
  108. # real last token. The stock fork hard-rejects non-uniform lengths.
  109. max_eff = max(effective_lengths)
  110. for i, (eff, chunk, pos_chunk) in enumerate(
  111. zip(effective_lengths, hidden_chunks, position_chunks)
  112. ):
  113. if eff < max_eff:
  114. pad = max_eff - eff
  115. hidden_chunks[i] = torch.cat(
  116. [chunk, chunk[-1:].expand(pad, -1).contiguous()], dim=0
  117. )
  118. position_chunks[i] = torch.cat(
  119. [pos_chunk, pos_chunk[-1:].expand(pad).contiguous()], dim=0
  120. )
  121. effective_lengths[:] = [max_eff] * len(effective_lengths)
  122. return torch.cat(hidden_chunks, dim=0), torch.cat(position_chunks, dim=0)"""
  123. assert src.count(OLD) == 1, f"expected 1 block, got {src.count(OLD)}"
  124. P.write_text(src.replace(OLD, NEW))
  125. print("patched dspark_proposer.py: always-pad to uniform context")