Repository: bentoml/BentoML

Issue:
Can't pass __init__ parameters to a Runnable class when crating a Runner.
**Describe the bug**
Can't pass `__init__` parameters to a Runnable class when creating a Runner.

**To Reproduce**
When running this example from docs:
```python
import bentoml
import torch

class MyModelRunnable(bentoml.Runnable):
    SUPPORT_NVIDIA_GPU = True
    SUPPORT_CPU_MULTI_THREADING = True

    def __init__(self, model_file):
        self.model = torch.load_model(model_file)

    @bentoml.Runnable.method(batchable=True, batch_dim=0)
    def predict(self, input_tensor):
        return self.model(input_tensor)

my_runner_1 = bentoml.Runner(
    MyModelRunnable,
    name="my_runner_1",
    runnable_init_params={
        "model_file": "./saved_model_1.pt",
    }
)
my_runner_2 = bentoml.Runner(
    MyModelRunnable,
    name="my_runner_2",
    runnable_init_params={
        "model_file": "./saved_model_2.pt",
    }
)

svc = bentoml.Service(__name__, runners=[my_runner_1, my_runner_2])
```
you get 
```python
TypeError: __init__() got an unexpected keyword argument 'runnable_init_params'
```
`__init__()` of the `Runner` class has `init_params` parameter that is referenced as `runnable init params` in class docstring, but whe you change the parameter:
```python
import bentoml
import torch


class MyModelRunnable(bentoml.Runnable):
    SUPPORT_NVIDIA_GPU = True
    SUPPORT_CPU_MULTI_THREADING = True

    def __init__(self, model_file):
        self.model = torch.load_model(model_file)

    @bentoml.Runnable.method(batchable=True, batch_dim=0)
    def predict(self, input_tensor):
        return self.model(input_tensor)


my_runner_1 = bentoml.Runner(
    MyModelRunnable,
    name="my_runner_1",
    init_params={
        "model_file": "./saved_model_1.pt",
    }
)
my_runner_2 = bentoml.Runner(
    MyModelRunnable,
    name="my_runner_2",
    init_params={
        "model_file": "./saved_model_2.pt",
    }
)

svc = bentoml.Service(__name__, runners=[my_runner_1, my_runner_2])
```
you still get an exception:
```python
    File "...\bentoml\_internal\runner\runner.py", line 189, in _init
      runner_handle = handle_class(self)
    File "...\bentoml\_internal\runner\runner_handle\local.py", line 23, in        
 __init__
      self._runnable = runner.runnable_class()
TypeError: __init__() missing 1 required positional argument: 'model_file'
```
**Expected behavior**
Parameters would be passed to a `Runnable` instance without errors.

**Screenshots/Logs**

**Environment:**
 - OS: Windows 10 x64
 - Python Version Python 3.9.13
 - BentoML Version BentoML 1.0.0rc1


**Additional context**
From my understanding the second error can be fixed by changing:
https://github.com/bentoml/BentoML/blob/03ee3e40b9607a1eea84bb5ee0cd346ee2d1250d/bentoml/_internal/runner/runner_handle/local.py#L21-L23
to:
```python3
class LocalRunnerRef(RunnerHandle):
    def __init__(self, runner: Runner) -> None:  # pylint: disable=super-init-not-called
        self._runnable = runner.runnable_class(**runner.runnable_init_params)
``` 


Discussion:

Resolving patch:
diff --git a/bentoml/_internal/runner/runnable.py b/bentoml/_internal/runner/runnable.py
--- a/bentoml/_internal/runner/runnable.py
+++ b/bentoml/_internal/runner/runnable.py
@@ -3,6 +3,7 @@
 import typing as t
 import logging
 from abc import ABC
+from abc import abstractmethod
 from typing import overload
 from typing import TYPE_CHECKING
 
@@ -29,6 +30,10 @@ class Runnable(ABC):
 
     methods: dict[str, RunnableMethod[t.Any, t.Any, t.Any]] | None = None
 
+    @abstractmethod
+    def __init__(self, **kwargs: t.Any):
+        ...
+
     @classmethod
     def add_method(
         cls: t.Type[T],
diff --git a/bentoml/_internal/runner/runner.py b/bentoml/_internal/runner/runner.py
--- a/bentoml/_internal/runner/runner.py
+++ b/bentoml/_internal/runner/runner.py
@@ -59,9 +59,9 @@ async def async_run(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
 @attr.define(slots=False, frozen=True, eq=False)
 class Runner:
     runnable_class: t.Type[Runnable]
-    runnable_init_params: t.Dict[str, t.Any]
+    runnable_init_params: dict[str, t.Any]
     name: str
-    models: t.List[Model]
+    models: list[Model]
     resource_config: Resource
     runner_methods: list[RunnerMethod[t.Any, t.Any, t.Any]]
     scheduling_strategy: t.Type[Strategy]
@@ -72,7 +72,7 @@ def __init__(
         self,
         runnable_class: t.Type[Runnable],
         *,
-        init_params: t.Dict[str, t.Any] | None = None,
+        runnable_init_params: t.Dict[str, t.Any] | None = None,
         name: str | None = None,
         scheduling_strategy: t.Type[Strategy] = DefaultStrategy,
         models: t.List[Model] | None = None,
@@ -87,7 +87,7 @@ def __init__(
         """
         Args:
             runnable_class: runnable class
-            init_params: runnable init params
+            runnable_init_params: runnable init params
             name: runner name
             scheduling_strategy: scheduling strategy
             models: list of required bento models
@@ -102,7 +102,9 @@ def __init__(
         name = runnable_class.__name__ if name is None else name
         models = [] if models is None else models
         runner_method_map: dict[str, RunnerMethod[t.Any, t.Any, t.Any]] = {}
-        runner_init_params = {} if init_params is None else init_params
+        runnable_init_params = (
+            {} if runnable_init_params is None else runnable_init_params
+        )
         method_configs = {} if method_configs is None else {}
         custom_resources = {} if custom_resources is None else {}
         resource_config = Resource(
@@ -111,6 +113,11 @@ def __init__(
             custom_resources=custom_resources or {},
         )
 
+        if runnable_class.methods is None:
+            raise ValueError(
+                f"Runnable class '{runnable_class.__name__}' has no methods!"
+            )
+
         for method_name, method in runnable_class.methods.items():
             method_max_batch_size = method_configs.get(method_name, {}).get(
                 "max_batch_size"
@@ -137,7 +144,7 @@ def __init__(
 
         self.__attrs_init__(  # type: ignore
             runnable_class=runnable_class,
-            runnable_init_params=runner_init_params,
+            runnable_init_params=runnable_init_params,
             name=name,
             models=models,
             resource_config=resource_config,
diff --git a/bentoml/_internal/runner/runner_handle/local.py b/bentoml/_internal/runner/runner_handle/local.py
--- a/bentoml/_internal/runner/runner_handle/local.py
+++ b/bentoml/_internal/runner/runner_handle/local.py
@@ -20,7 +20,7 @@
 
 class LocalRunnerRef(RunnerHandle):
     def __init__(self, runner: Runner) -> None:  # pylint: disable=super-init-not-called
-        self._runnable = runner.runnable_class()
+        self._runnable = runner.runnable_class(**runner.runnable_init_params)
 
     def run_method(
         self,


Test patch: