Text Classification
Transformers
Safetensors
English
gemma4
image-text-to-text
system-one
system-two
adaptive-thinking
typed-decisions
decision-model
calibrated-probabilities
jev
noul
choice
score
lora
mixture-of-experts
multimodal
vllm
Eval Results (legacy)
Instructions to use autotrust/GEV-26B-Decide with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use autotrust/GEV-26B-Decide with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="autotrust/GEV-26B-Decide")# Load model directly from transformers import AutoProcessor, AutoModelForMultimodalLM processor = AutoProcessor.from_pretrained("autotrust/GEV-26B-Decide") model = AutoModelForMultimodalLM.from_pretrained("autotrust/GEV-26B-Decide", device_map="auto") - Notebooks
- Google Colab
- Kaggle
serve_decide.py: answer read-out that works with speculative decoding (+ fallback), system2_only; serve.sh: --max-logprobs 256, MTP=1 option
Browse files- README.md +9 -8
- serve.sh +9 -3
- serve_decide.py +36 -4
README.md
CHANGED
|
@@ -172,7 +172,8 @@ Lower `threshold` or `think_budget` to trade accuracy for speed; `thinking: "off
|
|
| 172 |
|
| 173 |
MMLU-Pro and BBH (except six questions) ran with speculative decoding, which does not change the output distribution.
|
| 174 |
Six BBH questions with 18 options hit a vLLM error in the speculative-decoding read-out path and were answered by the same
|
| 175 |
-
server without speculative decoding.
|
|
|
|
| 176 |
|
| 177 |
## Images
|
| 178 |
|
|
@@ -289,13 +290,13 @@ requests.post("http://localhost:8000/v1/chat/completions", json={
|
|
| 289 |
* Adaptive thinking adds nothing when it does not think, and about 1 second per 250 thinking tokens when it does
|
| 290 |
(see [Latency](#latency)).
|
| 291 |
|
| 292 |
-
**Faster thinking with speculative decoding.**
|
| 293 |
-
`--speculative-config '{"model": "google/gemma-4-26B-A4B-it-assistant", "num_speculative_tokens": 4}'`
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
|
| 300 |
If you call `/v1/completions` for System 1 yourself, pass `top_k: 0` and `top_p: 1.0`: the model's generation config sets
|
| 301 |
`top_k=64` and `top_p=0.95`, which vLLM applies as request defaults and which would truncate the returned probabilities.
|
|
|
|
| 172 |
|
| 173 |
MMLU-Pro and BBH (except six questions) ran with speculative decoding, which does not change the output distribution.
|
| 174 |
Six BBH questions with 18 options hit a vLLM error in the speculative-decoding read-out path and were answered by the same
|
| 175 |
+
server without speculative decoding; the current `serve_decide.py` reads answers through a path that works with
|
| 176 |
+
speculative decoding.
|
| 177 |
|
| 178 |
## Images
|
| 179 |
|
|
|
|
| 290 |
* Adaptive thinking adds nothing when it does not think, and about 1 second per 250 thinking tokens when it does
|
| 291 |
(see [Latency](#latency)).
|
| 292 |
|
| 293 |
+
**Faster thinking with speculative decoding.** `MTP=1 bash GEV-26B-Decide/serve.sh` adds Google's 0.9 GB draft model for
|
| 294 |
+
this backbone (`--speculative-config '{"model": "google/gemma-4-26B-A4B-it-assistant", "num_speculative_tokens": 4}'`).
|
| 295 |
+
On one B200 it speeds up System 2 about 1.8–1.9×: 247 → 438 tokens per second for a single request, 7,072 → 13,505 tokens
|
| 296 |
+
per second at 128 concurrent requests (mean acceptance length 3.5–3.7 of 4). System 1 decisions are unchanged, but System 1
|
| 297 |
+
throughput at high concurrency drops (257 → 140 decisions per second with 64 clients); use it when you mostly think.
|
| 298 |
+
`serve_decide.py` reads answers with the token restriction that works under speculative decoding, which needs
|
| 299 |
+
`--max-logprobs 256` (set in `serve.sh`).
|
| 300 |
|
| 301 |
If you call `/v1/completions` for System 1 yourself, pass `top_k: 0` and `top_p: 1.0`: the model's generation config sets
|
| 302 |
`top_k=64` and `top_p=0.95`, which vLLM applies as request defaults and which would truncate the returned probabilities.
|
serve.sh
CHANGED
|
@@ -1,16 +1,22 @@
|
|
| 1 |
#!/usr/bin/env bash
|
| 2 |
# Serve autotrust/GEV-26B-Decide with vLLM: one engine, both systems, text and images.
|
| 3 |
# System 2: the unmodified gemma-4-26B-A4B-it (served name autotrust/GEV-26B-Decide) on the OpenAI endpoints
|
| 4 |
-
# System 1: POST /v1/decide (2-256 options,
|
| 5 |
# serve_decide.py is the standard vLLM OpenAI server (same flags) with /v1/decide added.
|
| 6 |
# Requirements: a vLLM build with Gemma-4 support plus patches/vllm-gemma4-lm-head-lora.patch (LoRA on Gemma-4's tied
|
| 7 |
# lm_head, vocabulary 262,144), tested with a vLLM development build from September 2026.
|
| 8 |
# MAX_MODEL_LEN: up to 262144 (the backbone's native context).
|
|
|
|
|
|
|
| 9 |
set -e
|
| 10 |
MODEL_DIR=${MODEL_DIR:-GEV-26B-Decide}
|
| 11 |
[ -d "$MODEL_DIR" ] || hf download autotrust/GEV-26B-Decide --local-dir "$MODEL_DIR"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
exec python3 "$MODEL_DIR/serve_decide.py" --model "$MODEL_DIR" --served-model-name autotrust/GEV-26B-Decide \
|
| 13 |
--enable-lora --max-lora-rank 32 --lora-modules jev-decision="$MODEL_DIR/adapter_vllm" \
|
| 14 |
-
--logprobs-mode processed_logprobs --max-model-len ${MAX_MODEL_LEN:-65536} --enable-prefix-caching \
|
| 15 |
--max-num-seqs 256 --trust-request-chat-template --limit-mm-per-prompt '{"image": 8}' \
|
| 16 |
-
--port ${PORT:-8000}
|
|
|
|
| 1 |
#!/usr/bin/env bash
|
| 2 |
# Serve autotrust/GEV-26B-Decide with vLLM: one engine, both systems, text and images.
|
| 3 |
# System 2: the unmodified gemma-4-26B-A4B-it (served name autotrust/GEV-26B-Decide) on the OpenAI endpoints
|
| 4 |
+
# System 1: POST /v1/decide (2-256 options, adaptive thinking), or the LoRA module "jev-decision" directly
|
| 5 |
# serve_decide.py is the standard vLLM OpenAI server (same flags) with /v1/decide added.
|
| 6 |
# Requirements: a vLLM build with Gemma-4 support plus patches/vllm-gemma4-lm-head-lora.patch (LoRA on Gemma-4's tied
|
| 7 |
# lm_head, vocabulary 262,144), tested with a vLLM development build from September 2026.
|
| 8 |
# MAX_MODEL_LEN: up to 262144 (the backbone's native context).
|
| 9 |
+
# MTP=1: speculative decoding with google/gemma-4-26B-A4B-it-assistant (System 2 about 1.8-1.9x faster; lower System 1
|
| 10 |
+
# throughput at high concurrency).
|
| 11 |
set -e
|
| 12 |
MODEL_DIR=${MODEL_DIR:-GEV-26B-Decide}
|
| 13 |
[ -d "$MODEL_DIR" ] || hf download autotrust/GEV-26B-Decide --local-dir "$MODEL_DIR"
|
| 14 |
+
EXTRA=()
|
| 15 |
+
if [ "${MTP:-0}" = "1" ]; then
|
| 16 |
+
EXTRA=(--speculative-config '{"model": "google/gemma-4-26B-A4B-it-assistant", "num_speculative_tokens": 4}')
|
| 17 |
+
fi
|
| 18 |
exec python3 "$MODEL_DIR/serve_decide.py" --model "$MODEL_DIR" --served-model-name autotrust/GEV-26B-Decide \
|
| 19 |
--enable-lora --max-lora-rank 32 --lora-modules jev-decision="$MODEL_DIR/adapter_vllm" \
|
| 20 |
+
--logprobs-mode processed_logprobs --max-logprobs 256 --max-model-len ${MAX_MODEL_LEN:-65536} --enable-prefix-caching \
|
| 21 |
--max-num-seqs 256 --trust-request-chat-template --limit-mm-per-prompt '{"image": 8}' \
|
| 22 |
+
"${EXTRA[@]}" --port ${PORT:-8000}
|
serve_decide.py
CHANGED
|
@@ -86,6 +86,7 @@ class DecideRequest(BaseModel):
|
|
| 86 |
chat_template_kwargs: dict | None = None
|
| 87 |
return_reasoning: bool = False
|
| 88 |
debug: bool = False
|
|
|
|
| 89 |
|
| 90 |
|
| 91 |
# ------------------------------------------------------------------ setup
|
|
@@ -137,6 +138,7 @@ def setup(args):
|
|
| 137 |
"{%- endif -%}{%- endfor -%}{%- endif -%}{%- endfor -%}")
|
| 138 |
names = args.served_model_name
|
| 139 |
S.update(tok=tok, has_effort="reasoning_effort" in (tok.chat_template or ""), think_cache={},
|
|
|
|
| 140 |
head=head, temps=temps, labels=[l for l, _ in dec], label_ids=[t for _, t in dec], ans_labels=ans, prof=prof,
|
| 141 |
think_pre=pre, think_post=post, raw=raw, end_think_id=tok.convert_tokens_to_ids(prof["end_think"]),
|
| 142 |
model=(names[0] if isinstance(names, list) else names) or args.model, model_type=mtype)
|
|
@@ -194,11 +196,32 @@ class Ctx:
|
|
| 194 |
self.completion_tokens += out.usage.completion_tokens or 0
|
| 195 |
|
| 196 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
async def _logprobs(ctx, content, has_img, ids, model, allowed=None):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
"""One-token read-out: {token_id: logprob} for `ids` after the content (text or chat parts)."""
|
| 199 |
lp = {}
|
| 200 |
-
|
| 201 |
-
|
|
|
|
| 202 |
kw = dict(logprob_token_ids=chunk) if allowed is None else dict(allowed_token_ids=allowed)
|
| 203 |
if has_img:
|
| 204 |
r = ChatCompletionRequest(model=model, messages=[{"role": "user", "content": content}], chat_template=S["raw"],
|
|
@@ -228,7 +251,7 @@ async def s1_pass(ctx, kind, parts, has_img, question, opts):
|
|
| 228 |
ids = head["verbalizer_ids"][lo:hi]; bias = head["bias"][lo:hi]; lines = opts
|
| 229 |
content = ([{"type": "text", "text": f"{prof['prefix']}[kind] {kind}\n[state] "}] + parts +
|
| 230 |
[{"type": "text", "text": f"\n[question] {question}\n[options]\n" + "\n".join(lines) + "\n[decision]:"}])
|
| 231 |
-
lp = await _logprobs(ctx, content, has_img, ids, LORA, allowed=
|
| 232 |
return _softmax([(max(lp.get(t, -1e9), -1e9) + b) / temps[kind] for t, b in zip(ids, bias)])
|
| 233 |
|
| 234 |
|
|
@@ -302,7 +325,7 @@ async def s2_dist(ctx, kind, parts, has_img, question, opts, budget, want_text,
|
|
| 302 |
ntok = out.usage.completion_tokens if out.usage else None
|
| 303 |
read = user + [{"type": "text", "text": thought + S["prof"]["end_think"] + S["prof"]["after_think"] + "Answer: ("}]
|
| 304 |
ids = [t for _, t in labs]
|
| 305 |
-
lp = await _logprobs(ctx, read, True, ids, S["model"], allowed=
|
| 306 |
p = _softmax([max(lp.get(t, -1e9), -1e9) for t in ids])
|
| 307 |
if kind == "noul":
|
| 308 |
p = [p[1], p[0]] # (A) yes / (B) no -> [P(false), P(true)]
|
|
@@ -356,6 +379,15 @@ async def decide(req: DecideRequest, raw: Request):
|
|
| 356 |
parts, has_img = _parts(req.state)
|
| 357 |
ctx = Ctx(raw)
|
| 358 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 359 |
p1 = await s1_dist(ctx, req.kind, parts, has_img, req.question, opts, strategy)
|
| 360 |
probs, think = p1, None
|
| 361 |
if thinking == "on" or (thinking == "auto" and max(p1) < tau):
|
|
|
|
| 86 |
chat_template_kwargs: dict | None = None
|
| 87 |
return_reasoning: bool = False
|
| 88 |
debug: bool = False
|
| 89 |
+
system2_only: bool = False # skip System 1: think and return System 2's answer distribution (for callers that already have p1)
|
| 90 |
|
| 91 |
|
| 92 |
# ------------------------------------------------------------------ setup
|
|
|
|
| 138 |
"{%- endif -%}{%- endfor -%}{%- endif -%}{%- endfor -%}")
|
| 139 |
names = args.served_model_name
|
| 140 |
S.update(tok=tok, has_effort="reasoning_effort" in (tok.chat_template or ""), think_cache={},
|
| 141 |
+
max_logprobs=int(getattr(args, "max_logprobs", 20) or 20),
|
| 142 |
head=head, temps=temps, labels=[l for l, _ in dec], label_ids=[t for _, t in dec], ans_labels=ans, prof=prof,
|
| 143 |
think_pre=pre, think_post=post, raw=raw, end_think_id=tok.convert_tokens_to_ids(prof["end_think"]),
|
| 144 |
model=(names[0] if isinstance(names, list) else names) or args.model, model_type=mtype)
|
|
|
|
| 196 |
self.completion_tokens += out.usage.completion_tokens or 0
|
| 197 |
|
| 198 |
|
| 199 |
+
def _readout_allowed(ids):
|
| 200 |
+
"""Read-out path: restrict the next token to `ids` and return all of them as top logprobs when the server allows that
|
| 201 |
+
many (--max-logprobs); otherwise ask for `ids` explicitly with logprob_token_ids (chunks of 128). The first path also
|
| 202 |
+
works with speculative decoding; logprob_token_ids does not in this vLLM build. Both give the same distribution."""
|
| 203 |
+
return ids if len(ids) <= S["max_logprobs"] else None
|
| 204 |
+
|
| 205 |
+
|
| 206 |
async def _logprobs(ctx, content, has_img, ids, model, allowed=None):
|
| 207 |
+
"""One-token read-out with a fallback to the other read-out path if the first one fails."""
|
| 208 |
+
try:
|
| 209 |
+
return await _logprobs_once(ctx, content, has_img, ids, model, allowed)
|
| 210 |
+
except Upstream:
|
| 211 |
+
raise
|
| 212 |
+
except Exception:
|
| 213 |
+
other = None if allowed is not None else (ids if len(ids) <= S["max_logprobs"] else None)
|
| 214 |
+
if other is None and allowed is None:
|
| 215 |
+
raise
|
| 216 |
+
return await _logprobs_once(ctx, content, has_img, ids, model, other)
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
async def _logprobs_once(ctx, content, has_img, ids, model, allowed=None):
|
| 220 |
"""One-token read-out: {token_id: logprob} for `ids` after the content (text or chat parts)."""
|
| 221 |
lp = {}
|
| 222 |
+
step = CHUNK if allowed is None else len(ids)
|
| 223 |
+
for i in range(0, len(ids), step):
|
| 224 |
+
chunk = ids[i: i + step]
|
| 225 |
kw = dict(logprob_token_ids=chunk) if allowed is None else dict(allowed_token_ids=allowed)
|
| 226 |
if has_img:
|
| 227 |
r = ChatCompletionRequest(model=model, messages=[{"role": "user", "content": content}], chat_template=S["raw"],
|
|
|
|
| 251 |
ids = head["verbalizer_ids"][lo:hi]; bias = head["bias"][lo:hi]; lines = opts
|
| 252 |
content = ([{"type": "text", "text": f"{prof['prefix']}[kind] {kind}\n[state] "}] + parts +
|
| 253 |
[{"type": "text", "text": f"\n[question] {question}\n[options]\n" + "\n".join(lines) + "\n[decision]:"}])
|
| 254 |
+
lp = await _logprobs(ctx, content, has_img, ids, LORA, allowed=_readout_allowed(ids))
|
| 255 |
return _softmax([(max(lp.get(t, -1e9), -1e9) + b) / temps[kind] for t, b in zip(ids, bias)])
|
| 256 |
|
| 257 |
|
|
|
|
| 325 |
ntok = out.usage.completion_tokens if out.usage else None
|
| 326 |
read = user + [{"type": "text", "text": thought + S["prof"]["end_think"] + S["prof"]["after_think"] + "Answer: ("}]
|
| 327 |
ids = [t for _, t in labs]
|
| 328 |
+
lp = await _logprobs(ctx, read, True, ids, S["model"], allowed=_readout_allowed(ids))
|
| 329 |
p = _softmax([max(lp.get(t, -1e9), -1e9) for t in ids])
|
| 330 |
if kind == "noul":
|
| 331 |
p = [p[1], p[0]] # (A) yes / (B) no -> [P(false), P(true)]
|
|
|
|
| 379 |
parts, has_img = _parts(req.state)
|
| 380 |
ctx = Ctx(raw)
|
| 381 |
try:
|
| 382 |
+
if req.system2_only:
|
| 383 |
+
if req.kind == "score":
|
| 384 |
+
return _err("system2_only is supported for noul and choice")
|
| 385 |
+
p2, think = await s2_dist(ctx, req.kind, parts, has_img, req.question, opts, req.think_budget, req.return_reasoning, tmpl)
|
| 386 |
+
k = max(range(len(p2)), key=p2.__getitem__)
|
| 387 |
+
return {"kind": req.kind, "options": opts, "probabilities": p2, "choice_index": k, "choice": opts[k], "system": 2,
|
| 388 |
+
"model": S["model"], "thinking": {"used": True, "budget": req.think_budget, **think},
|
| 389 |
+
"usage": {"prompt_tokens": ctx.prompt_tokens, "completion_tokens": ctx.completion_tokens},
|
| 390 |
+
"elapsed_seconds": time.time() - t0, "num_model_requests": ctx.requests}
|
| 391 |
p1 = await s1_dist(ctx, req.kind, parts, has_img, req.question, opts, strategy)
|
| 392 |
probs, think = p1, None
|
| 393 |
if thinking == "on" or (thinking == "auto" and max(p1) < tau):
|