🧠 第二章 模型部署:在本地使用DeepSeek
📄 一句话概括

三项一起量:连通性 5/5、出字速度 25.0 tok/s、上下文超过 8192 就被截断。 只测第一项就宣布部署完成,是这一章最常见的翻车方式——因为后两项的失败方式都不报错。

📝 本节和前面几节的关系

上一节把最后一个坑填了。这一节做验收:怎么判断这套东西到底「能用」,而不是「看起来能用」。

一、三项指标,三种不同的失败

连通性:请求能不能成功返回。失败方式是报错,最容易被发现。 出字速度:每秒能出几个 token。失败方式是「慢」,不报错,但交互体感接近卡死。 上下文长度:能记住多长的内容。失败方式是「忘」,连慢都不算,它只是答得似是而非。 三种失败里,第一种会闹,第二种会烦,第三种会骗人。 所以验收必须三项一起做,只看第一项等于没验收。 这一节的三项,正好对应前面三节讲的三件事:服务在不在(2.1.3)、带宽够不够(2.1.5)、 配置对不对(2.1.6)。验收就是把它们合起来量一遍。

二、速度不要靠感觉,读它自己报的数

Ollama 的每个响应里都带着两个字段: eval_count(生成了几个 token)和 eval_duration(生成花了多少纳秒)。 两个一除就是 tok/s。 不用秒表掐,也不用自己数汉字——它自己报的数比任何外部测量都准,因为不含网络和界面渲染的时间。 Demo 里读到的是 5 个 token、0.20 秒,算出来 25.0 tok/s。 这个数在假服务上没有意义(它没有真推理),但算法就是这两个字段相除,换成真模型直接就能用。 还有一个细节:第一次请求总是慢的,因为要把权重加载进显存。 所以判断速度要去掉第一次,用第二、三次的数。Demo 里那个「中位数比最小值更可信」说的就是这件事。

三、上下文被截断,它不会告诉你

模型有一个上下文窗口上限。超过的部分直接被丢掉,而且不报错、不警告、不提示。 表现是什么?你贴了一份十页的文档,问第十页的细节,它答得似是而非; 问第一页的内容,它说「文中没有提到」。你会以为是模型笨,其实是内容根本没进去。 Demo 里扫了三档:设 2048 吃进 2048,设 8192 吃进 8192, 设 32768 还是只吃进 8192——因为模型本身的上限就 8192。配置写得再大,也大不过模型。 所以判断方法是:贴一段开头带明显标记的长文,问它开头写了什么。答得出来,上下文够; 答不出来,就是被截了。这一招比看参数表可靠。

四、合格线怎么定

连通性 5/5,一次失败都不行。偶发失败比稳定失败更难查,通常是显存不够触发了换页。 出字速度 > 5 tok/s。这是交互的底线——再低,人就开始怀疑是不是卡死了。 对比一下:正常人打字约 3~5 个字每秒,一个汉字约 1.5 个 token, 所以 5 tok/s 大概是「跟你打字差不多快」。 上下文 ≥ 业务需要的长度。 这个数没有通用值,读一篇公众号文章 2000 字够,读合同、读代码库就不够。 按最长的那份材料定,不是按平均值。 三项都过,才算部署完成。 任何一项不过,都还有活要干——而这三项的修法完全不同:连通性查服务、速度换硬件、上下文改配置。

五、动手 Demo:5/5 连通、25.0 tok/s、上下文 8192 封顶——三项才算验收

连发五次请求统计成功率和延迟分布,读响应里的 eval_count 和 eval_duration 算出字速度, 再扫三档上下文看实际吃进多少;最后把三项指标和它们的合格线列成一张验收表。

lat, bad = [], 0
for _ in range(5):
    st, body, _h, dt = post(url + u'/v1/chat/completions',
                            {u'model': u'deepseek-r1:7b',
                             u'messages': [{u'role': u'user',
                                           u'content': u'你好'}],
                             u'stream': False})
    bad += (st != 200)
    lat.append(dt * 1000)

tps = ev / (evd / 1e9)      # eval_count ÷ eval_duration(秒)

实跑输出:

