Download src/diffusion_lm/chat_server.py from goldenfox/marimo-diffusion: direct link, hf CLI and curl.
- Browser
- Download file 10.5 kB
-
https://huggingface.co/goldenfox/marimo-diffusion/resolve/main/src/diffusion_lm/chat_server.py
- Command line
-
hf download hf://goldenfox/marimo-diffusion/src/diffusion_lm/chat_server.py
-
curl -L -o chat_server.py https://huggingface.co/goldenfox/marimo-diffusion/resolve/main/src/diffusion_lm/chat_server.py
10.5 kB
| """OpenAI-compatible chat endpoint over the reasoning engine. | |
| Serves ``/v1/chat/completions`` and ``/v1/models`` so any OpenAI-API chat client can talk to a | |
| hybrid checkpoint with its own sampler — the model cannot run under llama.cpp-family runtimes, | |
| whose autoregressive decoding never matches the block-denoising objective. | |
| Clients resend the full message history on every request and carry no thinking notes, while the | |
| training layout replaces messages outside the visible window with the ledger of notes taken on | |
| them. The server therefore caches each turn's notes keyed by a hash of the exact history that | |
| produced it: a client that resends history verbatim reconstructs the same ledger the playground | |
| would hold. On a cache miss (server restart, edited history) the older turns simply contribute | |
| nothing, which is the playground's restart behaviour as well. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import hashlib | |
| import json | |
| import threading | |
| import time | |
| import uuid | |
| from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer | |
| from pathlib import Path | |
| from diffusion_lm.claims import ( | |
| KEEP_MESSAGES, | |
| SYSTEM, | |
| chat_prefix, | |
| ledger_line, | |
| ledger_notes, | |
| merge_notes, | |
| ) | |
| from diffusion_lm.reasoning_playground import ReasoningEngine | |
| from diffusion_lm.train import resolve_device | |
| MAX_ANSWER_TOKENS = 384 | |
| NOTE_CACHE_LIMIT = 4096 | |
| def _turn_key(system: str, messages: list[dict[str, str]]) -> str: | |
| payload = json.dumps( | |
| [system] + [[m['role'], m['content']] for m in messages], | |
| ensure_ascii=False, sort_keys=False, | |
| ) | |
| return hashlib.sha256(payload.encode('utf-8')).hexdigest() | |
| class ChatService: | |
| """One engine plus the note cache; generation is serialized on the GPU.""" | |
| def __init__(self, engine: ReasoningEngine, model_id: str, keep_messages: int) -> None: | |
| self.engine = engine | |
| self.model_id = model_id | |
| self.keep_messages = keep_messages | |
| self.notes: dict[str, str] = {} | |
| self.lock = threading.Lock() | |
| def _attach_notes(self, system: str, messages: list[dict[str, str]]) -> list[dict[str, str]]: | |
| attached = [] | |
| for index, message in enumerate(messages): | |
| entry = {'role': message['role'], 'content': message['content']} | |
| if message['role'] == 'assistant': | |
| note = self.notes.get(_turn_key(system, messages[: index + 1])) | |
| if note: | |
| entry['note'] = note | |
| attached.append(entry) | |
| return attached | |
| def build_prefix(self, system: str, messages: list[dict[str, str]]) -> str: | |
| attached = self._attach_notes(system, messages) | |
| older = merge_notes(ledger_notes(attached, self.keep_messages)) | |
| window = attached[max(0, len(attached) - self.keep_messages):] | |
| return chat_prefix( | |
| [{'role': m['role'], 'content': m['content']} for m in window], | |
| system=system, extra=ledger_line(older), | |
| ) | |
| def remember(self, system: str, messages: list[dict[str, str]], | |
| answer: str, note: str) -> None: | |
| if not note: | |
| return | |
| if len(self.notes) >= NOTE_CACHE_LIMIT: | |
| self.notes.clear() | |
| turn = messages + [{'role': 'assistant', 'content': answer}] | |
| self.notes[_turn_key(system, turn)] = note | |
| def generate(self, system: str, messages: list[dict[str, str]], *, temperature: float, | |
| top_p: float, max_tokens: int, seed: int): | |
| """Yield ``(answer_so_far, note, done)``; the note arrives with the final snapshot.""" | |
| prefix = self.build_prefix(system, messages) | |
| blocks: list[tuple[int, str]] = [] | |
| answer = '' | |
| with self.lock: | |
| for _, answer, _ in self.engine.stream_chat( | |
| prefix, | |
| temperature=temperature, | |
| # 16 measured optimal on qwen06b-genchat-sft (2026-08-12 five-point sweep): | |
| # best numeric fidelity, clean doubling, half the latency of 32. Below 8 both | |
| # fidelity and fluency degrade while latency barely moves. | |
| steps_per_block=16, | |
| max_answer_tokens=min(MAX_ANSWER_TOKENS, max_tokens), | |
| top_p=top_p, | |
| seed=seed, | |
| blocks_out=blocks, | |
| ): | |
| yield answer, '', False | |
| note = '; '.join(text for _, text in blocks if text) | |
| self.remember(system, messages, answer.strip(), note) | |
| yield answer, note, True | |
| def _split_messages(raw: list[dict]) -> tuple[str, list[dict[str, str]]]: | |
| system = SYSTEM | |
| messages = [] | |
| for message in raw: | |
| role, content = message.get('role'), str(message.get('content') or '') | |
| if role == 'system': | |
| system = content or system | |
| elif role in ('user', 'assistant'): | |
| messages.append({'role': role, 'content': content}) | |
| return system, messages | |
| class Handler(BaseHTTPRequestHandler): | |
| service: ChatService | |
| def log_message(self, format: str, *args) -> None: # noqa: A002 | |
| pass | |
| def _json(self, status: int, body: dict) -> None: | |
| data = json.dumps(body, ensure_ascii=False).encode('utf-8') | |
| self.send_response(status) | |
| self.send_header('Content-Type', 'application/json') | |
| self.send_header('Content-Length', str(len(data))) | |
| self.send_header('Access-Control-Allow-Origin', '*') | |
| self.end_headers() | |
| self.wfile.write(data) | |
| def do_OPTIONS(self) -> None: # noqa: N802 | |
| self.send_response(204) | |
| self.send_header('Access-Control-Allow-Origin', '*') | |
| self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') | |
| self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization') | |
| self.end_headers() | |
| def do_GET(self) -> None: # noqa: N802 | |
| if self.path.rstrip('/') in ('/v1/models', '/models'): | |
| self._json(200, {'object': 'list', 'data': [ | |
| {'id': self.service.model_id, 'object': 'model', 'owned_by': 'mini-mdlm'}, | |
| ]}) | |
| else: | |
| self._json(404, {'error': 'not found'}) | |
| def do_POST(self) -> None: # noqa: N802 | |
| if self.path.rstrip('/') not in ('/v1/chat/completions', '/chat/completions'): | |
| self._json(404, {'error': 'not found'}) | |
| return | |
| try: | |
| length = int(self.headers.get('Content-Length', 0)) | |
| request = json.loads(self.rfile.read(length)) | |
| system, messages = _split_messages(request.get('messages') or []) | |
| if not messages or messages[-1]['role'] != 'user': | |
| raise ValueError('last message must be from the user') | |
| except (ValueError, json.JSONDecodeError) as error: | |
| self._json(400, {'error': {'message': str(error), 'type': 'invalid_request_error'}}) | |
| return | |
| temperature = float(request.get('temperature') or 0.8) | |
| top_p = float(request.get('top_p') or 0.95) | |
| max_tokens = int(request.get('max_tokens') or MAX_ANSWER_TOKENS) | |
| seed = int(request.get('seed') or 0) | |
| stream = bool(request.get('stream')) | |
| completion_id = f'chatcmpl-{uuid.uuid4().hex[:24]}' | |
| created = int(time.time()) | |
| snapshots = self.service.generate( | |
| system, messages, temperature=temperature, top_p=top_p, | |
| max_tokens=max_tokens, seed=seed, | |
| ) | |
| if not stream: | |
| answer = note = '' | |
| for answer, note, _ in snapshots: | |
| pass | |
| message = {'role': 'assistant', 'content': answer.strip()} | |
| if note: | |
| message['reasoning_content'] = note | |
| self._json(200, { | |
| 'id': completion_id, 'object': 'chat.completion', 'created': created, | |
| 'model': self.service.model_id, | |
| 'choices': [{'index': 0, 'message': message, 'finish_reason': 'stop'}], | |
| }) | |
| return | |
| self.send_response(200) | |
| self.send_header('Content-Type', 'text/event-stream') | |
| self.send_header('Cache-Control', 'no-cache') | |
| self.send_header('Access-Control-Allow-Origin', '*') | |
| self.end_headers() | |
| def chunk(delta: dict, finish: str | None = None) -> bytes: | |
| body = { | |
| 'id': completion_id, 'object': 'chat.completion.chunk', 'created': created, | |
| 'model': self.service.model_id, | |
| 'choices': [{'index': 0, 'delta': delta, 'finish_reason': finish}], | |
| } | |
| return f'data: {json.dumps(body, ensure_ascii=False)}\n\n'.encode('utf-8') | |
| try: | |
| self.wfile.write(chunk({'role': 'assistant'})) | |
| sent = '' | |
| for answer, _, done in snapshots: | |
| if len(answer) > len(sent): | |
| self.wfile.write(chunk({'content': answer[len(sent):]})) | |
| self.wfile.flush() | |
| sent = answer | |
| self.wfile.write(chunk({}, finish='stop')) | |
| self.wfile.write(b'data: [DONE]\n\n') | |
| except (BrokenPipeError, ConnectionResetError): | |
| pass | |
| def main() -> None: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument('--checkpoint', type=Path, | |
| default=Path('outputs/qwen06b-genchat-sft/inference-latest.pt')) | |
| parser.add_argument('--tokenizer', type=Path, | |
| default=Path('artifacts/tokenizer-qwen3-adaptive.json')) | |
| parser.add_argument('--model-id', default=None, | |
| help='name reported to clients; defaults to the checkpoint directory') | |
| parser.add_argument('--keep-messages', type=int, default=KEEP_MESSAGES) | |
| parser.add_argument('--host', default='127.0.0.1') | |
| parser.add_argument('--port', type=int, default=7998) | |
| parser.add_argument('--device', default='auto') | |
| args = parser.parse_args() | |
| device = resolve_device(args.device) | |
| engine = ReasoningEngine(args.checkpoint, args.tokenizer, device) | |
| if not engine.chat_ready: | |
| raise SystemExit(f'{args.checkpoint} is not an adaptive hybrid over a ChatML tokenizer') | |
| Handler.service = ChatService( | |
| engine, args.model_id or args.checkpoint.parent.name, args.keep_messages, | |
| ) | |
| server = ThreadingHTTPServer((args.host, args.port), Handler) | |
| print(f'serving {Handler.service.model_id} on http://{args.host}:{args.port}/v1') | |
| server.serve_forever() | |
| if __name__ == '__main__': | |
| main() | |