prain: skeleton
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.egg-info/
|
||||||
|
/models/
|
||||||
|
/build/
|
||||||
|
/.venv/
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
[project]
|
||||||
|
name = "prain"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Per-expert VRAM/RAM/disk placement for MoE models served by vLLM"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = [] # torch and vllm come from the gfx1030 vLLM image
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
prain = "prain.cli:main"
|
||||||
|
|
||||||
|
[project.entry-points."vllm.general_plugins"]
|
||||||
|
prain = "prain.plugin:register"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=64"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
packages = ["prain"]
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
"""Print greedy answers to fixed prompts from an OpenAI-compatible server, as JSON.
|
||||||
|
|
||||||
|
usage: python3 scripts/greedy.py http://127.0.0.1:8000 MODEL > answers.json
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
PROMPTS = [
|
||||||
|
"Explain what a mixture-of-experts language model is in two sentences.",
|
||||||
|
"Write a Python function that returns the n-th Fibonacci number.",
|
||||||
|
"Name three prime numbers greater than 100.",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def complete(base_url, model, prompt):
|
||||||
|
body = json.dumps({"model": model, "messages": [{"role": "user", "content": prompt}],
|
||||||
|
"temperature": 0, "max_tokens": 64}).encode()
|
||||||
|
request = urllib.request.Request(f"{base_url}/v1/chat/completions", body,
|
||||||
|
{"Content-Type": "application/json"})
|
||||||
|
with urllib.request.urlopen(request, timeout=1800) as response:
|
||||||
|
return json.load(response)["choices"][0]["message"]["content"]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
base_url, model = sys.argv[1], sys.argv[2]
|
||||||
|
print(json.dumps([complete(base_url, model, p) for p in PROMPTS], indent=1))
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||||
|
|
||||||
|
|
||||||
|
class TmpCase(unittest.TestCase):
|
||||||
|
"""A test case with a fresh temporary directory in self.dir."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
tmp = tempfile.TemporaryDirectory()
|
||||||
|
self.addCleanup(tmp.cleanup)
|
||||||
|
self.dir = tmp.name
|
||||||
|
|
||||||
|
def write(self, name, text):
|
||||||
|
path = os.path.join(self.dir, name)
|
||||||
|
with open(path, "w") as f:
|
||||||
|
f.write(text)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for(predicate, timeout=5.0):
|
||||||
|
end = time.monotonic() + timeout
|
||||||
|
while not predicate():
|
||||||
|
if time.monotonic() > end:
|
||||||
|
raise AssertionError("timed out waiting")
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
|
||||||
|
class StubVllm:
|
||||||
|
"""A fake OpenAI-compatible server on a free port. Records every POST body. Streamed
|
||||||
|
chats answer "Hel", "lo", "!"; others answer "ok". Set .delay (seconds, or a function of
|
||||||
|
the request body) to make answers slow, .pause (the same) to stream the pieces that far
|
||||||
|
apart, and .fail to an HTTP code to reject chats."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.bodies = []
|
||||||
|
self.delay = 0.0
|
||||||
|
self.pause = 0.0
|
||||||
|
self.fail = None
|
||||||
|
stub = self
|
||||||
|
|
||||||
|
class Handler(BaseHTTPRequestHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
self.send(200, "application/json", {"data": [{"id": "stub-model"}]})
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
body = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
|
||||||
|
stub.bodies.append(body)
|
||||||
|
time.sleep(stub.delay(body) if callable(stub.delay) else stub.delay)
|
||||||
|
if stub.fail:
|
||||||
|
return self.send(stub.fail, "application/json", {"error": "rejected"})
|
||||||
|
if not body.get("stream"):
|
||||||
|
return self.send(200, "application/json",
|
||||||
|
{"choices": [{"message": {"content": "ok"}}]})
|
||||||
|
events = [{"choices": [{"delta": {"content": p}}]} for p in ("Hel", "lo", "!")]
|
||||||
|
pause = stub.pause(body) if callable(stub.pause) else stub.pause
|
||||||
|
if pause: # stream piece by piece; HTTP/1.0 ends the body by closing
|
||||||
|
return self.trickle([f"data: {json.dumps(e)}\n\n" for e in events], pause)
|
||||||
|
text = "".join(f"data: {json.dumps(e)}\n\n" for e in events) + "data: [DONE]\n\n"
|
||||||
|
self.send(200, "text/event-stream", text)
|
||||||
|
|
||||||
|
def trickle(self, chunks, pause):
|
||||||
|
try:
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "text/event-stream")
|
||||||
|
self.end_headers()
|
||||||
|
for i, chunk in enumerate(chunks + ["data: [DONE]\n\n"]):
|
||||||
|
time.sleep(pause if i else 0)
|
||||||
|
self.wfile.write(chunk.encode())
|
||||||
|
self.wfile.flush()
|
||||||
|
except (BrokenPipeError, ConnectionResetError):
|
||||||
|
pass # the client aborted
|
||||||
|
|
||||||
|
def send(self, code, kind, payload):
|
||||||
|
data = (payload if isinstance(payload, str) else json.dumps(payload)).encode()
|
||||||
|
try:
|
||||||
|
self.send_response(code)
|
||||||
|
self.send_header("Content-Type", kind)
|
||||||
|
self.send_header("Content-Length", str(len(data)))
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(data)
|
||||||
|
except (BrokenPipeError, ConnectionResetError):
|
||||||
|
pass # the client aborted
|
||||||
|
|
||||||
|
def log_message(self, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
||||||
|
self.url = f"http://127.0.0.1:{self.server.server_port}"
|
||||||
|
threading.Thread(target=self.server.serve_forever, daemon=True).start()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.server.shutdown()
|
||||||
|
self.server.server_close()
|
||||||
Reference in New Issue
Block a user