--------------------------------------------------------------------
测试:三项指标一起量,才知道「能用」还是「不能用」
--------------------------------------------------------------------
  第一项,连通性:连发 5 次,一次都不许失败。
  成功/总次数             5/5
  延迟 最小                1.6 毫秒
  延迟 中位                1.6 毫秒
  延迟 最大                2.4 毫秒
  中位数比最小值更可信——**第一次请求总带着建连和加载模型的开销。**

  第二项,出字速度。这个词不能靠感觉,得算:
  生成 token               5
  生成耗时                 0.20 秒
  出字速度                 25.0 tok/s
  Ollama 的每个响应里都有这两个字段(eval_count/eval_duration),
  **不用秒表掐,直接读它自己报的数。**

  第三项,上下文。发一段超长内容,看它记不记得开头:
  设 2048,实际吃进        2048 token(超过模型上限就被截断)
  设 8192,实际吃进        8192 token(超过模型上限就被截断)
  设 32768,实际吃进       8192 token(超过模型上限就被截断)
  真实环境里这一项最容易被忽略:**内容被悄悄截掉,模型不报错,
  只是「忘」了开头几段。** 表现是答得似是而非,而不是报错。

  三项合起来才叫「能用」:
  指标       合格线             不合格的表现
  ---------------------------------------------------------
  连通性     5/5 成功          有失败就是服务或地址问题
  出字速度   > 5 tok/s          低于这个数,交互体感接近卡死
  上下文     ≥ 业务需要的长度   不够就是被截断,不报错
  只测第一项就宣布部署完成,是这一章最常见的翻车方式。

done -> _out_39d.txt

第一项:5/5 成功,延迟最小 1.8、中位 1.9、最大 2.7 毫秒。 这个量级说明请求走的是本机回环,没有真推理。 要说明的是:这个延迟数字不能当「模型快」的证据,因为假服务没有跑模型。 它能证明的是「链路是通的、脚本是对的」。 第二项:5 个 token、0.20 秒、25.0 tok/s。这个数同样是假服务自己报的。 要学的是算法:eval_count ÷ eval_duration。换成真模型,这两个字段照样在,直接除就行。 第三项最能说明问题:设 2048 吃进 2048、设 8192 吃进 8192、设 32768 还是只有 8192。 最后一行是关键——配置写大了,模型上限不会跟着变大。 最后那张三行表是这一节的结论。 「只测第一项就宣布部署完成」之所以是翻车方式,是因为它恰好是唯一会报错的那一项。后两项不报错, 要等到实际用起来才发现,那时候已经过了一周。

30 秒实验

  1. 把连发次数从 5 改成 50,再看延迟分布。 偶发的长尾延迟(比如某一次 50 毫秒)通常比平均值更有诊断价值。
  2. 把 eval_duration 手改成 5 秒,看算出来的 tok/s 掉到 1.0。 体会一下「1 tok/s 是什么体感」——那就是比手写还慢。
  3. 给上下文那一项加一条判断:把「配置值」和「模型上限」取小的那个,显式打印出「实际能用多少」。 这就是配置校验该做的事。

常见误区

  1. 「能对话就算部署完成」。能对话只过了第一项。速度和上下文都还没量,而它们的失败不报错。
  2. 「出字慢是正常的,忍忍就好」。5 tok/s 是交互底线,低于它就不是「慢」而是「不能用」。 这时候要换档位或换硬件,不是忍着。
  3. 「上下文设得越大越好」。超过模型上限的部分直接被丢,还会白占显存。 设成模型上限,不是设成你以为的数。
自测三问(点开对答案) 1. 为什么只测连通性不够? —— 因为速度和上下文这两项的失败都不报错。 速度慢了只是体感差,上下文不够只是「忘事」——两个都要实际用起来才发现。 2. 出字速度怎么算才准? —— 读响应里的 `eval_count` 除以 `eval_duration`(注意单位是纳秒)。 不要用秒表掐,那会把网络和界面渲染也算进去。 3. 怎么快速判断上下文够不够? —— 贴一段开头带明显标记的长文,问它开头写了什么。答不出来就是被截断了——比查参数表可靠。

第二章到这里结束:模型装好了、界面通了、三项指标也验过了。 第三章开始讲数据——模型能干什么,取决于你喂它什么。

相关笔记