过去两年最热闹的技术关键词,基本都是“大模型”。部署教程很多,模型下载地址也很多,但真正入手之后,不少人会卡在同一类问题上:模型能跑,但不知道内部每一条公式在干什么。要解释这个问题,我会从一条出现频率极高的公式开始:Softmax。它既是注意力机制里的权重计算入口,也是大模型生成下一个 token 时的概率来源。简单写就是这样:
softmax(z_i) = exp(z_i) / sum_j exp(z_j)这个公式看起来只像“归一化”,但它背后并不是什么复杂魔法。它的数学原型可以追溯到统计力学里的玻尔兹曼分布。所以这篇不写模型排名,也不写 API 价格,用一条公式把注意机制、损失函数、生成参数、显存占用和排查思路串起来。对只想调 API 的人来说,看懂它能少踩很多参数坑;对想本地部署、微调、跑批量任务的人来说,它能帮你判断瓶颈到底在显卡、显存、数据还是上下文长度。
1. 先看清:大模型里反复出现的核心公式是哪一条
1.1 自注意力公式中,Softmax 处在什么位置
基于 Transformer 的大模型,绝大多数结构都绕不开下面这个表达式:
Attention(Q, K, V) = softmax(QK^T / sqrt(d_k)) V这里 Q 是查询向量,K 是键向量,V 是值向量。Q 和 K 相乘,得到当前 token 与序列里其他 token 的相关度分数。除一个sqrt(d_k)是为了防止点积结果过大,把 softmax 推到梯度很小的区域。最后用 softmax 把分数变成一组和为 1 的概率权重,再用这些权重去加权 V。
这个公式的含金量不在于矩阵乘法本身,而在于“用 softmax 把交互分数变成可导、可解释、非负的权重”。没有 softmax,模型很难训练稳定;没有 softmax,多 token 之间的重要性也没法自然表达。
把这一步展开成直觉:如果一句话里有 20 个 token,模型需要判断当前 token 应该重点看哪几个历史 token,softmax 就是那个“分注意力预算”的开关。分数高的 token 拿到的权重高,分数低的 token 不会被完全忽略,但权重很小。
1.2 输出层又一次使用 Softmax:下一个 token 怎么被选出来
自注意力只是大模型内部计算的一部分。真正到生成阶段,模型还要做一次概率映射。
大模型每次生成一个 token,会把当前隐藏状态和词表权重做乘法,得到每一个词可能出现的分数,专业叫法叫 logits。这些 logits 可以是任意实数,有的甚至是负数,看起来很不直观。要让它们变成真正的概率,又需要 softmax:
p(next_token | context) = softmax(h W^T)这里 h 是最后一个隐藏状态,W 是词表投影矩阵。输出结果就是一个覆盖整个词表的概率分布。模型再根据这个分布采样,选出一个 token 继续生成。
所以你会发现,一个典型大模型至少有两次重要位置依赖 softmax:一次在注意力层分配关注权重,一次在输出层决定下一个词的概率。这也是我把这条公式放在最前面的原因:它不是一个模块的细节,而是贯穿训练和推理的共同基础。
1.3 玻尔兹曼分布与标题里的“他”
如果顺着这条公式往前找,会看到它和统计力学里的玻尔兹曼分布关系非常近:
p_i = exp(-E_i / (kT)) / sum_j exp(-E_j / (kT))E_i 表示系统某个状态的能量,k 是玻尔兹曼常数,T 是温度。形式上和 softmax 几乎完全一样。在机器学习里,logits 越大的 token,概率越高,就像能量越低的状态,出现概率越高。
所以如果要把标题里的“他”落在一个名字上,最合适的人选是路德维希·玻尔兹曼。他并不是深度学习框架的作者,也没有给 softmax 起过这个现代名称。他留下的是热力学和统计力学中关于大量微观粒子状态的概率分布思想。今天大模型之所以能用 token 概率做生成,底层骨架和他是相通的。
这里要澄清一句:Softmax 这个工程化名称是后来机器学习社区逐步形成的。你不需要争论“玻尔兹曼是不是发明了 softmax”,那会偏离重点。重点在于:真正被全球大模型广泛依赖的,不是一个孤立的“归一化 trick”,而是一条从物理学走过来的概率思想。它被封装进框架之后,大家只记得函数名,很少记得公式源头。
2. 不只注意力:训练和生成过程还藏着哪些底层公式
看懂了 softmax,只是第一步。大模型训练过程里最有代表性的损失函数、稳定网络的结构、自回归假设和优化器更新,也都是由公式推动的。下面按训练和推理的常见环节拆开讲。
2.1 交叉熵:你看到的 loss 曲线其实就是在算这个
每个语言模型的微调过程,都会打印一条 loss 曲线。loss 下降不代表模型一定变好,但 loss 不下降基本代表训练有问题。这个 loss 的常用数学形式是交叉熵:
L = -1/N * sum_i sum_vocab y_i,v * log p_i,vy_i,v 是一个人肉 token 的真实分布。对普通分类任务来说,它往往是 one-hot 形式,也就是正确答案的概率为 1,其他为 0。模型预测出 p_i,v 之后,真实标签对应位置的预测概率越接近 1,loss 越小。
大模型预训练和 SFT 微调,很大程度都在优化这一类目标。它的思想根源和信息论里的“不确定性”有关。如果你把预测概率 p 理解成模型对答案的置信度,交叉熵就是在惩罚“不该不自信的地方不自信,该自信的地方也没自信”。
实际做微调时,我一般会盯两组指标:训练集 loss 和验证集 loss。训练集 loss 下降但验证集 loss 回升,大概率是过拟合。验证集 loss 一开始就不降,不要立刻去调学习率,先看数据标签是否干净、prompt 模板是否统一、填充部分有没有参与 loss 计算。很多本地微调项目失败,不是公式问题,而是数据里混了大片无意义文本。
2.2 残差连接和归一化:为什么深层网络能训练起来
现在的开源大模型动辄几十层上百层。如果每层都做一次非线性变换,信息在逐层传播时很容易丢失。为了解决这个问题,Transformer 在子层外面加了残差连接,可以简写成:
output = x + sublayer(x)x 是输入,sublayer 可能是注意力层,也可能是前馈网络层。这个结构和“ResNet 解决深度网络退化”的思路一致,作用是让梯度能有一条更短路径回传,也让模型在网络加深时仍然保留原始输入信息。
另一类关键公式是归一化。很多大模型使用了 LayerNorm,形式大致是:
y = (x - mean) / sqrt(var + eps) * gamma + beta它把某一层特征拉回到稳定尺度,避免数值过大或过小。后面的 gamma 和 beta 是可学习参数,让模型可以自己决定要不要恢复部分原始尺度。eps是一个很小的常数,主要防止分母为 0。
这些公式在日常部署中看起来不起眼,但它们影响推理结果。如果你用两套不同的推理框架跑同一个模型,输出有细微差异,除了浮点精度外,归一化层的 epsilon、注意力分数缩放这类实现细节也会造成偏差。不要一看到框架输出差异就认为是模型损坏。
2.3 自回归:大模型本质上是按顺序预测下一个 token
大模型生成文字,核心逻辑可以看成一段条件概率连乘:
p(x_1, x_2, ..., x_n) = p(x_1) * p(x_2|x_1) * p(x_3|x_1,x_2) * ...这个拆解方式在概率论里叫链式法则。它不强求模型一次性写出完整答案,而是每次只预测下一个 token,再把新生成的 token 拼进输入,继续预测下一个。这种“先看历史,再预测下一个”的思路,让语言模型不需要像某些结构那样固定整个输出长度。
也是因为这个原因,大模型会话越长,需要“记住”的上下文就越多。推理时每生成一个 token,都会把历史 token 对应的键值缓存下来,专业叫法叫 KV Cache。这个缓存不是可选项,而是为了节省重复计算。代价是长对话、长文档会占用大量显存。
如果理解这一点,就不会对“为什么同一个模型在长文本任务上更慢”感到困惑。核心不是模型变笨了,而是它要处理的注意力关系和要保存的缓存都变多了。
2.4 优化器:Adam 把公式变成真正能用的参数
模型结构再精巧,也要靠优化器更新权重。当前大模型训练和微调里最常见的优化器是 Adam 及其变体。它的核心思想是给每个参数维护一阶动量 m 和二阶动量 v,然后自适应地调整学习率。简化形式是:
m_t = beta1 * m_{t-1} + (1 - beta1) * g_t v_t = beta2 * v_{t-1} + (1 - beta2) * g_t^2 w_new = w_old - lr * m_hat / (sqrt(v_hat) + eps)g_t 是当前梯度。m 类似梯度的平均值,v 类似梯度平方的平均值。如果某个参数的历史梯度波动很大,分母就会变大,实际更新步长会被压低;如果某个参数历史梯度很小,分母较小,更新会更积极。
这个公式看起来只在训练服务器上有用,其实它也在决定你能不能在自己的 GPU 上做微调。Adam 需要额外保存 m 和 v,也就是说,全参数微调时除了模型权重本身,还要为每个参与更新的参数保存多份状态。显存不够时,很多人第一反应是换更大的显卡,但更常见的方法是改用 LoRA 这类参数高效微调,或者用量化后的基座模型。
3. 这些公式在部署和参数调试时到底怎么用
公式不是用来背的,是用来判断参数效果的。我把部署、本地运行和微调中最容易踩坑的点,按公式来源重新解释一遍。
3.1 temperature、top_p、top_k 本质上是在改造 softmax 概率分布
几乎每次调大模型接口,都会看到 temperature 和 top_p。如果你不理解 softmax,可能觉得这些参数是玄学。其实它们都在改变输出概率分布。
带温度系数的 softmax 可以写成:
p_i = exp(z_i / temperature) / sum_j exp(z_j / temperature)当 temperature = 1 时,概率分布保持原始状态。temperature 越小,概率差异越被放大,模型更倾向选择高分 token;当 temperature 趋近 0,输出会接近贪婪解码,也就是每次都选概率最高的那个 token。temperature 越大,概率分布越平,低分 token 被选中的概率也变大,所以输出更随机。
top_k 是只保留概率最高的 k 个 token,然后重新归一化。top_p 是只保留累积概率达到 p 的最小 token 集合,然后再归一化。它们和 temperature 经常一起生效,但作用顺序并不完全一样。实际调试时,我建议不要把 temperature 一开始就调到很高,否则会得到“看起来通顺但不聚焦”的内容。先从 0.7 到 0.9 测,再微调 top_p,比盲目调满温度更有用。
还有一个新手经常搞混的坑:脚本里设置了 temperature,却没有开启 do_sample。很多接口默认使用贪婪解码,此时 temperature 并不会真正影响采样。你看到输出一直很固定,先检查采样开关,再回来调参数。
3.2 显存估算:为什么同一个模型,推理和微调差距很大
很多人本地部署大模型前,最关心“我的显卡能不能跑”。这不只是一个“能不能下载”的问题,而是一道显存估算题。
先看权重。一个 7B 参数的模型,用 FP16 精度存储,权重大约需要 14GB。如果量化到 4bit,权重大约降到 4GB 上下。这就是为什么很多低显存机器也能跑小参数量化模型的原因。
再看推理。模型推理不是只放权重就能跑,还需要保存激活值、中间结果和 KV Cache。KV Cache 和并发请求数、上下文长度强相关。即使显卡能加载 7B 模型,如果一次输入几十万字,KV Cache 也可能把显存吃满。
最后看微调。使用 Adam 做全参数微调,除了模型权重,还要保存梯度状态和优化器状态,显存开销可能明显高于推理。这也是 LoRA 流行的原因:它冻结原始权重,只训练一小部分低秩矩阵参数,显存占用和可训练参数量都低很多。
所以判断一张显卡能不能做某个任务,不要只看“显存够装模型”。要分清你是推理、服务、全参微调,还是只做 LoRA 微调。任务类型不一样,显存判断标准完全不同。
3.3 Ollama 和 vLLM 到底怎么选
热词里经常出现“ollama 部署大模型”“vllm 部署大模型”。这两个工具不是对立关系,定位不同。
Ollama 更适合个人电脑和轻量实验。它的安装、模型拉取、启动服务都比较简单,适合先验证模型能不能跑、效果是否符合预期。模型默认下载到用户目录,如果你想把模型放在 D 盘或者其他数据盘,需要先看它的模型目录和环境变量怎么设置,改完之后再重新拉模型。
vLLM 更适合批量推理和并发服务。它用了连续批处理和 PagedAttention 等机制,能在高并发下提高吞吐率。如果只是单条 prompt 测试,vLLM 的优势不一定明显;如果要做压力测试,或者给多个用户同时提供 API,再考虑迁移到 vLLM。
选择原则很简单:先是小规模跑通,再谈规模化。不要第一天就想着把并发开到最大。先用一个模型、一个请求验证链路,确认输出正常和日志正常,再逐步增加负载。
4. 从公式到工程:一次可复现的最小验证流程
前面把注意力、损失函数、优化器和采样逻辑都解释过了。下面给一套我实际用的最小验证流程,按这个顺序做,能避免大多数环境问题。
4.1 先确认环境,再启动模型
不管用什么框架,第一步都不是直接启动模型,而是先看机器状态。打开终端执行:
nvidia-smi运行结果里能看到驱动版本、CUDA 版本、显存总量和当前占用。如果连这个命令都报错,说明 GPU 驱动或 CUDA 环境没有配好,后面大概率跑不起来。没有 NVIDIA GPU 也可以先跑小参数量 CPU 模型,但要把并发和输入长度调低。
接着确认模型工具和模型目录齐全。以 Ollama 为例,可以先:
ollama list这条命令会列出本地已经拉取的模型。如果没有对应模型,再用ollama run <模型名>去拉取并启动。为了让演示可复现,我用占位符而不是具体版本号:
ollama run <你的模型名>不要一上来就拉最大参数模型。先用 1B 到 3B 的小模型跑通,再换更大模型。这样能更快判断问题是模型本身还是环境配置。
4.2 单条任务跑通之后,再测试 OpenAI 兼容接口
很多本地推理服务会对外开放类似 OpenAI 风格的接口。先用最简单的方式调用一次,确认模型返回内容是否正常。下面这段代码是通用示例,实际使用时把base_url和model改成你的服务地址:
from openai import OpenAI client = OpenAI( base_url="http://localhost:8000/v1", api_key="EMPTY", ) resp = client.chat.completions.create( model="your-model-name", messages=[ {"role": "user", "content": "用一句话解释什么是注意力机制"} ], temperature=0.7, max_tokens=256, ) print(resp.choices[0].message.content)如果返回正常,说明从模型加载、接口路由到参数传递这一整条链路已经通了。我建议在打印结果时把resp.usage也打出来,观察输入 token 数量和输出 token 数量。很多“模型答到一半就不出内容”的问题,不是模型坏掉,而是max_tokens设置太小,输出被截断了。
4.3 批量任务不能只看“能跑”,还要看输出完整性和失败重试
很多人本地部署成功后,第一件事就是把几十条测试数据填进去跑批量。这时最容易发生的问题是:任务跑到一半卡住,或者某几条输出为空,但程序没有报错。
处理方式不是立刻调并发,而是先做三件事:
- 记录每条输入对应的输出结果,字段里带上序号和原始 prompt。
- 设置失败重试机制,单次请求超时后就重新发起,而不是中断整个任务。
- 把日志输出到独立文件,不要只打印在屏幕上。
如果批量结果大量为空,先检查输入格式。有些模型对 chat 消息结构有要求,如果你把单条字符串塞进错误的 messages 字段,接口可能返回空内容或抛出异常。再检查输出目录权限和磁盘空间,很多任务不是被模型卡住,而是写到一半没有空间了。
4.4 常见问题的排查顺序
我平时遇到部署和推理问题,不会一上来就重装框架,而是按下面的顺序排查。
| 现象 | 优先排查方向 |
|---|---|
| 模型启动就报显存不足 | 查看当前显存占用,降低并发数或上下文长度限制 |
| 推理速度越来越慢 | 检查是否多轮对话导致 prompt 太长,KV Cache 占用是否过高 |
| 生成内容重复 | 检查采样开关是否打开,temperature 和 top_p 设置是否合理 |
| 输出被截断 | 检查 max_tokens 是否太小,finish_reason 是否为 length |
| 接口调用报错 | 先确认 base_url、模型名、请求字段是否和服务端匹配 |
| loss 在微调中不下降 | 先看数据清洗、标签对齐和 padding 是否参与 loss |
看日志比看心情重要。很多看起来像“模型跑不动”的问题,最后都落在端口占用、依赖版本、磁盘空间这些很普通的地方。不要因为一个报错就怀疑模型文件损坏,先看细节。
5. 名字被产品盖住,公式却没有退休
每次讨论大模型,我们更容易记住模型名称、公司品牌和跑分排名。但如果你一路看到这里,会发现底层技术链上站着很多不是热搜常客的名字。
5.1 从玻尔兹曼到香农,从马尔可夫到贝叶斯
刚才提到的玻尔兹曼分布,是 softmax 的统计力学原型。交叉熵和信息熵紧密相关,源头绕不开香农的信息论。自回归语言模型在结构上又是按照条件概率连乘来拆解序列的,背后也能看到马尔可夫这类概率模型的影响。很多工程论文里引用的方法,并不是凭空被发明的,而是前人在数学、物理、统计领域反复打磨过的结论。
我不是想说所有成果都要归到某一个人头上。现代大模型是几个人在不同年代留下的公式,加上大量工程优化后的结果。但“没人知道他的名字”这句话也不是完全夸张。你问一个正在写 softmax 的开发者,他可能知道函数名,但未必会继续去查它的物理源头。
这种情况在技术行业很常见。公式越基础,越容易被框架封装成一句函数调用。框架降低了使用门槛,也顺带遮住了源头。我们不需要每次调用 softmax 都背一遍物理史,但至少要保留一点“往回看”的意识。
5.2 看懂公式后,学习路线会比刷教程更清楚
很多朋友会搜“大模型学习路线”“大模型部署工具”“GPU 微调大模型”这类关键词。在我看来,最有效的路线不是先背一堆概念,而是从一个小模型开始,把它拆成三层:
第一层,会部署和调用。工具选 Ollama 或 vLLM 都可以,目标是跑通推理请求。第二层,会调生成参数。把 temperature、top_p、max_tokens、stop 这几个参数放在同一段代码里测试,看输出如何变化。第三层,会看训练和微调过程。在自己数据上做一次 LoRA 微调,观察 loss 曲线和验证集结果。
这三个层级不需要一次走完。很多人连第二层都没到,就急着做全参数微调,结果显存爆掉,数据也没整理好,最后只能怪模型。其实更稳妥的方法是先把小模型、小数据、短上下文跑顺,再慢慢扩大规模和复杂度。
5.3 我最后留给你的几条务实建议
如果只让我说几条最有用的经验,我会这样说:
一是单个请求没跑稳之前,不要开高并发。并发高只是加快速度,不会解决底层配置错误。二是遇到输出异常,先看请求里的 token 数量、max_tokens、finish_reason、采样参数,再怀疑模型。三是无论本地部署还是调用 API,都养成写日志的习惯。日志里记录 prompt、参数、输出、耗时和错误信息,排查问题时这些比记忆可靠得多。
至于那条“全球大模型都在用的公式”,我想你以后再去翻框架源码时,会多留心一层:softmax 不只是归一化,它把物理世界里“低能量状态更容易出现”的思想转化成了神经网络里的概率规则。名字可以暂时被技术名词盖住,公式本身的用途,短时间内不会消失。