Image-Text-to-Text
MLX
Safetensors
qwen3_5
decision-model
decision-making
calibration
jevbench
apple-silicon
conversational
8-bit precision
Instructions to use netprojects/h2o-lightning-4b-mlx-q8 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use netprojects/h2o-lightning-4b-mlx-q8 with MLX:
# Make sure mlx-vlm is installed # pip install --upgrade mlx-vlm from mlx_vlm import load, generate from mlx_vlm.prompt_utils import apply_chat_template from mlx_vlm.utils import load_config # Load the model model, processor = load("netprojects/h2o-lightning-4b-mlx-q8") config = load_config("netprojects/h2o-lightning-4b-mlx-q8") # Prepare input image = ["http://images.cocodataset.org/val2017/000000039769.jpg"] prompt = "Describe this image." # Apply chat template formatted_prompt = apply_chat_template( processor, config, prompt, num_images=1 ) # Generate output output = generate(model, processor, formatted_prompt, image) print(output) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Pi
How to use netprojects/h2o-lightning-4b-mlx-q8 with Pi:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "netprojects/h2o-lightning-4b-mlx-q8"
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "mlx-lm": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "netprojects/h2o-lightning-4b-mlx-q8" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Hermes Agent
How to use netprojects/h2o-lightning-4b-mlx-q8 with Hermes Agent:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "netprojects/h2o-lightning-4b-mlx-q8"
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default netprojects/h2o-lightning-4b-mlx-q8
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use netprojects/h2o-lightning-4b-mlx-q8 with OpenClaw:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "netprojects/h2o-lightning-4b-mlx-q8"
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "netprojects/h2o-lightning-4b-mlx-q8" \ --custom-provider-id mlx-lm \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
H2O v1.4.2 serving files (weights unchanged)
Browse files- .h2o_revision +1 -1
- h2o_lightning_shim.py +134 -16
- serve_config.json +3 -3
.h2o_revision
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
|
|
|
|
| 1 |
+
708e93926a034195d908e033fcb796c55398a6f5
|
h2o_lightning_shim.py
CHANGED
|
@@ -94,6 +94,15 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
| 94 |
|
| 95 |
SHIM_VERSION = "h2o-lightning-shim"
|
| 96 |
PRIMITIVES = ("choice", "noul", "score")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
EFFORT_TIERS = ("fast", "balanced", "thorough")
|
| 98 |
# vLLM 0.30 accepts at most 128 `logprob_token_ids` per request; a question with more options is read in chunks
|
| 99 |
LOGPROB_CHUNK = int(os.environ.get("SHIM_LOGPROB_CHUNK", "128"))
|
|
@@ -765,6 +774,13 @@ def format_answer(typ, names, descs, logprobs, temperature, noul_floor, threshol
|
|
| 765 |
}
|
| 766 |
|
| 767 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 768 |
MODES = ("causal", "bidirectional")
|
| 769 |
MODE_ALIASES = {"causal": "causal", "one-way": "causal", "one_way": "causal", "oneway": "causal",
|
| 770 |
"bidirectional": "bidirectional", "two-way": "bidirectional", "two_way": "bidirectional",
|
|
@@ -958,6 +974,10 @@ class Contract:
|
|
| 958 |
self.temperature_source = "SHIM_TEMPERATURE" if t_env else "serve_config.json"
|
| 959 |
self.noul_floor = float(f_env) if f_env else float(cfg["noul_floor"])
|
| 960 |
self.noul_floor_source = "SHIM_NOUL_FLOOR" if f_env else "serve_config.json"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 961 |
if not all(0.05 <= t <= 20.0 for t in self.temperature_by_type.values()):
|
| 962 |
raise ValueError(f"temperature {self.temperature_by_type} is outside [0.05, 20]")
|
| 963 |
if self.noul_floor and not 0.5 < self.noul_floor < 1.0:
|
|
@@ -1022,18 +1042,20 @@ class Contract:
|
|
| 1022 |
tw = cfg.get("two_way")
|
| 1023 |
self.two_way = TwoWay(tw, self, cfg.get("_config_dir"), env) if isinstance(tw, dict) else None
|
| 1024 |
|
| 1025 |
-
def question_parts(self, state_text, q, qid="q"):
|
| 1026 |
-
"""(user text, option names, descriptions, labels) for one question
|
|
|
|
| 1027 |
names, descs = options_of(q, len(self.labels))
|
| 1028 |
if len(names) > len(self.labels):
|
| 1029 |
raise Unprocessable(f"question {qid}: {len(names)} options exceeds {len(self.labels)}")
|
| 1030 |
labels = self.labels[:len(names)]
|
| 1031 |
instr = render_instr(q.get("instructions", "")).lstrip() or self.default_instructions
|
| 1032 |
-
|
|
|
|
| 1033 |
|
| 1034 |
-
def question_prompt(self, state_text, q, qid="q"):
|
| 1035 |
"""(prompt text, option names, descriptions, labels) for one question."""
|
| 1036 |
-
user, names, descs, labels = self.question_parts(state_text, q, qid)
|
| 1037 |
return render_chat(self.system, user, self.prefill, self.chat), names, descs, labels
|
| 1038 |
|
| 1039 |
def image_prompt(self, state_text, q, image_urls, qid="q"):
|
|
@@ -1400,9 +1422,16 @@ class Shim:
|
|
| 1400 |
return self.c.two_way.default if self.v is not None else "bidirectional"
|
| 1401 |
|
| 1402 |
def mode(self, body):
|
| 1403 |
-
"""The attention mode of a request: its `attention_mode`
|
| 1404 |
-
|
| 1405 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1406 |
m = _mode_of(asked, self.default_mode())
|
| 1407 |
if self.two_way_flag:
|
| 1408 |
return m # one engine serves both through the per-request flag
|
|
@@ -1907,17 +1936,102 @@ class Shim:
|
|
| 1907 |
if not all(isinstance(t, list) for t in toks):
|
| 1908 |
toks = [self.t.tokenize(w[4]) for w in work]
|
| 1909 |
shared = common_prefix_len(toks)
|
| 1910 |
-
|
| 1911 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1912 |
|
| 1913 |
def fit_request(self, raw_state, full_txt, questions, n_images=0):
|
| 1914 |
"""[(record text, user-turn override) per question], notes: selector headtail cuts the record once (v1.4.1);
|
| 1915 |
selector v2 fits each question to the context (fit_states)."""
|
| 1916 |
if self.c.selector == "headtail":
|
| 1917 |
st = self.truncate_state(full_txt)
|
| 1918 |
-
|
|
|
|
| 1919 |
return self.fit_states(raw_state, full_txt, questions, n_images)
|
| 1920 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1921 |
def full_input(self, prompts, image_tokens=0):
|
| 1922 |
"""Input tokens as if the whole record had been read: the full prompts, their shared head once, plus the
|
| 1923 |
image tokens (BILLING = THE FULL INPUT: a cut record gains speed, not a lower count)."""
|
|
@@ -2036,6 +2150,7 @@ class Shim:
|
|
| 2036 |
"noul_floor": {"value": c.noul_floor, "source": c.noul_floor_source},
|
| 2037 |
"temperature_by_type": dict(c.temperature_by_type),
|
| 2038 |
"temperature_source": c.temperature_source, "max_options": len(c.labels),
|
|
|
|
| 2039 |
"vllm_max_model_len": self.max_model_len,
|
| 2040 |
**({"images": {"max_images": c.max_images, "max_pixels": c.max_pixels or None,
|
| 2041 |
"temperature": c.image_temperature, "verified": bool(self.chat_ok)}}
|
|
@@ -2225,11 +2340,14 @@ def make_handler(shim):
|
|
| 2225 |
|
| 2226 |
def _send(self, code, obj):
|
| 2227 |
payload = json.dumps(obj).encode()
|
| 2228 |
-
|
| 2229 |
-
|
| 2230 |
-
|
| 2231 |
-
|
| 2232 |
-
|
|
|
|
|
|
|
|
|
|
| 2233 |
|
| 2234 |
def do_GET(self):
|
| 2235 |
if self.path.startswith("/health"):
|
|
|
|
| 94 |
|
| 95 |
SHIM_VERSION = "h2o-lightning-shim"
|
| 96 |
PRIMITIVES = ("choice", "noul", "score")
|
| 97 |
+
# THE DI PROFILE'S CONDITIONAL RE-READ (off unless SHIM_REREAD_BELOW is set; serve.sh PROFILE=di sets 0.8). A text
|
| 98 |
+
# question whose served top probability is under the threshold (a yes/no answer's max(P, 1 - P) after the floor; PROFILE=di
|
| 99 |
+
# pins the floor at 0.801, so there yes/no questions are read once; with the floor off they would be eligible) is read once more in the same attention mode with the user turn written
|
| 100 |
+
# twice, the second copy after REREAD_SEP, and is answered with the 2:1 first-weighted mean of the two reads'
|
| 101 |
+
# probability vectors (REREAD_FIRST_WEIGHT; chosen on held-out data after the plain-mean rule passed). The re-read is
|
| 102 |
+
# skipped (the first answer stands) when the question's own text was cut to fit, the doubled prompt would not fit the
|
| 103 |
+
# engine's context, or the engine refuses it: a request never fails because of the re-read. Images are read once.
|
| 104 |
+
REREAD_SEP = "\n\nRead the same input again before answering:\n"
|
| 105 |
+
REREAD_FIRST_WEIGHT = 2.0 / 3.0
|
| 106 |
EFFORT_TIERS = ("fast", "balanced", "thorough")
|
| 107 |
# vLLM 0.30 accepts at most 128 `logprob_token_ids` per request; a question with more options is read in chunks
|
| 108 |
LOGPROB_CHUNK = int(os.environ.get("SHIM_LOGPROB_CHUNK", "128"))
|
|
|
|
| 774 |
}
|
| 775 |
|
| 776 |
|
| 777 |
+
|
| 778 |
+
def served_top(answer):
|
| 779 |
+
"""An answer's top probability as served: a yes/no answer's max(P, 1 - P) (after the floor), else its top option."""
|
| 780 |
+
if answer["type"] == "noul":
|
| 781 |
+
return max(answer["noul"], 1.0 - answer["noul"])
|
| 782 |
+
return max(answer["probabilities"].values())
|
| 783 |
+
|
| 784 |
MODES = ("causal", "bidirectional")
|
| 785 |
MODE_ALIASES = {"causal": "causal", "one-way": "causal", "one_way": "causal", "oneway": "causal",
|
| 786 |
"bidirectional": "bidirectional", "two-way": "bidirectional", "two_way": "bidirectional",
|
|
|
|
| 974 |
self.temperature_source = "SHIM_TEMPERATURE" if t_env else "serve_config.json"
|
| 975 |
self.noul_floor = float(f_env) if f_env else float(cfg["noul_floor"])
|
| 976 |
self.noul_floor_source = "SHIM_NOUL_FLOOR" if f_env else "serve_config.json"
|
| 977 |
+
rb = env.get("SHIM_REREAD_BELOW", "").strip()
|
| 978 |
+
self.reread_below = float(rb) if rb else None
|
| 979 |
+
if self.reread_below is not None and not 0.0 < self.reread_below <= 1.0:
|
| 980 |
+
raise ValueError(f"SHIM_REREAD_BELOW={rb!r}: a probability in (0, 1], or unset (read once)")
|
| 981 |
if not all(0.05 <= t <= 20.0 for t in self.temperature_by_type.values()):
|
| 982 |
raise ValueError(f"temperature {self.temperature_by_type} is outside [0.05, 20]")
|
| 983 |
if self.noul_floor and not 0.5 < self.noul_floor < 1.0:
|
|
|
|
| 1042 |
tw = cfg.get("two_way")
|
| 1043 |
self.two_way = TwoWay(tw, self, cfg.get("_config_dir"), env) if isinstance(tw, dict) else None
|
| 1044 |
|
| 1045 |
+
def question_parts(self, state_text, q, qid="q", twice=False):
|
| 1046 |
+
"""(user text, option names, descriptions, labels) for one question; `twice`: the user turn written again after
|
| 1047 |
+
REREAD_SEP (the DI profile's re-read)."""
|
| 1048 |
names, descs = options_of(q, len(self.labels))
|
| 1049 |
if len(names) > len(self.labels):
|
| 1050 |
raise Unprocessable(f"question {qid}: {len(names)} options exceeds {len(self.labels)}")
|
| 1051 |
labels = self.labels[:len(names)]
|
| 1052 |
instr = render_instr(q.get("instructions", "")).lstrip() or self.default_instructions
|
| 1053 |
+
user = user_turn(state_text, instr, names, descs, labels, self.layout)
|
| 1054 |
+
return (user + REREAD_SEP + user if twice else user), names, descs, labels
|
| 1055 |
|
| 1056 |
+
def question_prompt(self, state_text, q, qid="q", twice=False):
|
| 1057 |
"""(prompt text, option names, descriptions, labels) for one question."""
|
| 1058 |
+
user, names, descs, labels = self.question_parts(state_text, q, qid, twice)
|
| 1059 |
return render_chat(self.system, user, self.prefill, self.chat), names, descs, labels
|
| 1060 |
|
| 1061 |
def image_prompt(self, state_text, q, image_urls, qid="q"):
|
|
|
|
| 1422 |
return self.c.two_way.default if self.v is not None else "bidirectional"
|
| 1423 |
|
| 1424 |
def mode(self, body):
|
| 1425 |
+
"""The attention mode of a request: its `attention_mode` (top level, or `kwargs.attention_mode`, the form the
|
| 1426 |
+
hosted gateway and the 31B package take; given in both with different values is a 400, as the gateway does),
|
| 1427 |
+
else the deployment's default. A mode whose engine is not running is a 400 (never a silent answer in the other
|
| 1428 |
+
mode)."""
|
| 1429 |
+
asked = None
|
| 1430 |
+
if isinstance(body, dict):
|
| 1431 |
+
kw = body.get("kwargs") if isinstance(body.get("kwargs"), dict) else {}
|
| 1432 |
+
if "attention_mode" in body and "attention_mode" in kw and body["attention_mode"] != kw["attention_mode"]:
|
| 1433 |
+
raise BadRequest("attention_mode given twice with different values (top level and kwargs)")
|
| 1434 |
+
asked = body.get("attention_mode", kw.get("attention_mode"))
|
| 1435 |
m = _mode_of(asked, self.default_mode())
|
| 1436 |
if self.two_way_flag:
|
| 1437 |
return m # one engine serves both through the per-request flag
|
|
|
|
| 1936 |
if not all(isinstance(t, list) for t in toks):
|
| 1937 |
toks = [self.t.tokenize(w[4]) for w in work]
|
| 1938 |
shared = common_prefix_len(toks)
|
| 1939 |
+
if self.c.reread_below is None:
|
| 1940 |
+
return self.respond(work, reads, shared, asked, t0, self.temperatures(mode), full_input=full, mode=mode,
|
| 1941 |
+
notes=notes)
|
| 1942 |
+
rereads, extra = self.reread(work, reads, questions, fits, read, mode)
|
| 1943 |
+
out = self.respond(work, reads, shared, asked, t0, self.temperatures(mode), full_input=full, mode=mode,
|
| 1944 |
+
notes=notes)
|
| 1945 |
+
out["usage"]["input_tokens"] += extra
|
| 1946 |
+
out["usage"]["processed_input_tokens"] += extra
|
| 1947 |
+
out["effort_used"]["reread"] = {"below": self.c.reread_below, "first_weight": round(REREAD_FIRST_WEIGHT, 4), "questions": rereads}
|
| 1948 |
+
return out
|
| 1949 |
+
|
| 1950 |
+
def reread(self, work, reads, questions, fits, read, mode):
|
| 1951 |
+
"""The DI profile's conditional re-read (REREAD_SEP): each text question whose SERVED first-read top
|
| 1952 |
+
probability (the measured rule: a choice's or score's top option; a yes/no answer's max(P, 1 - P) after the
|
| 1953 |
+
floor; PROFILE=di pins the floor at 0.801, so there yes/no questions are read once; with the floor off they
|
| 1954 |
+
would be eligible) is under c.reread_below is
|
| 1955 |
+
read again, in the same mode, with its user turn written twice, and is answered with the 2:1 first-weighted mean
|
| 1956 |
+
of the two reads' probability vectors at the mode's temperature (REREAD_FIRST_WEIGHT). `reads` takes, in place
|
| 1957 |
+
of the first read's log-probabilities, T * log(mean), whose softmax at T is that mean. Returns (questions
|
| 1958 |
+
re-read, the re-reads' prompt tokens). A question whose own text was cut to fit, a doubled prompt over the
|
| 1959 |
+
engine's context, or one the engine refuses keeps the first read: a request never fails because of the re-read."""
|
| 1960 |
+
t_by, _it, floor = self.temperatures(mode)
|
| 1961 |
+
todo = []
|
| 1962 |
+
for i, ((qid, typ, names, descs, _text, labels, thr), (lp, _u), (state_txt, user)) in enumerate(zip(work, reads, fits)):
|
| 1963 |
+
t = t_by[typ] if isinstance(t_by, dict) else t_by
|
| 1964 |
+
if user is not None or served_top(format_answer(typ, names, descs, lp, t, floor, thr)) >= self.c.reread_below:
|
| 1965 |
+
continue
|
| 1966 |
+
todo.append((i, t, self.c.question_prompt(state_txt, questions[qid], qid, twice=True)[0], labels))
|
| 1967 |
+
if not todo:
|
| 1968 |
+
return 0, 0
|
| 1969 |
+
if isinstance(self.max_model_len, int):
|
| 1970 |
+
todo = [x for x in todo if len(self.t.tokenize(x[2])) + 8 <= self.max_model_len]
|
| 1971 |
+
|
| 1972 |
+
def again(text, labels):
|
| 1973 |
+
try:
|
| 1974 |
+
return read(text, labels, None)
|
| 1975 |
+
except (UpstreamError, Unprocessable):
|
| 1976 |
+
return None
|
| 1977 |
+
futs = [(i, t, self.pool.submit(again, text, labels)) for i, t, text, labels in todo]
|
| 1978 |
+
n, extra = 0, 0
|
| 1979 |
+
for i, t, f in futs:
|
| 1980 |
+
r = f.result()
|
| 1981 |
+
if r is None:
|
| 1982 |
+
continue
|
| 1983 |
+
p1, p2 = probabilities(reads[i][0], t), probabilities(r[0], t)
|
| 1984 |
+
reads[i] = ([t * math.log(max(REREAD_FIRST_WEIGHT * a + (1.0 - REREAD_FIRST_WEIGHT) * b, 1e-300))
|
| 1985 |
+
for a, b in zip(p1, p2)], reads[i][1])
|
| 1986 |
+
extra += int(r[1].get("prompt_tokens") or 0)
|
| 1987 |
+
n += 1
|
| 1988 |
+
return n, extra
|
| 1989 |
|
| 1990 |
def fit_request(self, raw_state, full_txt, questions, n_images=0):
|
| 1991 |
"""[(record text, user-turn override) per question], notes: selector headtail cuts the record once (v1.4.1);
|
| 1992 |
selector v2 fits each question to the context (fit_states)."""
|
| 1993 |
if self.c.selector == "headtail":
|
| 1994 |
st = self.truncate_state(full_txt)
|
| 1995 |
+
notes = ["head+tail (selector headtail)"] if st != full_txt else []
|
| 1996 |
+
return self.fit_headtail(full_txt, st, questions, n_images, notes), notes
|
| 1997 |
return self.fit_states(raw_state, full_txt, questions, n_images)
|
| 1998 |
|
| 1999 |
+
def fit_headtail(self, full_txt, st, questions, n_images, notes):
|
| 2000 |
+
"""Selector headtail's per-question CONTEXT CLAMP: a question whose prompt with the head+tail record `st` fits
|
| 2001 |
+
vLLM's context reads it as before; one that does not (SHIM_MAX_STATE_TOKENS near the context with a long
|
| 2002 |
+
question) gets the record cut by head and tail to the room its own prompt leaves, the same per-question budget
|
| 2003 |
+
selector v2 uses (max_model_len less the prompt without the record, the images' bound and CONTEXT_SLACK); a
|
| 2004 |
+
question that does not fit even without the record is answered without it (fit_question). Never a 422 for
|
| 2005 |
+
length."""
|
| 2006 |
+
c, C = self.c, self.max_model_len
|
| 2007 |
+
img = n_images * self.image_tokens_bound()
|
| 2008 |
+
limit = (C - CONTEXT_SLACK) if isinstance(C, int) and C > 0 else None
|
| 2009 |
+
out = []
|
| 2010 |
+
for qid, q in questions.items():
|
| 2011 |
+
text = c.question_prompt(st, q, qid)[0]
|
| 2012 |
+
if limit is None or len(text.encode("utf-8", "ignore")) + img <= limit or len(self.t.tokenize(text)) + img <= limit:
|
| 2013 |
+
out.append((st, None))
|
| 2014 |
+
continue
|
| 2015 |
+
room = limit - len(self.t.tokenize(c.question_prompt("", q, qid)[0])) - img
|
| 2016 |
+
if room < MIN_STATE_BUDGET:
|
| 2017 |
+
notes.append("question over the context; record left out")
|
| 2018 |
+
out.append(("", self.fit_question(q, qid, limit, img, notes)))
|
| 2019 |
+
continue
|
| 2020 |
+
budget = min(room, c.max_state_tokens)
|
| 2021 |
+
cut = self.cut_text(full_txt, budget)
|
| 2022 |
+
for _ in range(3): # the exact count (the record's seams with the prompt can add a token)
|
| 2023 |
+
n = len(self.t.tokenize(c.question_prompt(cut, q, qid)[0])) + img
|
| 2024 |
+
if n <= limit:
|
| 2025 |
+
break
|
| 2026 |
+
budget -= (n - limit) + 8
|
| 2027 |
+
if budget < MIN_STATE_BUDGET:
|
| 2028 |
+
cut = ""
|
| 2029 |
+
break
|
| 2030 |
+
cut = self.cut_text(full_txt, budget)
|
| 2031 |
+
notes.append("head+tail cut to the context")
|
| 2032 |
+
out.append((cut, None))
|
| 2033 |
+
return out
|
| 2034 |
+
|
| 2035 |
def full_input(self, prompts, image_tokens=0):
|
| 2036 |
"""Input tokens as if the whole record had been read: the full prompts, their shared head once, plus the
|
| 2037 |
image tokens (BILLING = THE FULL INPUT: a cut record gains speed, not a lower count)."""
|
|
|
|
| 2150 |
"noul_floor": {"value": c.noul_floor, "source": c.noul_floor_source},
|
| 2151 |
"temperature_by_type": dict(c.temperature_by_type),
|
| 2152 |
"temperature_source": c.temperature_source, "max_options": len(c.labels),
|
| 2153 |
+
**({"reread_below": c.reread_below} if c.reread_below is not None else {}),
|
| 2154 |
"vllm_max_model_len": self.max_model_len,
|
| 2155 |
**({"images": {"max_images": c.max_images, "max_pixels": c.max_pixels or None,
|
| 2156 |
"temperature": c.image_temperature, "verified": bool(self.chat_ok)}}
|
|
|
|
| 2340 |
|
| 2341 |
def _send(self, code, obj):
|
| 2342 |
payload = json.dumps(obj).encode()
|
| 2343 |
+
try:
|
| 2344 |
+
self.send_response(code)
|
| 2345 |
+
self.send_header("Content-Type", "application/json")
|
| 2346 |
+
self.send_header("Content-Length", str(len(payload)))
|
| 2347 |
+
self.end_headers()
|
| 2348 |
+
self.wfile.write(payload)
|
| 2349 |
+
except (BrokenPipeError, ConnectionResetError): # the client left first (its own deadline): nobody to answer
|
| 2350 |
+
self.close_connection = True
|
| 2351 |
|
| 2352 |
def do_GET(self):
|
| 2353 |
if self.path.startswith("/health"):
|
serve_config.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
| 16 |
"noul": 0.8,
|
| 17 |
"score": 0.65
|
| 18 |
},
|
| 19 |
-
"noul_floor": 0
|
| 20 |
"prompt": {
|
| 21 |
"chat_template": true,
|
| 22 |
"enable_thinking": false,
|
|
@@ -286,7 +286,7 @@
|
|
| 286 |
"max_options": 255,
|
| 287 |
"image": {
|
| 288 |
"max_images": 4,
|
| 289 |
-
"max_pixels":
|
| 290 |
"temperature": 1.0
|
| 291 |
},
|
| 292 |
"two_way": {
|
|
@@ -297,7 +297,7 @@
|
|
| 297 |
"noul": 0.8,
|
| 298 |
"score": 0.65
|
| 299 |
},
|
| 300 |
-
"noul_floor": 0
|
| 301 |
"image_temperature": 1.0,
|
| 302 |
"probe_tolerance": 0.05,
|
| 303 |
"probe_reference": [
|
|
|
|
| 16 |
"noul": 0.8,
|
| 17 |
"score": 0.65
|
| 18 |
},
|
| 19 |
+
"noul_floor": 0,
|
| 20 |
"prompt": {
|
| 21 |
"chat_template": true,
|
| 22 |
"enable_thinking": false,
|
|
|
|
| 286 |
"max_options": 255,
|
| 287 |
"image": {
|
| 288 |
"max_images": 4,
|
| 289 |
+
"max_pixels": 1638400,
|
| 290 |
"temperature": 1.0
|
| 291 |
},
|
| 292 |
"two_way": {
|
|
|
|
| 297 |
"noul": 0.8,
|
| 298 |
"score": 0.65
|
| 299 |
},
|
| 300 |
+
"noul_floor": 0,
|
| 301 |
"image_temperature": 1.0,
|
| 302 |
"probe_tolerance": 0.05,
|
| 303 |
"probe_reference": [
|