刚拿到模型文件在本机启动,最容易反复重来的往往不是参数调错,而是权重目录少了一层、依赖版本对不上。先把范围压到最小:确认权重、确认环境、用最小参数把服务拉起来,再发一条最短请求把链路走通,最后把这份配置存成基线。下面每一步都以本机命令输出和日志为准,出现不一致时先停下来对齐,再往下走。
先做三步确认再谈调参:权重文件与索引能否一一对上,运行时的驱动、运行时与推理框架版本是否彼此相容,最小参数下进程能否完成加载并监听端口。三步都通过后,用一条单轮极短请求确认端到端,再把配置、校验值和环境一起存档。任何一步失败就回到那一步修,不要靠叠加参数去碰运气;显存、耗时这类观察值只作前后对比的锚点,不宜当成性能结论。
确认权重文件与目录结构是否符合预期
加载失败里最常见的一类原因是文件不全或层级不对,而不是模型本身有问题。先假设路径写错、分片缺失、索引与分片不一致这三种情况,再逐个排除。不同权重格式的文件组合不一样,但通常需要看到:模型配置文件(例如 config.json 一类的元数据文件)、一个或多个权重文件(.safetensors / .bin / .gguf / .onnx 等,具体看格式)、分词器相关文件、以及分片格式下的索引文件。
ls -la /abs/path/to/model
find /abs/path/to/model -maxdepth 2 -type f -printf "%P %s\n" | sort
完整性比对分两步。第一步看大小:把每个文件的实际字节数与获取时记录的大小核对,明显偏小的分片通常就是没下完。第二步看摘要:发布方若给了校验清单,就逐行比对文件名和摘要值;没有清单时,至少比对分片数量。
sha256sum /abs/path/to/model/*.safetensors
cat provided.sha256 # 发布方若提供,逐行核对文件名与摘要
python -c "import json;d=json.load(open('/abs/path/to/model/model.safetensors.index.json'));print(sorted(set(d['weight_map'].values())))"
索引里列出的分片列表,应当和磁盘上实际存在的分片一一对应,多一个少一个都会在加载阶段报错。目录名建议用纯 ASCII 且不带空格,避免某些运行时在解析路径时出问题。这一步只回答“文件齐不齐、对不对得上”,不涉及任何推理性能判断。
确认运行环境与依赖版本
版本不匹配的问题最好在启动服务之前挑出来,而不是等到加载权重时才从报错里倒推。先把驱动、CUDA 运行时、Python 和推理框架四者的版本打出来,留一份记录。
nvidia-smi
nvcc `--version`
python -V
python -c "import torch;print(torch.__version__, torch.version.cuda)"
python -c "import your_framework as f;print(f.__version__)"
pip freeze > env.txt
判断依据是这几项之间的相互对应关系:驱动能支持的最高 CUDA 版本要覆盖实际使用的运行时版本;框架编译时链接的 CUDA 版本要和运行环境里加载的运行时一致;Python 版本要落在框架声明的支持区间内,这个区间可以从安装时的元数据里看到,不必凭印象猜。建议用独立的虚拟环境来装依赖,避免和系统里已有的包互相覆盖。
版本不匹配通常有几类明显的报错特征,看到时可以优先往这个方向查:undefined symbol 之类通常是扩展模块编译版本不一致;CUDA error: no kernel image is available for execution on the device 通常指向算力与编译目标不匹配;libcudart.so.XX: cannot open shared object file 是运行时库缺失或版本对不上;导入阶段就报 ImportError 则多为依赖冲突或 Python 版本不符。这些只是常见特征,实际以本机命令输出为准。
用最小参数启动服务,只看能否加载成功
把“能加载”和“能推理”拆成两次确认,第一次启动只验证加载,不要压测、不要并发、不要加自定义优化项。最小启动配置通常需要显式指定这几项:模型目录的绝对路径、权重精度或 dtype(不确定时先用与权重一致的自动推断)、可见设备、监听地址与端口、以及一个保守的最大上下文长度。
CUDA_VISIBLE_DEVICES=0 python -m your_framework.serve \
`--model` /abs/path/to/model \
`--dtype` auto \
`--max-model-len` 2048 \
`--host` 127.0.0.1 \
`--port` 8000
加载过程的日志是可以读的:正常的顺序一般是逐个读取权重分片、分配显存、完成模型构建,最后出现类似 ready、listening 或服务已绑定端口的行;此时进程稳定驻留,端口处于监听状态。失败则通常停在某一类信息上,例如 CUDA out of memory(上下文或精度设置过大)、找不到文件或分片(回看第一步)、形状或 dtype 不匹配(多为权重格式与加载方式不一致)。如果日志长时间没有新增且没有报错,先确认进程还在、GPU 是否被真正占用,再考虑是不是卡在读取大文件上。
发一条最短请求验证端到端
服务加载成功不等于推理链路通。用一条单轮、输入极短、输出上限很小的请求来验证,参数上把随机性关掉,便于重复执行时对比。
curl -s http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"your-model-name","messages":[{"role":"user","content":"hi"}],"max_tokens":16,"temperature":0}'
构造要点是那几个占位字段:接口地址按实际框架的路由调整,模型名要和服务端注册的名称或路径写法一致,messages 只放一轮,max_tokens 给小值,temperature 设 0。返回正常时关注三点:HTTP 状态码为 200、响应体里有 choices 且内容非空、usage 字段有可读的计数。返回异常时也各有观测点:4xx 多为请求结构问题,先核对字段名和模型名;5xx 要看服务端日志,常见是显存不足或算子执行失败;请求长时间挂起则要确认是否在排队、GPU 是否真的参与了计算。这一步通过,才说明从进程加载到实际推理的链路是通的。
把这份最小配置存档作为基线
跑通之后不要急着改参数,先把当前这份能跑的状态存下来,下次任何改动都从这里回退。需要存档的内容包括:模型目录路径与权重摘要值、环境记录(Python、框架、驱动与 CUDA 版本,即前面生成的 env.txt)、完整的启动命令及全部参数、服务监听的端口与模型名、以及那条最短请求的样例。可以放在同一个目录里便于整体对比。
mkdir -p baseline
cp start.sh env.txt request.json baseline/
sha256sum /abs/path/to/model/* > baseline/weights.sha256
之后每次调整参数,建议同时记录几项观察值:启动加载阶段占用的显存峰值(可用 nvidia-smi 观察)、从启动到出现 ready 的耗时、以及那条最短请求的往返耗时和输出内容是否一致。这些值只用于和自己上一次的结果作对比,用来判断改动是否带来副作用,不要单独拿出来当作性能结论。有了这份基线,参数就可以一次只动一项,出问题时也能迅速回到已知可用的状态。