Skip to content

Commit 4c8f309

Browse files
authored
Merge pull request #2 from aiwantaozi/fix/1
fix: benchmark run hang indefinitely is macos with default multiprocessing/data loader settings
2 parents 0b055ae + 102b735 commit 4c8f309

2 files changed

Lines changed: 82 additions & 2 deletions

File tree

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ What it adds
1212
- ShareGPT dataset conversion to GuideLLM-compatible JSONL.
1313
- A JSON summary output format for benchmark reports.
1414
- Custom response handler for accurate TTFT/ITL metrics with reasoning tokens (e.g., DeepSeek-R1).
15+
- Optional backend mode to preserve HTTP error details (`message/type/code`) in failed request records.
1516

1617
Install
1718
-------
@@ -57,6 +58,32 @@ benchmark-runner benchmark \
5758
--progress-auth YOUR_TOKEN
5859
```
5960

61+
HTTP Error Details for Failed Requests
62+
--------------------------------------
63+
GuideLLM's default `openai_http` backend does not always preserve response-body
64+
error payloads in request-level benchmark errors. Benchmark Runner provides an
65+
opt-in backend type that enriches failed request errors using OpenAI-style error
66+
fields (`error.message`, `error.type`, `error.code`):
67+
68+
```bash
69+
benchmark-runner benchmark run \
70+
--target http://localhost:8000/v1 \
71+
--backend openai_http_error_detail \
72+
--profile constant \
73+
--rate 10 \
74+
--max-requests 100 \
75+
--sample-requests 20 \
76+
--data "prompt_tokens=128,output_tokens=256" \
77+
--processor PROCESSOR_PATH
78+
```
79+
80+
When a request fails, `requests.errored[*].info.error` in benchmark outputs will
81+
contain text similar to:
82+
`HTTP 400: ... (type=BadRequestError, code=400)`.
83+
84+
Note: if `--sample-requests 0` is used, request-level samples are omitted by design,
85+
including failed request details.
86+
6087
ShareGPT dataset support
6188
------------------------
6289
If a dataset filename contains "sharegpt" and ends with `.json` or `.jsonl`,
@@ -123,6 +150,24 @@ Install development dependencies:
123150
pip install -e ".[dev]"
124151
```
125152

153+
macOS Notes
154+
-----------
155+
Benchmark Runner applies two macOS-only runtime defaults to avoid known
156+
multiprocessing hangs:
157+
- switch GuideLLM multiprocessing context from `fork` to `spawn` (unless
158+
`GUIDELLM__MP_CONTEXT_TYPE` is explicitly set)
159+
- default `--data-num-workers` to `0` unless provided on the CLI
160+
161+
References:
162+
- https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods
163+
- https://bugs.python.org/issue33725
164+
165+
To disable these defaults for debugging/experiments:
166+
167+
```bash
168+
BENCHMARK_RUNNER_DISABLE_MACOS_WORKAROUNDS=1 benchmark-runner benchmark run ...
169+
```
170+
126171
License
127172
-------
128173
See repository license information.

benchmark_runner/main.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
from __future__ import annotations
1919

2020
import asyncio
21+
import os
22+
import platform
2123
from pathlib import Path
2224

2325
import click
@@ -42,12 +44,44 @@
4244
)
4345
from guidellm.scheduler import StrategyType
4446
from guidellm.schemas import GenerativeRequestType
45-
from guidellm.settings import print_config
47+
from guidellm.settings import print_config, settings as guidellm_settings
4648
from guidellm.utils import Console, DefaultGroupHandler, get_literal_vals
4749
from guidellm.utils import cli as cli_tools
4850

49-
STRATEGY_PROFILE_CHOICES: list[str] = list(get_literal_vals(ProfileType | StrategyType))
5051
"""Available strategy and profile type choices for benchmark execution."""
52+
STRATEGY_PROFILE_CHOICES: list[str] = list(get_literal_vals(ProfileType | StrategyType))
53+
54+
55+
DISABLE_MACOS_WORKAROUNDS_ENV = "BENCHMARK_RUNNER_DISABLE_MACOS_WORKAROUNDS"
56+
"""Set to 1/true/yes to disable runtime macOS defaults for process/data workers."""
57+
58+
59+
def apply_macos_runtime_workarounds(kwargs: dict) -> None:
60+
if platform.system() != "Darwin":
61+
return
62+
63+
if os.environ.get(DISABLE_MACOS_WORKAROUNDS_ENV, "").lower() in {
64+
"1",
65+
"true",
66+
"yes",
67+
}:
68+
return
69+
70+
# Why this exists:
71+
# - GuideLLM defaults to multiprocessing "fork", which can hang on macOS in
72+
# mixed runtime stacks (tokenizers/torch/http clients).
73+
# - See Python multiprocessing docs and macOS fork notes:
74+
# https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods
75+
# https://bugs.python.org/issue33725
76+
if (
77+
"GUIDELLM__MP_CONTEXT_TYPE" not in os.environ
78+
and guidellm_settings.mp_context_type == "fork"
79+
):
80+
guidellm_settings.mp_context_type = "spawn"
81+
82+
# Keep DataLoader single-process by default on macOS unless user overrides it.
83+
if "data_num_workers" not in kwargs:
84+
kwargs["data_num_workers"] = 0
5185

5286

5387
@click.group()
@@ -393,6 +427,7 @@ def benchmark():
393427
def run(**kwargs): # noqa: C901
394428
# Only set CLI args that differ from click defaults
395429
kwargs = cli_tools.set_if_not_default(click.get_current_context(), **kwargs)
430+
apply_macos_runtime_workarounds(kwargs)
396431

397432
# Handle remapping for request params
398433
request_type = kwargs.pop("request_type", None)

0 commit comments

Comments
 (0)