You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DynamicCache的cache_position越界错误原因及无额外字符修复方案

问题:使用DynamicCache缓存初始提示词触发IndexError错误

场景描述

使用Hugging Face Transformers库的DynamicCache缓存初始提示词"I have a dream",后续计划每次生成输出后追加到当前提示词,再执行生成任务。运行代码时触发IndexError: index -1 is out of bounds for dimension 0 with size 0,错误源于访问cache_position[-1];但在初始提示词后添加空格,代码可正常运行。需明确问题根源及无需额外字符的解决方法。

复现代码

import os 
import copy
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, DynamicCache

# 假设已初始化model和tokenizer
max_length = 6
prompt_cache = DynamicCache()
INITIAL_PROMPT = "I have a dream"

inputs_initial_prompt = tokenizer(INITIAL_PROMPT, return_tensors="pt", padding=True).to("cuda")
# 缓存初始提示词的KV
with torch.no_grad():
    prompt_cache = model(**inputs_initial_prompt, past_key_values = prompt_cache).past_key_values.to("cuda")

input_text = INITIAL_PROMPT
# input_text = INITIAL_PROMPT + " "  # 添加空格可正常运行

responses = []
for  _ in range(max_length):
    new_inputs = tokenizer(input_text, return_tensors="pt", padding=True).to("cuda")
    past_key_values = copy.deepcopy(prompt_cache)
    outputs = model.generate(**new_inputs, past_key_values=past_key_values,max_new_tokens=20,pad_token_id=tokenizer.eos_token_id, do_sample=True, temperature=0.7)
    output_text = tokenizer.decode(outputs[0])
    print(output_text[len(input_text):])
    input_text = output_text
    print("#"*24)

错误栈

---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[61], line 20
     18 new_inputs = tokenizer(input_text, return_tensors="pt", padding=True).to("cuda")
     19 past_key_values = copy.deepcopy(prompt_cache)
---> 20 outputs = model.generate(**new_inputs, past_key_values=past_key_values,max_new_tokens=20,pad_token_id=tokenizer.eos_token_id, do_sample=True, temperature=0.7)
     21 output_text = tokenizer.decode(outputs[0])
     22 print(output_text[len(input_text):])

File /media/data1/haque/.conda/envs/llm/lib/python3.12/site-packages/torch/utils/_contextlib.py:116, in context_decorator.<locals>.decorate_context(*args, **kwargs)
    113 @functools.wraps(func)
    114 def decorate_context(*args, **kwargs):
    115     with ctx_factory():
--> 116         return func(*args, **kwargs)

File /media/data1/haque/.conda/envs/llm/lib/python3.12/site-packages/transformers/generation/utils.py:2252, in GenerationMixin.generate(self, inputs, generation_config, logits_processor, stopping_criteria, prefix_allowed_tokens_fn, synced_gpus, assistant_model, streamer, negative_prompt_ids, negative_prompt_attention_mask, **kwargs)
   2244     input_ids, model_kwargs = self._expand_inputs_for_generation(
   2245         input_ids=input_ids,
   2246         expand_size=generation_config.num_return_sequences,
   2247         is_encoder_decoder=self.config.is_encoder_decoder,
   2248         **model_kwargs,
   2249     )
   2251     # 12. run sample (it degenerates to greedy search when `generation_config.do_sample=False`)
--> 2252     result = self._sample(
   2253         input_ids,
   2254         logits_processor=prepared_logits_processor,
   2255         stopping_criteria=prepared_stopping_criteria,
   2256         generation_config=generation_config,
   2257         synced_gpus=synced_gpus,
   2258         streamer=streamer,
   2259         **model_kwargs,
   2260     )
   2262 elif generation_mode in (GenerationMode.BEAM_SAMPLE, GenerationMode.BEAM_SEARCH):
   2263     # 11. prepare beam search scorer
   2264     beam_scorer = BeamSearchScorer(
   2265         batch_size=batch_size,
   2266         num_beams=generation_config.num_beams,
   (...)
   2271         max_length=generation_config.max_length,
   2272     )

File /media/data1/haque/.conda/envs/llm/lib/python3.12/site-packages/transformers/generation/utils.py:3244, in GenerationMixin._sample(self, input_ids, logits_processor, stopping_criteria, generation_config, synced_gpus, streamer, **model_kwargs)
   3239 is_prefill = True
   3240 while self._has_unfinished_sequences(
   3241     this_peer_finished, synced_gpus, device=input_ids.device, cur_len=cur_len, max_length=max_length
   3242 ):
   3243     # prepare model inputs
