Guest User

vllm_llama_test

a guest
Jun 17th, 2026
70
0
36 days
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.23 KB | Source Code | 0 0
  1. import asyncio
  2. import random
  3. import aiohttp
  4. import time
  5. import matplotlib.pyplot as plt
  6. import json
  7. import os
  8.  
  9. SERVER_HOST = "127.0.0.0"
  10. SERVER_PORT = "8080"
  11. SERVER_URL = f"http://{SERVER_HOST}:{SERVER_PORT}/v1/completions"
  12.  
  13. # Имя модели, указанное в параметре --served-model-name при запуске vLLM / llama.cpp
  14. MODEL_NAME = "*"
  15.  
  16. # Базовые строки для построения промптов
  17. BASE_DOG = "The quick brown fox jumps over the lazy dog. "
  18. BASE_CAT = "The quick brown fox jumps over the lazy cat. "
  19.  
  20. async def send_vllm_request(session, prompt, slot_id):
  21.     """
  22.    Отправляет потоковый запрос в vLLM / llama.cpp с поддержкой сохранения кеша.
  23.    """
  24.     payload = {
  25.         "model": MODEL_NAME,
  26.         "prompt": prompt,
  27.         "max_tokens": 64,
  28.         "temperature": 1.0,
  29.         "stream": True,
  30.         #"id_slot": slot_id,          # Иногда с этим ллама хуже работает
  31.         "cache_prompt": True,        
  32.         "stream_options": {"include_usage": True}
  33.     }
  34.  
  35.     start_time = time.perf_counter()
  36.     ttft = None
  37.     generated_text = ""
  38.     prompt_tokens = 0
  39.     completion_tokens = 0
  40.  
  41.     async with session.post(SERVER_URL, json=payload, timeout=900) as resp:
  42.         if resp.status != 200:
  43.             err_text = await resp.text()
  44.             raise RuntimeError(f"HTTP {resp.status}: {err_text}")
  45.  
  46.         async for line in resp.content:
  47.             line = line.decode('utf-8').strip()
  48.             if not line or line == "data: [DONE]":
  49.                 continue
  50.  
  51.             if line.startswith("data: "):
  52.                 data_str = line[6:]
  53.                 try:
  54.                     data = json.loads(data_str)
  55.                 except json.JSONDecodeError:
  56.                     continue
  57.  
  58.                 # Фиксируем TTFT по первому пришедшему чанку с текстом
  59.                 if ttft is None and "choices" in data and len(data["choices"]) > 0:
  60.                     ttft = time.perf_counter() - start_time
  61.  
  62.                 if data.get("choices") and data["choices"][0].get("text"):
  63.                     generated_text += data["choices"][0]["text"]
  64.  
  65.                 if data.get("usage"):
  66.                     prompt_tokens = data["usage"].get("prompt_tokens", 0)
  67.                     completion_tokens = data["usage"].get("completion_tokens", 0)
  68.  
  69.     total_time = time.perf_counter() - start_time
  70.  
  71.     if completion_tokens == 0 and generated_text:
  72.         completion_tokens = max(1, len(generated_text) // 4)
  73.     elif completion_tokens == 0:
  74.         completion_tokens = 1
  75.  
  76.     if ttft is not None:
  77.         tg_time = max(0.001, total_time - ttft)
  78.     else:
  79.         ttft = total_time
  80.         tg_time = 0.001
  81.  
  82.     return ttft, tg_time, prompt_tokens, completion_tokens, generated_text
  83.  
  84. async def run_experiment(num_streams, max_context, chunk_text):
  85.     print(f"\n=== СТАРТ ТЕСТА: {num_streams} ПОТОК(ОВ) | ЛИМИТ {max_context} ТОКЕНОВ ===")
  86.  
  87.     # Инициализируем структуру. Промпт растет строго линейно.
  88.     streams_state = [{"prompt": chunk_text, "ctx_size": 0} for _ in range(num_streams)]
  89.  
  90.     history = {
  91.         "context_sizes": [],
  92.         "pp_speeds_wall": [],
  93.         "tg_speeds_wall": []
  94.     }
  95.  
  96.     step = 1
  97.     timeout = aiohttp.ClientTimeout(total=2400)
  98.  
  99.     async with aiohttp.ClientSession(timeout=timeout) as session:
  100.         while True:
  101.             if step > 1 and streams_state[0]["ctx_size"] >= max_context:
  102.                 break
  103.  
  104.             try:
  105.                 # Передаем индекс `i` в качестве slot_id. Он стабилен между итерациями (0, 1, 2...)
  106.                 responses = await asyncio.gather(*(
  107.                     send_vllm_request(session, s["prompt"], i) for i, s in enumerate(streams_state)
  108.                 ))
  109.             except Exception as e:
  110.                 print(f"[Ошибка запроса]: {e}")
  111.                 break
  112.  
  113.             pp_ms_list = []
  114.             tg_ms_list = []
  115.             total_pp_tokens = 0
  116.             total_tg_tokens = 0
  117.             ctx_sizes = []
  118.  
  119.             for idx, res in enumerate(responses):
  120.                 ttft, tg_time, p_tokens, c_tokens, content = res
  121.  
  122.                 pp_ms_list.append(ttft * 1000)
  123.                 tg_ms_list.append(tg_time * 1000)
  124.  
  125.                 # Вычисляем дельту новых токенов
  126.                 new_tokens = p_tokens - streams_state[idx]["ctx_size"]
  127.                 if new_tokens <= 0:
  128.                     new_tokens = p_tokens
  129.  
  130.                 total_pp_tokens += new_tokens
  131.                 total_tg_tokens += c_tokens
  132.  
  133.                 streams_state[idx]["ctx_size"] = p_tokens
  134.  
  135.                 # Важно: склеиваем без лишних пробелов в стыках, чтобы не ломать токенизацию границ строк в llama.cpp
  136.                 streams_state[idx]["prompt"] += content + chunk_text
  137.                 ctx_sizes.append(p_tokens)
  138.  
  139.             avg_pp_ms = sum(pp_ms_list) / len(pp_ms_list) if pp_ms_list else 0.001
  140.             avg_tg_ms = sum(tg_ms_list) / len(tg_ms_list) if tg_ms_list else 0.001
  141.  
  142.             pp_speed_wall = total_pp_tokens / (avg_pp_ms / 1000)
  143.             tg_speed_wall = total_tg_tokens / (avg_tg_ms / 1000)
  144.  
  145.             cache_str = f"{min(ctx_sizes)}..{max(ctx_sizes)}"
  146.             pp_ms_str = f"[{', '.join(f'{x:.1f}' for x in pp_ms_list)}]"
  147.             tg_ms_str = f"[{', '.join(f'{x:.1f}' for x in tg_ms_list)}]"
  148.  
  149.             print(f"Шаг {step:<2}, кэш {cache_str:<13} | "
  150.                   f"pp(TTFT) - {pp_ms_str:<25}ms [≈{pp_speed_wall:<6.1f} t/s] | "
  151.                   f"tg(TPOT) - {tg_ms_str:<25}ms [≈{tg_speed_wall:<6.1f} t/s]")
  152.  
  153.             history["context_sizes"].append(max(ctx_sizes))
  154.             history["pp_speeds_wall"].append(pp_speed_wall)
  155.             history["tg_speeds_wall"].append(tg_speed_wall)
  156.  
  157.             step += 1
  158.  
  159.     return history
  160.  
  161. def save_and_plot(all_experiments_data, filename_suffix):
  162.     dir_name = os.path.dirname(filename_suffix)
  163.     if dir_name:
  164.         os.makedirs(dir_name, exist_ok=True)
  165.  
  166.     json_path = f"{filename_suffix}.json"
  167.     img_path = f"{filename_suffix}.png"
  168.  
  169.     with open(json_path, "w") as f:
  170.         json.dump(all_experiments_data, f, indent=4)
  171.  
  172.     colors = {1:'blue', 2:'green', 4:'orange', 8:'red', 16:'purple'}
  173.     fig, axs = plt.subplots(2, 2, figsize=(20, 14))
  174.  
  175.     ax_left_pp, ax_right_pp = axs[0, 0], axs[0, 1]
  176.     ax_left_tg, ax_right_tg = axs[1, 0], axs[1, 1]
  177.  
  178.     for num_streams, history in all_experiments_data.items():
  179.         num_streams = int(num_streams)
  180.         color = colors.get(num_streams, 'black')
  181.  
  182.         ctx_single = history["context_sizes"]
  183.         ctx_total = [c * num_streams for c in ctx_single]
  184.  
  185.         pp_total = history["pp_speeds_wall"]
  186.         pp_ind = [p / num_streams for p in pp_total]
  187.  
  188.         tg_total = history["tg_speeds_wall"]
  189.         tg_ind = [t / num_streams for t in tg_total]
  190.  
  191.         ax_left_pp.plot(ctx_single, pp_total, linestyle='-', marker='x', color=color, label=f'{num_streams} Поток(ов) - Полный PP')
  192.         ax_left_pp.plot(ctx_single, pp_ind, linestyle=':', marker='x', color=color, alpha=0.8, label=f'{num_streams} Поток(ов) - Индив. PP')
  193.  
  194.         ax_left_tg.plot(ctx_single, tg_total, linestyle='-', marker='x', color=color, label=f'{num_streams} Поток(ов) - Полный TG')
  195.         ax_left_tg.plot(ctx_single, tg_ind, linestyle=':', marker='x', color=color, alpha=0.8, label=f'{num_streams} Поток(ов) - Индив. TG')
  196.  
  197.         ax_right_pp.plot(ctx_total, pp_total, linestyle='-', marker='x', color=color, label=f'{num_streams} Поток(ов) - Полный PP')
  198.         ax_right_pp.plot(ctx_total, pp_ind, linestyle=':', marker='x', color=color, alpha=0.8, label=f'{num_streams} Поток(ов) - Индив. PP')
  199.  
  200.         ax_right_tg.plot(ctx_total, tg_total, linestyle='-', marker='x', color=color, label=f'{num_streams} Поток(ов) - Полный TG')
  201.         ax_right_tg.plot(ctx_total, tg_ind, linestyle=':', marker='x', color=color, alpha=0.8, label=f'{num_streams} Поток(ов) - Индив. TG')
  202.  
  203.     for ax in [ax_left_pp, ax_left_tg, ax_right_pp, ax_right_tg]:
  204.         ax.grid(True, linestyle=':', alpha=0.6)
  205.         ax.legend(loc='upper right', fontsize=8)
  206.  
  207.     ax_left_pp.set_title('PP Скорость (TTFT) | Ось X: Кэш потока', fontweight='bold')
  208.     ax_left_tg.set_title('TG Скорость (TPOT) | Ось X: Кэш потока', fontweight='bold')
  209.     ax_right_pp.set_title('PP Скорость (TTFT) | Ось X: Сумма кэшей', fontweight='bold')
  210.     ax_right_tg.set_title('TG Скорость (TPOT) | Ось X: Сумма кэшей', fontweight='bold')
  211.  
  212.     fig.suptitle(f"Комплексный профиль производительности\nБаза: {os.path.basename(filename_suffix)}", fontsize=14, fontweight='bold', y=0.98)
  213.     fig.tight_layout(rect=[0, 0, 1, 0.95])
  214.     fig.savefig(img_path, dpi=150)
  215.  
  216.     print(f"\n[Успех] Данные сохранены в: {json_path}")
  217.     print(f"[Успех] График сохранен в: {img_path}")
  218.  
  219. async def main():
  220.     experiments_config = [
  221.         {"streams": 1, "max_context": 120000},
  222.         {"streams": 2, "max_context": 80000},
  223.         {"streams": 4, "max_context": 50000},
  224.         {"streams": 8, "max_context": 25000},
  225.         {"streams": 16, "max_context": 16000}
  226.     ]
  227.  
  228.     all_results = {}
  229.  
  230.     for idx, config in enumerate(experiments_config):
  231.         streams = config["streams"]
  232.         max_ctx = config["max_context"]
  233.  
  234.         reps = 105 if streams == 1 else (51 if streams == 2 else 26)
  235.         reps *= 4
  236.  
  237.         base_text = BASE_DOG if idx % 2 == 0 else BASE_CAT
  238.         chunk_text = str(random.randint(0, 9)) + base_text * reps
  239.  
  240.         await asyncio.sleep(2)
  241.  
  242.         history = await run_experiment(streams, max_ctx, chunk_text)
  243.         all_results[streams] = history
  244.  
  245.     save_and_plot(all_results, filename_suffix="result_custom_test/vllm_benchmark_results")
  246.  
  247. if __name__ == "__main__":
  248.     asyncio.run(main())
  249.  
Advertisement
Add Comment
Please, Sign In to add comment