系列 · NLP 技术前沿 · 第 12 篇

自然语言处理(十二):前沿技术与实战应用

系列收官:Agent 与工具调用(Function Calling、ReAct)、代码生成(Code Llama、Codex)、长上下文(Longformer、Infini-attention)、推理模型(o1、R1)、安全对齐、评估体系,以及基于 FastAPI + vLLM + Docker 的生产级部署。

经过十一章的探索,我们从原始文本一路走到了多模态基础模型。作为第十二章和最后一章,这里既是技术的最前沿,也是实际应用的起点——研究不再是纸上谈兵的论文,而是真正落地为服务:一个能够调用工具、编写和调试代码、完成上百步推理、处理 20 万 token 的合同文件,并通过 FastAPI 接口以 p95 延迟低于 300 毫秒支持上千并发用户的大型语言模型(LLM)。

能力的提升往往伴随着新的问题:模型可能自信地“编造”事实、在提示诱导下生成有害内容、意外泄露训练数据,或因部署不当导致计算成本失控。因此,本章分为两部分:前半部分聚焦模型能力的边界,包括智能体(Agent)、代码生成、长上下文处理与复杂推理;后半部分探讨如何将这些能力安全、可靠地落地为生产服务,涵盖安全性保障、系统性评估与高效部署,兼顾用户体验与成本可控。

自然语言处理(十二):前沿技术与实战应用 — 章节概览图


你将学到什么#

  • 智能体:Function Calling 协议与 ReAct 推理-行动循环,包含可运行的 Python 示例代码。
  • 代码生成:Codex、Code Llama 和 DeepSeek-Coder 在 HumanEval 基准上的表现对比,以及自我修复机制的工作原理。
  • 长上下文处理:滑动窗口、膨胀注意力和 Infini-attention 掩码的应用场景及选择建议。
  • 推理模型:o1 和 DeepSeek-R1 如何通过增加测试时计算量来提升准确性,以及为什么 CoT 现在需要结合策略条件。
  • 安全性:幻觉问题的分类体系、RLHF / DPO / Constitutional AI 对齐方法的比较,以及内容安全护栏的设计实践。
  • 评估方法:能力、安全性和效率三大基准测试的作用及其局限性,明确它们无法揭示的信息。
  • 生产环境部署:基于 FastAPI + vLLM + Docker 的参考架构、可观测性实现,以及具体的延迟优化目标。

前置知识#

NLP (12): 前沿与实践应用 —— 视觉化

  • 如果你已经读过本系列的前十一章,尤其是第 4 章 (Transformer)、第 6 章 (GPT)、第 8 章 (PEFT)、第 9 章 (LLM 内部机制)和第 10 章 (RAG),这将是一个很好的基础,这些内容会在后续讨论中频繁用到。
  • 需要对 Python 编程、基础的 asyncio 异步编程以及 Docker 容器的基本结构有一定的了解。
  • 如果你对强化学习有一些初步的认识,那么在阅读对齐部分时会更加得心应手。如果想深入学习,可以参考 强化学习第 12 章 —— RLHF 与 LLM 应用

Agent 与工具调用#

在指令微调(instruction tuning)之后,模型能力的最大飞跃莫过于学会了调用函数。一个普通的大型语言模型(LLM)本质上是一个“冻结的近似器”——它只能依赖训练时学到的知识,无法动态扩展。而具备智能体(Agent)能力的 LLM 则不同,它可以主动向外界请求信息、运行代码、查询数据库,并基于这些交互继续生成内容。这种转变让系统从一个“聪明的自动补全工具”升级为一个“可编程的执行器”。

ReAct 智能体架构

函数调用 —— 协议详解#

函数调用(Function Calling)是 OpenAI 在 2023 年中推广的一项技术,如今已成为 Claude、Gemini、Llama-3 和 Qwen 的标配功能。它是一种基于聊天完成接口(chat completion)的类型化协议:应用程序通过 JSON Schema 声明工具,模型则被训练成能够根据上下文选择直接回答问题或发起结构化的工具调用。整个过程分为五个清晰的阶段,没有复杂的黑箱操作:

