Skip to content

Commit 09d01f7

Browse files
SN4KEBYTEaniketmauryaCopilotpre-commit-ci[bot]
authored
🚀 Feature: warning for heavy __init__ method in LitAPI (#582)
* Feature: warning for heavy __init__ * Moved _TimedInitMeta to utils and made it private * Added warning message match * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Refactor warning message for heavy __init__ in _TimedInitMeta class to improve clarity and guidance. Update test to match new warning format. --------- Co-authored-by: Aniket Maurya <theaniketmaurya@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent e3136bf commit 09d01f7

File tree

3 files changed

+85
-3
lines changed

3 files changed

+85
-3
lines changed

src/litserve/api.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,21 @@
1515
import inspect
1616
import json
1717
import warnings
18-
from abc import ABC, abstractmethod
18+
from abc import ABC
1919
from queue import Queue
2020
from typing import TYPE_CHECKING, Callable, Optional, Union
2121

2222
from pydantic import BaseModel
2323

2424
from litserve.specs.base import LitSpec
25+
from litserve.utils import _TimedInitMeta
2526

2627
if TYPE_CHECKING:
2728
from litserve.loops.base import LitLoop
2829
from litserve.mcp import MCP
2930

3031

31-
class LitAPI(ABC):
32+
class LitAPI(ABC, metaclass=_TimedInitMeta):
3233
"""Define inference logic for the model.
3334
3435
LitAPI is the core abstraction for serving AI models with LitServe. It provides a clean
@@ -240,7 +241,6 @@ def _validate_async_methods(self):
240241
error_msg = "Async validation failed:\n" + "\n".join(f"- {err}" for err in errors)
241242
raise ValueError(error_msg)
242243

243-
@abstractmethod
244244
def setup(self, device):
245245
"""Setup the model so it can be called in `predict`."""
246246
pass

src/litserve/utils.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
import pdb
2020
import pickle
2121
import sys
22+
import time
2223
import uuid
24+
import warnings
25+
from abc import ABCMeta
2326
from contextlib import contextmanager
2427
from enum import Enum
2528
from typing import TYPE_CHECKING, Any, AsyncIterator, TextIO, Union
@@ -34,6 +37,10 @@
3437
_DEFAULT_LOG_FORMAT = (
3538
"%(asctime)s - %(processName)s[%(process)d] - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s"
3639
)
40+
# Threshold for detecting heavy initialization tasks.
41+
# A value of 1 second was chosen based on empirical observations
42+
# of typical initialization times in this project.
43+
_INIT_THRESHOLD = 1
3744

3845

3946
class LitAPIStatus:
@@ -232,3 +239,43 @@ def set_trace_if_debug(debug_env_var="LITSERVE_DEBUG", debug_env_var_value="1"):
232239
def is_package_installed(package_name: str) -> bool:
233240
spec = importlib.util.find_spec(package_name)
234241
return spec is not None
242+
243+
244+
class _TimedInitMeta(ABCMeta):
245+
def __new__(mcls, name, bases, namespace, **kwargs):
246+
cls = super().__new__(mcls, name, bases, namespace, **kwargs)
247+
cls._has_custom_setup = False
248+
249+
for base in bases:
250+
if hasattr(base, "setup"):
251+
base_setup = base.setup
252+
253+
if "setup" in namespace and namespace["setup"] is not base_setup:
254+
cls._has_custom_setup = True
255+
break
256+
else:
257+
if "setup" in namespace:
258+
cls._has_custom_setup = True
259+
260+
return cls
261+
262+
def __call__(cls, *args, **kwargs):
263+
start_time = time.perf_counter()
264+
instance = super().__call__(*args, **kwargs)
265+
elapsed = time.perf_counter() - start_time
266+
267+
if elapsed >= _INIT_THRESHOLD and not cls._has_custom_setup:
268+
warnings.warn(
269+
(
270+
f"{cls.__name__}.__init__ took {elapsed:.2f} seconds to execute. This suggests that you're "
271+
"loading a model or doing other heavy processing inside the constructor.\n\n"
272+
"To improve startup performance and avoid unnecessary work across processes, move any one-time "
273+
f"heavy initialization into the `{cls.__name__}.setup` method.\n\n"
274+
"The `LitAPI.setup` method is designed for deferred, process-specific loading — ideal for models "
275+
"and large resources."
276+
),
277+
RuntimeWarning,
278+
stacklevel=2,
279+
)
280+
281+
return instance

tests/unit/test_litapi.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
import json
15+
import time
1516

1617
import numpy as np
1718
import pytest
@@ -281,3 +282,37 @@ def test_enable_async_not_set():
281282
ValueError, match=r"predict must be an async generator or async function when enable_async=True"
282283
):
283284
ls.test_examples.SimpleLitAPI(enable_async=True)
285+
286+
287+
class HeavyInitAPI(ls.LitAPI):
288+
def __init__(self):
289+
super().__init__()
290+
time.sleep(2)
291+
292+
293+
class HeavyInitAPIWithSetup(HeavyInitAPI):
294+
def setup(self, device):
295+
pass
296+
297+
298+
class HeavySetupAPI(ls.LitAPI):
299+
def setup(self, device):
300+
time.sleep(2)
301+
302+
303+
def test_heavy_init_api_no_setup():
304+
with pytest.warns(RuntimeWarning, match="loading a model or doing other heavy processing inside the constructor"):
305+
HeavyInitAPI()
306+
307+
308+
def test_heavy_init_api_with_setup(recwarn):
309+
HeavyInitAPIWithSetup()
310+
311+
assert len(recwarn) == 0
312+
313+
314+
def test_heavy_setup_api(recwarn):
315+
api = HeavySetupAPI()
316+
api.setup("")
317+
318+
assert len(recwarn) == 0

0 commit comments

Comments
 (0)