patch_vllm_mrv2_speculator_cudagraph_pool.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. #!/usr/bin/env python3
  2. """Isolate MRV2 speculator graphs during CUDA-graph memory profiling."""
  3. from __future__ import annotations
  4. import sys
  5. from pathlib import Path
  6. TARGET_REL = Path("vllm/v1/worker/gpu/cudagraph_utils.py")
  7. MARKER = "spark-vllm-docker: isolate speculator profiling graphs"
  8. DECLARATION_ANCHOR = """ all_wrappers: list[Any] = []
  9. original_pools: dict[int, Any] = {}
  10. try:
  11. """
  12. DECLARATION_REPLACEMENT = """ all_wrappers: list[Any] = []
  13. original_pools: dict[int, Any] = {}
  14. speculator_managers: list[tuple[str, CudaGraphManager]] = []
  15. try:
  16. """
  17. POOL_ANCHOR = """ manager.pool = current_platform.graph_pool_handle()
  18. if manager.use_breakable_cg:
  19. """
  20. POOL_REPLACEMENT = f""" manager.pool = current_platform.graph_pool_handle()
  21. # {MARKER}. Speculator prefill and decode use separate
  22. # CudaGraphManager instances, so redirect them before their lazy capture
  23. # runners are created. Otherwise their discarded profiling graphs use
  24. # the persistent global pool and leave its allocator handle stale.
  25. if runner.speculator is not None:
  26. speculator_managers = [
  27. (name, value)
  28. for name, value in vars(runner.speculator).items()
  29. if isinstance(value, CudaGraphManager)
  30. ]
  31. for _, speculator_manager in speculator_managers:
  32. speculator_manager.pool = manager.pool
  33. if manager.use_breakable_cg:
  34. """
  35. CLEANUP_ANCHOR = """ CUDAGraphWrapper.clear_all_graphs()
  36. BreakableCUDAGraphWrapper.clear_all_graphs()
  37. for wrapper in all_wrappers:
  38. """
  39. CLEANUP_REPLACEMENT = """ CUDAGraphWrapper.clear_all_graphs()
  40. BreakableCUDAGraphWrapper.clear_all_graphs()
  41. # Drop speculator FULL graphs and their references to the throwaway
  42. # pool before the profiling state is garbage-collected. The real KV
  43. # initialization constructs fresh managers immediately afterwards.
  44. for name, speculator_manager in speculator_managers:
  45. speculator_manager.graphs.clear()
  46. speculator_manager.pool = None
  47. if (
  48. runner.speculator is not None
  49. and getattr(runner.speculator, name, None) is speculator_manager
  50. ):
  51. setattr(runner.speculator, name, None)
  52. for wrapper in all_wrappers:
  53. """
  54. class PatchError(RuntimeError):
  55. """The vulnerable profiler is present in an unsupported source layout."""
  56. def is_fixed(source: str) -> bool:
  57. local_fix = (
  58. MARKER in source
  59. and "speculator_manager.pool = manager.pool" in source
  60. and "speculator_manager.graphs.clear()" in source
  61. and "setattr(runner.speculator, name, None)" in source
  62. )
  63. manager_collection_fix = all(
  64. anchor in source
  65. for anchor in (
  66. "def _profiling_cudagraph_managers(",
  67. "speculator = runner.speculator",
  68. "candidate = getattr(speculator, name, None)",
  69. "graph_managers = _profiling_cudagraph_managers(runner)",
  70. "graph_manager.pool = manager.pool",
  71. "graph_manager.graphs.clear()",
  72. "graph_manager.pool = original_manager_pools[id(graph_manager)]",
  73. )
  74. )
  75. return local_fix or manager_collection_fix
  76. def is_affected_profiler(source: str) -> bool:
  77. return (
  78. "def profile_cudagraph_memory" in source
  79. and "manager.pool = current_platform.graph_pool_handle()" in source
  80. and "PIECEWISE, encoder and speculator graphs are measured in full" in source
  81. )
  82. def replace_once(source: str, anchor: str, replacement: str, label: str) -> str:
  83. count = source.count(anchor)
  84. if count != 1:
  85. raise PatchError(
  86. f"expected exactly one {label} anchor, found {count}; "
  87. "refusing to patch an unknown MRV2 profiler layout"
  88. )
  89. return source.replace(anchor, replacement, 1)
  90. def patch_source(source: str) -> tuple[str, str]:
  91. if is_fixed(source):
  92. return source, "Equivalent MRV2 speculator CUDA-graph pool fix is present; skipping"
  93. if not is_affected_profiler(source):
  94. return source, "Affected MRV2 CUDA-graph memory profiler is absent; skipping"
  95. patched = replace_once(
  96. source,
  97. DECLARATION_ANCHOR,
  98. DECLARATION_REPLACEMENT,
  99. "profiling-state declaration",
  100. )
  101. patched = replace_once(
  102. patched,
  103. POOL_ANCHOR,
  104. POOL_REPLACEMENT,
  105. "throwaway-pool assignment",
  106. )
  107. patched = replace_once(
  108. patched,
  109. CLEANUP_ANCHOR,
  110. CLEANUP_REPLACEMENT,
  111. "profiling cleanup",
  112. )
  113. if not is_fixed(patched):
  114. raise PatchError("MRV2 speculator CUDA-graph pool patch postcondition failed")
  115. return patched, "Applied MRV2 speculator CUDA-graph profiling-pool fix"
  116. def main() -> None:
  117. source_root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path.cwd()
  118. target = source_root / TARGET_REL
  119. if not target.exists():
  120. print(f"{TARGET_REL} is absent; targeted MRV2 profiling patch is not applicable")
  121. return
  122. source = target.read_text()
  123. try:
  124. patched, message = patch_source(source)
  125. except PatchError as exc:
  126. raise SystemExit(f"MRV2 speculator CUDA-graph pool patch failed: {exc}") from exc
  127. if patched != source:
  128. target.write_text(patched)
  129. print(message)
  130. if __name__ == "__main__":
  131. main()