Skip to content

Commit 9ac2693

Browse files
ngxsonggerganov
andauthored
server: fix n_cmpl not skipping processing prompt (#18663)
* server: fix n_cmpl not skipping processing * fix infinite loop on empty batch * cont : init child samplers + modify child logic * cont : cleanup * cont : improve n_cmpl logic - launch the parent task first so it finds the slot with best cache - parent task waits for child tasks to be launched - when a child task finishes - remove its cache * cont : remove redundant function * cont : reduce parent checks * fix : nullptr task dereference --------- Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
1 parent a61c8bc commit 9ac2693

2 files changed

Lines changed: 118 additions & 49 deletions

File tree

tools/server/server-context.cpp

Lines changed: 114 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ struct server_slot {
7979

8080
common_speculative * spec = nullptr;
8181

82+
// TODO: move members that belong to the task (such as `generated_text`, `has_new_line`) to task_results_state
83+
// see https://github.com/ggml-org/llama.cpp/pull/18283#issuecomment-3710175837
8284
std::unique_ptr<const server_task> task;
8385
std::unique_ptr<const server_task> task_prev; // used for debugging
8486

@@ -153,7 +155,7 @@ struct server_slot {
153155

154156
common_sampler_ptr smpl;
155157

156-
llama_token sampled; // in speculative mode, this is the last accepted token
158+
llama_token sampled; // in speculative mode, this is the last accepted token
157159
llama_tokens drafted;
158160

159161
// stats
@@ -201,12 +203,46 @@ struct server_slot {
201203
alora_invocation_start = -1;
202204
}
203205

206+
// remove cached prompt + tokens
207+
void clear(bool allow_processing) {
208+
if (!allow_processing) {
209+
GGML_ASSERT(!is_processing());
210+
}
211+
212+
SLT_INF(*this, "clearing slot with %zu tokens\n", prompt.tokens.size());
213+
214+
llama_memory_seq_rm(llama_get_memory(ctx), id, -1, -1);
215+
prompt.tokens.clear();
216+
}
217+
218+
void init_sampler() const {
219+
const int64_t t_start = ggml_time_us();
220+
221+
common_sampler_reset(smpl.get());
222+
223+
int n_text = 0;
224+
225+
for (int i = 0; i < (int) prompt.tokens.size(); i++) {
226+
const llama_token id = prompt.tokens[i];
227+
228+
if (id != LLAMA_TOKEN_NULL) {
229+
common_sampler_accept(smpl.get(), id, false);
230+
n_text++;
231+
}
232+
}
233+
234+
SLT_INF(*this, "init sampler, took %0.2f ms, tokens: text = %d, total = %d\n",
235+
(ggml_time_us() - t_start) / 1000.0, n_text, (int) prompt.tokens.size());
236+
}
237+
238+
// TODO: move to server_task
204239
bool need_embd() const {
205240
GGML_ASSERT(task);
206241

207242
return server_task_type_need_embd(task->type);
208243
}
209244

245+
// TODO: move to server_task
210246
bool need_logits() const {
211247
GGML_ASSERT(task);
212248

@@ -258,10 +294,13 @@ struct server_slot {
258294
SLT_WRN(*this, "%s", "slot is not processing\n");
259295
return;
260296
}
297+
261298
generated_token_probs.push_back(token);
262299
}
263300

264301
int get_n_draft_max() const {
302+
GGML_ASSERT(task);
303+
265304
if (!can_speculate()) {
266305
return 0;
267306
}
@@ -287,12 +326,14 @@ struct server_slot {
287326
}
288327

289328
// note: a slot can also be either a parent or a child
329+
// TODO: move to server_task
290330
bool is_parent() const {
291-
return is_processing() && task->n_children > 0;
331+
return task->n_children > 0;
292332
}
293333

334+
// TODO: move to server_task
294335
bool is_child() const {
295-
return is_processing() && task->id_parent >= 0;
336+
return task->id_parent >= 0;
296337
}
297338

298339
void release() {
@@ -301,10 +342,16 @@ struct server_slot {
301342

302343
SLT_INF(*this, "stop processing: n_tokens = %d, truncated = %d\n", prompt.n_tokens(), truncated);
303344

304-
t_last_used = ggml_time_us();
345+
t_last_used = ggml_time_us();
305346
t_token_generation = (ggml_time_us() - t_start_generation) / 1e3;
347+
306348
state = SLOT_STATE_IDLE;
307349

350+
// do not keep context of the child slots - the parent's context is enough
351+
if (is_child()) {
352+
clear(false);
353+
}
354+
308355
task_prev = std::move(task);
309356
task.reset();
310357

@@ -425,14 +472,22 @@ struct server_slot {
425472
}
426473

427474
void copy_state_to(server_slot & other) const {
428-
llama_memory_seq_rm(llama_get_memory(ctx), other.id, 0, -1);
429-
llama_memory_seq_cp(llama_get_memory(ctx), id, other.id, 0, -1);
475+
GGML_ASSERT(state == SLOT_STATE_DONE_PROMPT);
476+
477+
llama_memory_seq_rm(llama_get_memory(ctx), other.id, -1, -1);
478+
llama_memory_seq_cp(llama_get_memory(ctx), id, other.id, -1, -1);
479+
430480
other.n_decoded = n_decoded;
431481
other.n_remaining = n_remaining;
432482
other.i_batch = i_batch;
483+
484+
other.t_start_process_prompt = t_start_process_prompt;
485+
other.t_prompt_processing = t_prompt_processing;
433486
other.n_prompt_tokens_cache = n_prompt_tokens_cache;
434487
other.n_prompt_tokens_processed = n_prompt_tokens_processed;
488+
435489
other.prompt = prompt.clone();
490+
other.init_sampler();
436491
}
437492
};
438493

@@ -745,6 +800,7 @@ struct server_context_impl {
745800
}
746801

747802
slots.clear();
803+
748804
for (int i = 0; i < params_base.n_parallel; i++) {
749805
server_slot slot;
750806

@@ -993,7 +1049,7 @@ struct server_context_impl {
9931049
ret->prompt_save(*prompt_cache);
9941050

9951051
if (!ret->prompt_load(*prompt_cache, task.tokens)) {
996-
clear_slot(*ret);
1052+
ret->clear(false);
9971053
}
9981054

9991055
prompt_cache->update();
@@ -1005,17 +1061,6 @@ struct server_context_impl {
10051061
return ret;
10061062
}
10071063

1008-
void clear_slot(server_slot & slot, bool allow_processing = false) const {
1009-
if (!allow_processing) {
1010-
GGML_ASSERT(!slot.is_processing());
1011-
}
1012-
1013-
SLT_WRN(slot, "clearing slot with %zu tokens\n", slot.prompt.tokens.size());
1014-
1015-
llama_memory_seq_rm(llama_get_memory(ctx), slot.id, -1, -1);
1016-
slot.prompt.tokens.clear();
1017-
}
1018-
10191064
// return true if at least one slot has been cleared
10201065
// TODO: improve logic
10211066
// - smarter decision which slot to clear (LRU or longest prompt?)
@@ -1036,7 +1081,7 @@ struct server_context_impl {
10361081
if (slot.prompt.n_tokens() > 0) {
10371082
SRV_WRN("purging slot %d with %zu tokens\n", slot.id, slot.prompt.tokens.size());
10381083

1039-
clear_slot(slot);
1084+
slot.clear(false);
10401085

10411086
res = true;
10421087

@@ -1182,7 +1227,7 @@ struct server_context_impl {
11821227
? SLOT_STATE_WAIT_OTHER // wait for the parent to process prompt
11831228
: SLOT_STATE_STARTED;
11841229

1185-
SLT_INF(slot, "%s", "processing task\n");
1230+
SLT_INF(slot, "processing task, is_child = %d\n", slot.is_child());
11861231

11871232
return true;
11881233
}
@@ -1819,7 +1864,7 @@ struct server_context_impl {
18191864
// Erase token cache
18201865
const size_t n_erased = slot->prompt.tokens.size();
18211866

1822-
clear_slot(*slot);
1867+
slot->clear(false);
18231868

18241869
auto res = std::make_unique<server_task_result_slot_erase>();
18251870
res->id = task.id;
@@ -2053,8 +2098,29 @@ struct server_context_impl {
20532098
continue;
20542099
}
20552100

2101+
// check if this is a child slot
2102+
if (slot.state == SLOT_STATE_WAIT_OTHER) {
2103+
SLT_DBG(slot, "%s", "waiting for parent slot to complete\n");
2104+
continue;
2105+
}
2106+
20562107
// this slot still has a prompt to be processed
20572108
if (slot.state == SLOT_STATE_PROCESSING_PROMPT || slot.state == SLOT_STATE_STARTED) {
2109+
// wait for all children to be launched
2110+
if (slot.is_parent()) {
2111+
int n_launched = 0;
2112+
for (auto & other : slots) {
2113+
if (other.is_processing() && other.is_child() && other.task->id_parent == slot.task->id) {
2114+
++n_launched;
2115+
}
2116+
}
2117+
2118+
if (n_launched < slot.task->n_children) {
2119+
SLT_DBG(slot, "waiting for children to be launched, n_children = %d, n_launched = %d\n", slot.task->n_children, n_launched);
2120+
continue;
2121+
}
2122+
}
2123+
20582124
const auto & input_tokens = slot.task->tokens;
20592125

20602126
// TODO: maybe move branch to outside of this loop in the future
@@ -2355,7 +2421,7 @@ struct server_context_impl {
23552421
if (!llama_memory_seq_rm(llama_get_memory(ctx), slot.id, p0, -1)) {
23562422
SLT_WRN(slot, "failed to truncate tokens with position >= %d - clearing the memory\n", p0);
23572423

2358-
clear_slot(slot, /*allow_processing=*/true);
2424+
slot.clear(true);
23592425

23602426
// there is no common part left
23612427
slot.n_prompt_tokens_cache = 0;
@@ -2455,16 +2521,6 @@ struct server_context_impl {
24552521

24562522
GGML_ASSERT(batch.n_tokens > 0);
24572523

2458-
common_sampler_reset(slot.smpl.get());
2459-
2460-
// Process all prompt tokens through sampler system
2461-
for (int i = 0; i < slot.task->n_tokens(); ++i) {
2462-
llama_token id = input_tokens[i];
2463-
if (id != LLAMA_TOKEN_NULL) {
2464-
common_sampler_accept(slot.smpl.get(), id, false);
2465-
}
2466-
}
2467-
24682524
// extract the logits only for the last token
24692525
batch.logits[batch.n_tokens - 1] = true;
24702526

@@ -2473,6 +2529,8 @@ struct server_context_impl {
24732529

24742530
SLT_INF(slot, "prompt done, n_tokens = %d, batch.n_tokens = %d\n", slot.prompt.n_tokens(), batch.n_tokens);
24752531

2532+
slot.init_sampler();
2533+
24762534
const auto pos_min = llama_memory_seq_pos_min(llama_get_memory(ctx), slot.id);
24772535
const auto pos_max = llama_memory_seq_pos_max(llama_get_memory(ctx), slot.id);
24782536

@@ -2519,11 +2577,6 @@ struct server_context_impl {
25192577
}
25202578
}
25212579

2522-
if (batch.n_tokens == 0) {
2523-
SRV_WRN("%s", "no tokens to decode\n");
2524-
return;
2525-
}
2526-
25272580
SRV_DBG("decoding batch, n_tokens = %d\n", batch.n_tokens);
25282581

25292582
if (slot_batched) {
@@ -2540,6 +2593,10 @@ struct server_context_impl {
25402593
llama_set_embeddings(ctx, slot_batched->need_embd());
25412594
}
25422595

2596+
if (batch.n_tokens == 0) {
2597+
SRV_WRN("%s", "no tokens to decode\n");
2598+
}
2599+
25432600
int32_t i_next = 0;
25442601

25452602
// process the created batch of tokens
@@ -2591,7 +2648,7 @@ struct server_context_impl {
25912648

25922649
// note: it's complicated to keep track of how much of the current batch has been
25932650
// processed before the error occurred, so we simply clear the entire context
2594-
clear_slot(slot);
2651+
slot.clear(false);
25952652
}
25962653
}
25972654

@@ -2615,27 +2672,34 @@ struct server_context_impl {
26152672
// on successful decode, restore the original batch size
26162673
n_batch = llama_n_batch(ctx);
26172674

2675+
// handle `n_cmpl > 1` tasks - when the main prompt is processed, activate all child tasks too
26182676
for (auto & slot : slots) {
2619-
// may need to copy state to other slots
26202677
if (slot.state == SLOT_STATE_DONE_PROMPT && slot.is_parent()) {
2621-
std::vector<server_slot *> child_slots;
2678+
SLT_INF(slot, "parent task prompt done, n_children = %d\n", slot.task->n_children);
2679+
2680+
std::vector<server_slot *> children;
26222681
for (auto & other : slots) {
26232682
if (other.state == SLOT_STATE_WAIT_OTHER && slot.task->id == other.task->id_parent) {
2624-
child_slots.push_back(&other);
2683+
children.push_back(&other);
26252684
}
26262685
}
26272686

26282687
// we can only proceed if all child slots are having the correct tasks
2629-
if (child_slots.size() == slot.task->n_children) {
2688+
if (slot.task->n_children == (int) children.size()) {
26302689
// copy state to the child slots
2631-
for (auto & child : child_slots) {
2632-
SLT_INF(slot, "copying state to child %d\n", child->id);
2690+
for (auto & child : children) {
2691+
SLT_INF(slot, " - copying state to child %d\n", child->id);
2692+
2693+
GGML_ASSERT(child->state == SLOT_STATE_WAIT_OTHER);
2694+
26332695
slot.copy_state_to(*child);
26342696
child->state = SLOT_STATE_DONE_PROMPT;
26352697
}
26362698
}
26372699
}
2700+
}
26382701

2702+
for (auto & slot : slots) {
26392703
// optionally send prompt processing progress
26402704
if (slot.state == SLOT_STATE_PROCESSING_PROMPT || slot.state == SLOT_STATE_DONE_PROMPT) {
26412705
if (slot.task->params.stream && slot.task->params.return_progress) {
@@ -2720,7 +2784,7 @@ struct server_context_impl {
27202784
continue;
27212785
}
27222786

2723-
size_t n_draft = slot.drafted.size();
2787+
const size_t n_draft = slot.drafted.size();
27242788

27252789
// the accepted tokens from the speculation
27262790
const auto ids = common_sampler_sample_and_accept_n(slot.smpl.get(), ctx, slot.i_batch_dft, slot.drafted);
@@ -2923,9 +2987,11 @@ std::unique_ptr<server_res_generator> server_routes::handle_completions_impl(
29232987
task.params.oaicompat_cmpl_id = completion_id;
29242988
task.params.oaicompat_model = meta->model_name;
29252989

2990+
// prepare child tasks
29262991
if (task.params.n_cmpl > 1) {
29272992
task.n_children = task.params.n_cmpl - 1;
2928-
for (size_t j = 0; j < task.n_children; j++) {
2993+
2994+
for (int j = 0; j < task.n_children; j++) {
29292995
server_task child = task.create_child(task.id, rd.get_new_id());
29302996

29312997
// use different sampling seed for each child
@@ -2938,7 +3004,8 @@ std::unique_ptr<server_res_generator> server_routes::handle_completions_impl(
29383004
}
29393005
}
29403006

2941-
tasks.push_back(std::move(task));
3007+
// note: the parent task always launches first
3008+
tasks.insert(tasks.begin(), std::move(task));
29423009
}
29433010

29443011
rd.post_tasks(std::move(tasks));

tools/server/server-task.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ struct server_task {
121121
int id_slot = -1;
122122

123123
// used by parallel sampling (multiple completions from same prompt)
124-
size_t n_children = 0; // number of tasks reusing this prompt
125-
int id_parent = -1;
124+
int n_children = 0; // number of tasks reusing this prompt
125+
int id_parent = -1;
126126

127127
// used by SERVER_TASK_TYPE_INFERENCE
128128
task_params params;
@@ -173,11 +173,13 @@ struct server_task {
173173

174174
server_task create_child(int id_parent, int id_child) const {
175175
server_task copy;
176+
176177
copy.id = id_child;
177178
copy.id_parent = id_parent;
178179
copy.params = params;
179180
copy.type = type;
180181
copy.tokens = tokens.clone();
182+
181183
return copy;
182184
}
183185

0 commit comments

Comments
 (0)