Skip to content

Commit 4e9eaad

Browse files
authored
Add generate_from_pos and prefill_prompt APIs in llama runner (#3)
* Add generate_from_pos and prefill_prompt APIs in llama runner * Update runner with correct implementation and update tokenizer commit * Update prefill_prompt function with proper error checking and remove extra logs from generate_from_pos * Address review comments
1 parent 6f9764e commit 4e9eaad

6 files changed

Lines changed: 196 additions & 17 deletions

File tree

‎examples/models/llama/runner/runner.cpp‎

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,121 @@ Error Runner::generate(
356356
return Error::Ok;
357357
}
358358

359+
Result<uint64_t> Runner::prefill_prompt(
360+
const std::string& prompt,
361+
int64_t& start_pos,
362+
int8_t bos,
363+
int8_t eos) {
364+
std::vector<uint64_t> prompt_tokens =
365+
ET_UNWRAP_TOKENIZER(tokenizer_->encode(prompt, bos, eos));
366+
int64_t total_context_len = start_pos + prompt_tokens.size();
367+
if (total_context_len >= metadata_.at(kMaxContextLen)) {
368+
ET_LOG(
369+
Error,
370+
"num_prompt_tokens + start_pos %ld >= max_seq_len_ %ld, Max seq length exceeded - please increase max seq len value in your export script ",
371+
total_context_len,
372+
metadata_.at(kMaxContextLen));
373+
return Result<uint64_t>(Error::InvalidState);
374+
}
375+
return text_prefiller_->prefill(prompt_tokens, start_pos);
376+
}
377+
378+
Error Runner::generate_from_pos(
379+
const std::string& prompt,
380+
const ::executorch::extension::llm::GenerationConfig& config,
381+
int64_t start_pos,
382+
std::function<void(const std::string&)> token_callback,
383+
std::function<void(const ::executorch::extension::llm::Stats&)>
384+
stats_callback) {
385+
ET_CHECK_MSG(!prompt.empty(), "Prompt cannot be null");
386+
if (!is_loaded()) {
387+
stats_->model_load_start_ms = llm::time_in_ms();
388+
ET_CHECK_OK_OR_RETURN_ERROR(load());
389+
stats_->model_load_end_ms = llm::time_in_ms();
390+
}
391+
if (config.warming) {
392+
ET_LOG(Info, "Doing a warmup run...");
393+
}
394+
395+
RUNNER_ET_LOG(
396+
config.warming,
397+
"RSS after loading model: %f MiB (0 if unsupported)",
398+
llm::get_rss_bytes() / 1024.0 / 1024.0);
399+
400+
// Wrap the token_callback with print function
401+
std::function<void(const std::string&)> wrapped_callback =
402+
[token_callback, config](const std::string& piece) {
403+
if (token_callback) {
404+
token_callback(piece);
405+
}
406+
};
407+
// First token time only measures the time it takes to encode the prompt and
408+
// return a response token.
409+
stats_->inference_start_ms = llm::time_in_ms();
410+
411+
// print prompts
412+
if (config.echo) {
413+
wrapped_callback(prompt);
414+
}
415+
416+
uint64_t prefill_next_token =
417+
ET_UNWRAP(prefill_prompt(prompt, start_pos, /*bos=*/0, /*eos=*/0));
418+
stats_->first_token_ms = llm::time_in_ms();
419+
stats_->prompt_eval_end_ms = llm::time_in_ms();
420+
stats_->num_prompt_tokens = start_pos;
421+
422+
ET_CHECK_MSG(
423+
start_pos < metadata_.at(kMaxContextLen),
424+
"start_pos %ld >= max_context_len_ %" PRId64
425+
", Max context length exceeded - please increase max context len value in your export script",
426+
start_pos,
427+
metadata_.at(kMaxContextLen));
428+
429+
int max_new_tokens =
430+
config.resolve_max_new_tokens(metadata_.at(kMaxContextLen), start_pos);
431+
432+
ET_LOG(Info, "Max new tokens resolved: %d", max_new_tokens);
433+
434+
// print the first token from prefill. No prev_token so use prefill_next_token
435+
wrapped_callback(ET_UNWRAP_TOKENIZER(
436+
tokenizer_->decode(prefill_next_token, prefill_next_token)));
437+
RUNNER_ET_LOG(
438+
config.warming,
439+
"RSS after prompt prefill: %f MiB (0 if unsupported)",
440+
llm::get_rss_bytes() / 1024.0 / 1024.0);
441+
442+
// Generate tokens
443+
int64_t num_generated_tokens = ET_UNWRAP(text_token_generator_->generate(
444+
/*tokens=*/{prefill_next_token},
445+
/*start_pos=*/start_pos,
446+
/*max_new_tokens=*/max_new_tokens + start_pos - 1,
447+
/*temperature=*/temperature_ == -1.0f ? config.temperature : temperature_,
448+
/*token_callback=*/wrapped_callback));
449+
450+
stats_->inference_end_ms = llm::time_in_ms();
451+
if (!config.warming) {
452+
printf("\n");
453+
}
454+
455+
RUNNER_ET_LOG(
456+
config.warming,
457+
"RSS after finishing text generation: %f MiB (0 if unsupported)",
458+
llm::get_rss_bytes() / 1024.0 / 1024.0);
459+
460+
stats_->num_generated_tokens = num_generated_tokens;
461+
462+
if (config.warming) {
463+
ET_LOG(Info, "Warmup run finished!");
464+
} else {
465+
// Do not print report during warmup
466+
::executorch::llm::print_report(*stats_);
467+
}
468+
if (stats_callback) {
469+
stats_callback(*stats_);
470+
}
471+
return Error::Ok;
472+
}
473+
359474
Error Runner::warmup(const std::string& prompt, int32_t max_new_tokens) {
360475
// Create a GenerationConfig for warmup
361476
llm::GenerationConfig config{

‎examples/models/llama/runner/runner.h‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@ class ET_EXPERIMENTAL Runner : public executorch::extension::llm::IRunner {
6464
int32_t max_new_tokens);
6565
void stop() override;
6666

67+
::executorch::runtime::Error generate_from_pos(
68+
const std::string& prompt,
69+
const ::executorch::extension::llm::GenerationConfig& config,
70+
int64_t start_pos,
71+
std::function<void(const std::string&)> token_callback = {},
72+
std::function<void(const ::executorch::extension::llm::Stats&)>
73+
stats_callback = {}) override;
74+
75+
::executorch::runtime::Result<uint64_t> prefill_prompt(
76+
const std::string& prompt,
77+
int64_t& start_pos,
78+
int8_t bos = 0,
79+
int8_t eos = 0) override;
80+
6781
private:
6882
bool shouldStop_{false};
6983

‎extension/android/jni/jni_layer_llama.cpp‎

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <executorch/examples/models/llava/runner/llava_runner.h>
1818
#include <executorch/extension/llm/runner/image.h>
1919
#include <executorch/extension/llm/runner/irunner.h>
20+
#include <executorch/runtime/core/result.h>
2021
#include <executorch/runtime/platform/log.h>
2122
#include <executorch/runtime/platform/platform.h>
2223
#include <executorch/runtime/platform/runtime.h>
@@ -237,13 +238,17 @@ class ExecuTorchLlmJni : public facebook::jni::HybridClass<ExecuTorchLlmJni> {
237238
jint eos) {
238239
facebook::jni::local_ref<jlongArray> tuple_result =
239240
facebook::jni::make_long_array(2);
240-
if (model_type_category_ != MODEL_TYPE_CATEGORY_MULTIMODAL) {
241+
if (model_type_category_ != MODEL_TYPE_CATEGORY_MULTIMODAL &&
242+
model_type_category_ != MODEL_TYPE_CATEGORY_LLM) {
241243
tuple_result->pin()[0] = static_cast<jint>(Error::NotSupported);
242244
return tuple_result;
243245
}
244246

245-
auto&& result = multi_modal_runner_->prefill_prompt(
246-
prompt->toStdString(), start_pos, bos, eos);
247+
auto&& result = model_type_category_ == MODEL_TYPE_CATEGORY_MULTIMODAL
248+
? multi_modal_runner_->prefill_prompt(
249+
prompt->toStdString(), start_pos, bos, eos)
250+
: runner_->prefill_prompt(prompt->toStdString(), start_pos, bos, eos);
251+
247252
tuple_result->pin()[0] = static_cast<jint>(Error::Ok);
248253
if (result.ok()) {
249254
tuple_result->pin()[1] = static_cast<jlong>(start_pos);
@@ -295,16 +300,28 @@ class ExecuTorchLlmJni : public facebook::jni::HybridClass<ExecuTorchLlmJni> {
295300
jlong start_pos,
296301
facebook::jni::alias_ref<ExecuTorchLlmCallbackJni> callback,
297302
jboolean echo) {
298-
if (model_type_category_ != MODEL_TYPE_CATEGORY_MULTIMODAL) {
299-
return static_cast<jint>(Error::NotSupported);
303+
if (model_type_category_ == MODEL_TYPE_CATEGORY_MULTIMODAL) {
304+
return static_cast<jint>(multi_modal_runner_->generate_from_pos(
305+
prompt->toStdString(),
306+
seq_len,
307+
start_pos,
308+
[callback](const std::string& result) { callback->onResult(result); },
309+
[callback](const llm::Stats& stats) { callback->onStats(stats); },
310+
echo));
311+
} else if (model_type_category_ == MODEL_TYPE_CATEGORY_LLM) {
312+
executorch::extension::llm::GenerationConfig config{
313+
.echo = static_cast<bool>(echo),
314+
.seq_len = seq_len,
315+
.temperature = temperature_,
316+
};
317+
return static_cast<jint>(runner_->generate_from_pos(
318+
prompt->toStdString(),
319+
config,
320+
start_pos,
321+
[callback](const std::string& result) { callback->onResult(result); },
322+
[callback](const llm::Stats& stats) { callback->onStats(stats); }));
300323
}
301-
return static_cast<jint>(multi_modal_runner_->generate_from_pos(
302-
prompt->toStdString(),
303-
seq_len,
304-
start_pos,
305-
[callback](const std::string& result) { callback->onResult(result); },
306-
[callback](const llm::Stats& stats) { callback->onStats(stats); },
307-
echo));
324+
return static_cast<jint>(Error::NotSupported);
308325
}
309326

310327
void stop() {

‎extension/llm/runner/irunner.h‎

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@
1010

1111
#pragma once
1212

13+
#include <executorch/extension/llm/runner/stats.h>
14+
#include <executorch/runtime/core/error.h>
15+
#include <executorch/runtime/core/result.h>
16+
1317
#include <cstdint>
1418
#include <functional>
1519
#include <memory>
1620
#include <string>
1721

18-
#include <executorch/extension/llm/runner/stats.h>
19-
#include <executorch/runtime/core/error.h>
20-
2122
namespace executorch {
2223
namespace extension {
2324
namespace llm {
@@ -125,6 +126,38 @@ class ET_EXPERIMENTAL IRunner {
125126
* Stop the generation process.
126127
*/
127128
virtual void stop() = 0;
129+
130+
/**
131+
* Generate tokens from the given prompt, starting from the given position.
132+
* @param prompt The text prompt to LLaMa.
133+
* @param start_pos The starting position in KV cache of the input in the LLM.
134+
* @param config Generation configuration parameters
135+
* @param token_callback What to do after a token is generated.
136+
* @param stats_callback What to do with Stats.
137+
* @return Error::Ok if successful, an error otherwise
138+
*/
139+
virtual runtime::Error generate_from_pos(
140+
const std::string& prompt,
141+
const GenerationConfig& config,
142+
int64_t start_pos = 0,
143+
std::function<void(const std::string&)> token_callback = {},
144+
std::function<void(const ::executorch::extension::llm::Stats&)>
145+
stats_callback = {}) = 0;
146+
147+
/**
148+
* Prefill an LLama Module with the given text input.
149+
* @param prompt The text prompt to LLama.
150+
* @param start_pos The starting position in KV cache of the input in the LLM.
151+
* It's passed as reference and will be updated inside this function.
152+
* @param bos The number of BOS (begin of sequence) token.
153+
* @param eos The number of EOS (end of sequence) token.
154+
* @return The generated token of the LLama Module after prefill prompt.
155+
*/
156+
virtual ::executorch::runtime::Result<uint64_t> prefill_prompt(
157+
const std::string& prompt,
158+
int64_t& start_pos,
159+
int8_t bos = 0,
160+
int8_t eos = 0) = 0;
128161
};
129162

130163
} // namespace llm

‎extension/llm/runner/text_token_generator.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ class ET_EXPERIMENTAL TextTokenGenerator {
151151
*/
152152
bool inline is_loaded() const {
153153
// Implementation to check if resources are loaded
154-
return tokenizer_->is_loaded() && text_decoder_runner_->is_method_loaded();
154+
return text_decoder_runner_->is_method_loaded();
155155
}
156156

157157
private:

‎extension/llm/tokenizers‎

0 commit comments

Comments
 (0)