函数调用流水线

  1. 工具声明:应用程序为每个工具注册名称、描述以及参数的 JSON Schema。
  2. 路由决策:模型读取用户输入,决定是直接回答还是调用工具。
  3. 参数生成:通过约束解码生成符合 schema 的 JSON 参数。
  4. 工具执行:由应用程序(而非模型)负责运行工具函数,最好在沙箱环境中执行。
  5. 结果整合:执行结果以 tool 消息的形式追加到对话历史中,模型据此生成最终回复。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import json
from openai import OpenAI

client = OpenAI()

TOOLS = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "查询指定城市的当前天气,返回摄氏度温度与简短状态。",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "城市英文名"},
                "unit": {"type": "string", "enum": ["celsius", "fahrenheit"], "default": "celsius"},
            },
            "required": ["city"],
        },
    },
}]

def get_weather(city: str, unit: str = "celsius") -> dict:
    # 真实实现会调用天气 API。
    return {"city": city, "temp": 25, "unit": unit, "condition": "sunny"}

def chat(user_msg: str) -> str:
    messages = [{"role": "user", "content": user_msg}]
    resp = client.chat.completions.create(
        model="gpt-4o-mini", messages=messages, tools=TOOLS, tool_choice="auto"
    )
    msg = resp.choices[0].message
    messages.append(msg)

    if msg.tool_calls:
        for call in msg.tool_calls:
            args = json.loads(call.function.arguments)
            result = get_weather(**args)
            messages.append({
                "role": "tool",
                "tool_call_id": call.id,
                "content": json.dumps(result, ensure_ascii=False),
            })
        resp = client.chat.completions.create(model="gpt-4o-mini", messages=messages)
        return resp.choices[0].message.content
    return msg.content

开发者常犯的三个错误。首先,让模型自己决定是否调用工具tool_choice="auto")。如果强行调用,模型可能会生成毫无意义的参数。其次,始终在沙箱中执行工具——只要你的 schema 允许,模型完全可能生成类似 delete_all_users() 的危险调用。最后,工具描述本身即是一种 prompt,需持续调优,直至模型能稳定选择正确工具。

ReAct —— 推理与行动的循环#

函数调用是一种单轮接口,而ReAct(Yao et al., ICLR 2023, arXiv:2210.03629 )将其扩展为一个迭代的 思考 -> 行动 -> 观察 循环。通过这种方式,模型可以分解复杂任务、根据中间结果分支处理,甚至重新规划路径。这一架构成为了 LangChain、AutoGPT、OpenAI Assistants、Claude 工具模式以及 2025 年大多数生产级智能体的核心骨架。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import re
from dataclasses import dataclass
from typing import Callable

@dataclass
class Tool:
    name: str
    description: str
    fn: Callable[[str], str]

REACT_PROMPT = """你是一个谨慎的推理智能体,可以使用以下工具:
{tool_block}

每一轮严格按下面的格式输出:
Thought: <一句话推理>
Action: <工具名>
Action Input: <单行参数>

完成时改用:
Thought: <推理>
Action: Final Answer
Action Input: <给用户的最终回答>

Question: {question}
{scratchpad}"""

class ReActAgent:
    def __init__(self, llm: Callable[[str], str], tools: list[Tool], max_steps: int = 8):
        self.llm = llm
        self.tools = {t.name: t for t in tools}
        self.max_steps = max_steps

    def _render_tools(self) -> str:
        return "\n".join(f"- {t.name}: {t.description}" for t in self.tools.values())

    def _parse(self, txt: str):
        m = re.search(r"Action:\s*(.+?)\s*\nAction Input:\s*(.+)", txt, re.S)
        if not m:
            raise ValueError(f"无法解析模型输出:\n{txt}")
        return m.group(1).strip(), m.group(2).strip()

    def run(self, question: str) -> str:
        scratch = ""
        for step in range(self.max_steps):
            prompt = REACT_PROMPT.format(
                tool_block=self._render_tools(),
                question=question,
                scratchpad=scratch,
            )
            out = self.llm(prompt)
            try:
                action, action_input = self._parse(out)
            except ValueError:
                return f"[第 {step} 步格式错误]\n{out}"

            if action == "Final Answer":
                return action_input
            if action not in self.tools:
                obs = f"未知工具 '{action}',可用:{list(self.tools)}"
            else:
                obs = self.tools[action].fn(action_input)

            scratch += f"\n{out}\nObservation: {obs}"
        return "[到达最大步数] " + scratch[-500:]

