Files
2026-07-13 13:17:40 +08:00

130 lines
8.2 KiB
Markdown

(serve-model-composition)=
# Deploy Compositions of Models
With this guide, you can:
* Compose multiple {ref}`deployments <serve-key-concepts-deployment>` containing ML models or business logic into a single {ref}`application <serve-key-concepts-application>`
* Independently scale and configure each of your ML models and business logic steps
:::{note}
The deprecated `RayServeHandle` and `RayServeSyncHandle` APIs have been fully removed as of Ray 2.10.
:::
## Compose deployments using DeploymentHandles
When building an application, you can `.bind()` multiple deployments and pass them to each other's constructors. At runtime, inside the deployment code Ray Serve substitutes the bound deployments with {ref}`DeploymentHandles <serve-key-concepts-deployment-handle>` that you can use to call methods of other deployments. This capability lets you divide your application's steps, such as preprocessing, model inference, and post-processing, into independent deployments that you can independently scale and configure.
Use {mod}`handle.remote <ray.serve.handle.DeploymentHandle.remote>` to send requests to a deployment. These requests can contain ordinary Python args and kwargs, which DeploymentHandles can pass directly to the method. The method call returns a {mod}`DeploymentResponse <ray.serve.handle.DeploymentResponse>` that represents a future to the output. You can `await` the response to retrieve its result or pass it to another downstream {mod}`DeploymentHandle <ray.serve.handle.DeploymentHandle>` call.
(serve-model-composition-deployment-handles)=
## Basic DeploymentHandle example
This example has two deployments:
```{literalinclude} doc_code/model_composition/language_example.py
:start-after: __hello_start__
:end-before: __hello_end__
:language: python
:linenos: true
```
In line 42, the `LanguageClassifier` deployment takes in the `spanish_responder` and `french_responder` as constructor arguments. At runtime, Ray Serve converts these arguments into `DeploymentHandles`. `LanguageClassifier` can then call the `spanish_responder` and `french_responder`'s deployment methods using this handle.
For example, the `LanguageClassifier`'s `__call__` method uses the HTTP request's values to decide whether to respond in Spanish or French. It then forwards the request's name to the `spanish_responder` or the `french_responder` on lines 19 and 21 using the `DeploymentHandle`s. The format of the calls is as follows:
```python
response: DeploymentResponse = self.spanish_responder.say_hello.remote(name)
```
This call has a few parts:
* `self.spanish_responder` is the `SpanishResponder` handle taken in through the constructor.
* `say_hello` is the `SpanishResponder` method to invoke.
* `remote` indicates that this is a `DeploymentHandle` call to another deployment.
* `name` is the argument for `say_hello`. You can pass any number of arguments or keyword arguments here.
This call returns a `DeploymentResponse` object, which is a reference to the result, rather than the result itself. This pattern allows the call to execute asynchronously. To get the actual result, `await` the response. `await` blocks until the asynchronous call executes and then returns the result. In this example, line 25 calls `await response` and returns the resulting string.
(serve-model-composition-await-warning)=
:::{warning}
You can use the `response.result()` method to get the return value of remote `DeploymentHandle` calls. However, avoid calling `.result()` from inside a deployment because it blocks the deployment from executing any other code until the remote method call finishes. Using `await` lets the deployment process other requests while waiting for the remote method call to finish. You should use `await` instead of `.result()` inside deployments.
:::
You can copy the preceding `hello.py` script and run it with `serve run`. Make sure to run the command from a directory containing `hello.py`, so it can locate the script:
```console
$ serve run hello:language_classifier
```
You can use this client script to interact with the example:
```{literalinclude} doc_code/model_composition/language_example.py
:start-after: __hello_client_start__
:end-before: __hello_client_end__
:language: python
```
While the `serve run` command is running, open a separate terminal window and run the script:
```console
$ python hello_client.py
Hola Dora
```
:::{note}
Composition lets you break apart your application and independently scale each part. For instance, suppose this `LanguageClassifier` application's requests were 75% Spanish and 25% French. You could scale your `SpanishResponder` to have 3 replicas and your `FrenchResponder` to have 1 replica, so you can meet your workload's demand. This flexibility also applies to reserving resources like CPUs and GPUs, as well as any other configurations you can set for each deployment.
With composition, you can avoid application-level bottlenecks when serving models and business logic steps that use different types and amounts of resources.
:::
## Chaining DeploymentHandle calls
Ray Serve can directly pass the `DeploymentResponse` object that a `DeploymentHandle` returns, to another `DeploymentHandle` call to chain together multiple stages of a pipeline. You don't need to `await` the first response, Ray Serve manages the `await` behavior under the hood. When the first call finishes, Ray Serve passes the output of the first call, instead of the `DeploymentResponse` object, directly to the second call.
For example, the code sample below defines three deployments in an application:
- An `Adder` deployment that increments a value by its configured increment.
- A `Multiplier` deployment that multiplies a value by its configured multiple.
- An `Ingress` deployment that chains calls to the adder and multiplier together and returns the final response.
Note how the response from the `Adder` handle passes directly to the `Multiplier` handle, but inside the multiplier, the input argument resolves to the output of the `Adder` call.
```{literalinclude} doc_code/model_composition/chaining_example.py
:start-after: __chaining_example_start__
:end-before: __chaining_example_end__
:language: python
```
## Streaming DeploymentHandle calls
You can also use `DeploymentHandles` to make streaming method calls that return multiple outputs. To make a streaming call, the method must be a generator and you must set `handle.options(stream=True)`. Then, the handle call returns a {mod}`DeploymentResponseGenerator <ray.serve.handle.DeploymentResponseGenerator>` instead of a unary `DeploymentResponse`. You can use `DeploymentResponseGenerators` as a sync or async generator, like in an `async for` code block. Similar to `DeploymentResponse.result()`, avoid using a `DeploymentResponseGenerator` as a sync generator within a deployment, as that blocks other requests from executing concurrently on that replica. Note that you can't pass `DeploymentResponseGenerators` to other handle calls.
Example:
```{literalinclude} doc_code/model_composition/streaming_example.py
:start-after: __streaming_example_start__
:end-before: __streaming_example_end__
:language: python
```
## Advanced: Pass a DeploymentResponse in a nested object [FULLY DEPRECATED]
:::{warning}
Passing a `DeploymentResponse` to downstream handle calls in nested objects is fully deprecated and no longer supported. Please manually use `DeploymentResponse._to_object_ref()` instead to pass the corresponding object reference in nested objects.
Passing a `DeploymentResponse` object as a top-level argument or keyword argument is still supported.
:::
## Advanced: Convert a DeploymentResponse to a Ray ObjectRef
Under the hood, each `DeploymentResponse` corresponds to a Ray `ObjectRef`, or an `ObjectRefGenerator` for streaming calls. To compose `DeploymentHandle` calls with Ray Actors or Tasks, you may want to resolve the response to its `ObjectRef`. For this purpose, you can use the {mod}`DeploymentResponse._to_object_ref <ray.serve.handle.DeploymentResponse>` and {mod}`DeploymentResponse._to_object_ref_sync <ray.serve.handle.DeploymentResponse>` developer APIs.
Example:
```{literalinclude} doc_code/model_composition/response_to_object_ref_example.py
:start-after: __response_to_object_ref_example_start__
:end-before: __response_to_object_ref_example_end__
:language: python
```