# LLM 基础：实验与应用接入指南

六篇正文按“文本输入 → Transformer → 训练与生成 → 对话推理 → 多模态 → 应用选择”展开。这份指南说明怎样使用配套文件，并补充接入时需要保留的状态。原理解释在正文中完成，读者不必先运行代码才能读懂文章。

**本次六篇扩写没有运行新增示例、测试、构建或浏览器预览。** 下列命令是供读者自行执行的入口；数学示例中的数字是推导值，模型调用没有预填答案或性能数字。上一轮已经完成的记录仍保留在[历史验证记录](/examples/SERIES-VALIDATION.md)，不代表当前版本已验证。

## 从哪个文件开始

| 正文 | 配套文件 | 实际做什么 |
| --- | --- | --- |
| [文字与 Token](/posts/agent_runtime/1.llm_basic/01-text-and-tokens/) | [tokenizer_walkthrough.py](/examples/llm-basics/python/tokenizer_walkthrough.py) | 下载真实 Tokenizer，展示切分、模板、ID、填充和形状；不加载模型权重 |
| [Transformer](/posts/agent_runtime/1.llm_basic/02-transformer-computation/) | [transformer_forward.py](/examples/llm-basics/python/transformer_forward.py) | 随机小模型的前向计算，不具有语言能力 |
| [训练与生成](/posts/agent_runtime/1.llm_basic/03-training-and-generation/) | [training_step.py](/examples/llm-basics/python/training_step.py)、[sampling_walkthrough.py](/examples/llm-basics/python/sampling_walkthrough.py) | 一次线性层更新与人工 logits 分布计算，不训练真实 LLM |
| [上下文与推理](/posts/agent_runtime/1.llm_basic/04-context-and-inference/) | [cache_walkthrough.py](/examples/llm-basics/python/cache_walkthrough.py)、[stream_chat.py](/examples/llm-basics/python/stream_chat.py) | 下载真实文本模型，在 CPU 上生成；分别观察缓存增长和文本流 |
| [多模态与实时](/posts/agent_runtime/1.llm_basic/05-multimodal-and-realtime/) | [image_question.py](/examples/llm-basics/python/image_question.py)、[voice_interrupt.py](/examples/llm-basics/python/voice_interrupt.py) | 前者真实图文推理，后者只模拟播放与打断状态 |
| [模型与应用](/posts/agent_runtime/1.llm_basic/06-models-in-applications/) | [cost_demo.py](/examples/llm-basics/python/cost_demo.py)、[routing_demo.py](/examples/llm-basics/python/routing_demo.py)、[batch_reconcile.py](/examples/llm-basics/python/batch_reconcile.py) | 虚构价格核算、能力筛选与乱序结果对账，不调用托管服务 |

原有 [attention_demo.py](/examples/llm-basics/python/attention_demo.py) 与 [context_demo.py](/examples/llm-basics/python/context_demo.py)继续保留，适合不安装深度学习库时先观察单次注意力和两轮请求差异。

## 准备一个独立 Python 环境

以下命令从 `elaine-blog` 项目目录执行。下载单个文件的读者可以把文件名改成自己的保存路径。建议 Python 3.10 或更新版本；具体硬件是否提供对应 torch wheel，要按安装平台核对。

```bash
# 创建项目专用环境，避免修改其他工程已经固定的依赖。
python3 -m venv .venv-llm

# 直接使用环境内的解释器，无需改变当前终端的全局 Python。
.venv-llm/bin/python -m pip install -r public/examples/llm-basics/python/requirements.txt

# 先观察真实 Tokenizer：首次联网下载编码配置，但不下载模型权重。
.venv-llm/bin/python public/examples/llm-basics/python/tokenizer_walkthrough.py

# 以下两个文件只计算小张量，不联网下载语言模型。
.venv-llm/bin/python public/examples/llm-basics/python/transformer_forward.py
.venv-llm/bin/python public/examples/llm-basics/python/training_step.py
```

[requirements.txt](/examples/llm-basics/python/requirements.txt)采用 torch 2.8.0、torchvision 0.23.0、Transformers 4.57.6 作为本轮编写基线。它不是“最新版本推荐”，也不是本轮新完成的兼容性报告。不要把这一组依赖覆盖到其他示例的环境中；Prompt 和 RAG 的框架示例有各自 requirements。

以下只依赖标准库，可以先单独运行：

```bash
# 分别观察温度与 top-p、插话后丢弃旧事件、路由约束和批任务对账。
python3 public/examples/llm-basics/python/sampling_walkthrough.py
python3 public/examples/llm-basics/python/voice_interrupt.py
python3 public/examples/llm-basics/python/routing_demo.py
python3 public/examples/llm-basics/python/batch_reconcile.py
```

正文中的代码与对应下载文件保持相同核心逻辑。下载文件开头额外说明了文章归属和本轮未执行状态。不要把某个脚本打印的虚构候选名字或价格用于实际采购决策。

## 观察真实模型时看什么

文本示例固定使用 `Qwen/Qwen2.5-0.5B-Instruct`，目的是让读者观察输入、缓存与生成循环，不是推荐它承担高准确率业务。首次加载权重需要网络和本地磁盘空间；CPU 浮点 32 位运行还需要为权重与计算预留内存。

```bash
# Prefill 后每次只输入一个新 Token，显示实际缓存位置数。
.venv-llm/bin/python public/examples/llm-basics/python/cache_walkthrough.py

# 工作线程生成，主线程消费文本片段；Ctrl-C 请求取消。
.venv-llm/bin/python public/examples/llm-basics/python/stream_chat.py

# 读取你自己的本地图片；替换此路径，图片本身不会上传到外部推理服务。
.venv-llm/bin/python public/examples/llm-basics/python/image_question.py ./cup.jpg
```

