File size: 10,497 Bytes
88a6753
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
"""OpenAI/vLLM-compatible serving shim for the DiffusionGemma engine.

The published deployment is vLLM plus its structured decision server. This shim speaks the small part of vLLM's
HTTP API that server uses — chat/completions with ``vllm_xargs`` diffusion requests — so the unmodified upstream
server runs on top of the MLX engine. That keeps the decision layer (prompt, templates, canvas, slot reads,
adaptive thinking) bit-for-bit the published one instead of a re-implementation.

Diffusion xargs understood (as sent by runtime/structured_server.py):
  diffusion_seed_canvas   initial canvas token ids
  diffusion_canvas_length canvas width
  diffusion_max_steps     denoising steps
  diffusion_read_only     return logprobs at every canvas position, do not sample
  diffusion_pinned        canvas positions held at their seed values
  diffusion_constrained   the unembedding is read over ``logprob_token_ids`` only
  diffusion_samples       number of draws
and ``djev_bidir`` = flat [a1, b1, a2, b2, ...] prompt ranges that attend bidirectionally (patch_vllm_bidir.py).
"""

from __future__ import annotations

import argparse
import time
from typing import Any

import mlx.core as mx
import numpy as np
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pydantic import BaseModel

from .engine import CANVAS_LENGTH, Engine

app = FastAPI()
ENGINE: Engine | None = None
OPTS: dict[str, Any] = {"canvas": CANVAS_LENGTH, "max_prompt": 8192, "model": "super-decider"}


class ChatRequest(BaseModel):
    model: str | None = None
    messages: list[dict] | None = None
    prompt: Any = None
    max_tokens: int | None = None
    logprobs: Any = None
    top_logprobs: int | None = None
    logprob_token_ids: list[int] | None = None
    return_tokens_as_token_ids: bool = False
    chat_template_kwargs: dict | None = None
    vllm_xargs: dict | None = None
    stop_token_ids: list[int] | None = None
    seed: int | None = None
    temperature: float | None = None
    stream: bool = False


def _tok(t: int, as_id: bool) -> str:
    return f"token_id:{t}" if as_id else ENGINE.tokenizer.decode([t])


def _block_ids(n_prompt: int, ranges) -> mx.array | None:
    """Per-token bidirectional group ids from the flat ``djev_bidir`` ranges, -1 outside them."""
    if not ranges:
        return None
    ids = np.full(n_prompt, -1, dtype=np.int32)
    for k, i in enumerate(range(0, len(ranges) - 1, 2)):
        a, b = int(ranges[i]), int(ranges[i + 1])
        ids[max(a, 0) : min(b, n_prompt)] = k
    return mx.array(ids)


def _usage(prompt_tokens: int, completion_tokens: int) -> dict:
    return {
        "prompt_tokens": prompt_tokens,
        "completion_tokens": completion_tokens,
        "total_tokens": prompt_tokens + completion_tokens,
    }


def _read(req: ChatRequest, prompt_ids: list[int]):
    """A diffusion read: label logits at every canvas position, plus the canvas tokens themselves."""
    x = req.vllm_xargs or {}
    canvas = [int(t) for t in x.get("diffusion_seed_canvas") or []]
    canvas_len = int(x.get("diffusion_canvas_length") or OPTS["canvas"])
    steps = int(x.get("diffusion_max_steps") or 1)
    pinned_pos = [int(p) for p in x.get("diffusion_pinned") or []]
    samples = int(x.get("diffusion_samples") or 1)
    label_ids = [int(t) for t in (req.logprob_token_ids or [])]
    if not canvas:
        raise ValueError("a diffusion request needs diffusion_seed_canvas")
    canvas = (canvas + [0] * canvas_len)[:canvas_len]
    block_ids = _block_ids(len(prompt_ids), x.get("djev_bidir"))
    pinned = {p: canvas[p] for p in pinned_pos if 0 <= p < canvas_len}
    probs = ENGINE.read(
        prompt_ids,
        canvas,
        list(range(canvas_len)),
        label_ids,
        block_ids=block_ids,
        steps=steps,
        pinned=pinned,
        seed=int(req.seed or 0),
        samples=samples,
    )
    return canvas, label_ids, probs


def _top_rows(probs_row: np.ndarray, label_ids, as_id: bool, k: int):
    order = np.argsort(-probs_row)[: max(k, 1)]
    return [
        {"token": _tok(label_ids[i], as_id), "logprob": float(probs_row[i]), "bytes": None}
        for i in order
    ]


def _read_content(canvas, label_ids, probs, as_id: bool, top_k: int):
    """One logprobs entry per canvas position (``logprobs.content`` / ``top_logprobs``)."""
    rows = []
    for pos, tok in enumerate(canvas):
        tops = _top_rows(probs[pos], label_ids, as_id, top_k) if label_ids else []
        best = tops[0]["logprob"] if tops else 0.0
        rows.append(
            {"token": _tok(tok, as_id), "logprob": best, "bytes": None, "top_logprobs": tops}
        )
    return rows


