@@ -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+
112123def _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" ])
193204def 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