如何选择合适的方案?函数调用是默认的最佳选择:它结构清晰、prompt 简洁、单轮往返成本低。而ReAct则适用于需要多步规划、根据中间结果分支处理或从工具失败中恢复的场景,比如研究综述、数据分析、多跳问答和复杂预订等任务。对于介于两者之间的需求,现代框架允许将函数调用步骤组织成一张带显式状态的图,这通常是维护性最高的折中方案。

代码生成#

在众多应用领域中,代码生成是大语言模型(LLM)从“有趣的演示”蜕变为“不可或缺的工具”的最典型场景。GitHub 的数据显示,Copilot 用户大约会采纳三分之一的建议,而在基准任务中,开发效率提升了约 55%。这一转变背后的技术路径其实并不复杂:先用代码进行预训练,再通过指令微调,接着通过运行生成的代码来评估效果,最后加入一个自我修复的闭环机制。

代码生成流水线与 HumanEval pass@1

流水线结构#

现代代码生成 LLM 的流水线通常分为五个关键步骤:首先是用自然语言表达的意图,其次是经过增强的上下文(包括已打开的文件、代码仓库中的符号表、测试桩以及检索到的 API 文档),然后是由大量代码训练出的解码器负责生成代码,接下来是执行器对生成的代码进行编译并运行单元测试,最后是自我修复环节,将失败信息反馈给模型以优化输出。正是这个修复循环,使得一次性通过率(pass@1)从 65% 提升到了五次采样通过率(pass@5)的 85%。无论是 AlphaCode、Reflexion,还是 Code Llama 的指令版本,都采用了类似的机制。

模型与评测标准#

目前最常用的评测基准是 HumanEval(Chen et al., arXiv:2107.03374 , 2021),它包含 164 道手写的 Python 编程题,并通过实际运行代码来评分。pass@k 表示在 $k$ 次采样中至少有一个样本通过所有单元测试的概率;而 $\mathrm{pass}@1$ 则是最严格的单次成功率指标。需要注意两点:首先,HumanEval 的题目较短,且仅限于单文件和 Python 语言,因此其结果往往会高估模型在真实工程环境中的表现;其次,该数据集已被严重污染,2024 年之后的评测结果应结合 MBPP、LiveCodeBench、SWE-bench Verified 或 CRUXEval 等其他基准进行交叉验证。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch

class CodeLlamaInstruct:
    """Code Llama 7B-Instruct 的极简封装。"""

    def __init__(self, model_name: str = "codellama/CodeLlama-7b-Instruct-hf"):
        self.tok = AutoTokenizer.from_pretrained(model_name)
        self.model = AutoModelForCausalLM.from_pretrained(
            model_name, torch_dtype=torch.float16, device_map="auto"
        )

    def generate(self, instruction: str, max_new_tokens: int = 512) -> str:
        prompt = f"<s>[INST] {instruction.strip()} [/INST]"
        ids = self.tok(prompt, return_tensors="pt").to(self.model.device)
        with torch.no_grad():
            out = self.model.generate(
                **ids, max_new_tokens=max_new_tokens,
                temperature=0.2, top_p=0.95, do_sample=True,
            )
        text = self.tok.decode(out[0], skip_special_tokens=True)
        return text.split("[/INST]")[-1].strip()

展望 2025 年的应用场景:对于纯 Python 开发任务,DeepSeek-Coder-V2、Qwen2.5-Coder 和 GPT-4o 是首选工具;而对于涉及真实代码库和 Pull Request 的大型任务,SWE-bench Verified 是比 HumanEval 更贴近实际的评测标准,且榜单排名截然不同——Claude 3.5 / 3.7 Sonnet、GPT-4o 以及基于 Devin 的执行代理占据领先地位,这些方案往往通过强化执行能力弥补基础模型的不足。


长上下文建模#

标准的自注意力机制在时间和显存上的复杂度都是 $O(n^2)$ 。把上下文长度从 4K 提升到 8K,通常感觉不到太大压力;但要是从 32K 增加到 128K,显存占用就会直接爆炸。为了解决这个问题,研究者们提出了几类技术方案,实际应用中往往会将其中两三种结合起来使用。

长上下文注意力掩码

  • 滑动窗口(Longformer,Beltagy et al., 2020 )——每个查询只关注最近的 $w$ 个键值对,从而将复杂度降低到 $O(nw)$ 。这种方法擅长捕捉局部结构,而远距离信息则通过多层堆叠逐步传递。
  • 稀疏注意力 + 全局 token(BigBird、Longformer-global)——使用步长稀疏注意力,并额外引入少量“全局” token,所有查询都可以看到这些全局 token。这种设计特别适合问答任务,因为问题 token 往往需要访问整个文档的内容。
  • Infini-attentionMunkhdalai et al., 2024 )——结合一个小范围的局部窗口来保证精度,同时利用一个压缩记忆模块来总结更早的历史信息。这种方式在显存占用有限的情况下,能够实现近乎无限的等效上下文长度。
  • 位置编码扩展——RoPE base scaling、NTK-aware 插值、YaRN 和 LongRoPE 等方法通过重新参数化旋转频率,将原本在 4K 上下文训练的模型扩展到 128K,几乎不需要额外微调。
  • 参数高效长上下文微调——LongLoRA 将 shifted sparse attention 与 LoRA 技术结合,使得一个 7B 参数的模型可以在两张 A100 显卡上扩展到支持 100K 的上下文长度。

在实际应用中,现代长上下文大语言模型(LLM)通常会采用以下组合策略:预训练阶段使用 RoPE 扩展技术,在内核实现中采用滑动窗口或分组查询注意力(GQA),并在长文档混合数据集上继续训练。推理时,则通过 FlashAttention-2/3 和 PagedAttention(vLLM)等优化手段控制常数开销,确保效率和性能的平衡。

推理模型#

2024 到 2025 年,自然语言处理(NLP)领域迎来一个重要转折点:测试时计算能力。与其继续把基础模型做得更大,不如让模型在回答问题之前“多思考一会儿”。OpenAI 的 o1(2024 年 9 月发布)和 DeepSeek-R1(arXiv:2501.12948 ,2025 年 1 月发布)正是基于这一理念:它们会在内部生成一系列推理链条(chain-of-thought),对中间步骤进行评分,必要时回退调整,最终只将最终答案呈现给用户。

推理模型——链式思维与测试时扩展

这种改进背后有两个核心思想。首先,过程监督:不再仅仅关注最终答案的正确性,而是训练一个奖励模型,对推理过程中的每一步骤进行打分(Lightman 等人,《Let’s Verify Step by Step》,2023)。其次,可验证任务的结果强化学习(RL):对于数学题或代码生成这类可以自动验证的任务,利用验证器作为奖励信号,开展大规模强化学习。DeepSeek-R1 的“R1-Zero”方案进一步证明,只要基础模型足够强大,完全可以通过纯强化学习(无需监督微调,SFT)让模型自发涌现出推理能力,甚至还能表现出“灵光一现”和自我修正的行为。

当然,这种技术并非没有代价。推理模型运行速度慢、消耗 token 多——解决一道 AIME 数学题可能需要 1 万到 10 万个推理 token。更麻烦的是,这些模型会隐藏推理过程(例如 o1 对隐藏的推理 token 也收费),这引发了新的评估难题和信任问题。到了 2025 年,业界普遍采用的设计模式是路由机制:简单问题交给快速模型(如 GPT-4o 或 Claude Haiku)处理,而复杂推理任务则交给 o1、R1 或 Claude Sonnet 的“深思模式”。两者的成本和质量差距通常达到 10 到 50 倍。

安全、对齐与幻觉#

一个模型如果功能强大但存在安全隐患,那它根本无法投入使用;而一个虽然安全却毫无实用价值的模型,甚至还不如没有模型。所谓“对齐”,就是一门在“安全”和“有用”之间找到微妙平衡的工程学科。

幻觉问题的分类#

幻觉问题并不是单一的错误类型。根据 Huang 等人在《幻觉问题综述》(Survey of Hallucination, 2023)中的研究,可以将其分为以下几类:

  • 事实性错误:输出的内容与可验证的事实相矛盾,比如“居里夫人曾获得菲尔兹奖”。
  • 忠实性问题:在 RAG 或摘要生成等场景中,输出内容与提供的上下文不符。例如,合同明明写的是“60 天内付款”,但模型却声称是“30 天”。
  • 逻辑或算术错误:推理过程看似流畅,但实际上包含无效的逻辑步骤或计算错误。
  • 自相矛盾:在同一段对话中,模型的前后表述不一致。

针对这些问题,缓解措施需要多管齐下:通过 RAG 提升事实准确性(详见第 10 章 ),利用 约束解码 和 JSON 模式确保结构上的忠实性,采用 自一致性采样 + 多数投票 解决算术问题,借助 过程监督 和推理模型处理逻辑错误,通过 强制引用机制 提高可验证性,最后还可以通过 弃权训练(学会说“我不知道”)作为兜底策略。

对齐方法 — RLHF、DPO 与 Constitutional AI#

目前主流的对齐方法仍然遵循三阶段流程:首先在示范数据上进行监督微调(SFT),然后基于人类偏好对训练奖励模型,最后使用 PPO 强化学习优化模型以符合奖励目标。不过,DPO(Rafailov 等,2023)将后两个阶段合并为一个简单的有监督损失函数,因其更简单、成本更低,已经成为许多开源方案的默认选择。而 Constitutional AI(Bai 等,Anthropic, 2022)则通过一套书面原则让模型自我批评,大幅减少了对人工标注的依赖,使得大规模的安全调优变得更加可行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class GuardedLLM:
    """用输入和输出安全分类器包装任意聊天函数。"""

    def __init__(self, llm, in_filter, out_filter, refusal: str = "抱歉,我无法协助完成该请求。"):
        self.llm, self.inf, self.outf = llm, in_filter, out_filter
        self.refusal = refusal

    def chat(self, user_msg: str) -> str:
        if self.inf(user_msg).get("label") == "unsafe":
            return self.refusal
        reply = self.llm(user_msg)
        if self.outf(reply).get("label") == "unsafe":
            return self.refusal
        return reply

在实际生产环境中,输入过滤器主要用于拦截越狱攻击和提示注入(尤其是来自 RAG 上下文的注入,这已成为当前最主要的攻击方式);输出过滤器则负责检测个人隐私信息(PII)、毒性内容以及违反政策的输出。这两个过滤器本身都是小型分类器(如 Llama-Guard-3、ShieldGemma、OpenAI Moderation),相比让主模型自行监管,它们的成本更低且更加可靠。

评估#

基准测试是我们用来尽量减少自我欺骗的最佳工具。在评估中,有三个关键维度需要关注,但大多数团队往往在其中两个维度上投入不足。

维度公开基准实际衡量的内容
能力MMLU、GPQA-Diamond、GSM8K、MATH、HumanEval、MBPP、SWE-bench、MT-Bench、Arena-Hard、C-Eval (中文)知识、推理和生成能力,但容易受到数据污染的影响
安全性TruthfulQA、ToxiGen、HarmBench、JailbreakBench、BBQ拒答行为、偏见以及对越狱攻击的鲁棒性
效率MLPerf-Inference、vLLM bench、MMLU-Pro tokens/answer吞吐量、p50/p95 延迟、每百万 token 的成本

