Hermes Bot commited on
Commit
523e468
·
unverified ·
1 Parent(s): c326da8

feat: add projects/tinybard/modal_inference.py and update configs

Browse files
Files changed (1) hide show
  1. modal_inference.py +127 -0
modal_inference.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import modal
3
+
4
+ # Define the Modal app
5
+ app = modal.App("tinybard-inference")
6
+
7
+ # Use a standard CUDA image with vLLM installed
8
+ image = (
9
+ modal.Image.debian_slim(python_version="3.10")
10
+ .pip_install(
11
+ "vllm==0.5.4",
12
+ "huggingface_hub",
13
+ "fastapi",
14
+ "uvicorn",
15
+ )
16
+ )
17
+
18
+ # Default model (can be overridden via environment variable)
19
+ MODEL_ID = os.environ.get("MODEL_ID", "Qwen/Qwen2.5-1.5B-Instruct")
20
+
21
+
22
+ @app.cls(
23
+ gpu="L4", # L4 is cost-efficient and widely available
24
+ image=image,
25
+ secrets=[modal.Secret.from_name("huggingface-secret")], # Optional, for gated models
26
+ timeout=600,
27
+ )
28
+ class InferenceModel:
29
+ @modal.enter()
30
+ def load_model(self):
31
+ from vllm import AsyncLLMEngine
32
+ from vllm.engine.arg_utils import AsyncEngineArgs
33
+
34
+ # Configure AsyncLLMEngine
35
+ engine_args = AsyncEngineArgs(
36
+ model=MODEL_ID,
37
+ max_model_len=2048,
38
+ gpu_memory_utilization=0.9,
39
+ trust_remote_code=True,
40
+ )
41
+ self.engine = AsyncLLMEngine.from_engine_args(engine_args)
42
+ log_info = f"Model {MODEL_ID} loaded successfully."
43
+ print(log_info)
44
+
45
+ @modal.method()
46
+ async def generate(self, prompt: str, max_tokens: int = 226, temperature: float = 0.7) -> str:
47
+ from vllm import SamplingParams
48
+ import uuid
49
+
50
+ sampling_params = SamplingParams(
51
+ temperature=temperature,
52
+ max_tokens=max_tokens,
53
+ )
54
+ request_id = str(uuid.uuid4())
55
+ results_generator = self.engine.generate(prompt, sampling_params, request_id)
56
+
57
+ final_output = None
58
+ async for request_output in results_generator:
59
+ final_output = request_output
60
+
61
+ if final_output and final_output.outputs:
62
+ return final_output.outputs[0].text
63
+ return ""
64
+
65
+
66
+ # Create a FastAPI wrapper to expose an OpenAI-compatible /v1/chat/completions endpoint
67
+ from fastapi import FastAPI, Request, HTTPException
68
+ from fastapi.responses import JSONResponse
69
+
70
+ web_app = FastAPI(title="TinyBard Inference API")
71
+
72
+
73
+ @web_app.post("/v1/chat/completions")
74
+ async def chat_completions(request: Request):
75
+ body = await request.json()
76
+ messages = body.get("messages", [])
77
+ model = body.get("model", MODEL_ID)
78
+ max_tokens = body.get("max_tokens", 220)
79
+ temperature = body.get("temperature", 0.7)
80
+
81
+ # Translate chat messages to plain text prompt (standard ChatML or basic dialogue)
82
+ prompt = ""
83
+ for msg in messages:
84
+ role = msg.get("role", "user")
85
+ content = msg.get("content", "").strip()
86
+ if role == "system":
87
+ prompt += f"System Instructions:\n{content}\n\n"
88
+ elif role == "user":
89
+ prompt += f"User:\n{content}\n\n"
90
+ elif role == "assistant":
91
+ prompt += f"Assistant:\n{content}\n\n"
92
+ prompt += "Assistant:\n"
93
+
94
+ # Call the Modal class method
95
+ try:
96
+ model_instance = InferenceModel()
97
+ text = await model_instance.generate.remote.aio(
98
+ prompt, max_tokens=max_tokens, temperature=temperature
99
+ )
100
+ except Exception as e:
101
+ raise HTTPException(status_code=500, detail=f"Modal inference failed: {str(e)}")
102
+
103
+ # Return OpenAI-compatible JSON structure
104
+ return JSONResponse(
105
+ {
106
+ "id": "chatcmpl-modal",
107
+ "object": "chat.completion",
108
+ "model": model,
109
+ "choices": [
110
+ {
111
+ "index": 0,
112
+ "message": {
113
+ "role": "assistant",
114
+ "content": text.strip(),
115
+ },
116
+ "finish_reason": "stop",
117
+ }
118
+ ],
119
+ }
120
+ )
121
+
122
+
123
+ # Expose the web app via Modal ASGI
124
+ @app.function(image=image)
125
+ @modal.asgi_app()
126
+ def api():
127
+ return web_app