From 9592ceefad4e819f03347c5757a2383c750911f4 Mon Sep 17 00:00:00 2001 From: shoumikhin Date: Sat, 22 Aug 2026 14:07:41 -0700 Subject: [PATCH] Do not hand the tokenizer loader an unusable special token list ExecuTorchLLMTextRunner ends up holding an empty vector when the caller gives it no special tokens, and an empty vector is not the same as no vector. load_tokenizer takes it at face value and builds a Tiktoken whose BOS and EOS indices point past the end of an empty list, which aborts the process in the 1.4.0 prebuilts instead of returning an error. Pass nothing when the list is empty so the tokenizer uses its own defaults, and copy the list instead of moving it so a retry after a failed load still has it. A single entry hits the same abort through the EOS index alone, and defaults cannot stand in for it because the caller did state a list. Report that as an error before the loader runs, so the message names the real cause instead of a tokenizer that would not load. --- .../Exported/ExecuTorchLLMTextRunner.mm | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/extension/llm/apple/ExecuTorchLLM/Exported/ExecuTorchLLMTextRunner.mm b/extension/llm/apple/ExecuTorchLLM/Exported/ExecuTorchLLMTextRunner.mm index ea858f1df9e..9cb5f5d5f27 100644 --- a/extension/llm/apple/ExecuTorchLLM/Exported/ExecuTorchLLMTextRunner.mm +++ b/extension/llm/apple/ExecuTorchLLM/Exported/ExecuTorchLLMTextRunner.mm @@ -56,9 +56,31 @@ - (BOOL)isLoaded { - (BOOL)loadWithError:(NSError**)error { if (![self isLoaded]) { + // The loader pins the begin of sequence index at 0 and the end of sequence + // index at 1, so one entry cannot name both. Caught here because the loader + // reports it as a tokenizer that would not load, which hides the cause. + if (_specialTokens->size() == 1) { + if (error) { + *error = [NSError errorWithDomain:ExecuTorchLLMErrorDomain + code:-1 + userInfo:@{NSLocalizedDescriptionKey: @"Special tokens must name a begin and an end of sequence token, or be empty"}]; + } + return NO; + } + // An empty list means the caller had none to give, not that the tokenizer + // should have none. Copy rather than move so a retry after a failed load + // still has them. + std::unique_ptr> specialTokens; + if (!_specialTokens->empty()) { + specialTokens = + std::make_unique>(*_specialTokens); + } _runner = llm::create_text_llm_runner( _modelPath.UTF8String ?: "", - llm::load_tokenizer(_tokenizerPath.UTF8String ?: "", std::move(_specialTokens)) + llm::load_tokenizer( + _tokenizerPath.UTF8String ?: "", + std::move(specialTokens) + ) ); if (!_runner) { if (error) {