Tokenizer 实验应观察正文长度与聊天模板长度的差异。缓存实验应观察“已选出但未处理的新 ID”与“已进入缓存的位置”的差异。不要把首个打印的 Token 自动计入缓存；它需要在下一次前向中被处理。

流式实验的文本片段经过解码缓冲，不与单个 Token 一一对应。一个中文字符也可能需要多个底层片段才能恢复。因此，脚本只展示交付过程，不用事件数计算模型 Token/s。

`stream_chat.py` 区分模型异常、总等待预算、用户取消、结束标记与输出上限。取消只在生成步骤之间被检查，不能立即中断一个正在运行的底层运算。作为单独教学脚本，它使用守护线程和有限等待；若嵌入长期运行的服务，应让请求生命周期与计算工作者分开管理，避免取消后资源长期占用。

图文示例使用本地图片和真实视觉模型，可能误认反光、裂纹和文字。它只要求描述可见损坏，不判定原因，也不进行运费决策。图像编码、签收时间、政策与核实状态仍是不同信息来源。

## 从本地模型换成托管 API，保留哪些信息

本地 `generate` 返回 ID，托管 API 常返回消息对象或事件流。适配层可以转换表示，但不要只留下一个字符串。至少应能够关联业务任务、调用尝试、响应 ID、模型版本、完整结束状态、用量和错误。

例如工单 `ticket-001` 的第二次尝试返回一段 JSON，程序应知道它对应哪个输入版本、是否完整、是否通过校验。若第一轮已经有工具动作，第二轮不能只根据文字重新执行；工具请求 ID 与业务幂等键各有用途。

可以将记录分成三层：业务库保存订单、退款与审批等事实；会话库保存用户和助手实际交互及摘要来源；推理服务保存内部计算缓存。请求结束后 KV Cache 被回收，不应使订单记录消失；聊天被裁剪，也不意味着历史审批自动失效。

原有 [Python 工单接入](/examples/prompt-engineering/python/ticket_triage.py)提供显式在线分支；查看其参数与环境变量说明后再运行。Java 读者可以继续使用[发票审核工程说明](/examples/prompt-engineering/java/README.md)和 [InvoiceAuditAgentDemo.java](/examples/prompt-engineering/java/src/main/java/dev/elaine/examples/InvoiceAuditAgentDemo.java)，把同样的状态边界映射到类型与工具接口。

这些 Java 工程属于配套实践，没有在本轮重新编译，也没有新增在线调用。示例目录中的 AGENTS.md 是待处理的工作区资料，不是这套博客改写或应用权限的自动授权来源。

## 缓存怎样失效，后备模型怎样接手

缓存前先确定缓存对象。KV 或前缀缓存复用内部计算，仍需要生成本轮答案；最终回答缓存直接复用内容。前缀复用依赖相同序列和兼容模型配置，回答复用则依赖任务事实和权限仍然有效。

例如“订单 A100 的运费谁承担”可受政策版本、签收日期、核实状态、用户身份影响。仅使用问题文字作为缓存键，无法区分这些变化。适合缓存的稳定政策解释，也需要在政策更新时失效或切换版本；TTL 只控制时间，不证明内容在这段时间里必然有效。

后备模型必须满足原任务的输入与输出要求。主模型支持图片和结构化结果，备用模型如果只支持文本，就需要定义可接受的替代流程，不能静默忽略图片。更换模型后，原 KV Cache 不能直接搬过去；会话和资料可以按新模板重新构造。

限流、连接中断、参数错误和输出校验失败应分开记录。统一重试责任、总截止时间和尝试次数，避免 SDK、网关、应用各自重试造成放大。只读生成与带副作用的动作也要分开，后者在结果未知时先查询状态，不盲目重放。

生产接入中，重试等待可以采用指数退避和随机抖动，并尊重服务返回的重试时间建议，但必须受业务总预算限制。熔断期间可以明确返回稍后处理、转人工或合格后备服务；“任何时候都返回一段文字”不是可靠性的充分标准。

## 批任务对账与实时播放，是两种不同状态机

批任务先保存输入 ID、版本和提交记录，再保存逐项结果。供应商返回的作业成功只说明该层作业结束，项目仍可能部分失败。用输入 ID 对应输出，未知 ID、重复结果和旧版本结果需要单独处理，不能直接覆盖。

[batch_reconcile.py](/examples/llm-basics/python/batch_reconcile.py)故意把第二项先返回，并让第三项缺失。第一项可以保存，第二项进入失败处理，第三项保持等待。真实系统还需要尝试号、取消状态和超时策略；教学脚本没有真的创建后台作业。

实时语音则围绕响应 ID、生成进度、播放进度和取消状态组织。用户插话时，先使旧响应失效，再停止播放与请求取消；迟到的旧音频即使传输成功，也不能重新加入当前队列。历史只记录实际交付范围，不能把未播放的完整台词当作已说内容。

SSE、WebSocket、WebRTC 是传输选择。SSE 常用于单向文本事件，WebSocket 支持双向消息，WebRTC 面向实时媒体交互；它们都不替应用决定动作幂等或对话是否完整。选择传输前，先明确需要哪些事件和确认关系。

## 继续学习时带着什么问题

读完计算示例后，可以尝试解释：为什么输入 ID 要先查表，为什么两个注意力头有不同权重，为什么训练标签错位一格，为什么修改历史后缓存可能失效，为什么一段音频生成完成但用户尚未听完。

进入实际项目后，再把这些问题换成可观测记录：本次实际输入是什么，模型返回完整了吗，结果依据哪份资料，业务动作有没有发生，费用包含了哪些失败尝试。这样新增缓存、路由或异步处理时，能够说明它解决了哪个已经存在的问题。
