Repository: bentoml/BentoML

Issue:
bug: Triton Integration failed with an AttributeError
### Describe the bug

I try to integrate Triton Inference Server into BentoML and I follow both the official documentation(https://docs.bentoml.org/en/latest/integrations/triton.html) and the following example(https://github.com/bentoml/BentoML/tree/main/examples/triton/onnx). However, I get an AttributeError with the following message:
" '_TritonRunner' object has no attribute 'onnx_fp_16'", where onnx_fp_16 is my model deployed in Triton.
The code in my service.py file is:
```
import bentoml
from bentoml.io import JSON
from src.tokenizer import RDATokenizer
from src.request import RDARequest
from src.response import RDAResponse
from src.config import  load_config
from src.utils.model import load_topics_and_mapping, get_multi_label_binarizer, post_predict, load_taxonomy_root,\
    load_parent_child_relationship

configs = load_config()
rda_triton_runner = bentoml.triton.Runner("rdav2",
                                          model_repository="src/model_repository",
                                          cli_args=["--model-control-mode=explicit",
                                                    "--load-model=onnx_fp_16",
                                                    "--log-verbose=1"])

svc = bentoml.Service("rdav2", runners=[rda_triton_runner])
tokenizer = RDATokenizer(tokenizer_path=configs.tokenizer_path)
labels_binarizer = get_multi_label_binarizer(config=configs)
topics, topics_mapping = load_topics_and_mapping(config=configs)
tax_root = load_taxonomy_root(configs.tax_root_path)
parent_child = load_parent_child_relationship(config=configs)
id_to_fos = {id_: fos for fos, id_ in topics_mapping.items()}


@svc.api(input=JSON(pydantic_model=RDARequest), output=JSON(pydantic_model=RDAResponse))
async def predict(rda_request: RDARequest) -> RDAResponse:
    """
    The input will be the title, abstract, k and, threshold and the output will be the prediction of the model
    :param rda_request: the request object
    :return: a dictionary where the key is the metadata name and the value is the value of the metadata.
    the metadata will be a list with the tags, the tags ids, the probabilities, the ancestros and the ancestors id
    """
    title = rda_request.title
    abstract = rda_request.abstract
    k = rda_request.k
    threshold = rda_request.threshold
    text = title + ". " + abstract
    encoded_text = await tokenizer.tokenize(text=text)
    logits = await rda_triton_runner.onnx_fp_16.async_run(encoded_text)
    logits = logits[0].squeeze()
    response = await post_predict(logits=logits,
                                  labels_binarizer=labels_binarizer,
                                  k=k,
                                  threshold=threshold,
                                  topics=topics,
                                  topics_mapping=topics_mapping,
                                  tax_roots=tax_root,
                                  parent_child=parent_child,
                                  id_to_fos=id_to_fos)
    return response
```
My _bentofile.yaml_ is the following:
```
service: "service:svc"
include:
- "*.py"
- "/model_repository"
- "/configs"
- "/checkpoints"
python:
  requirements_txt: "requirements.txt"
docker:
  base_image: nvcr.io/nvidia/tritonserver:22.12-py3
```
I tried to run the service with a container and the commands I typed to do so were the followings:
- bentoml build --version 0.0.2
- bentoml containerize rdav2:0.0.2
- docker run -it --rm -p 3000:3000 rdav2:0.0.2 serve --production

I guess there is something wrong in __rda_triton_runner__ object, because when I debug the service, I get an empty list in the __models__ parameter. I am not sure about that, and that's the reason I opened this issue.

### To reproduce

_No response_

### Expected behavior

_No response_

### Environment

bentoml[triton]: 1.0.32
python: 3.8
platform: Ubuntu: 20.10


Discussion:
Are you using the src layout folder by any chance? I saw that is the import you are using in the service.py. Additionally there aren't any `src` folder under `include` for your bentofile.yaml
In the src are all my .py files called in the service.py. Also, I tried ./model_repository and the triton could not find the model. I run it through the container, maybe that's the reason why my model path is under the src folder. 
I assume that you are including all model_repository into the container?
> I assume that you are including all model_repository into the container?

Exactly. When I am inside the container with docker exec command, my model is under the following path:
src/model_repository/onnx_fp_16/1/model.onnx

Resolving patch:
diff --git a/src/bentoml/triton.py b/src/bentoml/triton.py
--- a/src/bentoml/triton.py
+++ b/src/bentoml/triton.py
@@ -20,6 +20,7 @@
     handle_triton_exception as _handle_triton_exception,
 )
 from ._internal.utils import LazyLoader as _LazyLoader
+from .exceptions import StateException as _StateException
 
 if t.TYPE_CHECKING:
     import tritonclient.grpc.aio as _tritongrpcclient
@@ -168,18 +169,27 @@ def init_local(self, quiet: bool = False) -> None:
             "TritonRunner '%s' will not be available for development mode.", self.name
         )
 
+    def _set_handle(
+        self, handle_class: type[RunnerHandle], *args: t.Any, **kwargs: t.Any
+    ) -> None:
+        if not isinstance(self._runner_handle, _DummyRunnerHandle):
+            raise _StateException("Runner already initialized")
+
+        runner_handle = handle_class(self, *args, **kwargs)
+        _object_setattr(self, "_runner_handle", runner_handle)
+
     def init_client(
         self,
         handle_class: type[RunnerHandle] | None = None,
         *args: t.Any,
         **kwargs: t.Any,
     ):
-        from ._internal.runner.runner_handle.remote import TritonRunnerHandle
-
         if handle_class is None:
-            handle_class = TritonRunnerHandle
+            from ._internal.runner.runner_handle.remote import TritonRunnerHandle
 
-        super().init_client(handle_class=handle_class, *args, **kwargs)
+            self._set_handle(TritonRunnerHandle)
+        else:
+            self._set_handle(handle_class, *args, **kwargs)
 
     def destroy(self):
         _object_setattr(self, "_runner_handle", _DummyRunnerHandle())


Test patch: