LongCite:大模型长文本问答的细粒度引用生成框架解析与实践
1. 项目概述
最近在折腾大语言模型的长文本问答应用时,遇到了一个挺头疼的问题:模型给出的答案,尤其是基于长文档的复杂回答,到底哪句话是依据哪个原文片段生成的?这事儿在需要高可信度的场景里,比如学术研究、法律咨询或者企业知识库,就成了一个关键瓶颈。你总不能每次都让用户自己去几十页的文档里大海捞针,验证模型说的对不对吧?这体验太差了。
正好,清华大学的THUDM团队开源了一个叫 LongCite 的项目,直击这个痛点。简单来说,LongCite是一个专门训练大模型在长上下文问答中生成 细粒度引用 的框架。它能让模型在给出答案的同时,精确地标注出答案中每个陈述所对应的原文句子位置,就像写论文时标注参考文献一样。这对于提升AI生成内容的可验证性和可信度,意义重大。无论你是想构建一个更可靠的企业级问答系统,还是想深入研究大模型的长文本理解与归因能力,LongCite都提供了一个非常扎实的起点和一套完整的工具链。
2. LongCite的核心价值与设计思路拆解
2.1 长文本问答的“黑箱”困境
在深入LongCite之前,我们先得理解它要解决什么问题。传统的大模型长文本问答,流程通常是“输入长文档+问题 -> 模型生成答案”。这个答案看起来可能很流畅、很专业,但它是一个“黑箱”。用户,甚至开发者自己,都很难判断:
- 答案是否准确 :模型是否真的从给定的上下文中找到了正确答案,还是凭借其内部知识(可能过时或错误)进行了“脑补”?
- 依据是否充分 :答案中的每一个事实性陈述,是否都能在原文中找到确切的支撑?
- 如何快速验证 :当用户对某个点存疑时,如何能最快捷地定位到原文的相关部分进行核对?
没有引用的答案,就像一份没有出处的报告,权威性大打折扣。尤其是在处理法律条文、技术手册、财报等严肃文档时,这种可追溯性至关重要。
2.2 LongCite的解决方案:从“回答”到“回答+证明”
LongCite的思路很清晰:不仅要模型“答对”,还要它“证明自己答对了”。它通过监督微调,教会模型一种新的输出格式。模型不再仅仅输出一段纯文本答案,而是输出一个结构化的结果,包含:
- 答案 :自然语言形式的最终回答。
- 带引用的陈述 :将答案分解为若干个独立的、原子化的事实陈述,并为每个陈述附上一个或多个指向原文具体句子的引用索引。
例如,对于问题“罗伯特·格迪斯的职业是什么?”,模型不会只输出“他是一名建筑师”。而是可能输出:
陈述1: 罗伯特·格迪斯是一名建筑师。 [引用自原文句子#7]
陈述2: 他曾于1965年至1982年担任普林斯顿大学建筑学院院长。 [引用自原文句子#7]
这样,任何阅读答案的人都可以瞬间定位到原文的第7句话进行核实:“Robert Geddes, 99, architect, dean of the Princeton University School of Architecture (1965–1982) (b. 1923).”
2.3 技术路径:CoF数据构建管道
教会模型做这种细粒度引用,最大的挑战在于 高质量训练数据的匮乏 。人工为海量的长文档问答对标注句子级引用,成本高到无法承受。LongCite的核心创新之一,就是其名为 CoF 的自动化数据构建管道。
CoF,即“从粗到细”,是一个巧妙的、分阶段的数据合成流程:
- 阶段一:问答生成 。首先,利用强大的大模型,基于给定的长文档,自动生成一系列多样化的问答对。这解决了“问什么”的问题。
- 阶段二:块级引用生成 。让模型为生成的答案,初步标注出相关的文档“块”。这里的“块”可能是一个段落或几个连续的句子。这一步先做一个粗粒度的关联。
- 阶段三:句子级引用生成 。这是最关键的一步。在上一步定位的粗粒度“块”内部,再次调用模型进行精细化分析,将答案中的每一个事实陈述,精确地关联到块内的 单个或多个句子 上。这一步实现了从“块”到“句”的细化。
- 阶段四:后处理与过滤 。对生成的数据进行清洗、去重和质量过滤,剔除引用不准确或问答质量低的样本,最终形成高质量的监督微调数据集。
这个管道的精妙之处在于,它用大模型的能力来“标注”大模型,通过分步拆解降低了单次任务的复杂度,从而在可控的成本下,生成了规模达4.5万的高质量训练数据 LongCite-45k 。这为后续的模型微调奠定了坚实的基础。
注意 :CoF管道虽然自动化程度高,但其生成数据的质量高度依赖于所使用的“教师模型”的能力。在构建自己的数据时,选择合适的基础模型并设计严谨的提示词至关重要。
3. 模型部署与快速上手实践
3.1 环境准备与模型选择
LongCite团队基于两个优秀的开源基座模型进行了微调,并开源了训练好的模型:
- LongCite-glm4-9b : 基于智谱AI的GLM-4-9B模型微调。
- LongCite-llama3.1-8b : 基于Meta的Llama-3.1-8B模型微调。
两个模型均支持高达128K的上下文长度。选择哪个取决于你的偏好和硬件条件。GLM系列对中文支持通常更友好,而Llama系列拥有更广泛的社区生态。我的实验环境是一台配备单张24GB显存显卡的服务器,这两个8B/9B量级的模型都可以在量化后流畅运行。
首先,搭建基础环境。建议使用Python 3.9+,并创建一个新的虚拟环境。
# 创建并激活虚拟环境
conda create -n longcite python=3.9
conda activate longcite
# 安装核心依赖,注意transformers版本要求
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 根据你的CUDA版本调整
pip install transformers>=4.43.0 datasets accelerate sentencepiece
3.2 使用Transformers库直接调用
这是最简单直接的本地测试方式。以下代码演示了如何使用 LongCite-glm4-9b 模型进行一次问答。
import json
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
# 1. 加载模型和分词器
model_name = 'THUDM/LongCite-glm4-9b' # 也可替换为 'THUDM/LongCite-llama3.1-8b'
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.bfloat16, # 使用BF16精度节省显存
trust_remote_code=True,
device_map='auto' # 自动将模型层分配到可用设备上
)
# 2. 准备长上下文和问题
context = '''
W. Russell Todd, 94, United States Army general (b. 1928). February 13. Tim Aymar, 59, heavy metal singer (Pharaoh) (b. 1963). Marshall \"Eddie\" Conway, 76, Black Panther Party leader (b. 1946). Roger Bonk, 78, football player (North Dakota Fighting Sioux, Winnipeg Blue Bombers) (b. 1944). Conrad Dobler, 72, football player (St. Louis Cardinals, New Orleans Saints, Buffalo Bills) (b. 1950). Brian DuBois, 55, baseball player (Detroit Tigers) (b. 1967). Robert Geddes, 99, architect, dean of the Princeton University School of Architecture (1965–1982) (b. 1923). Tom Luddy, 79, film producer (Barfly, The Secret Garden), co-founder of the Telluride Film Festival (b. 1943). David Singmaster, 84, mathematician (b. 1938).
'''
query = "What was Robert Geddes' profession?"
# 3. 调用模型专用方法进行查询
result = model.query_longcite(
context,
query,
tokenizer=tokenizer,
max_input_length=128000, # 最大输入长度
max_new_tokens=1024 # 生成答案的最大token数
)
# 4. 解析并打印结果
print("问题:", query)
print("\n--- 模型生成的答案 ---")
print(result['answer'])
print("\n--- 带引用的详细陈述 ---")
print(json.dumps(result['statements_with_citations'], indent=2, ensure_ascii=False))
print("\n--- 上下文(已分句) ---")
# 分句结果是一个列表,这里打印前几个作为示例
for i, sent in enumerate(result['splited_context'][:5]):
print(f"[{i}] {sent}")
关键参数解析 :
torch_dtype=torch.bfloat16:这是显存紧张时的救星。BF16浮点数格式在几乎不损失精度的情况下,比FP32节省一半显存。如果你的显卡不支持BF16,可以尝试torch.float16。device_map='auto':让accelerate库自动处理模型在GPU和CPU间的分层加载,对于大模型非常友好。max_input_length:务必根据你的实际上下文长度设置。虽然模型支持128K,但传入过长的上下文会急剧增加计算和显存开销。query_longcite:这是LongCite模型自定义的方法,封装了处理长上下文、分句、生成带引用答案的完整逻辑,是项目提供的核心接口。
3.3 部署为交互式Web服务
如果你想搭建一个类似Demo的聊天界面,方便团队测试或展示,可以使用项目提供的Streamlit应用。
# 首先安装streamlit
pip install streamlit
# 克隆LongCite仓库(如果尚未克隆)
git clone https://github.com/THUDM/LongCite.git
cd LongCite
# 运行Demo,指定使用的GPU
CUDA_VISIBLE_DEVICES=0 streamlit run demo.py --server.fileWatcherType none
执行后,浏览器会自动打开一个本地页面(通常是 http://localhost:8501 )。在界面中,你可以粘贴长文本,输入问题,模型会返回带高亮引用的答案,交互体验非常好。
3.4 高性能生产级部署:使用vLLM
对于需要高并发、低延迟的生产环境,使用原始的Transformers进行推理效率较低。LongCite项目贴心地提供了基于 vLLM 的推理示例。vLLM是一个高性能的LLM推理和服务引擎,以其高效的PagedAttention注意力算法而闻名,能极大提升吞吐量。
# 安装vLLM
pip install vllm
然后,你可以参考项目中的 vllm_inference.py 脚本。其核心思路是利用vLLM的 LLM 类加载模型,并通过构造特定的提示模板来模拟 query_longcite 的功能。以下是一个简化的概念性代码:
from vllm import LLM, SamplingParams
import json
# 初始化vLLM引擎
llm = LLM(model="THUDM/LongCite-glm4-9b",
trust_remote_code=True,
max_model_len=128000, # 与模型支持的上下文一致
tensor_parallel_size=1) # 如果多卡,可以设置并行数
# 构造符合模型训练格式的提示词
def build_prompt(context, query):
# 这里需要根据LongCite模型具体的对话模板来构造
# 通常是 [Context] ... [/Context] [Question] ... [/Question] 的格式
# 具体格式请查阅模型卡或训练代码
prompt_template = f"[Context]\n{context}\n[/Context]\n\n[Question]\n{query}\n[/Question]\n\n[Answer]"
return prompt_template
sampling_params = SamplingParams(temperature=0.1, top_p=0.9, max_tokens=1024)
# 批量推理
prompts = [build_prompt(context1, query1), build_prompt(context2, query2)]
outputs = llm.generate(prompts, sampling_params)
for output in outputs:
generated_text = output.outputs[0].text
# 后续需要解析 generated_text,提取答案和引用信息
# 这部分解析逻辑需要自己实现,以匹配模型的输出格式
实操心得 :vLLM部署时,最大的挑战在于 输出解析 。LongCite自定义的
query_longcite方法内部完成了分句、引用关联等复杂后处理。当使用vLLM等纯推理引擎时,你需要自己实现这部分解析逻辑,或者将模型输出设计成易于解析的结构化格式(如JSON)。在真正投入生产前,务必对解析逻辑进行充分测试。
4. 模型训练与数据准备详解
如果你不满足于使用开源的预训练模型,想要在自己的领域数据上训练一个专属的“引用大师”,那么理解其训练流程至关重要。
4.1 获取与理解训练数据
首先,下载官方开源的LongCite-45k数据集,这是训练的基石。
from datasets import load_dataset
dataset = load_dataset('THUDM/LongCite-45k')
print(dataset)
# 通常包含 'train' 和 'validation' 分割
# 查看一条样本的结构
sample = dataset['train'][0]
print(json.dumps(sample, indent=2, ensure_ascii=False))
一条典型的数据样本可能包含以下关键字段:
context: 长文本上下文。question: 针对上下文的问题。answer: 标准的答案文本。statements_with_citations: 一个列表,其中每个元素是一个字典,包含statement(陈述)和citations(对应的原文句子索引列表)。这就是模型要学习的目标。splited_context: 将原文分割成的句子列表,citations中的索引即指向此列表。
4.2 数据混合策略
仅用4.5万条引用数据训练,模型可能会“偏科”,即只擅长生成引用,而丢失了基础的语言理解和通用对话能力。因此, 混合训练 是常见的策略。你可以将LongCite-45k与通用的指令微调数据混合,例如ShareGPT、Alpaca数据等。
from datasets import concatenate_datasets, load_dataset
# 加载通用SFT数据
general_dataset = load_dataset("anon8231489123/ShareGPT_Vicuna_unfiltered", split="train")
# 这里需要对general_dataset进行格式化,使其符合你的训练模板(不含citation字段)
formatted_general_data = general_dataset.map(format_general_func)
# 加载LongCite数据
cite_dataset = load_dataset('THUDM/LongCite-45k', split='train')
# 混合数据集,可以按比例采样
mixed_dataset = concatenate_datasets([cite_dataset, formatted_general_data])
混合比例需要根据任务调整。如果希望模型强于引用,可以增加LongCite数据的权重(如7:3);如果希望更均衡,可以1:1混合。
4.3 训练框架选择与实施
官方推荐使用 Megatron-LM 进行训练,这是一个由NVIDIA开发的大规模分布式训练框架,性能高效但配置复杂。对于大多数研究者和中小团队,更轻量级的方案是采用 LongAlign 项目的训练代码。
LongAlign同样来自THUDM团队,它基于流行的深度学习框架(如DeepSpeed),提供了支持32K长度训练的良好实现。你可以按照LongAlign的仓库说明设置环境。训练脚本的核心是构造一个能处理长序列和特殊损失函数的训练循环。
关键训练配置通常包括:
- 模型结构 :使用支持长上下文的模型,如GLM-4或Llama-3.1,并启用相应的注意力机制(如FlashAttention-2)。
- 序列长度 :设置为32K,以充分利用LongCite数据的特性。
- 损失函数 :标准的语言建模损失(交叉熵)。模型学习的目标是生成包含正确引用标记的完整序列。
- 学习率与调度 :采用较小的学习率(如1e-5到5e-5),配合余弦退火或线性预热调度。
- 梯度累积 :在单卡显存有限的情况下,通过梯度累积来模拟更大的批量大小。
一个简化的训练步骤示意如下:
# 假设在LongAlign环境中
cd /path/to/LongAlign
# 修改训练配置文件,指定数据路径、模型名称、序列长度等参数
vim config/train_longcite.yaml
# 启动训练
deepspeed --num_gpus=4 train.py --config config/train_longcite.yaml
注意事项 :长序列训练对硬件要求极高,32K序列的注意力计算开销是平方级的。务必确保有足够的GPU显存(可能需要多张A100/H100),并启用激活检查点、混合精度训练等优化技术。对于资源有限的团队,可以考虑在现有LongCite模型上进行 LoRA微调 ,只更新少量参数,以适应特定领域的术语或文档格式。
5. 评估体系:LongBench-Cite详解
如何衡量一个模型生成引用的好坏?LongCite团队提出了一个自动化的评测基准—— LongBench-Cite 。
5.1 基准构成与评估维度
LongBench-Cite基于两个已有的长文本评测集构建:
- LongBench :包含多种长文本理解任务。
- LongBench-Chat :包含长文本对话任务。
从这些数据集中,筛选出需要事实性回答的问答对,并为其构建句子级的真实引用标注(可能通过自动或人工方式)。评估主要围绕两个核心维度:
- 答案正确性 :模型生成的答案本身是否准确?这衡量的是模型的基础问答能力。
- 引用质量 :模型生成的引用是否精确、完整?这衡量的是模型的归因能力。
5.2 自动化评估流程
评估流程高度自动化,依赖于强大的大模型作为“裁判”。项目提供了完整的评估脚本。
第一步:生成预测结果 。 你需要用你的模型(比如你微调好的LongCite模型)和基线模型(比如GPT-4o)在LongBench-Cite测试集上运行,生成答案和引用。
# 对微调模型进行预测
python LongBench-Cite/pred_sft.py \
--model_name_or_path /path/to/your/longcite-model \
--input_data LongBench-Cite/data/test.jsonl \
--output_data ./results/sft_predictions.jsonl
# 对普通大模型进行少样本预测(作为基线)
python LongBench-Cite/pred_one_shot.py \
--model_name gpt-4o \
--input_data LongBench-Cite/data/test.jsonl \
--output_data ./results/oneshot_predictions.jsonl
pred_one_shot.py 脚本通常会实现一个“少样本提示”策略,在提示词中给模型展示一两个带引用的例子,引导其按照相同格式输出。
第二步:评估引用质量 。 运行 eval_cite.py ,该脚本会使用GPT-4o作为评判员,对比模型生成的引用和真实引用,从多个角度打分。
# 在脚本内部,评估提示词可能类似这样:
judge_prompt = f"""
你是一个评估AI生成引用质量的专家。请比较“模型生成的引用”和“真实引用”。
生成引用: {pred_citations}
真实引用: {gold_citations}
请从以下方面评估:
1. 精确度:生成的引用是否都指向了支持陈述的必要句子?有无多余或错误引用?
2. 召回率:是否所有必要的真实引用都被生成出来了?有无遗漏?
3. 位置准确性:引用的句子索引是否完全正确?
请给出综合评分(1-5分)。
"""
评估脚本会汇总所有样本的得分,计算出平均引用质量分数。
第三步:评估答案正确性 。 运行 eval_correct.py ,同样使用GPT-4o作为裁判,判断模型生成的答案在事实层面上是否正确,忽略引用部分。
judge_prompt = f"""
请判断“模型答案”相对于“标准答案”在事实层面上是否正确。
上下文: {context}
问题: {question}
标准答案: {gold_answer}
模型答案: {pred_answer}
请只关注事实内容是否正确,忽略表述差异。正确输出“是”,错误输出“否”。
"""
5.3 解读评估结果与模型对比
通过上述流程,你会得到类似官方论文中的评测表格。解读时需关注:
- 综合表现 :LongCite微调模型在“答案正确性”上应与强大基线(如GPT-4o)相当或略低,但在“引用质量”上应有显著优势。这证明了微调的有效性——它在不大幅损害基础能力的前提下,赋予了模型强大的归因能力。
- 引用质量细分 :观察“精确度”和“召回率”。高精确度低召回率,说明模型引用保守,只引用最有把握的句子,可能漏掉一些支撑信息。低精确度高召回率,说明模型引用激进,可能包含无关句子。理想的模型应在两者间取得平衡。
- 不同任务类型表现 :分析模型在“单文档QA”、“多文档摘要”、“对话”等不同子任务上的表现差异。这有助于你了解模型的优势场景和薄弱环节。
实操心得 :自动化评估虽然方便,但成本不低(调用GPT-4o API)。在小规模验证时,可以抽样进行 人工评估 ,制定更细致的评分标准(如引用是否必要、是否精确到最小信息单元等),这对深入理解模型错误模式非常有帮助。此外,评估结果依赖于“裁判模型”的能力和倾向性,需要保持批判性眼光。
6. 常见问题与排查技巧实录
在实际部署和实验LongCite的过程中,我遇到了一些典型问题,这里记录下来供大家参考。
6.1 显存溢出问题
问题描述 :在加载模型或处理长上下文时,出现 CUDA out of memory 错误。
排查与解决 :
- 量化加载 :这是最有效的手段。使用
bitsandbytes库进行4位或8位量化。from transformers import BitsAndBytesConfig bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True, bnb_4bit_quant_type="nf4" ) model = AutoModelForCausalLM.from_pretrained( model_name, quantization_config=bnb_config, # 使用量化配置 trust_remote_code=True, device_map='auto' ) - 降低精度 :如果不用量化,确保使用了
torch.bfloat16或torch.float16。 - 限制输入长度 :通过
max_input_length参数严格控制输入模型的上下文token数量。可以使用tokenizer先进行编码,估算长度。tokens = tokenizer.encode(context) if len(tokens) > 120000: # 留一些空间给问题和其他模板token # 进行截断或摘要处理 context = some_truncate_function(context, target_length=120000) - 使用CPU卸载 :对于非常大的模型,可以结合
accelerate和device_map设置,将部分层卸载到CPU内存,但会显著降低推理速度。
6.2 模型生成格式错误或引用缺失
问题描述 :模型生成的输出不是预期的JSON结构,或者 statements_with_citations 字段为空。
排查与解决 :
- 检查提示模板 :确保你调用API或构造输入时,使用的提示格式与模型训练时一致。LongCite模型很可能依赖于特定的上下文标记(如
[Context]...[/Context])。直接使用官方提供的query_longcite方法是最稳妥的。 - 检查后处理逻辑 :如果使用vLLM等自定义服务,你的输出解析代码必须能正确处理模型输出的原始文本。模型可能输出的是带有特殊标记的文本,需要正则表达式或字符串匹配来提取结构化信息。仔细查看原始输出,理解其模式。
- 调整生成参数 :尝试降低
temperature(如设为0.1),增加top_p(如0.9),使生成结果更确定、更集中,减少“胡言乱语”的概率。 - 确认模型完整性 :重新下载模型文件,检查是否有损坏。
6.3 引用不准确或冗余
问题描述 :模型能生成引用,但引用的句子与陈述不完全相关,或者引用了过多不必要的句子。
排查与解决 :
- 这是当前技术的局限 :细粒度引用本身是一个难题,模型可能无法完美区分哪些句子是核心支撑,哪些只是背景信息。评估时需结合“精确度”和“召回率”综合看待。
- 优化上下文质量 :确保输入模型的上下文是清晰、结构化的文本。如果原文是混乱的PDF解析结果或包含大量无关信息,会干扰模型的判断。在预处理阶段,可以尝试对文档进行清洗、分段、去重。
- 后处理过滤 :可以在模型输出后,增加一个简单的规则或轻量级模型过滤层,剔除那些置信度低(例如,模型生成引用时附带的分数低)或明显冗余的引用。
- 考虑多粒度引用 :对于某些场景,句子级引用可能过于严格。可以探索让模型同时输出“重要段落”和“关键句子”的多粒度引用,为用户提供更灵活的验证路径。
6.4 训练过程中的不收敛或效果差
问题描述 :在自己数据上微调后,模型要么不生成引用格式,要么生成质量很差。
排查与解决 :
- 数据质量检查 :首先检查你的训练数据格式是否正确。
statements_with_citations字段是否与官方数据格式严格一致?引用索引是否在splited_context的范围内? - 混合数据比例 :如果通用对话数据比例过高,模型可能会“忘记”如何生成引用。尝试提高LongCite类型数据的采样比例。
- 学习率与步数 :从预训练模型微调时,学习率不宜过大。尝试更小的学习率(如5e-6)和更多的训练步数,并保存多个检查点进行验证。
- 损失函数监控 :除了总损失,可以尝试计算一个额外的“引用格式合规性”的指标,例如,输出文本中是否包含
[citation]标记或正确JSON结构的比例,在验证集上监控它。 - 基础模型选择 :确保你选择的基础模型本身具备较强的长文本理解能力。在一个仅支持4K上下文的基础模型上微调128K的引用任务,效果必然不佳。
LongCite项目为大模型的可解释性和可信赖性迈出了坚实的一步。将这套技术集成到你的产品中,意味着你能向用户提供的不再是一个“神秘”的答案,而是一份附带“证据链”的清晰报告。这其中的工程细节和调优工作虽然繁琐,但当看到用户能够轻松验证并信任AI的输出时,你会觉得这一切都是值得的。从我自己的实践来看,先从官方预训练模型入手,在小规模内部场景中试点,逐步迭代数据预处理和后处理流程,是风险最低、收益最明确的落地路径。
更多推荐


所有评论(0)