llm-arena / deployment /modal_app.py
IntimateUser6969's picture
Latency_reduction, async calls to both, tool calling, memory harness added
4d64b06
Raw
History Blame Contribute Delete
3.04 kB
"""Modal serverless deployment for Qwen2.5-0.5B-Instruct.
Deploy with:
modal deploy deployment/modal_app.py
The web endpoint URL becomes the MODAL_ENDPOINT env var consumed by OSSAssistant.
"""
from __future__ import annotations
import modal
import torch
app = modal.App("qwen-assistant")
image = (
modal.Image.debian_slim(python_version="3.11")
.pip_install(
"transformers>=4.45.0",
"torch>=2.4.0",
"accelerate>=0.34.0",
"fastapi",
"uvicorn",
)
)
@app.cls(
image=image,
gpu="T4",
scaledown_window=300,
min_containers=1
)
class QwenModel:
"""Holds a loaded Qwen2.5-0.5B-Instruct model for serverless inference.
The model is loaded once when the container starts (@modal.enter) and
reused across concurrent requests until the container is recycled.
"""
@modal.enter()
def load_model(self) -> None:
"""Load tokeniser and model into GPU memory on container start."""
from transformers import AutoModelForCausalLM, AutoTokenizer
model_name = "Qwen/Qwen2.5-0.5B-Instruct"
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
self.model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="cuda",
)
@modal.method()
def generate(self, messages: list[dict], max_tokens: int = 512) -> dict:
"""Run chat-template inference and return content + token count.
Args:
messages: List of {"role": str, "content": str} dicts.
max_tokens: Maximum new tokens to generate.
Returns:
{"content": str, "tokens_used": int}
"""
import torch
text = self.tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
)
model_inputs = self.tokenizer([text], return_tensors="pt").to(self.model.device)
input_len = model_inputs["input_ids"].shape[-1]
with torch.no_grad():
generated_ids = self.model.generate(
**model_inputs,
max_new_tokens=max_tokens,
do_sample=True,
temperature=0.7,
pad_token_id=self.tokenizer.eos_token_id,
)
generated_tokens = generated_ids[0][input_len:]
content = self.tokenizer.decode(generated_tokens, skip_special_tokens=True)
tokens_used = len(generated_tokens)
return {"content": content, "tokens_used": tokens_used}
@app.function(image=image)
@modal.fastapi_endpoint(method="POST")
def chat_endpoint(request: dict) -> dict:
"""HTTP POST endpoint consumed by OSSAssistant when USE_MODAL=True.
Expected request body:
{"messages": [...], "max_tokens": int (optional)}
Returns:
{"content": str, "tokens_used": int}
"""
model = QwenModel()
return model.generate.remote(
request["messages"],
request.get("max_tokens", 512),
)