-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrun_multi_concurrency_benchmark_local_v2.py
More file actions
executable file
Β·471 lines (384 loc) Β· 15.6 KB
/
Copy pathrun_multi_concurrency_benchmark_local_v2.py
File metadata and controls
executable file
Β·471 lines (384 loc) Β· 15.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
#!/usr/bin/env python3
"""
Run GuideLLM benchmarks with multiple concurrency rates from LOCAL machine.
This script runs on your laptop/workstation and executes commands in the pod remotely.
Captures console output for each run.
Usage:
python3 run_multi_concurrency_benchmark_local_v2.py
"""
import subprocess
import time
import json
import sys
from datetime import datetime
from typing import List, Tuple
# Configuration
NAMESPACE = "kserve-e2e-perf"
GUIDELLM_POD = "guidellm-052-mehulm"
POD_LABEL = "serving.kserve.io/inferenceservice=qwen3-vl-30b-a3b-inst-10ce087b"
TARGET_URL = "http://qwen3-vl-30b-a3b-inst-10ce087b-predictor.kserve-e2e-perf.svc.cluster.local:8080/v1"
HEALTH_URL = "http://qwen3-vl-30b-a3b-inst-10ce087b-predictor.kserve-e2e-perf.svc.cluster.local:8080/health"
DATA_PATH = "/datasets/gpt_oss_perf.parquet"
OUTPUT_DIR = "/results"
LOCAL_OUTPUT_DIR = "./benchmark-results" # Local directory to copy results
# Concurrency rates to test
CONCURRENCY_RATES = [1, 10, 50, 100, 200, 300]
# Timeouts
POD_RESTART_TIMEOUT = 600 # 10 minutes
HEALTH_CHECK_TIMEOUT = 300 # 5 minutes
HEALTH_CHECK_INTERVAL = 10 # 10 seconds
def run_command(cmd: List[str], check: bool = True, capture_output: bool = True) -> Tuple[int, str, str]:
"""Run a shell command and return exit code, stdout, stderr."""
print(f"\nπ§ Running: {' '.join(cmd)}")
result = subprocess.run(
cmd,
capture_output=capture_output,
text=True,
check=False
)
if check and result.returncode != 0:
print(f"β Command failed with exit code {result.returncode}")
print(f"STDERR: {result.stderr}")
if not capture_output:
sys.exit(1)
return result.returncode, result.stdout, result.stderr
def restart_pod():
"""Delete the pod to trigger restart."""
print("\n" + "=" * 80)
print(f"π Restarting pod with label: {POD_LABEL}")
print("=" * 80)
# Get current pod name
cmd_get_pod = [
"oc", "get", "pods",
"-l", POD_LABEL,
"-n", NAMESPACE,
"-o", "jsonpath={.items[0].metadata.name}"
]
_, current_pod, _ = run_command(cmd_get_pod, check=True)
current_pod = current_pod.strip()
if not current_pod:
print("β No pod found with the label")
sys.exit(1)
print(f"Current pod: {current_pod}")
# Delete the pod
cmd = ["oc", "delete", "pod", current_pod, "-n", NAMESPACE, "--wait=false"]
returncode, stdout, stderr = run_command(cmd, check=False)
if returncode != 0:
print(f"β οΈ Pod delete returned code {returncode}, continuing anyway...")
else:
print(f"β Pod deletion initiated")
# Wait a bit for deletion to start
time.sleep(5)
def wait_for_pod_ready():
"""Wait for the pod to be ready."""
print("\n" + "=" * 80)
print(f"β³ Waiting for pod to be ready...")
print("=" * 80)
start_time = time.time()
while time.time() - start_time < POD_RESTART_TIMEOUT:
# Get pod status
cmd = [
"oc", "get", "pods",
"-l", POD_LABEL,
"-n", NAMESPACE,
"-o", "jsonpath={.items[0].status.phase}"
]
returncode, stdout, stderr = run_command(cmd, check=False)
if returncode == 0 and stdout.strip() == "Running":
# Check if ready
cmd_ready = [
"oc", "get", "pods",
"-l", POD_LABEL,
"-n", NAMESPACE,
"-o", "jsonpath={.items[0].status.conditions[?(@.type=='Ready')].status}"
]
returncode_ready, stdout_ready, _ = run_command(cmd_ready, check=False)
if returncode_ready == 0 and stdout_ready.strip() == "True":
# Get the new pod name
cmd_name = [
"oc", "get", "pods",
"-l", POD_LABEL,
"-n", NAMESPACE,
"-o", "jsonpath={.items[0].metadata.name}"
]
_, pod_name, _ = run_command(cmd_name, check=False)
elapsed = time.time() - start_time
print(f"β Pod is ready: {pod_name.strip()}")
print(f" Elapsed time: {elapsed:.1f} seconds")
return pod_name.strip()
# Show progress
elapsed = time.time() - start_time
print(f" β³ Waiting... ({elapsed:.0f}s / {POD_RESTART_TIMEOUT}s) - Status: {stdout.strip()}")
time.sleep(10)
print(f"β Pod did not become ready within {POD_RESTART_TIMEOUT} seconds")
sys.exit(1)
def check_health():
"""Check if the health endpoint is responding from inside the guidellm pod."""
print("\n" + "=" * 80)
print(f"π₯ Checking health endpoint: {HEALTH_URL}")
print("=" * 80)
start_time = time.time()
while time.time() - start_time < HEALTH_CHECK_TIMEOUT:
# Run curl from inside the guidellm pod
cmd = [
"oc", "exec", "-n", NAMESPACE, GUIDELLM_POD, "--",
"curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", HEALTH_URL
]
returncode, stdout, stderr = run_command(cmd, check=False)
if returncode == 0 and stdout.strip() == "200":
elapsed = time.time() - start_time
print(f"β Health check passed!")
print(f" Elapsed time: {elapsed:.1f} seconds")
return True
elapsed = time.time() - start_time
print(f" β³ Waiting for health... ({elapsed:.0f}s / {HEALTH_CHECK_TIMEOUT}s) - HTTP: {stdout.strip()}")
time.sleep(HEALTH_CHECK_INTERVAL)
print(f"β Health check did not pass within {HEALTH_CHECK_TIMEOUT} seconds")
sys.exit(1)
def run_benchmark(concurrency: int) -> Tuple[str, str]:
"""Run GuideLLM benchmark with specified concurrency rate.
Returns:
Tuple of (output_file, console_log_file) paths in the pod
"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_file = f"{OUTPUT_DIR}/gpt-oss-parquet-concurrency{concurrency}_{timestamp}.json"
console_log_file = f"{OUTPUT_DIR}/gpt-oss-parquet-concurrency{concurrency}_{timestamp}_console.log"
print("\n" + "=" * 80)
print(f"π Running benchmark with concurrency: {concurrency}")
print(f"π Output file: {output_file}")
print(f"π Console log: {console_log_file}")
print("=" * 80)
# Build the guidellm command
guidellm_cmd = (
f"guidellm benchmark "
f"--target {TARGET_URL} "
f"--data {DATA_PATH} "
f"--data-column-mapper '{{\"text_column\":\"text_input\"}}' "
f"--data-samples 500 "
f"--processor gpt2 "
f"--rate-type concurrent "
f"--rate {concurrency} "
f"--output-path {output_file}"
)
# Build command that captures output to log file AND displays it
full_cmd = f"{guidellm_cmd} 2>&1 | tee {console_log_file}"
# Run guidellm inside the pod
cmd = [
"oc", "exec", "-n", NAMESPACE, GUIDELLM_POD, "--",
"bash", "-c", full_cmd
]
start_time = time.time()
print(f"\nπ§ Executing in pod: {guidellm_cmd}\n")
print(f"π Console output will be saved to: {console_log_file}\n")
# Run without capturing output so we see live progress
returncode, _, _ = run_command(cmd, check=False, capture_output=False)
elapsed = time.time() - start_time
if returncode == 0:
print(f"\nβ Benchmark completed successfully!")
print(f" Elapsed time: {elapsed:.1f} seconds ({elapsed/60:.1f} minutes)")
print(f" Results saved to: {output_file}")
print(f" Console log saved to: {console_log_file}")
return output_file, console_log_file
else:
print(f"\nβ Benchmark failed with exit code {returncode}")
print(f" Elapsed time: {elapsed:.1f} seconds")
return None, None
def create_summary(results: List[dict]):
"""Create a summary file in the pod and copy it locally."""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
summary_file = f"{OUTPUT_DIR}/benchmark_summary_{timestamp}.json"
local_summary_file = f"{LOCAL_OUTPUT_DIR}/benchmark_summary_{timestamp}.json"
summary = {
"timestamp": timestamp,
"namespace": NAMESPACE,
"target": TARGET_URL,
"data": DATA_PATH,
"data_samples": 500,
"concurrency_rates": CONCURRENCY_RATES,
"results": results
}
# Write summary to a temp file locally
import tempfile
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as tmp:
json.dump(summary, tmp, indent=2)
tmp_path = tmp.name
# Copy to pod
cmd_copy = [
"oc", "cp", tmp_path,
f"{NAMESPACE}/{GUIDELLM_POD}:{summary_file}"
]
run_command(cmd_copy, check=True)
# Copy from pod to local directory
subprocess.run(["mkdir", "-p", LOCAL_OUTPUT_DIR], check=True)
cmd_copy_local = [
"oc", "cp",
f"{NAMESPACE}/{GUIDELLM_POD}:{summary_file}",
local_summary_file
]
run_command(cmd_copy_local, check=True)
print(f"\nπ Summary saved to:")
print(f" Pod: {summary_file}")
print(f" Local: {local_summary_file}")
return summary_file, local_summary_file
def copy_results_to_local(results: List[dict]):
"""Copy all benchmark results from pod to local machine."""
print("\n" + "=" * 80)
print("π₯ Copying results to local machine")
print("=" * 80)
subprocess.run(["mkdir", "-p", LOCAL_OUTPUT_DIR], check=True)
for result in results:
if result["status"] == "success":
# Copy JSON result file
if result.get("output_file"):
remote_file = result["output_file"]
local_file = f"{LOCAL_OUTPUT_DIR}/{remote_file.split('/')[-1]}"
cmd = [
"oc", "cp",
f"{NAMESPACE}/{GUIDELLM_POD}:{remote_file}",
local_file
]
returncode, _, _ = run_command(cmd, check=False)
if returncode == 0:
print(f" β Copied JSON: {local_file}")
else:
print(f" β Failed to copy: {remote_file}")
# Copy console log file
if result.get("console_log_file"):
remote_log = result["console_log_file"]
local_log = f"{LOCAL_OUTPUT_DIR}/{remote_log.split('/')[-1]}"
cmd_log = [
"oc", "cp",
f"{NAMESPACE}/{GUIDELLM_POD}:{remote_log}",
local_log
]
returncode_log, _, _ = run_command(cmd_log, check=False)
if returncode_log == 0:
print(f" β Copied LOG: {local_log}")
else:
print(f" β Failed to copy log: {remote_log}")
def main():
"""Main execution loop."""
print("\n" + "=" * 80)
print("π― Multi-Concurrency Benchmark Runner (Local Execution)")
print("=" * 80)
print(f"\nConfiguration:")
print(f" Namespace: {NAMESPACE}")
print(f" GuideLLM Pod: {GUIDELLM_POD}")
print(f" Model Pod Label: {POD_LABEL}")
print(f" Target URL: {TARGET_URL}")
print(f" Data: {DATA_PATH}")
print(f" Concurrency: {CONCURRENCY_RATES}")
print(f" Output dir (pod): {OUTPUT_DIR}")
print(f" Output dir (local): {LOCAL_OUTPUT_DIR}")
# Verify oc is available
returncode, _, _ = run_command(["which", "oc"], check=False)
if returncode != 0:
print("\nβ 'oc' command not found. Please install OpenShift CLI.")
sys.exit(1)
# Verify guidellm pod exists
cmd = ["oc", "get", "pod", GUIDELLM_POD, "-n", NAMESPACE]
returncode, _, _ = run_command(cmd, check=False)
if returncode != 0:
print(f"\nβ GuideLLM pod '{GUIDELLM_POD}' not found in namespace '{NAMESPACE}'")
sys.exit(1)
results = []
for i, concurrency in enumerate(CONCURRENCY_RATES, 1):
print("\n" + "=" * 80)
print(f"π BENCHMARK {i}/{len(CONCURRENCY_RATES)} - Concurrency: {concurrency}")
print("=" * 80)
try:
# Step 1: Restart pod (skip for first iteration)
if i > 1:
restart_pod()
# Step 2: Wait for pod to be ready
new_pod_name = wait_for_pod_ready()
# Step 3: Health check
check_health()
# Give it a bit more time to stabilize
print(f"\nβΈοΈ Waiting 30 seconds for model to fully load...")
time.sleep(30)
else:
print(f"\nβοΈ Skipping restart for first iteration")
# Still do health check
check_health()
# Step 4: Run benchmark
output_file, console_log_file = run_benchmark(concurrency)
if output_file and console_log_file:
results.append({
"concurrency": concurrency,
"output_file": output_file,
"console_log_file": console_log_file,
"status": "success",
"timestamp": datetime.now().isoformat()
})
else:
results.append({
"concurrency": concurrency,
"output_file": None,
"console_log_file": None,
"status": "failed",
"timestamp": datetime.now().isoformat()
})
except KeyboardInterrupt:
print("\n\nβ οΈ Interrupted by user")
break
except Exception as e:
print(f"\nβ Error during concurrency {concurrency}: {e}")
results.append({
"concurrency": concurrency,
"output_file": None,
"console_log_file": None,
"status": "error",
"error": str(e),
"timestamp": datetime.now().isoformat()
})
# Create summary
print("\n" + "=" * 80)
print("π Creating Summary Report")
print("=" * 80)
summary_file, local_summary_file = create_summary(results)
# Copy results to local machine
copy_results_to_local(results)
# Final report
print("\n" + "=" * 80)
print("β
BENCHMARK SUITE COMPLETE")
print("=" * 80)
print(f"\nResults:")
for result in results:
status_icon = "β" if result["status"] == "success" else "β"
print(f" {status_icon} Concurrency {result['concurrency']:3d}: {result['status']}")
if result.get("output_file"):
print(f" β JSON: {result['output_file']}")
if result.get("console_log_file"):
print(f" β LOG: {result['console_log_file']}")
print(f"\nπ Summary:")
print(f" Pod: {summary_file}")
print(f" Local: {local_summary_file}")
print(f"\nπ Local results directory: {LOCAL_OUTPUT_DIR}")
print(f" Contents:")
import os
if os.path.exists(LOCAL_OUTPUT_DIR):
files = sorted(os.listdir(LOCAL_OUTPUT_DIR))
for f in files:
size = os.path.getsize(os.path.join(LOCAL_OUTPUT_DIR, f))
size_mb = size / (1024 * 1024)
print(f" - {f} ({size_mb:.2f} MB)")
# Exit with error if any failed
failed = [r for r in results if r["status"] != "success"]
if failed:
print(f"\nβ οΈ {len(failed)} benchmark(s) failed")
sys.exit(1)
else:
print(f"\nπ All {len(results)} benchmarks completed successfully!")
sys.exit(0)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n\nβ οΈ Script interrupted by user")
sys.exit(130)
except Exception as e:
print(f"\nβ Fatal error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)