Skip to content

Commit 7599202

Browse files
author
Dmytro Parfeniuk
committed
Complete with Code quality configuration
1 parent 7897e8d commit 7599202

File tree

11 files changed

+111
-39
lines changed

11 files changed

+111
-39
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ install:
55

66
.PHONY: install.dev
77
install.dev:
8-
python -m pip install .[dev]
8+
python -m pip install -e .[dev]
99

1010

1111
.PHONY: build
@@ -63,3 +63,4 @@ clean:
6363
find . -type d -name "__pycache__" -exec rm -r {} +
6464
rm -rf .mypy_cache
6565
rm -rf .pytest_cache
66+
rm -rf .tox

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,53 @@ set -o allexport; source .env; set +o allexport
1818
| --------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1919
| OPENAI_BASE_URL | http://127.0.0.1:8080 | The host where the `openai` library will make requests to. For running integration tests it is required to have the external OpenAI compatible server running. |
2020
| OPENAI_API_KEY | invalid | [OpenAI Platform](https://platform.openai.com/api-keys) to create a new API key. This value is not used for tests. |
21+
22+
</br>
23+
24+
# Code Quality
25+
26+
The variety of tools are available for checking the code quality.
27+
28+
All the code quality tools configuration is placed into next files: `pyproject.toml`, `Makefile`, `tox.ini`
29+
30+
**To provide the code quality next tools are used:**
31+
32+
- `pytest` as a testing framework
33+
- `ruff` as a linter
34+
- `black` & `isort` as formatters
35+
- `mypy` as a static type checker
36+
- `tox` as a automation tool
37+
- `make` as a automation tool (works only on Unix by default)
38+
39+
**Checking code quality using CLI**
40+
41+
All the tools could be run right from the CLI.
42+
43+
Recommended command template: `python -m pytest test.integration`
44+
45+
46+
**Checking code quality using Makefile**
47+
48+
Using `Makefile` you can run almost all common scripts to check the code quality, install dependencies, and to provide auto fixes. All the commands from the `Makefile` are valid to be used in the CLI.
49+
50+
Here are some commands
51+
52+
```sh
53+
# install dev dependencies
54+
make install.dev
55+
56+
# run unit tests
57+
make test.unit
58+
59+
# run quality checkers
60+
make quality
61+
62+
# run autofixes
63+
make style
64+
```
65+
66+
**Checking code quality using tox**
67+
68+
The `tox` is an automation tool for running code quality checkers for selected Python versions. The configuration is placed in the `tox.ini`.
69+
70+
To run the automation just run: `python -m tox`

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
TODO
1+
TODO

pyproject.toml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,19 @@ dependencies = [
3636

3737
[project.optional-dependencies]
3838
dev = [
39-
"black",
40-
"isort",
41-
"mypy",
39+
"guidellm[code_quality]",
4240
"pre-commit",
43-
"pytest",
44-
"ruff",
4541
"sphinx",
46-
"tox",
4742
]
4843
code_quality = [
4944
"black",
5045
"isort",
5146
"mypy",
5247
"pytest",
48+
"pytest-mock",
5349
"ruff",
50+
"tox",
51+
"types-requests"
5452
]
5553

5654

@@ -84,6 +82,10 @@ exclude = ["venv", ".tox"]
8482
# Check: https://mypy.readthedocs.io/en/latest/config_file.html#import-discovery
8583
follow_imports = 'silent'
8684

85+
[[tool.mypy.overrides]]
86+
module = ["transformers.*", "datasets.*"]
87+
ignore_missing_imports=true
88+
8789

8890
[tool.ruff]
8991
line-length = 88

src/guidellm/backend/base.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ def submit(self, request: TextGenerationRequest) -> TextGenerationResult:
102102
result.output_token(response.add_token)
103103
elif response.type_ == "final":
104104
result.end(
105-
response.output or "",
106105
response.prompt_token_count,
107106
response.output_token_count,
108107
)

src/guidellm/core/result.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,7 @@ def end_time(self) -> float:
148148
:rtype: float
149149
"""
150150

151-
self._recording_started()
152151
assert self._end_time
153-
154152
return self._end_time
155153

156154
@property
@@ -173,6 +171,11 @@ def decode_times(self) -> Distribution:
173171
"""
174172
return self._decode_times
175173

174+
@property
175+
def last_time(self) -> float:
176+
assert self._last_time
177+
return self._last_time
178+
176179
def start(self, prompt: str):
177180
"""
178181
Start the text generation by recording the prompt and start time.
@@ -193,16 +196,16 @@ def _recording_started(self, raise_exception: bool = True) -> bool:
193196
"""
194197
Ensure that the benchmark text generation recording is started.
195198
196-
We can assume that if the `self.start_time` & `self.end_time` exist
199+
We can assume that if the `self._start_time` exist,
197200
then the `start()` has been called.
198201
"""
199202

200-
if (self.start_time is not None) and (self.end_time is not None):
203+
if self._start_time is not None:
201204
return True
202205
else:
203206
if raise_exception is True:
204207
raise ValueError(
205-
"Last time is not specified. "
208+
"start time is not specified. "
206209
"Did you make the `text_generation_benchmark.start()`?"
207210
)
208211
else:
@@ -219,11 +222,11 @@ def output_token(self, token: str):
219222
current_counter = perf_counter()
220223

221224
if not self._first_token_set:
222-
self._first_token_time = current_counter - self.end_time
225+
self._first_token_time = current_counter - self.last_time
223226
self._first_token_set = True
224227
logger.debug(f"First token decode time: {self._first_token_time}")
225228
else:
226-
decode_time = current_counter - self.end_time
229+
decode_time = current_counter - self.last_time
227230
self._decode_times.add_data([decode_time])
228231
logger.debug(f"Token '{token}' decoded in {decode_time} seconds")
229232

@@ -232,7 +235,6 @@ def output_token(self, token: str):
232235

233236
def end(
234237
self,
235-
output: str,
236238
prompt_token_count: Optional[int] = None,
237239
output_token_count: Optional[int] = None,
238240
):
@@ -254,7 +256,7 @@ def end(
254256
self._output_token_count = output_token_count or self._output_word_count
255257
self._prompt_token_count = prompt_token_count or self._prompt_word_count
256258

257-
logger.info(f"Text generation ended with output: '{output}'")
259+
logger.info(f"Text generation ended with output: '{self.output}'")
258260

259261

260262
class TextGenerationError:

src/guidellm/main.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ def main(
9696
)
9797

9898
if data_type == "emulated":
99-
request_generator: RequestGenerator = EmulatedRequestGenerator(config=data, tokenizer=tokenizer)
99+
request_generator: RequestGenerator = EmulatedRequestGenerator(
100+
config=data, tokenizer=tokenizer
101+
)
100102
elif data_type == "file":
101103
request_generator = FileRequestGenerator(file_path=data, tokenizer=tokenizer)
102104
elif data_type == "transformers":

src/guidellm/request/emulated.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,10 @@ def _load_emulated_data(self) -> List[str]:
141141
.replace("! ", "!\n")
142142
.replace("? ", "?\n")
143143
)
144-
lines = lines.split("\n")
145-
lines = [line.strip() for line in lines if line and line.strip()]
144+
_lines: List[str] = lines.split("\n")
145+
_lines = [line.strip() for line in lines if line and line.strip()]
146146

147-
return lines
147+
return _lines
148148

149149
def _token_count(self, text: str) -> int:
150150
return (

src/guidellm/scheduler/scheduler.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import asyncio
22
import time
3-
from typing import Iterable, Optional, Union
3+
from typing import Iterable, List, Optional, Union
44

55
from guidellm.backend import Backend
66
from guidellm.core import TextGenerationBenchmark, TextGenerationError
@@ -71,22 +71,23 @@ async def _run_async(self) -> TextGenerationBenchmark:
7171
result_set = TextGenerationBenchmark(
7272
mode=self._load_gen_mode.value, rate=self._load_gen_rate
7373
)
74-
if (not self._load_gen_rate):
74+
if not self._load_gen_rate:
7575
raise ValueError("Invalid empty value for self._load_gen_rate")
7676
load_gen = LoadGenerator(self._load_gen_mode, self._load_gen_rate)
7777

78-
tasks = []
78+
tasks: List[asyncio.tasks.Task] = []
7979
start_time = time.time()
8080
counter = 0
81+
8182
try:
82-
for task, task_start_time in zip(self._task_iterator(), load_gen.times()):
83+
for _task, task_start_time in zip(self._task_iterator(), load_gen.times()):
8384
pending_time = task_start_time - time.time()
8485

8586
if pending_time > 0:
8687
await asyncio.sleep(pending_time)
8788

8889
tasks.append(
89-
asyncio.create_task(self._run_task_async(task, result_set))
90+
asyncio.create_task(self._run_task_async(_task, result_set))
9091
)
9192
counter += 1
9293

@@ -106,7 +107,7 @@ async def _run_async(self) -> TextGenerationBenchmark:
106107

107108
await asyncio.gather(*tasks)
108109
except asyncio.CancelledError:
109-
# Cancel all pending tasks
110+
# Cancel all pending asyncio.Tasks instances
110111
for task in tasks:
111112
if not task.done():
112113
task.cancel()

src/guidellm/scheduler/task.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import functools
23
from typing import Any, Callable, Dict, Optional
34

45
from loguru import logger
@@ -25,10 +26,11 @@ def __init__(
2526
params: Optional[Dict[str, Any]] = None,
2627
err_container: Optional[Callable] = None,
2728
):
28-
self._func = func
29-
self._params = params or {}
30-
self._err_container = err_container
31-
self._cancel_event = asyncio.Event()
29+
self._func: Callable[..., Any] = func
30+
self._params: Dict[str, Any] = params or {}
31+
self._err_container: Optional[Callable] = err_container
32+
self._cancel_event: asyncio.Event = asyncio.Event()
33+
3234
logger.info(
3335
f"Task created with function: {self._func.__name__} and "
3436
f"params: {self._params}"
@@ -43,15 +45,19 @@ async def run_async(self) -> Any:
4345
"""
4446
logger.info(f"Running task asynchronously with function: {self._func.__name__}")
4547
try:
48+
loop = asyncio.get_running_loop()
49+
4650
result = await asyncio.gather(
47-
asyncio.to_thread(self._func, **self._params),
51+
loop.run_in_executor(
52+
None, functools.partial(self._func, **self._params)
53+
),
4854
self._check_cancelled(),
4955
return_exceptions=True,
5056
)
5157
if isinstance(result[0], Exception):
5258
raise result[0]
5359

54-
if self.is_cancelled():
60+
if self.cancelled is True:
5561
raise asyncio.CancelledError("Task was cancelled")
5662

5763
logger.info(f"Task completed with result: {result[0]}")
@@ -92,7 +98,7 @@ def run_sync(self) -> Any:
9298
else self._err_container(**self._params, error=err)
9399
)
94100

95-
def cancel(self):
101+
def cancel(self) -> None:
96102
"""
97103
Cancel the task.
98104
"""
@@ -105,7 +111,8 @@ async def _check_cancelled(self):
105111
"""
106112
await self._cancel_event.wait()
107113

108-
def is_cancelled(self) -> bool:
114+
@property
115+
def cancelled(self) -> bool:
109116
"""
110117
Check if the task is cancelled.
111118

0 commit comments

Comments
 (0)