patch_vllm_spark_kv_cache_cleanup.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. #!/usr/bin/env python3
  2. """Clean profiling allocations before vLLM sizes and creates the KV cache."""
  3. import re
  4. import sys
  5. from pathlib import Path
  6. source_root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path.cwd()
  7. target = source_root / "vllm/v1/worker/gpu_worker.py"
  8. if not target.exists():
  9. raise SystemExit(f"{target} not found; cannot apply KV cache cleanup patch")
  10. text = target.read_text()
  11. lines = text.splitlines(keepends=True)
  12. changed = False
  13. profile_cleanup_present = (
  14. "profile_result.after_profile.measure()" in text
  15. and "diff_from_create.non_torch_memory" in text
  16. )
  17. prealloc_cleanup_present = (
  18. "memory_reserved(self.device)" in text
  19. and "memory_allocated(self.device)" in text
  20. and "empty_cache()" in text
  21. )
  22. needs_cleanup = not (profile_cleanup_present and prealloc_cleanup_present)
  23. if needs_cleanup and not re.search(r"(?m)^import gc$", text):
  24. insert_at = None
  25. last_future_import = None
  26. for i, line in enumerate(lines):
  27. if line.startswith("from __future__ import "):
  28. last_future_import = i
  29. elif insert_at is None and (
  30. line.startswith("import ") or line.startswith("from ")
  31. ):
  32. insert_at = i
  33. if last_future_import is not None:
  34. lines.insert(last_future_import + 1, "import gc\n")
  35. elif insert_at is not None:
  36. lines.insert(insert_at, "import gc\n")
  37. else:
  38. lines.insert(0, "import gc\n")
  39. changed = True
  40. def find_line(pattern: str) -> tuple[int, re.Match[str]]:
  41. regex = re.compile(pattern)
  42. for index, line in enumerate(lines):
  43. match = regex.match(line)
  44. if match:
  45. return index, match
  46. raise SystemExit(f"Could not find expected vLLM pattern: {pattern}")
  47. def insert_after_docstring(func_index: int, func_indent: str, block: list[str]) -> None:
  48. insert_at = func_index + 1
  49. if insert_at < len(lines):
  50. stripped = lines[insert_at].lstrip()
  51. quote = None
  52. if stripped.startswith(chr(34) * 3):
  53. quote = chr(34) * 3
  54. elif stripped.startswith(chr(39) * 3):
  55. quote = chr(39) * 3
  56. if quote is not None:
  57. if stripped.count(quote) >= 2 and not stripped.startswith(quote * 2):
  58. insert_at += 1
  59. else:
  60. insert_at += 1
  61. while insert_at < len(lines):
  62. if quote in lines[insert_at]:
  63. insert_at += 1
  64. break
  65. insert_at += 1
  66. lines[insert_at:insert_at] = block
  67. if not profile_cleanup_present:
  68. snapshot_line = (
  69. r"^(?P<indent>[ \t]+)free_gpu_memory = "
  70. r"profile_result\.after_profile\.free_memory\n$"
  71. )
  72. index, match = find_line(snapshot_line)
  73. indent = match.group("indent")
  74. lines[index:index] = [
  75. f"{indent}# spark-vllm-docker: post-profile cleanup before KV sizing\n",
  76. f'{indent}if self.device_config.device_type == "cuda":\n',
  77. f"{indent} before_cleanup = profile_result.after_profile.free_memory\n",
  78. f'{indent} if hasattr(self.model_runner, "_cleanup_profiling_kv_cache"):\n',
  79. f"{indent} self.model_runner._cleanup_profiling_kv_cache()\n",
  80. f"{indent} gc.collect()\n",
  81. f"{indent} torch.cuda.synchronize(self.device)\n",
  82. f"{indent} torch.cuda.empty_cache()\n",
  83. f"{indent} profile_result.after_profile.measure()\n",
  84. f"{indent} diff_from_create = (\n",
  85. f"{indent} profile_result.after_profile "
  86. "- profile_result.before_create\n",
  87. f"{indent} )\n",
  88. f"{indent} profile_result.non_torch_increase = (\n",
  89. f"{indent} diff_from_create.non_torch_memory\n",
  90. f"{indent} )\n",
  91. f"{indent} profile_result.non_kv_cache_memory = (\n",
  92. f"{indent} profile_result.non_torch_increase\n",
  93. f"{indent} + profile_result.torch_peak_increase\n",
  94. f"{indent} + profile_result.weights_memory\n",
  95. f"{indent} )\n",
  96. f"{indent} cleanup_freed = (\n",
  97. f"{indent} profile_result.after_profile.free_memory - before_cleanup\n",
  98. f"{indent} )\n",
  99. f"{indent} if cleanup_freed > 0:\n",
  100. f"{indent} logger.info_once(\n",
  101. f'{indent} "Freed %.2f GiB before KV cache sizing; "\n',
  102. f'{indent} "non-torch profile increase is %.2f GiB.",\n',
  103. f"{indent} cleanup_freed / (1024**3),\n",
  104. f"{indent} profile_result.non_torch_increase / (1024**3),\n",
  105. f"{indent} )\n",
  106. "\n",
  107. ]
  108. changed = True
  109. if not prealloc_cleanup_present:
  110. func_index = None
  111. func_indent = None
  112. for i, line in enumerate(lines):
  113. match = re.match(
  114. r"^(?P<indent>[ \t]+)def initialize_from_config"
  115. r"\(self,\s*kv_cache_config\b",
  116. line,
  117. )
  118. if match:
  119. func_index = i
  120. func_indent = match.group("indent")
  121. break
  122. if func_index is None or func_indent is None:
  123. raise SystemExit("Could not find initialize_from_config in vLLM gpu_worker.py")
  124. body_indent = func_indent + " "
  125. block = [
  126. f"{body_indent}# spark-vllm-docker: pre-KV cache allocator cleanup\n",
  127. f'{body_indent}if self.device_config.device_type == "cuda":\n',
  128. f"{body_indent} gc.collect()\n",
  129. f"{body_indent} torch.cuda.synchronize(self.device)\n",
  130. f"{body_indent} cached_memory = max(\n",
  131. f"{body_indent} torch.cuda.memory_reserved(self.device)\n",
  132. f"{body_indent} - torch.cuda.memory_allocated(self.device),\n",
  133. f"{body_indent} 0,\n",
  134. f"{body_indent} )\n",
  135. f"{body_indent} torch.cuda.empty_cache()\n",
  136. f"{body_indent} if cached_memory > 0:\n",
  137. f"{body_indent} logger.info_once(\n",
  138. f'{body_indent} "Cleared %.2f GiB of cached CUDA allocator '
  139. 'memory before "\n',
  140. f'{body_indent} "KV cache allocation.",\n',
  141. f"{body_indent} cached_memory / (1024**3),\n",
  142. f"{body_indent} )\n",
  143. "\n",
  144. ]
  145. insert_after_docstring(func_index, func_indent, block)
  146. changed = True
  147. if changed:
  148. target.write_text("".join(lines))
  149. print("Applied Spark KV cache cleanup patch")
  150. else:
  151. print("Equivalent Spark KV cache cleanup already present; skipping")