Paris99 commited on
Commit
dcbe8a2
·
verified ·
1 Parent(s): 332c75e

H2O v1.4.2 serving files (weights unchanged)

Browse files
Files changed (3) hide show
  1. .h2o_revision +1 -1
  2. h2o_lightning_shim.py +134 -16
  3. serve_config.json +3 -3
.h2o_revision CHANGED
@@ -1 +1 @@
1
- d7f1b05aa0b4e789b3896116f309ef842440e260
 
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
- return user_turn(state_text, instr, names, descs, labels, self.layout), names, descs, labels
 
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`, else the deployment's default. A mode whose engine
1404
- is not running is a 400 (never a silent answer in the other mode)."""
1405
- asked = body.get("attention_mode") if isinstance(body, dict) else None
 
 
 
 
 
 
 
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
- return self.respond(work, reads, shared, asked, t0, self.temperatures(mode), full_input=full, mode=mode,
1911
- notes=notes)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- return [(st, None)] * len(questions), (["head+tail (selector headtail)"] if st != full_txt else [])
 
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
- self.send_response(code)
2229
- self.send_header("Content-Type", "application/json")
2230
- self.send_header("Content-Length", str(len(payload)))
2231
- self.end_headers()
2232
- self.wfile.write(payload)
 
 
 
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.801,
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": 1048576,
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.801,
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": [