以下是三个值得长期坚持的习惯:

  1. 构建私有评测集:从真实用户场景中提取 100–500 条提示,这是唯一能够与实际上线效果强相关的指标。
  2. 采用成对比较法:利用 LLM 作为评判者(结合思维链推理),并辅以定期的人工抽检——绝对分数可能会漂移,但成对比较的结果更加稳定。
  3. 逐版本跟踪回归:使用固定的评估框架(如 lm-evaluation-harnessevalplusinspect_ai)进行测试——如果私有评测集上的得分下降 2 分,即使 MMLU 分数上升,也应阻止发布。

生产部署#

这里是漂亮的 Notebook 遇上现实的地方。我们接下来要介绍的参考技术栈——前端用 FastAPI、中间层用 vLLM、整体打包用 Docker + K8s,再加上 Prometheus 全链路监控——是 2025 年大多数生产团队的标准配置,剩下的无非是 Triton、TGI 和 TensorRT-LLM 的选型偏好问题。

生产环境部署架构

推理服务层#

别再用 transformers.generate 上生产了。它适合快速原型开发,但在生产环境中简直是灾难:没有连续批处理、没有 PagedAttention、KV 缓存管理也很原始。推荐使用 vLLM(开源界的默认选择)、TensorRT-LLM(NVIDIA 提供,H100 上性能最佳)或 TGI(Hugging Face 提供,运维最简单)。这三者都支持连续批处理、分页 KV 缓存、前缀缓存和投机解码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# vLLM 作为库使用——生产环境通常直接运行 `vllm serve`。
from vllm import LLM, SamplingParams

llm = LLM(
    model="meta-llama/Meta-Llama-3.1-8B-Instruct",
    dtype="bfloat16",
    max_model_len=8192,
    gpu_memory_utilization=0.92,
    enable_prefix_caching=True,
)
params = SamplingParams(temperature=0.7, top_p=0.95, max_tokens=512)
out = llm.generate(["用两句话解释 LoRA。"], params)
print(out[0].outputs[0].text)

API 层#

FastAPI 是目前阻力最小的选择:异步支持、Pydantic 数据校验、开箱即用的 OpenAPI 文档,以及无需额外配置的 SSE 流式传输。容易被忽略但至关重要的部分是请求卫生——严格的输入限制、请求 ID 跟踪、结构化日志记录,以及 GPU 池饱和时的降级策略。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field
import asyncio, time, uuid, logging

logger = logging.getLogger("nlp-api")
app = FastAPI(title="NLP 服务", version="1.0")

class ChatRequest(BaseModel):
    message: str = Field(min_length=1, max_length=8000)
    max_tokens: int = Field(256, ge=1, le=2048)
    temperature: float = Field(0.7, ge=0.0, le=2.0)
    stream: bool = False

async def _stream(prompt: str, max_tokens: int, temperature: float):
    # 替换为真实的 vLLM 异步生成器。
    for tok in prompt.split():
        await asyncio.sleep(0.02)
        yield f"data: {tok} \n\n"
    yield "data: [DONE]\n\n"

@app.post("/v1/chat")
async def chat(req: ChatRequest, request: Request):
    rid = str(uuid.uuid4())
    t0 = time.perf_counter()
    try:
        if req.stream:
            return StreamingResponse(
                _stream(req.message, req.max_tokens, req.temperature),
                media_type="text/event-stream",
                headers={"X-Request-Id": rid},
            )
        # 非流式路径……
        reply = "stub"
        return {"id": rid, "reply": reply}
    except Exception as exc:
        logger.exception("rid=%s 请求失败: %s", rid, exc)
        raise HTTPException(500, "内部错误") from exc
    finally:
        logger.info("rid=%s 延迟_ms=%.1f", rid, (time.perf_counter() - t0) * 1000)

@app.get("/healthz")
async def health(): return {"status": "ok"}

容器化#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
FROM nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04

