Skip to content

Commit 5b66acb

Browse files
committed
server : reset the prompt counters when a recompute park restarts a prefill
(cherry picked from commit 5356710)
1 parent d8daa8f commit 5b66acb

2 files changed

Lines changed: 44 additions & 0 deletions

File tree

tools/server/server-context.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,10 @@ struct server_slot {
593593
if (preempt_tokens.empty()) {
594594
state = state_before_preempt; // its prompt had not been processed yet, so it is processed again from the start
595595

596+
// the park dropped the cells the prompt step had already filled, and the restart does not pass through SLOT_STATE_STARTED, where these two are set: left alone they would count the dropped prefix a second time
597+
stats.n_prompt_cached = 0;
598+
stats.n_prompt_processed = 0;
599+
596600
return true;
597601
}
598602

tools/server/tests/unit/test_preempt.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,3 +1025,43 @@ def test_two_image_chats_that_outgrow_the_parking_budget_both_finish():
10251025
for res in results:
10261026
assert res.status_code == 200, res.body
10271027
assert res.body["tokens_predicted"] == n_predict
1028+
1029+
1030+
def test_a_park_during_the_prefill_does_not_count_the_restarted_prompt_twice():
1031+
# a park with no budget for the state drops the cells of a prompt that is still being processed, and the resume starts that prefill again from nothing: what it had counted before the park has to go with the cells, or the request reports more prompt tokens than it has
1032+
# the park is made to fail its host allocation, so it drops the cells instead: the state of a half processed prompt is small enough to fit any budget
1033+
os.environ["LLAMA_SERVER_PREEMPT_FAIL_SAVE"] = "1"
1034+
_start(n_ctx=2048, n_batch=256)
1035+
1036+
resident = []
1037+
t = threading.Thread(target=lambda: resident.append(_complete(1900, _PROMPT_A)), daemon=True)
1038+
t.start()
1039+
1040+
# the pool has to be nearly full before the second prompt starts, so that it is that prefill which runs out of cells
1041+
deadline = time.time() + 90
1042+
while t.is_alive() and time.time() < deadline:
1043+
slots = server.make_request("GET", "/slots").body
1044+
if any(slot.get("n_prompt_tokens", 0) >= 1600 for slot in slots):
1045+
break
1046+
time.sleep(0.005)
1047+
else:
1048+
pytest.fail("the resident never grew into the pool")
1049+
1050+
n_prompt = 500
1051+
n_predict = 8
1052+
comments, final = _stream_completion(n_predict, _prompt_of(n_prompt, _PROMPT_B))
1053+
t.join(120)
1054+
1055+
assert resident and resident[0].status_code == 200, resident
1056+
assert "error" not in final, final
1057+
assert comments and comments[0] == ": preempted", comments
1058+
1059+
# a park that dropped fewer cells than the resume has tokens to put back is a park taken mid-prefill, which is the case this test is about
1060+
parks = [(int(cells), int(again)) for cells, again in re.findall(
1061+
r"preempted: (\d+) cells dropped .*? (\d+) tokens to re-prefill", _log())]
1062+
assert any(0 < cells < again for cells, again in parks), parks
1063+
1064+
timings = final["timings"]
1065+
assert timings["prompt_n"] == n_prompt, timings
1066+
assert timings["cache_n"] == 0, timings
1067+
assert timings["predicted_n"] == n_predict, timings

0 commit comments

Comments
 (0)