mirror of
https://github.com/Niko1221/Strata.git
synced 2026-11-07 06:15:58 -10:00
serve: repeat_penalty / repeat_last_n (llama.cpp's names) for the config's sampling block and for requests (#1819)
This commit is contained in:
1 parent
2ea10c2e9a
commit
70ac6dd41f
3 files changed
+48
-3
No files matched your search
+1
-1
@@ -1198,7 +1198,7 @@ engine arguments. The run config's optional `sampling` block sets the defaults f
|
||||
(`"sampling": {"temperature": 1.0, "top_p": 0.95, "top_k": 20}`); a request's own fields always win, and with no
|
||||
block at all a request without sampling keys decodes greedy. The penalties (`presence_penalty`, `frequency_penalty`,
|
||||
`repetition_penalty`, with `penalty_last_n` capping how many recent tokens they count over, default 64 when any
|
||||
penalty is set) ride the same path; they count the tokens the request has consumed, so a repetition penalty
|
||||
penalty is set; llama.cpp's names `repeat_penalty` and `repeat_last_n` work too, in the config's `sampling` block and in a request, #1819) ride the same path; they count the tokens the request has consumed, so a repetition penalty
|
||||
suppresses what the model itself just said, not the prompt alone. Since engine 0.1.19 they apply to every token
|
||||
the speculative decoding checks at once, exactly as if it decoded one token at a time (before, only the first of
|
||||
each batch got them). That makes requests with penalties 1-11% slower than in 0.1.18: the draft layer guesses
|
||||
|
||||
+21
-2
@@ -5860,15 +5860,28 @@ def lift_reasoning_effort_arg(cfg: dict) -> None:
|
||||
f"{cfg['reasoning_effort']!r}", flush=True)
|
||||
|
||||
|
||||
# #1819: llama.cpp clients call the repetition penalty and its window repeat_penalty / repeat_last_n; they are the
|
||||
# same settings as repetition_penalty / penalty_last_n (the /props answer already reports them under those names)
|
||||
SAMPLING_ALIASES = {"repeat_penalty": "repetition_penalty", "repeat_last_n": "penalty_last_n"}
|
||||
|
||||
|
||||
def sampling_defaults_from_config(cfg: dict) -> dict:
|
||||
"""The run config's optional `sampling` block: defaults for the sampling fields a request leaves out, so
|
||||
a plain client gets configured sampling instead of greedy. Supported: temperature, top_p, top_k, min_p,
|
||||
presence_penalty, repetition_penalty, frequency_penalty, penalty_last_n, seed. The request's own fields
|
||||
presence_penalty, repetition_penalty (also written repeat_penalty), frequency_penalty, penalty_last_n (also
|
||||
repeat_last_n), seed. The request's own fields
|
||||
always win - an explicit temperature=0 still means greedy, a field set to null falls back to the default.
|
||||
A bad value refuses to start the server (a typo'd config should not quietly change sampling); unknown keys
|
||||
are named at startup and ignored."""
|
||||
out = {}
|
||||
for key, value in (cfg.get("sampling") or {}).items():
|
||||
block = dict(cfg.get("sampling") or {})
|
||||
for alias, canon in SAMPLING_ALIASES.items(): # #1819: llama.cpp's names for the same two fields
|
||||
if block.get(alias) is not None:
|
||||
if block.get(canon) is not None:
|
||||
raise SystemExit(f"[strata] config sampling: both {alias!r} and {canon!r} are set; they are the same "
|
||||
f"setting, keep one")
|
||||
block[canon] = block.pop(alias)
|
||||
for key, value in block.items():
|
||||
if value is None:
|
||||
continue
|
||||
number = isinstance(value, (int, float)) and not isinstance(value, bool)
|
||||
@@ -5968,6 +5981,12 @@ def check_request_sampling(req) -> None:
|
||||
types are checked: a number (int or float, never bool) for temperature, top_p, min_p and the three penalties; an
|
||||
integer (never bool or float) for top_k, penalty_last_n and seed. Ranges behave as before (a temperature <= 0 is
|
||||
greedy, top_k 0 or above 64 is clamped, a seed <= 0 is unset), and an absent or null field is not checked."""
|
||||
if isinstance(req, dict): # #1819: llama.cpp's names; the OpenAI-style field wins when both are sent
|
||||
if req.get("repeat_penalty") is not None and req.get("repetition_penalty") is None:
|
||||
req["repetition_penalty"] = req["repeat_penalty"]
|
||||
rl = req.get("repeat_last_n")
|
||||
if req.get("penalty_last_n") is None and isinstance(rl, int) and not isinstance(rl, bool) and rl > 0:
|
||||
req["penalty_last_n"] = rl # 0 and -1 (off / the whole context) keep the engine's default window
|
||||
for key, integer in (("temperature", False), ("top_p", False), ("min_p", False), ("presence_penalty", False),
|
||||
("frequency_penalty", False), ("repetition_penalty", False), ("top_k", True),
|
||||
("penalty_last_n", True), ("seed", True)):
|
||||
|
||||
@@ -130,6 +130,32 @@ class Hardening(unittest.TestCase):
|
||||
"max_tokens": 8, "temperature": "0.7", "top_k": "40"})
|
||||
self.assertEqual(code, 200, out)
|
||||
|
||||
def test_llama_cpp_names_for_the_repetition_penalty(self):
|
||||
"""#1819: repeat_penalty / repeat_last_n are repetition_penalty / penalty_last_n; the OpenAI-style name wins."""
|
||||
from serve.server import SAMPLING_ALIASES, check_request_sampling, sampling_defaults_from_config
|
||||
req = {"repeat_penalty": 1.1, "repeat_last_n": 128}
|
||||
check_request_sampling(req)
|
||||
self.assertEqual((req["repetition_penalty"], req["penalty_last_n"]), (1.1, 128))
|
||||
req = {"repeat_penalty": 1.3, "repetition_penalty": 1.05, "repeat_last_n": 9, "penalty_last_n": 32}
|
||||
check_request_sampling(req)
|
||||
self.assertEqual((req["repetition_penalty"], req["penalty_last_n"]), (1.05, 32))
|
||||
req = {"repeat_last_n": 0} # off / -1: the engine's default window, nothing is set
|
||||
check_request_sampling(req)
|
||||
self.assertNotIn("penalty_last_n", req)
|
||||
with self.assertRaises(ValueError):
|
||||
check_request_sampling({"repeat_penalty": "abc"})
|
||||
self.assertEqual(sampling_defaults_from_config({"sampling": {"repeat_penalty": 1.1, "repeat_last_n": 256}}),
|
||||
{"repetition_penalty": 1.1, "penalty_last_n": 256})
|
||||
self.assertEqual(sampling_defaults_from_config({"sampling": {"temperature": 0.6}}), {"temperature": 0.6})
|
||||
with self.assertRaises(SystemExit):
|
||||
sampling_defaults_from_config({"sampling": {"repeat_penalty": 1.1, "repetition_penalty": 1.2}})
|
||||
with self.assertRaises(SystemExit):
|
||||
sampling_defaults_from_config({"sampling": {"repeat_penalty": -1}})
|
||||
self.assertEqual(set(SAMPLING_ALIASES.values()), {"repetition_penalty", "penalty_last_n"})
|
||||
code, out = self.post("/v1/chat/completions", {"messages": [{"role": "user", "content": "hi"}],
|
||||
"max_tokens": 8, "repeat_penalty": 1.1, "repeat_last_n": 64})
|
||||
self.assertEqual(code, 200, out)
|
||||
|
||||
def test_in_type_values_and_nulls_still_answer(self):
|
||||
chat = {"messages": [{"role": "user", "content": "hi"}], "max_tokens": 8, "temperature": 0, "top_k": 0,
|
||||
"seed": 7}
|
||||
|
||||
Reference in new issue
Block a user