You can scale out your app to multiple containers by adding autoscaling.

Autoscaling can be passed as an argument to REST APIs or Task Queues.

Autoscaling is only available in apps that are deployed. You can’t use autoscaling in deployment previews or runs.

Scaling out horizontally (adding more containers)

When you deploy a Task Queue or REST API, Beam creates a queueing system that manages each task that’s created when your API is called.

You can configure how Beam will scale based on how many things are in the task queue.

Scale by Queue Depth

Our simplest autoscaling strategy allows you to scale by the number of tasks in the queue.

This allows you to control how many tasks each replica can process before scaling up. For example, you could setup an autoscaler to run 30 tasks per replica. When you pass 30 tasks in your queue, we will add a replica. When you pass 60, we’ll add another replicas (up until the max_replicas setting).

from beam import QueueDepthAutoscaler

autoscaling_config = QueueDepthAutoscaler(
    max_tasks_per_replica=30,
    max_replicas=3,
)

@app.rest_api(autoscaler=autoscaling_config)
def your_function():
    ...

Real-World Examples

Queue Depth

Suppose you have a task queue where each task takes 10 seconds to process.

from beam import App, Runtime, QueueDepthAutoscaler

app = App(name="model-training", runtime=Runtime())


@app.task_queue(
    autoscaler=QueueDepthAutoscaler(max_tasks_per_replica=3, max_replicas=3),
)
def my_task_queue():
    import time

    time.sleep(10.0)
    print("I'm done!")

Here’s how this would autoscale:

  • After we have a backlog of >3 tasks, we add a replica (up to 3 replicas).
  • If you have >= 3 tasks in the queue, you can expect it to take up to 30 seconds for that 3rd task to process.

Increasing throughput in a single container

You can increase throughput for your workloads by configuring the number of workers to launch per container. For example, if you have 4 workers on 1 container, you can run 4 tasks at once.

Workers are especially useful for CPU workloads, since you can increase throughput by adding workers and additional CPU cores, rather than using autoscaling to additional replicas.

from beam import App, Runtime, Image

app = App(
    name="high-throughput-app",
    runtime=Runtime(
        cpu=5, # Each worker runs on its own CPU core
        memory="8Gi",
        image=Image(),
    ),
)

# Launch on 5 workers per container to increase throughput
@app.rest_api(workers=5)
def predict():
    # These workloads will share compute resources with each other
    ...

Workers are always orchestrated together. When the container launches, all the workers launch.

This can result in higher throughput than using multiple containers with horizontal autoscaling.

Worker Use-Cases

Workers allow you to increase your per container throughput, vertically. Autoscaling allows to scale the number of containers and increase throughput horizontally

Each worker will share the CPU, Memory, and GPU defined in your app. This means that you’ll usually need to increase these values in order to utilize more workers.

For example, let’s say your model use 3Gi of GPU memory, and your app is deployed on a T4 GPU with 16Gi of GPU memory. You can safely deploy with 4 workers, since those will fit within the 16Gi of GPU memory available.

It’s not always possible to fit multiple workers onto a single machine. In order to use workers effectively, you’ll need to know how much compute is consumed by your task.

When you’ve added multiple workers, you’ll be able to see each time a new worker is started in your logs: