InternLM2-Chat-1.8B处理长文本技术详解:上下文窗口管理与总结生成

张开发
2026/4/7 17:29:36 15 分钟阅读

分享文章

InternLM2-Chat-1.8B处理长文本技术详解:上下文窗口管理与总结生成
InternLM2-Chat-1.8B处理长文本技术详解上下文窗口管理与总结生成你是不是也遇到过这样的问题拿到一篇几十页的技术文档或者一份冗长的报告想让AI帮忙总结一下核心内容或者回答几个具体问题结果模型要么回答得支离破碎要么干脆告诉你“文本太长了我处理不了”。这背后其实是一个很实际的技术限制大语言模型通常有一个固定的“上下文窗口”就像人的短期记忆一样容量是有限的。InternLM2-Chat-1.8B这个轻量级模型也不例外。它的上下文长度不足以一次性“吃下”整本《三国演义》。但别担心容量有限不代表我们就束手无策了。今天我就来跟你聊聊怎么用一些巧妙的策略让InternLM2-Chat-1.8B这类模型也能处理远超其窗口长度的文档。我们会从最基础的文本分块讲起再到滑动窗口这种更高级的技巧最后手把手带你搭建一个能处理任意长度文档的问答管道。整个过程我都会配上可以直接运行的Python代码保证你看完就能上手试试。1. 理解核心挑战上下文窗口与长文本在开始动手之前我们得先搞清楚问题到底出在哪。这就像医生看病得先诊断病因。大语言模型在生成每一个词的时候并不是凭空想象的。它会回顾之前已经生成的所有内容以及你提供给它的所有输入文本然后基于这个“上下文”来预测下一个最可能的词。这个模型在单次处理时能“看到”的文本总长度就是它的上下文窗口。对于InternLM2-Chat-1.8B这个版本来说这个窗口的大小是有限的。当你给它的文档长度超过了这个限制最直接的后果就是模型会“遗忘”窗口之外的内容。它可能只记住了文档开头和结尾的几句话中间最重要的部分反而被丢掉了这样生成的总结或答案自然就不准确、不完整。所以处理长文本的核心思路就变成了如何把一部长篇巨著拆解成模型能够消化的一章一章、一节一节并且让模型在理解每一部分的基础上最终形成一个对整体的连贯认知。听起来有点像我们人类阅读长篇报告的方法先通读各个章节再归纳中心思想。接下来我们就来看看具体怎么用技术实现这个思路。2. 基础策略文本分块与摘要串联最直观、也是最基础的方法就是把长文档切成一块一块的“豆腐干”让模型分别处理每一块然后再把各块的结果拼凑起来。这种方法虽然简单但在很多场景下足够有效。2.1 如何科学地“切豆腐干”切文本可不是随便找把刀乱砍。切得好模型理解起来就顺畅切得不好可能会把一个完整的句子或概念拦腰斩断导致模型获取的信息是破碎的。这里有几个实用的分块原则按语义切分这是最理想的方式。尽量在段落结束、章节标题处进行分割保证每一块在内容上是相对独立的。比如技术论文可以按“摘要、引言、方法、实验、结论”来分块。设置重叠区为了避免切割点正好落在关键信息上我们可以在相邻的两块文本之间设置一小段重叠区域。比如前一块的结尾100个字同时作为下一块的开头。这样能确保上下文信息的平滑过渡。控制块大小每块文本的长度需要显著小于模型的上下文窗口。因为除了文本本身你给模型的指令比如“请总结以下内容”也会占用窗口空间。通常块大小设置为窗口长度的60%-70%是个安全的起点。光说理论有点干我们直接看代码。下面是一个按固定字符数分块并添加重叠区域的简单示例def split_text_with_overlap(text, chunk_size500, overlap_size100): 将长文本分割成固定大小的块并添加重叠区域。 参数: text: 要分割的原始文本。 chunk_size: 每个文本块的目标大小字符数。 overlap_size: 相邻块之间的重叠字符数。 返回: 一个包含文本块的列表。 if chunk_size overlap_size: raise ValueError(chunk_size 必须大于 overlap_size) chunks [] start 0 text_length len(text) while start text_length: # 计算当前块的结束位置 end start chunk_size # 如果结束位置超过文本长度则取到文本末尾 if end text_length: chunk text[start:] chunks.append(chunk) break # 为了避免在单词或句子中间切断可以向后查找一个合适的断点如句号、换行 # 这里为了简单我们直接截取更复杂的实现可以寻找最近的标点或空格。 chunk text[start:end] chunks.append(chunk) # 移动起始位置减去重叠部分实现重叠 start chunk_size - overlap_size return chunks # 示例模拟一篇长文档 long_document 这里是一篇非常长的技术文档内容为了示例清晰我们仅用一段文字代替 自然语言处理是人工智能领域的重要分支。近年来基于Transformer架构的大模型取得了突破性进展。 这些模型如GPT系列、LLaMA系列在文本生成、理解、翻译等任务上表现出色。 然而模型参数量的增长也带来了巨大的计算开销和部署成本。 因此研究更高效的模型架构和训练方法如模型压缩、知识蒸馏、低秩适应等成为当前的热点。 长文本处理是另一个关键挑战需要设计专门的算法来扩展模型的上下文处理能力。 # 假设我们的模型窗口很小只能处理300字符 chunks split_text_with_overlap(long_document, chunk_size200, overlap_size50) for i, chunk in enumerate(chunks): print(f--- 块 {i1} (长度{len(chunk)}) ---) print(chunk[:150] ... if len(chunk) 150 else chunk) # 打印前150字符 print()这段代码会把文档切成200字符一块相邻块之间有50字符的重叠。你可以根据你的文档特点比如多是短句还是长段落来调整chunk_size和overlap_size。2.2 让模型处理每一块并汇总文本切好后下一步就是让InternLM2-Chat-1.8B模型来逐一处理这些块。对于每一块我们都可以让它执行一个特定的任务比如“提取本部分的关键信息”或“用一句话总结本部分”。处理完所有块之后你会得到一堆针对局部内容的总结。最后一步就是把这些局部总结再喂给模型一次让它生成一个最终的、全局性的摘要。# 假设我们已经有了模型加载和对话的函数 # from transformers import AutoTokenizer, AutoModelForCausalLM # model, tokenizer load_your_model() # 请替换为你的模型加载代码 def summarize_chunk(model, tokenizer, chunk_text, prompt_template请总结以下内容\n{}): 使用模型总结单个文本块。 prompt prompt_template.format(chunk_text) # 这里需要根据你使用的模型库如Transformers来编写具体的生成代码 # 以下是伪代码逻辑 inputs tokenizer(prompt, return_tensorspt, truncationTrue, max_length512) outputs model.generate(**inputs, max_new_tokens150) summary tokenizer.decode(outputs[0], skip_special_tokensTrue) # 简单处理移除可能重复的prompt if summary.startswith(prompt): summary summary[len(prompt):].strip() return summary def summarize_long_document_by_chunks(model, tokenizer, long_text): 通过分块总结的方式处理长文档。 # 1. 分块 chunks split_text_with_overlap(long_text, chunk_size600, overlap_size100) all_chunk_summaries [] print(f文档被分割成 {len(chunks)} 块。) # 2. 总结每一块 for i, chunk in enumerate(chunks): print(f正在处理第 {i1} 块...) chunk_summary summarize_chunk(model, tokenizer, chunk) all_chunk_summaries.append(f【第{i1}部分摘要】: {chunk_summary}) # 3. 汇总所有块的摘要生成最终总结 combined_summaries \n.join(all_chunk_summaries) final_prompt f你收到了一个长文档的各个部分摘要请基于这些摘要生成一个完整、连贯的文档总结\n{combined_summaries} final_summary summarize_chunk(model, tokenizer, combined_summaries, prompt_template{}) return final_summary # 使用示例 # final_result summarize_long_document_by_chunks(model, tokenizer, your_long_document) # print(最终总结, final_result)这种方法的好处是简单直接容易实现。但它也有个明显的缺点信息损失。模型在生成第一轮分块摘要时已经进行了一次压缩和抽象有些细节可能丢失了。当它基于这些可能已经失真的摘要去生成最终总结时误差可能会被放大。对于需要高精度回答具体问题而非整体总结的场景这就显得力不从心了。3. 进阶技巧滑动窗口与上下文管理如果你不只是想要一个整体总结而是希望模型能基于整篇文档回答任意位置的细节问题比如“在第三章第四节中作者提到的实验参数是多少”那么分块摘要法就捉襟见肘了。这时我们需要更精细的策略——滑动窗口。3.1 什么是滑动窗口你可以把滑动窗口想象成一个“阅读灯”。模型的上下文窗口就是这个灯光照亮的范围。要阅读一本很厚的书我们不会把书撕成一页一页而是用手移动这个阅读灯照亮当前正在阅读的那几页。在处理长文本问答时滑动窗口的工作流程是这样的定位当用户提出一个问题例如“第三章第四节的实验参数是什么”系统首先需要分析问题确定答案最可能出现在文档的哪个区域。取景系统以这个区域为中心截取一段长度等于模型上下文窗口的文本。这个窗口就像阅读灯照亮的范围。问答将这段“窗口文本”连同用户的问题一起提交给模型让它在这个有限的上下文中寻找答案。滑动如果模型在当前窗口内没有找到足够信心的答案或者我们需要验证答案系统可以将窗口向前或向后“滑动”一段距离获取新的上下文再次提问。这种方法的核心优势在于它让模型始终在原始的、未经压缩的文本上工作最大程度保留了信息的完整性特别适合事实性问答。3.2 构建一个简单的滑动窗口问答器实现一个完整的滑动窗口问答系统涉及检索、窗口选择、答案融合等多个模块。这里我们先实现一个最基础的版本假设我们已经知道答案可能出现在文档的某个大致位置例如通过关键词匹配找到然后围绕这个位置截取上下文。def sliding_window_qa(model, tokenizer, full_document, question, window_size1000, stride500): 一个简单的滑动窗口问答演示。 注意这是一个简化版本实际应用中需要更智能的窗口定位如使用检索器。 doc_length len(full_document) answers [] confidence_scores [] # 简化为用答案长度作为置信度代理实际应用需更复杂的评估 # 1. 简单定位这里我们假设从文档开头开始滑动。实际应用应使用检索。 start_pos 0 while start_pos doc_length: # 2. 截取窗口 end_pos min(start_pos window_size, doc_length) window_text full_document[start_pos:end_pos] # 3. 构建问答提示 # 使用InternLM2的对话格式 prompt f|im_start|user 请根据以下上下文回答问题。如果上下文不包含答案请直接说“根据上下文无法回答”。 上下文{window_text} 问题{question}|im_end| |im_start|assistant # 4. 调用模型生成答案 inputs tokenizer(prompt, return_tensorspt, truncationTrue, max_length2048) outputs model.generate(**inputs, max_new_tokens200, temperature0.1) answer tokenizer.decode(outputs[0], skip_special_tokensTrue) # 提取assistant的回复部分 answer answer.split(|im_start|assistant)[-1].strip() # 5. 收集答案这里简单收集实际需去重和融合 if 无法回答 not in answer and len(answer) 5: # 简单过滤 answers.append(answer) confidence_scores.append(len(answer)) # 代理置信度 # 6. 滑动窗口 start_pos stride if start_pos doc_length: break # 7. 返回结果这里返回所有答案实际应选择最可信的或进行融合 if answers: # 简单返回最长的答案作为演示 best_answer answers[confidence_scores.index(max(confidence_scores))] return best_answer, answers else: return 未在文档中找到明确答案。, [] # 使用示例 # question 模型压缩的主要方法有哪些 # answer, all_answers sliding_window_qa(model, tokenizer, long_document, question) # print(最佳答案, answer) # print(所有候选答案, all_answers)这个示例展示了滑动窗口的基本骨架。在实际项目中关键在于第一步的“定位”。我们通常不会傻傻地从头滑到尾那样效率太低。而是会先用一个快速的检索模型比如基于BM25或稠密向量的检索器根据问题找到文档中最相关的几个片段然后只在这些片段上应用滑动窗口进行精读。这就是检索增强生成RAG系统的核心思想之一。4. 实战搭建长文档问答管道现在我们把前面讲的知识组合起来尝试设计一个更健壮、更实用的长文档问答管道。这个管道会包含文本预处理、智能分块、向量检索和最终生成几个步骤。4.1 系统架构设计我们的管道大致会遵循以下流程文档加载与预处理读取你的长文档PDF、Word、TXT等进行清洗去除无关格式、特殊字符。文本分块与向量化将清洗后的文本按语义分块。然后使用一个嵌入模型如text2vec、BGE等将每一块文本转换为一个向量一堆数字这个向量代表了这块文本的语义。把这些向量和对应的文本块保存到向量数据库如Chroma、FAISS中。问题检索当用户提问时用同样的嵌入模型将问题也转换为向量。然后在向量数据库中搜索与问题向量最相似的几个文本块向量。这一步就是“定位”快速找到可能与答案相关的文档区域。上下文构建与生成将检索到的相关文本块作为上下文与用户问题一起构建一个Prompt发送给InternLM2-Chat-1.8B模型。模型基于这个“浓缩”的上下文生成最终答案。4.2 关键代码示例检索与生成这里我们使用langchain和chroma来简化向量数据库的操作。首先确保安装必要的库pip install langchain langchain-community chromadb sentence-transformersfrom langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.vectorstores import Chroma from langchain.embeddings import HuggingFaceEmbeddings from langchain.chains import RetrievalQA from langchain.llms import HuggingFacePipeline from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline import torch # 1. 加载长文档这里用字符串模拟实际可从文件读取 with open(your_long_document.txt, r, encodingutf-8) as f: long_text f.read() # 2. 文本分块使用LangChain提供的智能分块器能更好保持语义 text_splitter RecursiveCharacterTextSplitter( chunk_size500, # 每个块的大小 chunk_overlap100, # 块之间的重叠 length_functionlen, separators[\n\n, \n, 。, , , , , 、, , ] # 按此优先级分割 ) chunks text_splitter.split_text(long_text) print(f文档被分割成 {len(chunks)} 个文本块。) # 3. 加载嵌入模型用于将文本转换为向量 embedding_model HuggingFaceEmbeddings( model_nameBAAI/bge-small-zh-v1.5, # 一个不错的中文嵌入模型 model_kwargs{device: cuda if torch.cuda.is_available() else cpu}, encode_kwargs{normalize_embeddings: True} # 归一化提升检索效果 ) # 4. 创建向量数据库 vectorstore Chroma.from_texts( textschunks, embeddingembedding_model, persist_directory./chroma_db # 数据持久化目录 ) # 创建检索器这里设置检索前4个最相关的块 retriever vectorstore.as_retriever(search_kwargs{k: 4}) # 5. 加载InternLM2-Chat-1.8B作为语言模型 model_name internlm/internlm2-chat-1_8b tokenizer AutoTokenizer.from_pretrained(model_name, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_name, torch_dtypetorch.float16, device_mapauto, trust_remote_codeTrue ) # 构建LangChain可用的Pipeline pipe pipeline( text-generation, modelmodel, tokenizertokenizer, max_new_tokens512, temperature0.1, do_sampleTrue, ) llm HuggingFacePipeline(pipelinepipe) # 6. 创建检索增强问答链 qa_chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, # 最简单的方式将所有检索到的上下文塞进Prompt retrieverretriever, return_source_documentsTrue, # 返回参考来源 chain_type_kwargs{ prompt: ... # 可以在这里自定义Prompt模板让模型更专注于基于上下文回答 } ) # 7. 进行问答 question 这篇文档中提到的核心挑战是什么 result qa_chain({query: question}) print(问题, question) print(答案, result[result]) print(\n--- 参考来源 ---) for i, doc in enumerate(result[source_documents]): print(f来源 {i1} (长度{len(doc.page_content)}): {doc.page_content[:200]}...) # 打印前200字符这个管道已经具备了处理长文档问答的基本能力。RecursiveCharacterTextSplitter会尽量在句子的边界进行分割比我们之前简单的按字符切分更合理。BGE嵌入模型能很好地理解中文语义帮助我们找到与问题最相关的文本块。最后InternLM2-Chat-1.8B模型基于这些检索到的“证据”来生成答案准确度会远高于让它直接“盲猜”。你可以根据需要对各个环节进行调优比如调整分块大小、重叠长度、检索的文档数量k值或者设计更精巧的Prompt来指导模型如何利用上下文。5. 总结与建议聊了这么多从最基础的分块总结到滑动窗口再到一个完整的RAG问答管道核心思想其实就一个化整为零按需取用。面对InternLM2-Chat-1.8B这类轻量级模型的上下文限制我们不应该把它看作一个无法逾越的障碍而是一个需要巧妙设计工作流来适应的特性。在实际项目中选择哪种策略取决于你的具体需求。如果只是需要一份概览性的文档总结分块摘要法简单快捷。如果你的应用场景是知识库问答需要精准回答细节问题那么搭建一个基于向量检索的RAG管道几乎是标准答案。滑动窗口则像是一个折中的方案或者作为RAG系统中精读环节的补充。从我自己的使用经验来看有几点小建议可以分享。首先文本分块是基础这块做不好后面再高级的算法效果也会打折多花点时间根据你的文档类型调整分块参数很值得。其次检索模型的质量至关重要选择一个在中文上表现好的嵌入模型比如BGE或M3E能让你的系统“找得更准”。最后Prompt工程依然有效在把上下文和问题交给InternLM2之前用清晰的指令告诉它“请严格根据以下上下文回答”能显著减少它胡编乱造的情况。当然今天介绍的只是入门级的方案。工业级的系统还会考虑缓存、多路召回、答案重排序、置信度评估等更多复杂问题。但万变不离其宗理解了今天这些核心概念和代码你就已经拿到了解决长文本处理问题的钥匙。不妨就用你手头的一篇长文档从第一个分块总结的代码开始试试吧看看效果如何。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。

更多文章