ENV PYTHONUNBUFFERED=1 PIP_NO_CACHE_DIR=1
RUN apt-get update && apt-get install -y --no-install-recommends \
        python3.11 python3-pip git \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY requirements.txt .
RUN pip3 install --upgrade pip && pip3 install -r requirements.txt

COPY app/ ./app/
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
  CMD curl -fsS http://localhost:8000/healthz || exit 1

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]

在 Kubernetes 中部署时,确保 Pod 被调度到带有 nvidia.com/gpu: 1 标签的节点,收紧 CPU 和内存资源请求,配置 livenessProbe 检查 /healthz,并设置 readinessProbe 在模型加载完成后再标记为就绪(最常见的坑是在模型预热的 30–90 秒内就开始接流量)。

可观测性与服务目标#

无法度量的东西就无法优化。以下是最低限度的可观测性埋点:

  • 指标(Prometheus + Grafana):QPS、p50/p95/p99 延迟、GPU 利用率、显存占用、队列深度、缓存命中率、tokens/s。
  • 追踪(OpenTelemetry):每个请求一个主 span,子 span 覆盖检索、生成和后处理阶段。
  • 日志(Loki / ELK):结构化 JSON 格式,每条日志携带请求 ID。
  • 错误监控(Sentry):按异常类型聚合,速率突增时触发告警。

对于单张 A100 80GB 显卡运行 7-8B 参数量的模型,搭配 vLLM 和 bf16 精度,合理的上线目标是:~1500-2500 tokens/s/GPU 的聚合吞吐、p95 首 token 延迟 < 300 msp95 token 间延迟 < 80 ms~30 并发流。如果量化到 4-bit AWQ,吞吐量通常能翻倍,但代价是大多数基准测试中性能下降 1–2 分;建议通过小规模私有评测确认是否可接受后再切换。


常见问题#

Function Calling、ReAct 和图框架该怎么选? 如果只是调用单一工具且逻辑确定,直接用 Function Calling 就行。如果任务涉及多步骤、需要分支判断、重试机制或中间结果检查,那就考虑 ReAct 或者像 LangGraph 这样的图框架。如果你发现自己在 ReAct 的基础上手动实现状态机,那就该换成图框架了——它能让控制流更清晰,也更容易测试。

开源模型还是托管 API? 托管 APIs 在快速上线、功能上限和低频请求的成本上更有优势。而开源自托管则在数据主权、微调灵活性以及高吞吐场景下的成本上更胜一筹(对于 7-13B 参数量的模型,自有硬件的日处理量达到 5000 万到 1 亿 tokens 时,开源自托管会更划算)。

我真的需要推理模型吗? 如果是日常聊天、文本摘要、分类任务或者生成简短代码,完全没必要,非推理模型速度快、成本低(便宜 10 到 50 倍),效果也足够好。但如果是竞赛数学题、复杂代码生成、多步推理的研究任务,或者任何需要“系统 2”深度思考的任务,那推理模型就是必须的,两者的差距是质的而非量的。根据具体查询需求来选择合适的模型即可。

为什么我的智能体陷入死循环了? 大概率是以下三种原因之一:(1) 工具描述不够清晰,导致模型反复尝试错误的工具;(2) 忘记设置 Final Answer 标志位,模型不知道何时停止;(3) 观察内容过长,挤占了上下文空间,导致原始目标被遗忘。解决方法包括限制迭代次数、记录每一步的日志,以及将观察内容压缩到只保留关键信息。

扩展上下文最经济的方式是什么? 如果可以进行微调,推荐使用 RoPE base scaling 加上一次小规模的 LongLoRA 微调。如果没有微调条件,那就采用分块加 RAG 的方式(详见第 10 章 )。真正的百万 token 上下文很少是最佳选择——基于检索的方法不仅成本更低,调试起来也更方便,而且通常准确率更高。

系列收官#

12 章 NLP 旅程

