diff --git a/common/sampling.cpp b/common/sampling.cpp index 5a7a9b69..102108ac 100644 --- a/common/sampling.cpp +++ b/common/sampling.cpp @@ -655,15 +655,22 @@ static llama_token_data_array llama_sampling_prepare_impl( const auto& penalty_tokens = params.use_penalty_prompt_tokens ? params.penalty_prompt_tokens : prev; const int penalty_tokens_used_size = std::min((int)penalty_tokens.size(), penalty_last_n); if (penalty_tokens_used_size) { - const float nl_logit = logits[llama_token_nl(llama_get_model(ctx_main))]; + // llama_token_nl() can legitimately return LLAMA_TOKEN_NULL (-1). Some vocabs (e.g. Falcon3's + // BPE tokenizer) tokenize "\n" to zero tokens, and the loader's fallback + // (llama-vocab.cpp, linefeed_id = special_pad_id) runs before LLM_KV_TOKENIZER_PAD_ID is read + // from the GGUF, so it copies the BPE default, which is itself LLAMA_TOKEN_NULL. Reading + // logits[-1] is then an out-of-bounds access one float before the current position's logit + // row, which segfaults when that address happens to be unmapped. + const llama_token nl_token = llama_token_nl(llama_get_model(ctx_main)); + const float nl_logit = nl_token != LLAMA_TOKEN_NULL ? logits[nl_token] : 0.0f; llama_sample_repetition_penalties(ctx_main, &cur_p, penalty_tokens.data() + penalty_tokens.size() - penalty_tokens_used_size, penalty_tokens_used_size, penalty_repeat, penalty_freq, penalty_present); - if (!penalize_nl) { + if (!penalize_nl && nl_token != LLAMA_TOKEN_NULL) { for (size_t idx = 0; idx < cur_p.size; idx++) { - if (cur_p.data[idx].id == llama_token_nl(llama_get_model(ctx_main))) { + if (cur_p.data[idx].id == nl_token) { cur_p.data[idx].logit = nl_logit; break; }