Text Generation
Transformers
Safetensors
English
qwen3_5_text
v-jev
jev
systemone
decision-model
qwen3.5
structured-output
classification
conversational
Instructions to use haohan84/v-jev with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use haohan84/v-jev with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="haohan84/v-jev") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("haohan84/v-jev") model = AutoModelForCausalLM.from_pretrained("haohan84/v-jev", device_map="auto") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=256) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use haohan84/v-jev with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "haohan84/v-jev" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "haohan84/v-jev", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/haohan84/v-jev
- SGLang
How to use haohan84/v-jev with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "haohan84/v-jev" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "haohan84/v-jev", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "haohan84/v-jev" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "haohan84/v-jev", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use haohan84/v-jev with Docker Model Runner:
docker model run hf.co/haohan84/v-jev
File size: 13,652 Bytes
c34d8e0 | 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 | """Suggested inference code for v-jev: a state and typed questions in, probabilities out, in one forward pass.
from inference import VJev
jev = VJev("base") # or "high-base"; the tokenizer is read from the repo root
answers = jev.ask(state, questions)
`state` is text or any JSON value. `questions` and `answers` follow the Jev request shape (POST /v1/systemone):
noul {"type": "noul", "instructions": ...} -> {"type": "noul", "noul": p(yes)}
choice {"type": "choice", "instructions": ..., "criteria": {id: text}} -> {"type": "choice", "probabilities": {id: p}}
score {"type": "score", "instructions": ..., "criteria": [levels]} -> {"type": "score", "probabilities": {"0": p, ...}}
A noul may also take "criteria": {"true": text, "false": text}. A score may take its levels as an object
{id: text}, lowest first; its answer is then keyed by those ids.
Each answer also carries "na": the model's probability that the question cannot be answered from the state.
It is read from its own logit with a sigmoid, as it was trained (a separate yes/no target), not from the
softmax over the answers.
Nothing is generated. All questions of one request go into one prompt with one <ans> placeholder each, and
the model's output at each placeholder is read over the answer tokens that the question allows.
A choice may have up to 256 options. One question holds at most 64, so a longer list is asked in rounds, in
extra passes: the options are shuffled into groups of 63 plus "none of these", each group's winner goes on,
and this repeats until at most 63 are left for a final question, which also offers "none of these". The
answer has the finalists' probabilities, renormalised to sum to 1 (options that did not reach the final get
0), and "none": the final question's probability of "none of these". When every group picked "none of
these", each group's best real option goes on and the answer also says "all_groups_none": true.
The state and the question texts are data: any added token of the tokenizer written out in them is broken up
before the prompt is built, so it stays text. That covers the answer tokens ("<ans>", "<opt_3>", ...) and the
backbone's own ("<think>", "<tool_call>", "<|im_end|>", ...). Every added token starts with "<" and none
contains a space, so a space after the "<" breaks it, and the inserted space cannot complete a new one.
The state is otherwise inserted as is: text in it that imitates the prompt's "Questions:" or "Answers:" lines
cannot add answer positions, but it can sway the answers.
Needs torch and transformers (5.x, with Qwen3.5 support).
"""
import json
import os
import random
import re
import warnings
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
OPT = [f"<opt_{i}>" for i in range(1, 65)] # choice: up to 64 options
LVL = [f"<lvl_{i}>" for i in range(10)] # score: up to 10 levels, lowest first
NOUL = ["<no>", "<yes>"]
ANS, NA = "<ans>", "<na>"
MAX_OPTIONS, GROUP, NONE = 256, 63, "none of these" # long lists: groups of 63 plus "none of these"
PER_PROMPT = 8 # group questions packed into one prompt, fewer when the prompt would be too long
TRAINED_TOKENS = 4608 # the longest prompt of training and the benchmarks; longer ones are untested
class TooLong(ValueError):
"""The prompt is over max_tokens."""
def tokens_and_spec(key, q):
"""The answer tokens of one question, and the question as the model sees it."""
if not isinstance(q, dict) or not isinstance(q.get("instructions"), str):
raise ValueError(f'question "{key}": needs "instructions" as text')
kind, c = q.get("type"), q.get("criteria")
spec = {"type": kind, "instructions": q["instructions"]}
if kind == "noul":
if c is not None:
if not isinstance(c, dict) or not {"true", "false"} <= set(c):
raise ValueError(f'question "{key}": a noul takes no "criteria", or an object with "true" and "false"')
spec["criteria"] = {"<no>": c["false"], "<yes>": c["true"]}
return NOUL, spec
if kind == "choice":
if not isinstance(c, dict) or not 2 <= len(c) <= MAX_OPTIONS:
raise ValueError(f'question "{key}": a choice needs "criteria" as an object of 2 to {MAX_OPTIONS} options')
if len(c) > len(OPT) and NONE in c:
raise ValueError(f'question "{key}": with more than {len(OPT)} options, none may be named "{NONE}"; the rounds add it')
spec["criteria"] = {t: f"{name}: {text}" if text else name for t, (name, text) in zip(OPT, c.items())}
return OPT[: len(c)], spec
if kind == "score":
levels = list(c.values()) if isinstance(c, dict) else c
if not isinstance(levels, list) or not 2 <= len(levels) <= len(LVL):
raise ValueError(f'question "{key}": a score needs "criteria" as a list (or object) of 2 to {len(LVL)} levels, lowest first')
spec["criteria"] = dict(zip(LVL, [str(x) for x in levels]))
return LVL[: len(levels)], spec
raise ValueError(f'question "{key}": "type" must be noul, choice or score')
class VJev:
def __init__(self, checkpoint="base", repo=None, device=None, dtype=torch.bfloat16, max_tokens=None):
"""`checkpoint`: "base" or "high-base", the subfolder that holds the weights and their config.
`repo`: the repo folder or Hugging Face repo ID; the default is the folder of this file. The tokenizer is
read from its root.
`device`: the GPU if there is one, else the CPU.
`max_tokens`: longer requests are refused. The default is the backbone's own context length; memory
usually runs out first. Training and the benchmarks used prompts of at most 4,608 tokens."""
repo = repo or os.path.dirname(os.path.abspath(__file__))
self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")
self.tok = AutoTokenizer.from_pretrained(repo)
self.special = re.compile("|".join(sorted(map(re.escape, self.tok.get_added_vocab()), key=len, reverse=True)))
# Loaded tensor by tensor straight to the device, so there is one copy of the weights, not two.
self.lm = AutoModelForCausalLM.from_pretrained(repo, subfolder=checkpoint, dtype=dtype, device_map=self.device).eval()
self.max_tokens = max_tokens or self.lm.config.max_position_embeddings
self.ans_id = self.tok.convert_tokens_to_ids(ANS)
self.token_id = {t: self.tok.convert_tokens_to_ids(t) for t in OPT + LVL + NOUL + [NA]}
def plain(self, text):
"""Text from the caller, with every added token in it broken up ("<ans>" -> "< ans>", "<|im_end|>" -> "< |im_end|>")."""
return self.special.sub(lambda m: m[0][0] + " " + m[0][1:], text)
def prompt(self, state, specs):
"""One chat turn: state and questions from the user, the answer lines from the model, thinking off."""
lines = ["State:", self.plain(state if isinstance(state, str) else json.dumps(state, ensure_ascii=False)), "", "Questions:"]
for i, s in enumerate(specs, 1):
s = {**s, "instructions": self.plain(s["instructions"])}
if "criteria" in s:
s["criteria"] = {t: self.plain(str(text)) for t, text in s["criteria"].items()}
lines.append(json.dumps({f"q{i}": s}, ensure_ascii=False))
head = self.tok.apply_chat_template([{"role": "user", "content": "\n".join(lines)}], tokenize=False, add_generation_prompt=True, enable_thinking=False)
return head + "Answers:\n" + "\n".join(f"q{i}: {ANS}" for i in range(1, len(specs) + 1))
@torch.no_grad()
def read(self, state, questions):
"""One prompt, one pass: per question, the probabilities of its answers in order, and its "na"."""
tokens, specs = zip(*(tokens_and_spec(k, q) for k, q in questions.items()))
ids = self.tok(self.prompt(state, specs), return_tensors="pt", add_special_tokens=False)["input_ids"].to(self.device)
if ids.shape[1] > self.max_tokens:
raise TooLong(f"the request is {ids.shape[1]} tokens; the limit is {self.max_tokens} (max_tokens). Send fewer questions per request or a shorter state.")
if ids.shape[1] > TRAINED_TOKENS:
warnings.warn(f"the request is {ids.shape[1]} tokens; training and the benchmarks used at most {TRAINED_TOKENS}, so the answers are untested at this length", stacklevel=2)
hidden = self.lm.model(input_ids=ids, use_cache=False).last_hidden_state[0] # one pass, nothing to cache
at = hidden[ids[0] == self.ans_id].float() # one row per question
if at.shape[0] != len(specs): # answers would be read from the wrong rows
raise RuntimeError(f"the prompt has {at.shape[0]} answer positions for {len(specs)} questions")
out_rows = self.lm.get_output_embeddings().weight
out = {}
for k, key in enumerate(questions):
z = at[k] @ out_rows[[self.token_id[t] for t in tokens[k]] + [self.token_id[NA]]].float().T
out[key] = (torch.softmax(z[:-1], -1).tolist(), torch.sigmoid(z[-1]).item())
return out
def read_many(self, state, asks):
"""Several questions about one state, PER_PROMPT to a prompt, fewer when a prompt would be too long."""
got, items, size = {}, list(asks.items()), PER_PROMPT
while items:
part = dict(items[:size])
try:
got.update(self.read(state, part))
except TooLong:
if size == 1:
raise TooLong(f"one group of a long choice ({GROUP} options) does not fit in max_tokens ({self.max_tokens}) with this state; shorten the state or the option texts, or raise max_tokens") from None
size = max(1, size // 2)
continue
items = items[size:]
return got
def rounds(self, state, q):
"""A choice with more than 64 options: (finalists' probabilities, the final "none of these" probability,
"na" of the final question, whether every group of some round picked "none of these")."""
rng = random.Random(0) # the grouping is random but the same on every call
left, c, all_none = list(q["criteria"]), q["criteria"], False
while len(left) > GROUP:
rng.shuffle(left)
groups = [left[s : s + GROUP] for s in range(0, len(left), GROUP)]
asks = {str(g): {"type": "choice", "instructions": q["instructions"], "criteria": {**{o: c[o] for o in group}, NONE: None}} for g, group in enumerate(groups)}
got = self.read_many(state, asks)
best = [max(range(len(group) + 1), key=lambda i: got[str(g)][0][i]) for g, group in enumerate(groups)] # index len(group) is "none of these"
left = [group[i] for group, i in zip(groups, best) if i < len(group)]
if not left: # no group found a fit: go on with each group's best real option, and say so
all_none = True
left = [group[max(range(len(group)), key=lambda i: got[str(g)][0][i])] for g, group in enumerate(groups)]
p, na = self.read_many(state, {"final": {"type": "choice", "instructions": q["instructions"], "criteria": {**{o: c[o] for o in left}, NONE: None}}})["final"]
real = sum(p[:-1])
return {o: x / real for o, x in zip(left, p)}, p[-1], na, all_none
def ask(self, state, questions):
for k in questions:
tokens_and_spec(k, questions[k]) # validate everything before the first pass
long = {k for k, q in questions.items() if q["type"] == "choice" and len(q["criteria"]) > len(OPT)}
short = {k: q for k, q in questions.items() if k not in long}
got = self.read(state, short) if short else {}
answers = {}
for key, q in questions.items():
if key in long:
finalists, none, na, all_none = self.rounds(state, q)
answers[key] = {"type": "choice", "probabilities": {o: finalists.get(o, 0.0) for o in q["criteria"]}, "none": none, "na": na}
if all_none:
answers[key]["all_groups_none"] = True
continue
p, na = got[key]
if q["type"] == "noul":
answers[key] = {"type": "noul", "noul": p[1], "na": na}
else:
names = list(q["criteria"]) if isinstance(q["criteria"], dict) else [str(i) for i in range(len(p))]
answers[key] = {"type": q["type"], "probabilities": dict(zip(names, p)), "na": na}
return answers
if __name__ == "__main__":
import sys
jev = VJev(sys.argv[1] if len(sys.argv) > 1 else "base")
state = "Customer writes: Our nightly export has failed three nights in a row and month-end close is on Friday. If this is not fixed by Thursday we will have to look at other vendors."
questions = {
"export_failing": {"type": "noul", "instructions": "Is a scheduled data export currently failing?"},
"refund_asked": {"type": "noul", "instructions": "Does the customer ask for a refund?"},
"team": {"type": "choice", "instructions": "Which team should own the issue?",
"criteria": {"engineering": "Fixes broken product functionality", "billing": "Handles invoices, charges and refunds", "sales": "Handles plans, upgrades and renewals"}},
"churn_risk": {"type": "score", "instructions": "How likely is the customer to leave?",
"criteria": ["No sign of leaving", "Mild dissatisfaction", "Mentions alternatives", "States an intent to leave"]},
}
print(json.dumps(jev.ask(state, questions), indent=2))
|