Skip to content

Commit ae19539

Browse files
committed
tests : make the two generations that do not fit overlap by their lengths, not by the client's speed
(cherry picked from commit dcdb691)
1 parent c5dc0f9 commit ae19539

1 file changed

Lines changed: 28 additions & 10 deletions

File tree

tools/server/tests/unit/test_preempt.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,17 @@ def _wait_processing(slot_ids, timeout: float = 30.0):
109109
pytest.fail(f"slots {slot_ids} never showed as processing")
110110

111111

112+
def _complete_overlapping(n_predict, n_prompt, timeout: float = DEFAULT_REQUEST_TIMEOUT):
113+
"""A leader on slot 0 and a follower on slot 1 that certainly overlap: the follower is sent once the leader is seen processing, so the lengths and not the client's speed decide what the pool has to hold."""
114+
leader = _prompt_of(n_prompt[0], _PROMPT_A)
115+
other = _prompt_of(n_prompt[1], _PROMPT_B)
116+
with ThreadPoolExecutor(1) as pool:
117+
first = pool.submit(_complete, n_predict[0], leader, 0, 0.0, None, timeout)
118+
_wait_processing([0])
119+
second = _complete(n_predict[1], other, 1, 0.0, None, timeout)
120+
return [first.result(), second]
121+
122+
112123
def _wait_preempted(timeout: float = 30.0) -> bool:
113124
"""True once some slot is parked: its cells are in host RAM and it wants them back."""
114125
deadline = time.time() + timeout
@@ -191,27 +202,32 @@ def test_forced_parks_do_not_change_the_output(mode):
191202

192203
@pytest.mark.parametrize("knob", ["planner", "pages", "async", "last-resort", "last-resort-unlimited"])
193204
def test_two_generations_that_do_not_fit_together_both_finish(knob):
194-
# each request fits the pool alone (168 of 256 cells) but not together; without preemption both end with "Context size has been exceeded"
205+
# each request fits the pool alone (960 and 600 of 1024 cells) but not together; without preemption both end with "Context size has been exceeded"
195206
if knob == "pages":
196207
# a block allocator gives a whole block to one sequence, so the planner has to count cells: counting tokens it sees room the allocator cannot find
197208
os.environ["LLAMA_SERVER_PREEMPT_GRANULARITY"] = "64"
198209
if knob.startswith("last-resort"):
199210
os.environ["LLAMA_SERVER_PREEMPT_PLANNER"] = "off"
200211
if knob == "last-resort-unlimited":
201212
os.environ["LLAMA_ARG_PREEMPT_RAM"] = "-1"
202-
(_start_async if knob == "async" else _start)(n_ctx=256)
213+
n_ctx = 1024
214+
(_start_async if knob == "async" else _start)(n_ctx=n_ctx)
215+
216+
# the lengths, not the client's speed, decide the overlap: two equal requests fired together did not overlap on a Windows runner, the first finished before the second arrived, and the last resort never saw the two residents it needs.
217+
# the follower is sent once the leader is seen processing, so it holds its cells while the leader grows into the rest of the pool
218+
n_predict = (460, 400)
219+
results = _complete_overlapping(n_predict, (500, 200))
203220

204-
n_predict = 160
205-
results = _complete_all(n_predict)
206221
text = _log()
207222
_assert_recovered(text, "preempted as a last resort" if knob.startswith("last-resort") else "preempted:")
208-
_assert_completed(results, n_predict)
209-
for res in results:
223+
for res, n_wanted in zip(results, n_predict):
224+
assert res.status_code == 200, res.body
225+
assert res.body["timings"]["predicted_n"] == n_wanted
210226
assert res.body["truncated"] is False
211-
assert len(res.body["tokens"]) == n_predict
227+
assert len(res.body["tokens"]) == n_wanted
212228

213229
if knob == "pages":
214-
held = [int(n) for n in re.findall(r"kv (\d+)/256", text)]
230+
held = [int(n) for n in re.findall(rf"kv (\d+)/{n_ctx}", text)]
215231
wanted = [int(n) for n in re.findall(r"\(wanted (\d+)\)", text)]
216232
assert held and wanted, f"the planner logged no figures:\n{text}"
217233
assert all(n % 64 == 0 for n in held + wanted), f"not whole blocks: {held} {wanted}"
@@ -236,7 +252,8 @@ def test_a_request_that_cannot_be_helped_gets_the_context_error_and_the_server_l
236252
_start(n_ctx=256)
237253

238254
if knob == "ram-0":
239-
assert any(res.status_code != 200 for res in _complete_all(160))
255+
# the overflow has to be a matter of lengths: two equal requests fired together did not overlap on a Windows runner, and each one fits the pool alone
256+
assert any(res.status_code != 200 for res in _complete_overlapping((110, 100), (120, 60)))
240257
else:
241258
res = server.make_request("POST", "/completion", data={
242259
"n_predict": 160, "n_cmpl": 2, "prompt": _PROMPT_A,
@@ -263,7 +280,8 @@ def test_a_server_that_never_asked_for_parking_behaves_as_upstream():
263280
assert "preemption:" not in text, "a server that did not ask for parking announced it"
264281
assert _ASYNC_BANNER not in text, "the async park path was set up without being asked for"
265282

266-
assert any(res.status_code != 200 for res in _complete_all(160))
283+
# as above, the two have to be resident together for the pool to overflow at all
284+
assert any(res.status_code != 200 for res in _complete_overlapping((110, 100), (120, 60)))
267285

268286
text = _log()
269287
assert "Context size has been exceeded" in text

0 commit comments

Comments
 (0)