我们从最简单的空格分词起步,一路探索到负载均衡器背后支持流式生成的推理代理。回顾整个系列的知识地图,可以清晰地看到四个阶段的演进:

  • 基础篇(第 1-3 章)——文本如何转化为向量,序列如何映射为隐藏状态。
  • Transformer 篇(第 4-6 章)——注意力机制取代了传统的循环结构,预训练分化为 BERT 式的理解路径和 GPT 式的生成路径。
  • 适配篇(第 7-9 章)——通过提示工程(prompting)、参数高效微调(PEFT)以及大语言模型(LLM)内部机制,将预训练模型转化为可定制的工具。
  • 应用篇(第 10-12 章)——RAG 将模型与知识库紧密结合,多模态突破了纯文本的局限,而智能体(Agent)与生产级工程实践则完成了从研究到部署的闭环。

如果这十二章内容只能让你记住三点,我希望是以下这些:

  1. 架构不如数据和目标重要——Transformer 已经成为标配,真正的差异在于预训练数据的组合、指令数据的质量以及评估方法的设计。
  2. 工程能力胜过模型规模——一个经过精心设计的 8B 模型,搭配 RAG、严格的评测体系和安全护栏,其表现会远超一个粗糙搭建的 70B 模型服务。
  3. 前沿发展迅速,但核心原理稳定——无论是 embedding、注意力机制、微调、检索、对齐还是服务部署,这些基本概念在 2027 年依然适用,即便具体的模型名称已经更新换代。

感谢你耐心读完这十二章内容。现在,是时候动手去创造点什么了!

参考文献#

  • Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models, ICLR 2023, arXiv:2210.03629 .
  • Schick et al., Toolformer: Language Models Can Teach Themselves to Use Tools, NeurIPS 2023, arXiv:2302.04761 .
  • Roziere et al., Code Llama: Open Foundation Models for Code, Meta AI, 2023, arXiv:2308.12950 .
  • Chen et al., Evaluating Large Language Models Trained on Code (HumanEval / Codex), 2021, arXiv:2107.03374 .
  • Beltagy et al., Longformer: The Long-Document Transformer, 2020, arXiv:2004.05150 .
  • Munkhdalai et al., Leave No Context Behind: Efficient Infinite Context Transformers with Infini-attention, 2024, arXiv:2404.07143 .
  • Chen et al., LongLoRA: Efficient Fine-tuning of Long-Context LLMs, ICLR 2024, arXiv:2309.12307 .
  • Lightman et al., Let’s Verify Step by Step, 2023, arXiv:2305.20050 .
  • OpenAI, Learning to Reason with LLMs (o1 system card), 2024.
  • DeepSeek-AI, DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via RL, 2025, arXiv:2501.12948 .
  • Rafailov et al., Direct Preference Optimization, NeurIPS 2023, arXiv:2305.18290 .
  • Bai et al., Constitutional AI: Harmlessness from AI Feedback, Anthropic, 2022, arXiv:2212.08073 .
  • Kwon et al., Efficient Memory Management for Large Language Model Serving with PagedAttention (vLLM), SOSP 2023, arXiv:2309.06180 .
  • Dao, FlashAttention-2, 2023, arXiv:2307.08691 .

本系列

NLP 技术前沿 12 篇

  1. 01 自然语言处理(一):NLP 入门与文本预处理
  2. 02 自然语言处理(二):词向量与语言模型
  3. 03 自然语言处理(三):RNN 与序列建模
  4. 04 自然语言处理(四):注意力机制与 Transformer
  5. 05 自然语言处理(五):BERT 与预训练模型
  6. 06 自然语言处理(六):GPT 与生成式语言模型
  7. 07 自然语言处理(七):提示工程与 In-Context Learning
  8. 08 自然语言处理(八):模型微调与 PEFT
  9. 09 自然语言处理(九):大语言模型架构深度解析
  10. 10 自然语言处理(十):RAG 与知识增强系统
  11. 11 自然语言处理(十一):多模态大模型
  12. 12 自然语言处理(十二):前沿技术与实战应用 当前

读有所得?

GitHub 关注我 → 新文周更

GitHub