def _token_logprobs(tokens: list[int], lps, as_id: bool, chat: bool):
    """vLLM's legacy per-token logprobs object (``content`` for chat, ``tokens``/``token_logprobs`` for completions).

    ``top_logprobs`` stays empty: the model reads a label set, not a token ranking, so nothing here asks for it.
    """
    if lps is None:
        return None
    vals = [float(v) for v in lps]
    if chat:
        return {
            "content": [
                {"token": _tok(t, as_id), "logprob": v, "bytes": None, "top_logprobs": []}
                for t, v in zip(tokens, vals)
            ],
            "refusal": None,
        }
    return {
        "tokens": [_tok(t, as_id) for t in tokens],
        "token_logprobs": vals,
        "top_logprobs": [{} for _ in tokens],
        "text_offset": list(range(len(tokens))),
    }


def _generate(req: ChatRequest, prompt_ids: list[int]):
    x = req.vllm_xargs or {}
    want = req.logprobs is not None
    out = ENGINE.generate(
        prompt_ids,
        max_new_tokens=req.max_tokens,
        max_denoising_steps=int(x.get("diffusion_max_steps") or 0) or None,
        stop_ids=req.stop_token_ids,
        block_ids=_block_ids(len(prompt_ids), x.get("djev_bidir")),
        token_logprobs=want,
    )
    gen, lps = out if want else (out, None)
    if req.stop_token_ids:
        for k, t in enumerate(gen):
            if t in req.stop_token_ids:
                gen = gen[: k + 1]
                break
    if req.max_tokens:
        gen, lps = gen[: req.max_tokens], (lps[: req.max_tokens] if lps else lps)
    return gen, lps


@app.get("/v1/models")
@app.get("/health")
def models():
    return {"object": "list", "data": [{"id": OPTS["model"], "object": "model", "owned_by": "local"}]}


def _handle(req: ChatRequest, chat: bool):
    if chat:
        prompt_ids = ENGINE.chat_ids(
            req.messages or [],
            bool((req.chat_template_kwargs or {}).get("enable_thinking", False)),
        )
    else:
        prompt_ids = [int(t) for t in (req.prompt or [])]
    if len(prompt_ids) > OPTS["max_prompt"]:
        # A real 400: vLLM answers 400 here, and a 200 body without choices makes the
        # decision layer raise KeyError('choices') instead of reporting the length.
        return JSONResponse(
            status_code=400,
            content={
                "error": {
                    "code": 400,
                    "message": f"state too long: {len(prompt_ids)} tokens, this build serves "
                    f"{OPTS['max_prompt']} (raise --max-prompt; attention cost grows quadratically)",
                    "type": "BadRequestError",
                }
            },
        )
    as_id = bool(req.return_tokens_as_token_ids)
    diffusion = bool((req.vllm_xargs or {}).get("diffusion_seed_canvas"))
    created = int(time.time())
    if diffusion:
        canvas, label_ids, probs = _read(req, prompt_ids)
        # One choice per draw, the way vLLM answers a multi-sample request.
        choices = []
        for k in range(probs.shape[0]):
            content = _read_content(canvas, label_ids, probs[k], as_id, int(req.top_logprobs or 20))
            choice = {
                "index": k,
                "text": "",
                "finish_reason": "stop",
                "logprobs": {
                    "tokens": [c["token"] for c in content],
                    "token_logprobs": [c["logprob"] for c in content],
                    "top_logprobs": [{t["token"]: t["logprob"] for t in c["top_logprobs"]} for c in content],
                    "text_offset": list(range(len(content))),
                },
            }
            if chat:
                choice["message"] = {"role": "assistant", "content": ""}
                choice["logprobs"] = {"content": content, "refusal": None}
            choices.append(choice)
        body = {
            "id": "djev-local",
            "object": "chat.completion" if chat else "text_completion",
            "created": created,
            "model": OPTS["model"],
            "choices": choices,
            "usage": _usage(len(prompt_ids), len(canvas)),
            "system_fingerprint": "super-decider-metal",
        }
        return body

    gen, lps = _generate(req, prompt_ids)
    text = ENGINE.tokenizer.decode(gen)
    choice = {"index": 0, "text": text, "finish_reason": "stop"}
    if chat:
        choice["message"] = {"role": "assistant", "content": text}
    choice["logprobs"] = _token_logprobs(gen, lps, as_id, chat)
    body = {
        "id": "djev-local",
        "object": "chat.completion" if chat else "text_completion",
        "created": created,
        "model": OPTS["model"],
        "choices": [choice],
        "usage": _usage(len(prompt_ids), len(gen)),
        "system_fingerprint": "super-decider-metal",
    }
    return body


@app.post("/v1/chat/completions")
async def chat_completions(req: ChatRequest):
    return _handle(req, chat=True)


@app.post("/v1/completions")
async def completions(req: ChatRequest):
    return _handle(req, chat=False)


def main():
    global ENGINE
    ap = argparse.ArgumentParser(description="super-decider-metal engine server (vLLM-compatible subset)")
    ap.add_argument("--model", default="artifacts/mlx-nvfp4")
    ap.add_argument("--name", default="super-decider")
    ap.add_argument("--host", default="127.0.0.1")
    ap.add_argument("--port", type=int, default=8000)
    ap.add_argument("--canvas", type=int, default=CANVAS_LENGTH)
    ap.add_argument("--max-prompt", type=int, default=8192)
    args = ap.parse_args()
    OPTS.update(canvas=args.canvas, max_prompt=args.max_prompt, model=args.name)
    ENGINE = Engine(args.model, canvas_length=args.canvas)
    import uvicorn

    uvicorn.run(app, host=args.host, port=args.port, log_level="warning")


if __name__ == "__main__":
    main()