To serve Large Language Models (LLMs) with maximum utilization, a popular serving optimization technique known as continuous batching is required.
This tutorial guides you through implementing continuous batching using vllm-rbln and NVIDIA Triton Inference Server for Llama3-8B.
You can check out the actual commands required to compile the model and initialize triton vllm_backend on the model zoo.
Note
This tutorial assumes that you are familiar with compiling and running inference using the RBLN SDK. If you are not familiar with RBLN SDK, refer to RBLN Optimum tutorials and the API Documentation.
fromoptimum.rblnimportRBLNLlamaForCausalLM# Export HuggingFace PyTorch Llama3 model to RBLN compiled modelmodel_id="meta-llama/Meta-Llama-3-8B-Instruct"compiled_model=RBLNLlamaForCausalLM.from_pretrained(model_id=model_id,export=True,rbln_max_seq_len=8192,rbln_num_devices=1,rbln_batch_size=1,)compiled_model.save_pretrained("rbln-Llama-3-8B-Instruct")
Note
You can choose an appropriate batch size for your serving needs. Here, it is set to 1.
fromoptimum.rblnimportRBLNLlamaForCausalLM# Export HuggingFace PyTorch Llama3 model to RBLN compiled modelmodel_id="meta-llama/Meta-Llama-3-8B-Instruct"compiled_model=RBLNLlamaForCausalLM.from_pretrained(model_id=model_id,export=True,rbln_max_seq_len=8192,rbln_num_devices=4,# number of ATOM™+ for Rebellions Scalable Design (RSD)rbln_batch_size=4,# batch_size > 1 is recommended for continuous batching)compiled_model.save_pretrained("rbln-Llama-3-8B-Instruct")
Note
You can choose an appropriate batch size for your serving needs. Here, it is set to 4. The Llama3-8B model requires 4 RBLN NPUs, and you can find the recommended number of RBLN NPUs for each model in RBLN Optimum Multi-NPUs Supported Models.
NVIDIA Triton Inference Server's vLLM backend has its own model.py. Separate user-defined model.py is not required. For more detailed information, refer to Step 4. Send a Chat Completion Request.
B. Place the precompiled rbln-Llama-3-8B-Instruct directory into the cloned vllm_backend/samples/model_repository/vllm_model/1 directory:
+-- vllm_backend/ # Main directory for Triton vLLM backend
| +-- samples/ # Application example directory
| | +-- model_repository/ # Triton model repository (model_repositories)
| | | +-- vllm_model/ # Individual model for Triton serving
| | | | +-- config.pbtxt # Triton model configuration file
| | | | +-- 1/ # Version directory
| | | | | +-- model.json # Model configuration for vLLM serving
| | | | | +-- rbln-Llama-3-8B-Instruct/ # rbln compiled model files
| | | | | | +-- decoder.rbln
| | | | | | +-- prefill.rbln
| | | | | | +-- config.json
| | | | | | +-- (other model files)
| | +-- (other example files)
| +-- (other backend files)
Note
The vLLM backend for NVIDIA Triton Server doesn't need a model.py file unlike other vision model backends. All model processing logic is pre-included in the Docker container at backends/vllm/model.py, so you only need model.json for configuration.
You can either use the default config.pbtxt from the repository or create a new one using the template below. Note that input and output formats must match exactly as shown, since they're required by the vLLM backend (see Step 4: gRPC Client Inference Request).
The vLLM backend of the Triton inference server defines its own model.py, which is different from the model.py defined in the Resnet50 tutorial in terms of input/output signature. The input parameter was called INPUT__0 and the output was called OUTPUT__0. But the input parameter of vLLM has the name text_input and the output is called text_output. The client should be modified accordingly. Refer to vLLM model.py for more details.
The following shows the client code for vLLM backend. This client also requires tritonclient and grpcio packages.
Note
You need to appropriately apply a chat template for proper functioning. The chat template must be formatted with system, user, and assistant roles. Additionally, you must configure the sampling parameters like this:
importasyncioimportjsonimportnumpyasnpimporttritonclient.grpc.aioasgrpcclient# Define a simple chat message classclassChatMessage:def__init__(self,role,content):self.role=roleself.content=content# Apply a simple chat template to the messagesdefapply_chat_template(messages):lines=[]system_msg=ChatMessage(role="system",content="You are a helpful assistant.")formsgin[system_msg,*messages,ChatMessage(role="assistant",content="")]:lines.append(f"[{msg.role.capitalize()}]\n{msg.content}")return"\n".join(lines)asyncdeftry_request():url=("<host and port number of the triton inference server>"# e.g. "localhost:8001")client=grpcclient.InferenceServerClient(url=url,verbose=False)model_name="vllm_model"defcreate_request(messages,request_id):prompt=apply_chat_template(messages)print(f"prompt:\n{prompt}\n---")# print promptinput=grpcclient.InferInput("text_input",[1],"BYTES")prompt_data=np.array([prompt.encode("utf-8")])input.set_data_from_numpy(prompt_data)stream_setting=grpcclient.InferInput("stream",[1],"BOOL")stream_setting.set_data_from_numpy(np.array([True]))sampling_params={"temperature":0.0,"stop":["[User]","[System]","[Assistant]"],# add stop tokens}sampling_parameters=grpcclient.InferInput("sampling_parameters",[1],"BYTES")sampling_parameters.set_data_from_numpy(np.array([json.dumps(sampling_params).encode("utf-8")],dtype=object))inputs=[input,stream_setting,sampling_parameters]output=grpcclient.InferRequestedOutput("text_output")outputs=[output]return{"model_name":model_name,"inputs":inputs,"outputs":outputs,"request_id":request_id,}messages=[ChatMessage(role="user",content="What is the first letter of English alphabets?")]request_id="req-0"asyncdefrequests_gen():yieldcreate_request(messages,request_id)response_stream=client.stream_infer(requests_gen())prompt=apply_chat_template(messages)is_first_response=Trueasyncforresponseinresponse_stream:result,error=responseiferror:print("Error occurred!")else:output=result.as_numpy("text_output")foriinoutput:decoded=i.decode("utf-8")ifis_first_response:ifdecoded.startswith(prompt):decoded=decoded[len(prompt):]is_first_response=Falseprint(decoded,end="",flush=True)print("\n")# end of streamasyncio.run(try_request())
If the request works properly, the output is similar to the following.