Deploy HTTP Servers with ultra low latency on Modal
Modal offers a primitive for edge-deployed, low latency web services: the Modal Server.
Modal Servers are designed for applications with very demanding latency requirements, where a few tens of milliseconds of round-trip latency is unacceptable, like low latency LLM inference. That ends up meaning users and clients are required to do more work. For Modal’s higher-level primitives for web serving, see this guide.
This example documents a minimal Modal Server and client.
How to define a Modal Server
from pathlib import Path
import modalTo make a Modal Server, define a Python class
with a modal.enter-decorated method
that creates a subtask (thread or process) that listens for HTTP requests on some port.
Then wrap that class in the @app.server decorator,
passing in the port your server task is listening on
and a routing_region to specify where Modal should proxy your requests through.
This proxy will communicate directly with the containers running your server.
To reduce end-to-end latency, include a compute Region that matches the routing Region and containers will be deployed into that Region. Note that region-pinning has cost and resource availability implications! See the guide for details.
You can also pass the rest of your resource definitions,
like distributed Volume storage, CPU/memory resources,
and GPU type and count,
to @app.server.
Altogether, the minimal version of a Modal Server looks something like:
PORT = 8000
COMPUTE_REGION = "us"
ROUTING_REGION = "us-east"
app = modal.App("example-server")
@app.server(
compute_region=COMPUTE_REGION,
routing_region=ROUTING_REGION,
port=PORT,
unauthenticated=True,
)
class FileServer:
@modal.enter()
def start(self):
import subprocess
subprocess.Popen(["python", "-m", "http.server", f"{PORT}"])How to write a client and tests for a Modal Server
We test the file server defined above by requesting file from it. This one will do nicely.
We put the test in a local_entrypoint so that we can execute it from the command line:
modal run server.py@app.local_entrypoint()
def ping():
from urllib.error import HTTPError
from urllib.request import urlopen
url = FileServer.get_url()
this = Path(__file__).name
print(f"requesting {this} from Modal Server at {url}")
while True:
try:
print(urlopen(url + f"/{this}").read().decode("utf-8"))
break
except HTTPError as e:
if e.code == 503:
import time
time.sleep(1)
continue
else:
raise eNotice the retry loop! Modal Clses and Functions are serverless and scale to zero by default. When a Modal Server has scaled to zero, clients will get a 503 Service Unavailable error response from Modal. Those requests still trigger scale up, and once a container is ready, the 503s will stop and clients will receive the server’s responses.
Modal Servers also support “sticky routing” for improved cache locality within client sessions. For details, see this example.