第五章 性能基线与 GPU 卸载实验
Qwen2.5-7B-Instruct · Q4_K_M · llama.cpp · V100
本章承接第三、四章。实验基于已下载的官方量化 GGUF,不属于“自行完成量化”。所有数值均来自本次实测日志。
5.1 实验目标
前面已通过llama-cli成功运行 Qwen2.5-7B-Instruct Q4_K_M。现在建立能够复用的性能基线,并观察 GPU 卸载层数对推理速度和显存占用的影响。
要回答三个问题:
- 输入处理(Prompt Processing)与逐 Token 生成(Token Generation)的速度分别是多少?
- 同一模型使用单卡或多卡运行时,成绩有什么差异?
- 减少 GPU 卸载层数,节省了多少显存,又付出了多少生成速度代价?
5.2 测试环境与模型
| 项目 | 配置 |
|---|---|
| 系统 | Ubuntu 22.04.1 LTS |
| GPU | Tesla V100-PCIE-32GB;单卡实验指定 CUDA1 |
| CUDA 编译环境 | cuda/12.4 模块 |
| llama.cpp | commit0ee9435,CUDA 构建build-v100 |
| 模型 | Qwen2.5-7B-Instruct |
| 模型参数量(bench 输出) | 7.62 B |
| GGUF 格式/量化类型 | GGUF / Q4_K_M(输出名称Q4_K - Medium) |
| 模型文件大小(bench 输出) | 4.36 GiB |
| CPU 线程 | 4 |
模型文件是一个被拆成两片的 GGUF;-m指向00001-of-00002.gguf,程序会加载其余分片。
概念区别:GGUF 是模型文件格式;Q4_K_M 是其中的量化表示类型;llama.cpp是负责加载权重和执行推理的软件。下载官方 Q4_K_M GGUF 并运行,不等于从 FP16 权重亲自完成量化。
5.3 正式性能测试:pp512 与 tg128
pp512测模型处理 512 个输入 Token 的速度;tg128测生成 128 个 Token 的速度。两者应分别记录,不能混为一个“推理速度”。-r 3表示每种测试重复三次,输出平均值及标准差。
conda activate llmexportPYTHONNOUSERSITE=1module load cuda/12.4cd/slurm/home/zhengjie/xss_llmMODEL="/slurm/home/zhengjie/xss_llm/models/Qwen2.5-7B-Instruct-GGUF/qwen2.5-7b-instruct-q4_k_m-00001-of-00002.gguf"BENCH="./llama.cpp/build-v100/bin/llama-bench"mkdir-plogs/benchmarks"$BENCH"\-m"$MODEL"\-devCUDA1-smnone-ngl99\-t4-p512-n128-r3-omd\2>&1|teelogs/benchmarks/qwen2.5-7b-q4km-v100-single.md单卡完整卸载实测:
| 模式 | pp512(tokens/s) | tg128(tokens/s) |
|---|---|---|
CUDA1,-sm none,-ngl 99 | 3118.45 ± 144.84 | 116.00 ± 0.27 |
此前在未显式限定设备的 CUDA 配置下,-ngl 99的一组结果为pp512 = 2896.42 ± 9.56、tg128 = 112.98 ± 0.11tokens/s。这两组仅能作为不同配置下的实测记录,不能由此证明“单卡必然比多卡更快”,因为运行时的共享节点负载、时间等条件不完全相同。
5.4 三个容易混淆的参数
| 参数 | 意义 |
|---|---|
-ngl 99 | 允许尽可能多的模型层卸载到 GPU,不表示有 99 层,也不表示使用 99 张卡 |
-dev CUDA1 | 限定允许使用的设备为 CUDA1 |
-sm none | 不把模型拆分到多张 GPU |
llama-bench的输出出现backend = CUDA只能说明使用 CUDA 后端;如果同时有ngl = 0,不能把它理解成模型层已经全部在 GPU 上计算。
5.5 单卡 GPU 卸载层数对照实验
保持模型、单卡设备、线程数、输入/输出 Token 数量和重复次数一致,比较不同的-ngl。
"$BENCH"\-m"$MODEL"\-devCUDA1-smnone-ngl20\-t4-p512-n128-r3-omd\2>&1|teelogs/benchmarks/qwen2.5-7b-q4km-v100-single-ngl20.md| 单卡设置 | pp512(tokens/s) | tg128(tokens/s) |
|---|---|---|
-ngl 99 | 3118.45 ± 144.84 | 116.00 ± 0.27 |
-ngl 20 | 1152.13 ± 22.57 | 15.92 ± 0.03 |
降低卸载层数后,CPU 需要承担更多模型层的计算,CPU/GPU 之间还可能存在数据传输成本。单路生成必须依次经过各层,因此即使部分层在 GPU 上,CPU 侧也可能拖慢整体生成速度。这里观察到的是卸载方式变化的影响,不是对 FP16 与 Q4_K_M 量化效果的比较。
5.6 显存观察:只统计当前 llama-cli 进程
本次使用单卡 CUDA1,在两组交互式llama-cli测试中保持-c 2048、-t 4、-n 256、--temp 0和相同中文提问。等模型回答完并停留在下一轮输入提示时,使用:
nvidia-smi-i1nvidia-smi-i1\--query-compute-apps=pid,process_name,used_gpu_memory\--format=csv| 单卡设置 | llama-cli 进程显存 | 实际聊天 Generation |
|---|---|---|
-ngl 99 | 4772 MiB | 109.8 tokens/s |
-ngl 20 | 3570 MiB | 12.1 tokens/s |
在两次截图中,CUDA1 上另有一个约 306 MiB 的 Python 进程。因此,不能拿nvidia-smi的整卡总占用当成模型进程的占用。
从-ngl 99改为-ngl 20,本次静态快照记录的模型进程显存减少1202 MiB,但实际聊天生成也明显变慢。-ngl是“卸载层数”,不是显存百分比;运行时仍有 GPU 计算缓冲区、KV Cache 等资源。显存节省不代表这些权重消失:部分模型权重仍需占用主机内存。
测量边界:进程在等待下一轮输入时的nvidia-smi数值,是模型已加载后的显存快照,不是生成过程的峰值显存。此外,基准测试与聊天测试的工作负载不同,两类速度不要混入同一列进行严格比较。
5.7 实验结论与下一步
- Qwen2.5-7B Q4_K_M 的 GGUF 文件大小约4.36 GiB;本次在 CUDA1 上、
-ngl 99、-c 2048的llama-cli显存快照为4772 MiB。文件大小不等于运行显存,也不等于峰值显存。 - 在同一单卡正式 Benchmark 条件下,
tg128从-ngl 99的116.00降至-ngl 20的15.92 tokens/s。 - 本次交互测试记录中,少卸载模型层可减少 GPU 显存占用,但需要接受更低生成速度。这反映了端侧部署中的显存—速度取舍。
- 目前使用的是官方已经量化好的 Q4_K_M GGUF。后续可以从 Qwen2.5-7B-Instruct 原始模型出发,自行转换为高精度 GGUF,再使用
llama-quantize生成并比较不同精度版本;这才是“自行量化”阶段。
日志位于
logs/benchmarks/;保存实验记录时,应保留模型版本、llama.cpp commit、指定设备、-ngl、上下文长度、重复次数和测试时间等条件。
第六章 从原始权重到自制量化模型:F16、Q8_0 与 Q4_K_M
本章使用Qwen2.5-7B-Instruct和已编译的llama.cpp,完成“下载原始权重 → 转换 F16 GGUF → 自行量化 Q4_K_M 与 Q8_0 → 单卡推理及性能对照”的完整流程。此前使用的官方 Q4_K_M 文件作为参照;本章的 F16 GGUF、Q4_K_M 和 Q8_0 均由本实验转换或量化产生。
6.1 实验目标与文件规划
三个模型文件各有用途,不能混淆:
| 文件 | 存放位置 | 用途 |
|---|---|---|
| 原始 Safetensors(约 15G) | models/Qwen2.5-7B-Instruct/ | 原始高精度权重 |
| 自制 F16 GGUF(约 15G) | models/converted/Qwen2.5-7B-Instruct-F16.gguf | 后续量化的输入 |
| 自制 Q4_K_M GGUF(约 4.4G) | models/quantized/Qwen2.5-7B-Instruct-Q4_K_M.gguf | 低比特量化成果 |
| 自制 Q8_0 GGUF(约 7.6G) | models/quantized/Qwen2.5-7B-Instruct-Q8_0.gguf | 较高精度量化对照 |
之前下载的官方量化模型仍保存在models/Qwen2.5-7B-Instruct-GGUF/,供后续对照使用。本章的操作不会覆盖它。
6.2 下载并检查原始模型
在学校服务器的项目目录中,使用原有llm环境:
conda activate llmexportPYTHONNOUSERSITE=1cd/slurm/home/zhengjie/xss_llmmkdir-pmodels/Qwen2.5-7B-InstructexportHF_ENDPOINT=https://hf-mirror.comexportHF_HUB_DISABLE_XET=1hf download Qwen/Qwen2.5-7B-Instruct\--local-dir /slurm/home/zhengjie/xss_llm/models/Qwen2.5-7B-Instruct下载完成后检查:
ls-lhmodels/Qwen2.5-7B-Instruct/*.safetensorsdu-shmodels/Qwen2.5-7B-Instructlsmodels/Qwen2.5-7B-Instruct/config.json\models/Qwen2.5-7B-Instruct/tokenizer.json\models/Qwen2.5-7B-Instruct/model.safetensors.index.json**实际结果:**四个 Safetensors 权重分片均存在,目录约15G,模型配置、分词器和权重索引文件齐全。
6.3 将原始模型转换为 F16 GGUF
转换格式不等于 Q4_K_M 量化。本次源文件中的主要权重为 BF16;指定--outtype f16后,主要张量转成 F16,一些较小的归一化权重和偏置保留为 F32。GGUF 同时保存模型结构、分词器等推理所需元数据。
先确认转换脚本支持相关参数:
python-sllama.cpp/convert_hf_to_gguf.py--help再执行:
mkdir-pmodels/converted logs/conversionset-opipefail python-sllama.cpp/convert_hf_to_gguf.py\models/Qwen2.5-7B-Instruct\--outfilemodels/converted/Qwen2.5-7B-Instruct-F16.gguf\--outtypef16\2>&1|teelogs/conversion/qwen2.5-7b-f16.log转换日志记录了339 个张量、总大小约 15.2G,最终提示:
Model successfully exported to models/converted/Qwen2.5-7B-Instruct-F16.gguf实际文件检查:
Qwen2.5-7B-Instruct-F16.gguf 15G6.4 使用 llama-quantize 自主量化
llama-quantize的输入是上一节产生的高精度 GGUF,输出是指定类型的量化 GGUF。本实验使用Q4_K_M:它是混合精度量化配置,不能理解为“模型中的每个张量都严格使用 4 bit”。
mkdir-pmodels/quantized logs/quantizationset-opipefail ./llama.cpp/build-v100/bin/llama-quantize\models/converted/Qwen2.5-7B-Instruct-F16.gguf\models/quantized/Qwen2.5-7B-Instruct-Q4_K_M.gguf\Q4_K_M\2>&1|teelogs/quantization/qwen2.5-7b-q4km.log实际量化结果:
| 指标 | 实测值 |
|---|---|
| 处理张量数量 | 339 |
| 量化前模型权重数据大小 | 14526.27 MiB |
| 量化前平均精度 | 16.00 BPW |
| 量化后模型权重数据大小 | 4460.45 MiB |
| 量化后平均精度 | 4.91 BPW |
| 量化耗时 | 109929.16 ms,约 110 秒 |
输出文件的ls -lh大小 | 4.4G |
BPW是Bits Per Weight,即平均每个权重占用的 bit 数。量化后权重数据约为量化前的30.7%,约减少69.3%;这不是对整份 GGUF 文件逐字节测得的压缩率。
为什么 Q4_K_M 实测是 4.91 BPW?因为日志显示,不同张量采用了不同精度。例如:
blk.26.attn_q.weight f16 → q4_K blk.26.attn_v.weight f16 → q6_K blk.26.ffn_down.weight f16 → q6_K blk.26.ffn_gate.weight f16 → q4_K blk.26.attn_q.bias f32(保留)部分张量以 Q6_K 保存,部分以 Q4_K 保存,少量小张量仍为 F32。因此,Q4_K_M 是目标量化方案名,并不代表整模型平均恰好 4.00 BPW。本次使用的是 llama.cpp 的量化工具,不是 GPTQ 校准量化实验。
6.5 在单张 V100 上运行自制模型
量化文件生成后,还需要实际加载和推理,才能确认整个流程可用。实验沿用之前确定的CUDA1 单卡配置:
conda activate llmexportPYTHONNOUSERSITE=1module load cuda/12.4cd/slurm/home/zhengjie/xss_llmMODEL="/slurm/home/zhengjie/xss_llm/models/quantized/Qwen2.5-7B-Instruct-Q4_K_M.gguf"mkdir-plogs/inference ./llama.cpp/build-v100/bin/llama-cli\-m"$MODEL"\-devCUDA1\-smnone\-ngl99\-c2048\-t4\-n128\--temp0\--single-turn\-p"请用通俗易懂的语言解释什么是大语言模型,控制在100字以内。"\2>&1|teelogs/inference/qwen2.5-7b-self-q4km-first-run.log实际日志确认模型路径为models/quantized/Qwen2.5-7B-Instruct-Q4_K_M.gguf,加载类型为Q4_K - Medium,并能生成中文回答:
[ Prompt: 602.0 t/s | Generation: 84.3 t/s ]回答中出现“人类-like的文本”这一不自然措辞。它是值得记录的观察,但单次问答不足以判断该措辞是否由量化引起;需要高精度和量化模型在一致条件下、面对更多输入时才能评估输出差异。
6.6 官方与自制 Q4_K_M 的正式性能对照
首次问答通过后,为避免把交互式聊天的速度和基准测试混在一起,使用统一的llama-bench工作负载测试自制 Q4_K_M:单张 V100(CUDA1)、-sm none、-ngl 99、-t 4、-p 512、-n 128、每项重复 3 次。
MODEL="/slurm/home/zhengjie/xss_llm/models/quantized/Qwen2.5-7B-Instruct-Q4_K_M.gguf"BENCH="./llama.cpp/build-v100/bin/llama-bench"mkdir-plogs/benchmarksset-opipefail"$BENCH"\-m"$MODEL"-devCUDA1-smnone-ngl99\-t4-p512-n128-r3-omd\2>&1|teelogs/benchmarks/qwen2.5-7b-self-q4km-v100-single.md| 指标 | 官方 Q4_K_M | 自制 Q4_K_M |
|---|---|---|
| Benchmark 报告的模型大小 | 4.36 GiB | 4.36 GiB |
pp512:输入处理速度 | 3118.45 ± 144.84 t/s | 3050.62 ± 262.79 t/s |
tg128:逐 Token 生成速度 | 116.00 ± 0.27 t/s | 115.77 ± 0.33 t/s |
两份模型的体积和本次单卡吞吐量接近。结果只能说明本次测试条件下的表现,不等于证明两份文件逐字节一致或所有回答相同。±为基准程序在重复测量中报告的波动量,不应把本次微小差距直接归因为量化效果差异。
6.7 从同一份 F16 GGUF 自主量化 Q8_0
为比较不同量化类型,继续使用原始的F16 GGUF作为输入,而不是将 Q4_K_M 再转为 Q8_0:提高已经量化过的权重存储位数,不能恢复此前丢失的信息。
mkdir-pmodels/quantized logs/quantizationset-opipefail ./llama.cpp/build-v100/bin/llama-quantize\models/converted/Qwen2.5-7B-Instruct-F16.gguf\models/quantized/Qwen2.5-7B-Instruct-Q8_0.gguf\Q8_0\2>&1|teelogs/quantization/qwen2.5-7b-q8_0.log实际量化日志:
model size = 14526.27 MiB (16.00 BPW) quant size = 7717.68 MiB ( 8.50 BPW) quantize time = 87003.46 msls -lh显示输出文件约7.6G。Q8_0 的平均8.50 BPW并非错误:量化块还需保存缩放参数,且并非模型所有张量都按同一种低比特形式存储。量化约耗时87 秒,这是一次性的模型准备时间,不是推理延迟。
6.8 验证自制 Q8_0 并完成 Benchmark
使用和 Q4_K_M 相同的中文提问、单卡与推理参数验证 Q8_0:
conda activate llmexportPYTHONNOUSERSITE=1module load cuda/12.4cd/slurm/home/zhengjie/xss_llmMODEL="/slurm/home/zhengjie/xss_llm/models/quantized/Qwen2.5-7B-Instruct-Q8_0.gguf"./llama.cpp/build-v100/bin/llama-cli\-m"$MODEL"-devCUDA1-smnone-ngl99\-c2048-t4-n128--temp0--single-turn\-p"请用通俗易懂的语言解释什么是大语言模型,控制在100字以内。"\2>&1|teelogs/inference/qwen2.5-7b-self-q8_0-first-run.log实际加载类型为Q8_0,中文问答正常。本次交互输出速度:
[ Prompt: 354.3 t/s | Generation: 66.9 t/s ]然后按统一的单卡 Benchmark 条件测量:
BENCH="./llama.cpp/build-v100/bin/llama-bench"set-opipefail"$BENCH"\-m"$MODEL"-devCUDA1-smnone-ngl99\-t4-p512-n128-r3-omd\2>&1|teelogs/benchmarks/qwen2.5-7b-self-q8_0-v100-single.md实际测得:pp512 = 3698.46 ± 172.25 t/s,tg128 = 86.29 ± 0.14 t/s,Benchmark 报告模型大小7.54 GiB。
6.9 测量 F16 基线并汇总三模型数据
最后使用同一份自行转换的 F16 GGUF 和相同的llama-bench参数,补齐高精度基线。执行前应确认指定 GPU 有足够可用显存。
MODEL="/slurm/home/zhengjie/xss_llm/models/converted/Qwen2.5-7B-Instruct-F16.gguf"BENCH="./llama.cpp/build-v100/bin/llama-bench"set-opipefail"$BENCH"\-m"$MODEL"-devCUDA1-smnone-ngl99\-t4-p512-n128-r3-omd\2>&1|teelogs/benchmarks/qwen2.5-7b-f16-v100-single.md本次正式对照表(均为 Qwen2.5-7B-Instruct,build0ee9435):
| 指标 | F16(自制转换) | Q8_0(自制) | Q4_K_M(自制) |
|---|---|---|---|
| Benchmark 模型大小 | 14.19 GiB | 7.54 GiB | 4.36 GiB |
ls -lh约数 | 15G | 7.6G | 4.4G |
| 量化日志平均 BPW | 16.00 | 8.50 | 4.91 |
pp512(t/s) | 4357.11 ± 341.99 | 3698.46 ± 172.25 | 3050.62 ± 262.79 |
tg128(t/s) | 51.90 ± 0.05 | 86.29 ± 0.14 | 115.77 ± 0.33 |
测试条件:Tesla V100-PCIE-32GB;-dev CUDA1 -sm none -ngl 99 -t 4 -p 512 -n 128 -r 3 -o md。四张卡被程序发现,并不表示该次工作负载使用四张卡;本实验显式选择 CUDA1 单卡,且禁用模型切分。
结果解读:本次测试中,F16 的批量 Prompt 处理速度较高,Q4_K_M 的逐 Token 生成速度较高;Q8_0 介于两者之间。以 F16 为参照,Q8_0 和 Q4_K_M 的tg128分别约为1.66 倍和2.23 倍;这不是说它们在所有任务中都按相同比例加速,也不能单凭此表判定回答质量。
pp512测量一次性处理 512 个输入 Token 的吞吐量,tg128测量逐 Token 生成 128 个 Token 的吞吐量。llama-cli的实际问答速度与这两个固定负载不同,不应直接混用;另外,文件大小也不等于显存峰值。
6.10 本章成果与下一阶段
至此,已完整完成原始 Safetensors → F16 GGUF → 分别制作 Q8_0、Q4_K_M → 中文推理验证 → 同条件单卡 Benchmark。两种量化模型均可加载并生成中文回答,但单道中文问答只能作为功能检查,尚不能证明量化对回答质量的影响大小。
下一阶段进入llama-server部署与本地 API 调用:让模型以服务形式运行,再通过 HTTP 请求调用。部署演示可沿用已验证的自制 Q4_K_M;如需对照精度或资源消耗,也保留 F16 与 Q8_0 文件,不必再次下载或量化。
第七章 使用 llama-server 部署本地大模型
7.1 为什么已经有 llama-cli,还需要 llama-server
前面的llama-cli主要用于在终端中直接与模型交互,适合验证模型是否能够正常加载和生成文本。
而llama-server可以把本地 GGUF 模型部署成一个 HTTP 服务,使 Python 程序、网页或其他应用能够通过 API 调用模型。
整体调用流程可以理解为:
Python / 网页 / 其他客户端 ↓ HTTP 请求 ↓ llama-server ↓ 本地 GGUF 模型 ↓ 返回回答本章继续使用前面已经自主量化完成的 Qwen2.5-7B-Instruct Q4_K_M 模型。
7.2 启动 llama-server
模型路径:
/slurm/home/zhengjie/xss_llm/models/quantized/Qwen2.5-7B-Instruct-Q4_K_M.gguf启动命令:
conda activate llmexportPYTHONNOUSERSITE=1module load cuda/12.4cd/slurm/home/zhengjie/xss_llmMODEL="/slurm/home/zhengjie/xss_llm/models/quantized/Qwen2.5-7B-Instruct-Q4_K_M.gguf"mkdir-plogs/server ./llama.cpp/build-v100/bin/llama-server\-m"$MODEL"\-devCUDA1\-smnone\-ngl99\-c2048\-t4\--host127.0.0.1\--port18080\2>&1|teelogs/server/qwen2.5-7b-self-q4km-server.log服务端实际输出中出现:
llama_server: model loaded llama_server: listening on http://127.0.0.1:18080说明模型已经成功加载,服务开始监听本机18080端口。
这里使用127.0.0.1,表示服务只监听服务器本机,不直接暴露到外部网络。
7.3 使用 health 接口检查服务状态
在另一个终端执行:
curlhttp://127.0.0.1:18080/health实际返回:
{"status":"ok"}这说明llama-server已经处于可用状态,可以继续接收 API 请求。
7.4 使用 curl 调用聊天 API
llama-server启动后,可以通过/v1/chat/completions接口发送聊天请求。
curlhttp://127.0.0.1:18080/v1/chat/completions\-H"Content-Type: application/json"\-d'{ "model": "Qwen2.5-7B-Instruct-Q4_K_M.gguf", "messages": [ { "role": "user", "content": "请用通俗易懂的语言解释什么是大语言模型,控制在100字以内。" } ], "temperature": 0, "max_tokens": 128 }'实际请求成功返回 JSON,其中模型回答为:
大语言模型是一种强大的计算机程序,能理解和生成人类-like的文本,涵盖广泛话题。它通过大量数据学习,能回答问题、写文章,甚至创作诗歌和故事。本次请求记录:
| 指标 | 结果 |
|---|---|
| Prompt Tokens | 49 |
| Completion Tokens | 40 |
| Total Tokens | 89 |
| 生成速度 | 约 81.90 tokens/s |
这一步说明模型已经不再只能通过llama-cli使用,而是可以通过标准 HTTP 请求被其他程序调用。
7.5 使用 Python 调用本地模型 API
创建 Python 脚本:
cat>scripts/chat_local.py<<'PY' import json from urllib.request import Request, urlopen url = "http://127.0.0.1:18080/v1/chat/completions" payload = { "model": "Qwen2.5-7B-Instruct-Q4_K_M.gguf", "messages": [ { "role": "user", "content": "请用通俗易懂的语言解释什么是大语言模型,控制在100字以内。" } ], "temperature": 0, "max_tokens": 128, } request = Request( url, data=json.dumps(payload, ensure_ascii=False).encode("utf-8"), headers={"Content-Type": "application/json"}, method="POST", ) with urlopen(request, timeout=120) as response: result = json.load(response) print("模型回答:") print(result["choices"][0]["message"]["content"]) print("\nToken 用量:") print(result.get("usage", {})) PY执行:
python-sscripts/chat_local.py实际输出:
模型回答: 大语言模型是一种强大的计算机程序,能理解和生成人类-like的文本,就像和一个知识渊博的朋友聊天一样,它可以写文章、回答问题、创作诗歌等。 Token 用量: {'completion_tokens': 38, 'prompt_tokens': 49, 'total_tokens': 87, 'prompt_tokens_details': {'cached_tokens': 48}}这说明 Python 程序已经能够通过 HTTP API 获取本地模型回答。
其中cached_tokens: 48表示服务端报告部分输入 Token 使用了提示词缓存,它不是额外增加的 Token 消耗。
7.6 实现简单的多轮对话
为了让 Python 程序支持上下文,需要在客户端保存messages,并在每次请求时把历史消息一起发送给模型。
创建交互脚本:
importjsonfromurllib.requestimportRequest,urlopen URL="http://127.0.0.1:18080/v1/chat/completions"messages=[{"role":"system","content":"你是一个中文助手,请清楚、简洁地回答问题。"}]print("本地大模型聊天已启动,输入 /exit 退出。")whileTrue:question=input("\n你:").strip()ifquestion=="/exit":breakifnotquestion:continuemessages.append({"role":"user","content":question})payload={"model":"Qwen2.5-7B-Instruct-Q4_K_M.gguf","messages":messages,"temperature":0,"max_tokens":256}request=Request(URL,data=json.dumps(payload,ensure_ascii=False).encode("utf-8"),headers={"Content-Type":"application/json"},method="POST")try:withurlopen(request,timeout=120)asresponse:result=json.load(response)answer=result["choices"][0]["message"]["content"]print("\n模型:"+answer)messages.append({"role":"assistant","content":answer})messages=messages[:1]+messages[-12:]exceptExceptionasexc:print(f"\n请求失败:{exc}")messages.pop()实际测试中,程序已经可以连续进行多轮对话,说明客户端能够通过messages保存并传递上下文。
需要注意:HTTP API 本身不会自动替 Python 程序保存全部聊天历史,多轮对话依赖客户端主动维护上下文。
7.7 多轮对话中的幻觉现象
在多轮对话测试中,输入:
张一帆是谁模型给出了一个具体人物身份,并声称与电视剧《甄嬛传》有关。在用户继续质疑后,模型仍然重复这一说法。
这个实验说明,大语言模型有可能生成语句流畅、细节具体,但没有可靠依据的内容,这类现象通常称为“幻觉”。
因此,模型能够正常部署和生成文本,并不代表它输出的所有事实都可信。
量化主要解决的是模型存储空间、显存占用和推理效率问题,并不会自动解决事实准确性问题。
7.8 使用 System Prompt 降低随意编造
为了观察提示词约束的作用,重新开启独立请求,并增加更严格的 System Prompt:
curlhttp://127.0.0.1:18080/v1/chat/completions\-H"Content-Type: application/json"\-d'{ "model": "Qwen2.5-7B-Instruct-Q4_K_M.gguf", "messages": [ { "role": "system", "content": "你是严谨的中文助手。对于无法确认的人物身份,不要猜测,不要编造作品、演员或经历。请明确说明无法确认,并询问必要的背景信息。" }, { "role": "user", "content": "张一帆是谁?" } ], "temperature": 0, "max_tokens": 128 }'这次模型回答:
关于“张一帆”的身份,我没有找到足够的信息来确认具体是指哪个人物。这可能是一个普通的名字,也可能是某个特定人物的名称,但没有足够的上下文信息让我确定。您能提供更多的背景信息吗?与前一次相比,模型不再直接编造具体人物身份,而是表达不确定性并请求更多背景信息。
这说明明确的 System Prompt 可以改善模型在某些不确定问题上的回答方式,但不能证明提示词能够彻底消除幻觉。
另外,模型回答中的“没有找到足够的信息”并不代表它进行了联网搜索。本实验中的llama-server只加载本地 GGUF 模型,没有接入搜索工具或外部数据库。
7.9 检查 Python 多轮对话脚本
执行:
python-mpy_compile scripts/chat_interactive.py终端没有报错,说明脚本通过 Python 语法检查。
需要区分:py_compile只能验证语法是否正确,不能证明全部业务逻辑都正确。前面的实际多轮对话成功运行,才是功能能够工作的直接验证。
7.10 本章实践结果
本章已经完成从本地模型到可调用服务的完整链路:
自主量化 Q4_K_M GGUF ↓ llama-server 加载模型 ↓ HTTP 服务启动 ↓ curl 调用聊天 API ↓ Python 调用 API ↓ Python 多轮对话 ↓ 观察模型幻觉与提示词约束至此,自主量化得到的 GGUF 模型已经从“可以在终端中运行”,进一步发展为“可以被程序通过 HTTP API 调用”的本地大模型服务。