--> 3244     model_inputs = self.prepare_inputs_for_generation(input_ids, **model_kwargs)
   3246     # prepare variable output controls (note: some models won't accept all output controls)
   3247     model_inputs.update({"output_attentions": output_attentions} if output_attentions else {})

File /media/data1/haque/.conda/envs/llm/lib/python3.12/site-packages/transformers/generation/utils.py:388, in GenerationMixin.prepare_inputs_for_generation(self, input_ids, past_key_values, attention_mask, inputs_embeds, cache_position, **kwargs)
    384 if past_key_values is not None:
    385     model_inputs["past_key_values"] = past_key_values
    386     if (
    387         inputs_embeds is not None  # Exception 1
--> 388         or (is_torchdynamo_compiling() or cache_position[-1] >= input_ids.shape[1])  # Exception 3
    389     ):
    390         input_ids = input_ids[:, -cache_position.shape[0] :]
    391     elif input_ids.shape[1] != cache_position.shape[0]:  # Default case (the "else", a no op, is Exception 2)

IndexError: index -1 is out of bounds for dimension 0 with size 0

问题根源

  • 核心矛盾:初始提示词处理后,DynamicCache的cache_position为空张量(size 0),但generate阶段尝试访问cache_position[-1]导致越界。
  • 当直接使用初始提示词时,模型第一次forward处理完所有token后,cache_position没有被初始化对应长度的位置索引;而添加空格后,提示词的token数量变化,模型会正确生成匹配长度的cache_position,避免了空张量访问。
  • 错误栈中代码cache_position[-1] >= input_ids.shape[1]是触发点:空张量无法通过索引-1获取元素,直接抛出越界错误。

解决方法

方法1:手动传入匹配长度的cache_position

在调用generate时,根据初始提示词的token长度手动生成cache_position:

# 提前获取初始提示词的token长度
prompt_token_len = inputs_initial_prompt.input_ids.shape[1]

# 修改循环内的generate调用
outputs = model.generate(
    **new_inputs,
    past_key_values=past_key_values,
    cache_position=torch.arange(prompt_token_len, device="cuda"),  # 传入对应长度的位置索引
    max_new_tokens=20,
    pad_token_id=tokenizer.eos_token_id,
    do_sample=True,
    temperature=0.7
)

方法2:保存并复用模型返回的cache_position

第一次处理初始提示词时,同时保存模型返回的cache_position,后续generate时直接传入:

# 初始缓存阶段,同时保存cache_position
with torch.no_grad():
    outputs = model(**inputs_initial_prompt, past_key_values=prompt_cache)
    prompt_cache = outputs.past_key_values.to("cuda")
    prompt_cache_pos = outputs.cache_position.to("cuda")  # 保存cache_position

# 循环内传入保存的cache_position
outputs = model.generate(
    **new_inputs,
    past_key_values=past_key_values,
    cache_position=prompt_cache_pos,
    max_new_tokens=20,
    pad_token_id=tokenizer.eos_token_id,
    do_sample=True,
    temperature=0.7
)

方法3:优化生成逻辑,避免重复编码完整提示词

更高效的方式是每次只传入新增的token,而非重新编码整个提示词,同时自动复用更新后的缓存:

# 初始处理
inputs_initial_prompt = tokenizer(INITIAL_PROMPT, return_tensors="pt").to("cuda")
with torch.no_grad():
    outputs = model(**inputs_initial_prompt, past_key_values=prompt_cache)
    prompt_cache = outputs.past_key_values.to("cuda")

# 初始化当前输入为初始提示词的token
current_input_ids = inputs_initial_prompt.input_ids
current_past_cache = copy.deepcopy(prompt_cache)

responses = []
for _ in range(max_length):
    outputs = model.generate(
        input_ids=current_input_ids,
        past_key_values=current_past_cache,
        max_new_tokens=20,
        pad_token_id=tokenizer.eos_token_id,
        do_sample=True,
        temperature=0.7
    )
    # 提取本次新增的token
    new_tokens = outputs[0][len(current_input_ids[0]):]
    output_text = tokenizer.decode(new_tokens)
    print(output_text)
    # 更新输入为新增token,缓存自动更新为最新状态
    current_input_ids = new_tokens.unsqueeze(0)
    current_past_cache = outputs.past_key_values
    print("#"*24)

内容的提问来源于stack exchange,提问作者melatonin15

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 05:37:07