This tutorial deploys a precompiled Llama3-8B model on Ray Serve with RBLN NPUs.
The workflow covers:
Verifying the environment and compiled model.
Defining a Ray Serve deployment that targets RBLN hardware.
Launching the application with the Serve CLI.
Sending an inference request to validate the endpoint.
If you need help configuring Ray Serve itself, review the Ray Serve overview first. For a complete script-based example (from compilation to deployment), see the model zoo reference.
The following sections assume you already understand how to compile and execute models with the RBLN SDK. Revisit the RBLN Optimum and the vLLM guide if you need a refresher.
importosfromoptimum.rblnimportRBLNLlamaForCausalLMmodel_id="meta-llama/Meta-Llama-3-8B-Instruct"# Compile and exportmodel=RBLNLlamaForCausalLM.from_pretrained(model_id=model_id,export=True,rbln_batch_size=1,rbln_max_seq_len=8192,rbln_num_devices=1,)# Save compiled results to diskmodel.save_pretrained(os.path.basename(model_id))
Note
You need to select an appropriate batch size. In this case, it is set to 4.
importosfromoptimum.rblnimportRBLNLlamaForCausalLMmodel_id="meta-llama/Meta-Llama-3-8B-Instruct"# Compile and exportmodel=RBLNLlamaForCausalLM.from_pretrained(model_id=model_id,export=True,rbln_batch_size=4,rbln_max_seq_len=8192,rbln_num_devices=4,)# Save compiled results to diskmodel.save_pretrained(os.path.basename(model_id))
Ray exposes custom accelerators through the resources argument, so each task or deployment can request exactly the hardware it needs.
The Actor below shows how to request RBLN resources with @ray.remote(resources={"RBLN": <NUM_NPUS>}), where <NUM_NPUS> matches the rbln_num_devices value used at compile time. The companion RBLNActor helper retrieves the assigned device ID and passes it to the Serve deployment. See RBLN NPUs with Ray for additional background.
Ray Serve deployments are defined by annotating a class or function with @serve.deployment. This decorator registers the class as a Ray Serve service endpoint, allowing Ray Serve to manage the lifecycle (deployment, scaling, updates).
importjsonimportosfromunittest.mockimportMagicMockimportrayfromfastapiimportFastAPI,HTTPExceptionfromrayimportservefromstarlette.requestsimportRequestfromstarlette.responsesimportStreamingResponsefromvllmimportAsyncEngineArgs,AsyncLLMEnginefromvllm.entrypoints.openai.chat_completion.protocolimport(ChatCompletionRequest,)fromvllm.entrypoints.openai.chat_completion.servingimportOpenAIServingChatfromvllm.entrypoints.openai.completion.protocolimportCompletionRequestfromvllm.entrypoints.openai.completion.servingimportOpenAIServingCompletionfromvllm.entrypoints.openai.models.protocolimportBaseModelPathfromvllm.entrypoints.openai.models.servingimportOpenAIServingModelsfromvllm.entrypoints.serve.engine.protocolimportErrorResponsefromvllm.renderers.online_rendererimportOnlineRendererapp=FastAPI()ray.init(resources={"RBLN":1})@ray.remote(resources={"RBLN":1})classRBLNActor:defgetDeviceId(self):returnray.get_runtime_context().get_accelerator_ids()["RBLN"]@serve.deployment(num_replicas=1,ray_actor_options={"num_cpus":16})@serve.ingress(app)classLlama3_8B:def__init__(self,rbln_actor:RBLNActor):""" Initialize actor. :return: """self.engine=Noneself.rbln_actor=rbln_actorself.model_name="Meta-Llama-3-8B-Instruct"self.raw_request=Noneself.vllm_engine=Noneself.openai_serving_models=Noneself.completion_service=Noneself.chat_completion_service=Noneself.ids=ray.get(rbln_actor.getDeviceId.remote())self.os_environment_vars()self.initialize()defos_environment_vars(self):""" Redefine the environment variables to be passed to the RBLN runtime and vLLM :return: """ifself.idsisNoneorlen(self.ids)<=0:os.environ.pop("RBLN_DEVICES")os.environ["RBLN_DEVICES"]=",".join(self.ids)os.environ["VLLM_WORKER_MULTIPROC_METHOD"]="spawn"definitialize(self):""" Initialize model. This will be called during model loading time :return: """vllm_engine_args=AsyncEngineArgs(model=self.model_name)self.vllm_engine=AsyncLLMEngine.from_engine_args(vllm_engine_args)self.openai_serving_models=OpenAIServingModels(engine_client=self.vllm_engine,base_model_paths=[BaseModelPath(name=self.model_name,model_path=os.getcwd())],)openai_serving_render=OnlineRenderer(model_config=self.vllm_engine.model_config,renderer=self.vllm_engine.renderer,request_logger=None,chat_template=None,chat_template_content_format="auto",)self.completion_service=OpenAIServingCompletion(self.vllm_engine,self.openai_serving_models,online_renderer=openai_serving_render,request_logger=None,)self.chat_completion_service=OpenAIServingChat(self.vllm_engine,self.openai_serving_models,"assistant",online_renderer=openai_serving_render,request_logger=None,chat_template_content_format="auto",chat_template=None,)asyncdefisd():returnFalseself.raw_request=MagicMock()self.raw_request.headers={}self.raw_request.is_disconnected=isd@app.post("/v1/chat/completions")asyncdefchat_completion(self,http_request:Request):""" Handle chat completion request. :param http_request: The HTTP request object :return: The chat completion response """try:json_string:dict=awaithttp_request.json()exceptjson.JSONDecodeError:raiseHTTPException(status_code=400,detail="Invalid JSON format request")request:ChatCompletionRequest=ChatCompletionRequest.model_validate(json_string)g=awaitself.chat_completion_service.create_chat_completion(request,self.raw_request)ifisinstance(g,ErrorResponse):return[g.model_dump()]ifrequest.stream:asyncdefstream_generator():asyncforresponseing:yieldresponsereturnStreamingResponse(stream_generator(),media_type="text/event-stream")else:return[g.model_dump()]@app.post("/v1/completions")asyncdefcompletion(self,http_request:Request):""" Handle completion request. :param http_request: The HTTP request object :return: The completion response """try:json_string:dict=awaithttp_request.json()exceptjson.JSONDecodeError:raiseHTTPException(status_code=400,detail="Invalid JSON format request")request:CompletionRequest=CompletionRequest.model_validate(json_string)g=awaitself.completion_service.create_completion(request,self.raw_request)ifisinstance(g,ErrorResponse):return[g.model_dump()]ifrequest.stream:asyncdefstream_generator():asyncforresponseing:yieldresponsereturnStreamingResponse(stream_generator(),media_type="text/event-stream")else:return[g.model_dump()]rbln_actor=RBLNActor.remote()app=Llama3_8B.bind(rbln_actor)
importjsonimportosfromunittest.mockimportMagicMockimportrayfromfastapiimportFastAPI,HTTPExceptionfromrayimportservefromstarlette.requestsimportRequestfromstarlette.responsesimportStreamingResponsefromvllmimportAsyncEngineArgs,AsyncLLMEnginefromvllm.entrypoints.openai.chat_completion.protocolimport(ChatCompletionRequest,)fromvllm.entrypoints.openai.chat_completion.servingimportOpenAIServingChatfromvllm.entrypoints.openai.completion.protocolimportCompletionRequestfromvllm.entrypoints.openai.completion.servingimportOpenAIServingCompletionfromvllm.entrypoints.openai.models.protocolimportBaseModelPathfromvllm.entrypoints.openai.models.servingimportOpenAIServingModelsfromvllm.entrypoints.serve.engine.protocolimportErrorResponsefromvllm.renderers.online_rendererimportOnlineRendererapp=FastAPI()ray.init(resources={"RBLN":4})@ray.remote(resources={"RBLN":4})classRBLNActor:defgetDeviceId(self):returnray.get_runtime_context().get_accelerator_ids()["RBLN"]@serve.deployment(num_replicas=1,ray_actor_options={"num_cpus":16})@serve.ingress(app)classLlama3_8B:def__init__(self,rbln_actor:RBLNActor):""" Initialize actor. :return: """self.engine=Noneself.rbln_actor=rbln_actorself.model_name="Meta-Llama-3-8B-Instruct"self.raw_request=Noneself.vllm_engine=Noneself.openai_serving_models=Noneself.completion_service=Noneself.chat_completion_service=Noneself.ids=ray.get(rbln_actor.getDeviceId.remote())self.os_environment_vars()self.initialize()defos_environment_vars(self):""" Redefine the environment variables to be passed to the RBLN runtime and vLLM :return: """ifself.idsisNoneorlen(self.ids)<=0:os.environ.pop("RBLN_DEVICES")os.environ["RBLN_DEVICES"]=",".join(self.ids)os.environ["VLLM_WORKER_MULTIPROC_METHOD"]="spawn"definitialize(self):""" Initialize model. This will be called during model loading time :return: """vllm_engine_args=AsyncEngineArgs(model=self.model_name)self.vllm_engine=AsyncLLMEngine.from_engine_args(vllm_engine_args)self.openai_serving_models=OpenAIServingModels(engine_client=self.vllm_engine,base_model_paths=[BaseModelPath(name=self.model_name,model_path=os.getcwd())],)openai_serving_render=OnlineRenderer(model_config=self.vllm_engine.model_config,renderer=self.vllm_engine.renderer,request_logger=None,chat_template=None,chat_template_content_format="auto",)self.completion_service=OpenAIServingCompletion(self.vllm_engine,self.openai_serving_models,online_renderer=openai_serving_render,request_logger=None,)self.chat_completion_service=OpenAIServingChat(self.vllm_engine,self.openai_serving_models,"assistant",online_renderer=openai_serving_render,request_logger=None,chat_template_content_format="auto",chat_template=None,)asyncdefisd():returnFalseself.raw_request=MagicMock()self.raw_request.headers={}self.raw_request.is_disconnected=isd@app.post("/v1/chat/completions")asyncdefchat_completion(self,http_request:Request):""" Handle chat completion request. :param http_request: The HTTP request object :return: The chat completion response """try:json_string:dict=awaithttp_request.json()exceptjson.JSONDecodeError:raiseHTTPException(status_code=400,detail="Invalid JSON format request")request:ChatCompletionRequest=ChatCompletionRequest.model_validate(json_string)g=awaitself.chat_completion_service.create_chat_completion(request,self.raw_request)ifisinstance(g,ErrorResponse):return[g.model_dump()]ifrequest.stream:asyncdefstream_generator():asyncforresponseing:yieldresponsereturnStreamingResponse(stream_generator(),media_type="text/event-stream")else:return[g.model_dump()]@app.post("/v1/completions")asyncdefcompletion(self,http_request:Request):""" Handle completion request. :param http_request: The HTTP request object :return: The completion response """try:json_string:dict=awaithttp_request.json()exceptjson.JSONDecodeError:raiseHTTPException(status_code=400,detail="Invalid JSON format request")request:CompletionRequest=CompletionRequest.model_validate(json_string)g=awaitself.completion_service.create_completion(request,self.raw_request)ifisinstance(g,ErrorResponse):return[g.model_dump()]ifrequest.stream:asyncdefstream_generator():asyncforresponseing:yieldresponsereturnStreamingResponse(stream_generator(),media_type="text/event-stream")else:return[g.model_dump()]rbln_actor=RBLNActor.remote()app=Llama3_8B.bind(rbln_actor)
Use the Ray Serve CLI (serve run) to launch the application. The argument uses the module:application format, where module is the Python filename (without .py) and application is the exported Serve entry point.
In this sample, llama3-8b.py defines app, so the following command starts the deployment. Add extra options when connecting to a remote Ray cluster or when pinning RBLN_DEVICES to specific cards.
echo'{"model": "Meta-Llama-3-8B-Instruct","prompt": "A robot may not injure a human being","stream": false}'|curl-sN-H"Content-Type: application/json"-XPOST--data-binary@-\http://localhost:8000/v1/completions|jq.
[{"id":"cmpl-6e39b262d7064dc882c3c3655f396906","object":"text_completion","created":1762938935,"model":"Meta-Llama-3-8B-Instruct","choices":[{"index":0,"text":" or, through inaction, allow a human being to come to harm.\nA","logprobs":null,"finish_reason":"length","stop_reason":null,"token_ids":null,"prompt_logprobs":null,"prompt_token_ids":null}],"service_tier":null,"system_fingerprint":null,"usage":{"prompt_tokens":10,"total_tokens":26,"completion_tokens":16,"prompt_tokens_details":null},"kv_transfer_params":null}]
echo'{"model": "Meta-Llama-3-8B-Instruct","messages": [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Who won the world series in 2020?"}, {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."}, {"role": "user", "content": "Where was it played?"}],"temperature": 0.0,"max_tokens": 50,"stream": false}'|curl-sN-H"Content-Type: application/json"-XPOST--data-binary@-\http://localhost:8000/v1/chat/completions|jq.
[{"id":"chatcmpl-3bbd9db1dc1243549ab942a74dabd2d8","object":"chat.completion","created":1762941547,"model":"Meta-Llama-3-8B-Instruct","choices":[{"index":0,"message":{"role":"assistant","content":"The 2020 World Series was played at Globe Life Field in Arlington, Texas, which is the home stadium of the Texas Rangers. However, the series was played with no fans in attendance due to the COVID-19 pandemic.","refusal":null,"annotations":null,"audio":null,"function_call":null,"tool_calls":[],"reasoning_content":null},"logprobs":null,"finish_reason":"stop","stop_reason":null,"token_ids":null}],"service_tier":null,"system_fingerprint":null,"usage":{"prompt_tokens":59,"total_tokens":106,"completion_tokens":47,"prompt_tokens_details":null},"prompt_logprobs":null,"prompt_token_ids":null,"kv_transfer_params":null}]