1010
1111#include < executorch/extension/llm/runner/image_prefiller.h>
1212#include < executorch/extension/llm/runner/llm_runner_helper.h>
13+
1314#include < executorch/extension/llm/runner/multimodal_decoder_runner.h>
1415#include < executorch/extension/llm/runner/multimodal_prefiller.h>
1516#include < executorch/extension/llm/runner/multimodal_runner.h>
2627#include < pytorch/tokenizers/tekken.h>
2728#include < pytorch/tokenizers/tiktoken.h>
2829
30+ #include < limits>
31+
2932namespace executorch ::extension::llm {
3033
3134using ::executorch::extension::Module;
3235using ::executorch::runtime::Error;
3336
37+ void register_builtin_io_managers () {}
38+
3439std::unique_ptr<tokenizers::Tokenizer> load_tokenizer (
3540 const std::string& tokenizer_path,
3641 std::unique_ptr<std::vector<std::string>> special_tokens,
@@ -126,6 +131,13 @@ get_llm_metadata(tokenizers::Tokenizer* tokenizer, Module* module) {
126131 if (!get_result.ok ()) {
127132 return get_result.error ();
128133 }
134+ if (!get_result->isInt () && !get_result->isBool ()) {
135+ ET_LOG (
136+ Error,
137+ " Metadata method %s must return an integer or boolean." ,
138+ method_name.c_str ());
139+ return Error::InvalidArgument;
140+ }
129141 value = get_result->toScalar ().to <decltype (metadata)::mapped_type>();
130142 } else {
131143 ET_LOG (
@@ -148,6 +160,15 @@ get_llm_metadata(tokenizers::Tokenizer* tokenizer, Module* module) {
148160 metadata[llm::kMaxContextLen ]);
149161 }
150162
163+ for (const auto * name : {kMaxSeqLen , kMaxContextLen }) {
164+ const auto value = metadata.at (name);
165+ ET_CHECK_OR_RETURN_ERROR (
166+ value > 0 && value <= std::numeric_limits<int32_t >::max (),
167+ InvalidArgument,
168+ " Metadata %s must be in [1, INT32_MAX]." ,
169+ name);
170+ }
171+
151172 // Set tokenizer-related metadata
152173 metadata[llm::kBosId ] = tokenizer->bos_tok ();
153174 metadata[llm::kVocabSize ] = tokenizer->vocab_size ();
@@ -266,8 +287,32 @@ std::unique_ptr<TextLLMRunner> create_text_llm_runner(
266287 auto eos_ids = std::make_unique<std::unordered_set<uint64_t >>(
267288 llm::get_eos_ids (tokenizer.get (), module .get ()));
268289
269- // Create IOManager
270- std::unique_ptr<IOManager> io_manager = std::make_unique<IOManager>(*module );
290+ // Create the IOManager for the I/O contract the program declares, if any.
291+ register_builtin_io_managers ();
292+ std::string io_contract;
293+ if (auto names = module ->method_names ();
294+ names.ok () && names->count (kIoContract )) {
295+ auto contract = module ->get (kIoContract );
296+ if (!contract.ok () || !contract->isString ()) {
297+ ET_LOG (Error, " Failed to read the program's I/O contract label." );
298+ return nullptr ;
299+ }
300+ io_contract = std::string (contract->toString ());
301+ if (io_contract.empty ()) {
302+ ET_LOG (Error, " The program's I/O contract label is empty." );
303+ return nullptr ;
304+ }
305+ ET_LOG (Info, " I/O contract: %s" , io_contract.c_str ());
306+ }
307+ auto io_manager_result = make_io_manager (*module , io_contract);
308+ if (!io_manager_result.ok ()) {
309+ ET_LOG (
310+ Error,
311+ " Failed to create the IOManager: 0x%x." ,
312+ static_cast <unsigned >(io_manager_result.error ()));
313+ return nullptr ;
314+ }
315+ auto io_manager = std::move (*io_manager_result);
271316
272317 // Read vocab_size for Sampler
273318 int32_t vocab_size = static_cast <int32_t >(metadata.at (kVocabSize ));
@@ -289,6 +334,17 @@ std::unique_ptr<TextLLMRunner> create_text_llm_runner(
289334 method_names.count (kPrefillMethod ) > 0 ) {
290335 prefill_method_name = kPrefillMethod ;
291336 }
337+ // An IOManager may drive methods of its own contract's names.
338+ auto [io_prefill, io_decode] = io_manager->method_names (method_names);
339+ if (!io_prefill.empty ()) {
340+ prefill_method_name = io_prefill;
341+ }
342+ if (io_manager->load (
343+ prefill_method_name,
344+ io_decode.empty () ? prefill_method_name : io_decode) != Error::Ok) {
345+ ET_LOG (Error, " Failed to load the IOManager" );
346+ return nullptr ;
347+ }
292348
293349 // Create text_decoder_runner
294350 ET_LOG (Info, " Using method: %s" , prefill_method_name.c_str ());
@@ -302,7 +358,15 @@ std::unique_ptr<TextLLMRunner> create_text_llm_runner(
302358 // The decode stage gets its own decoder runner when the PTE exports a
303359 // separate decode method; otherwise both stages share text_decoder_runner.
304360 std::unique_ptr<TextDecoderRunner> decode_text_decoder_runner;
305- if (method_names.count (kDecodeMethod ) > 0 &&
361+ if (!io_decode.empty () && io_decode != prefill_method_name) {
362+ decode_text_decoder_runner = std::make_unique<TextDecoderRunner>(
363+ module .get (),
364+ io_manager.get (),
365+ io_decode,
366+ std::make_unique<Sampler>(vocab_size, init_temp),
367+ stats.get ());
368+ } else if (
369+ io_decode.empty () && method_names.count (kDecodeMethod ) > 0 &&
306370 prefill_method_name != kDecodeMethod ) {
307371 ET_LOG (Info, " Using decode method: %s" , kDecodeMethod );
308372 decode_text_decoder_runner = std::make_unique<TextDecoderRunner>(
@@ -316,12 +380,17 @@ std::unique_ptr<TextLLMRunner> create_text_llm_runner(
316380 ? decode_text_decoder_runner.get ()
317381 : text_decoder_runner.get ();
318382
319- // Create text_prefiller
383+ // Create text_prefiller. A prompt method with a fixed step size takes the
384+ // prompt in chunks of that size.
385+ const int64_t step_tokens =
386+ io_manager->max_tokens_per_step (prefill_method_name);
320387 auto text_prefiller = std::make_unique<TextPrefiller>(
321388 text_decoder_runner.get (),
322389 metadata.at (kUseKVCache ),
323- metadata.at (kEnableDynamicShape ),
324- metadata.at (kMaxSeqLen ));
390+ step_tokens > 1 ? true
391+ : static_cast <bool >(metadata.at (kEnableDynamicShape )),
392+ step_tokens > 1 ? step_tokens : metadata.at (kMaxSeqLen ),
393+ step_tokens > 1 ? decode_runner : nullptr );
325394
326395 // Create text_token_generator with stats
327396 auto text_token_generator = std::make_unique<TextTokenGenerator>(
0 commit comments