首页 / AI教程 / 正文
AI教程

AI模型部署教程:从本地环境到Docker服务化与监控告警的避坑实战指南

chuanbook chuanbook
发布于 2026 年 10 月 31 日
阅读 约29分钟
浏览 1
评论 0

1.1 AI模型部署教程学习路径与典型应用场景

我自己走过不少弯路才明白一件事:学AI模型部署,别一上来就冲着"跑通"去。你得先搞清楚自己要拿它干什么。我见过太多人装了CUDA、拉了镜像、折腾一晚上终于看到模型吐字了,然后呢?然后就没然后了。这套教程我打算按"能落地"的思路带你走一遍,从最基础的认知开始,一直到Docker服务化、监控告警,中间穿插我踩过的坑。

学习路径这件事,我的建议是这样的:先弄明白推理和训练的区别,再搞懂模型文件长什么样,接着上手本地跑一个最小可用的demo,最后才去碰容器化和服务化。有人会问能不能跳过前面直接Docker一键部署——能,但你出了问题连日志都看不懂,我亲身经历过那种绝望。典型场景我列举几个:本地知识库问答、代码补全助手、图片生成、语音转写。这些场景对硬件的要求差别很大,一个7B的对话模型和一张Stable Diffusion出图的显存需求完全不是一回事。

我给你一个更实在的判断标准:如果你只是想体验一下,笔记本上跑个量化后的小模型就够了;如果你要做团队内部服务,那得认真考虑并发、显存和响应延迟。我自己第一次部署是为了给一个小团队做文档问答,当时天真的以为16G内存的机器能扛住,结果模型加载到一半就OOM了。那次的教训比任何教程都深刻。

1.2 本地部署AI大模型教程的硬件、系统与网络要求

硬件这块我说点大实话。网上那些"最低配置"很多时候是"能启动"的配置,不是"能用"的配置。7B参数模型全精度需要大概14G显存,量化到4bit后能压到4-5G,可你要是想上下文长一点、并发多几个,那就得往上加。我现在的习惯是先按目标模型的两倍显存去规划,留足余量心里踏实。内存也别省,模型加载时会先读到内存再上显存,内存不够会直接卡死。

系统方面,Linux是首选,Ubuntu 22.04我用得最多,驱动和CUDA的兼容性最好。Windows也能跑,WSL2的体验这两年进步很大,但涉及多卡和某些推理框架时还是会别扭。macOS的M系列芯片有统一内存的优势,跑量化模型挺省心,就是生态上有些框架支持得慢半拍。这些差别你在选机器的时候就该想清楚,别等装到一半才发现某个依赖只有Linux版本。

网络要求容易被忽略,我吃过亏。模型权重动辄几个G到几十个G,下载速度直接决定你的心情。国内拉HuggingFace有时候很慢,配个镜像或者用hf_transfer加速会好很多。还有一种情况是内网部署,那就得提前把模型和依赖包全都离线准备好,我建议你把pip的wheel也一起缓存下来,不然到了没网的机器上你会想哭。顺便提一句,部署机器最好有稳定的上传带宽,如果你打算对外提供API服务的话。

1.3 模型格式、推理框架与依赖管理基础

模型格式这件事,刚接触时我也一脸懵。常见的就那几种:PyTorch的.bin/.safetensors、GGUF、ONNX,还有各家框架自己的格式。safetensors现在基本成了主流,加载快还安全,不像pickle格式有反序列化风险。GGUF是llama.cpp那套生态用的,主打CPU和低显存推理,量化选项特别丰富。ONNX更偏跨平台部署,适合做推理优化和硬件适配。

推理框架的选择要看你手里的硬件和想做的事。vLLM我用来做高并发API服务,吞吐确实猛;llama.cpp适合在CPU或者小显存卡上跑;Transformers是万能胶水,啥都能干但速度一般;TensorRT-LLM在N卡上性能很顶,代价是配置门槛高。我的做法是先用Transformers跑通验证效果,再根据性能需求换框架。别一上来就追新框架,容易陷进去出不来。

依赖管理这块我强烈建议用conda或者venv隔离环境,别在系统Python里乱装。CUDA版本、PyTorch版本、驱动版本三者之间的对应关系是个经典坑,装之前去官网查兼容表,五分钟能省你五小时。我现在习惯把环境导出成environment.yml或者requirements.txt,再配一个Dockerfile,换机器的时候一键重建。有一次我升级了驱动没注意CUDA版本,结果整个推理服务起不来,排查了一下午才发现是版本错配。

1.4 模型许可、数据安全与部署成本评估

许可协议一定要看,别嫌麻烦。有些模型是Apache 2.0,商用基本没限制;有些是自定义协议,比如要求你标注来源、限制用户规模,甚至有禁止商用的条款。我见过有人把研究许可的模型直接拿去做付费产品,这种风险不值得冒。下载模型页面上的License部分花两分钟读一下,比事后补救强太多。

数据安全方面,本地部署的一大好处就是数据不出内网,这也是很多人选择自建的核心原因。不过本地不等于安全,模型服务的API端口如果暴露在公网又没做鉴权,那跟裸奔没区别。我的习惯是默认只监听127.0.0.1,需要外部访问时走反向代理加token验证。日志里也别随手打印用户输入,尤其是涉及隐私的业务场景。

成本评估我一般分三块算:硬件成本、电费和时间成本。个人玩票的话一张二手卡加一台旧机器就能起步;团队用的话,租云GPU按小时计费有时候比自己买更划算,特别是负载不稳定的情况。还有一个隐形成本是维护,驱动升级、框架迭代、模型更新,这些都要占人力。我自己的经验是,小规模场景租云更省心,长期稳定负载再考虑自建。把这些算清楚再动手,后面会少很多纠结。

2.1 模型下载、权重校验与目录规划

模型下载这一步看着简单,坑却不少。我习惯先去HuggingFace或者ModelScope上找到目标模型的Files页面,把config.json、tokenizer相关文件和权重文件看清楚,别只盯着那几个.safetensors。有一次我只下了权重,忘了tokenizer的special_tokens_map,跑起来分词结果乱七八糟,排查半天才发现少文件。用huggingface-cli下载时加个--local-dir参数,文件会按原始结构落盘,比手动点下载靠谱得多。国内网络拉大文件容易断,我一般会开hf_transfer或者用镜像站,几十G的权重断一次重来真的很磨人。

权重校验这件事很多人跳过,我建议别省。正规仓库会提供sha256或者文件大小对照,下载完用sha256sum跑一遍,对不上就重下。我遇到过一次下载到99%卡住,文件表面完整实际尾部损坏,加载时报了个莫名其妙的unexpected end of file,浪费了一晚上。现在我的习惯是下载完先校验,再写个简单的加载脚本试跑一次,确认模型能正常初始化再往下走。

目录规划我吃过亏之后变得很讲究。我的习惯是按models/{模型名}/{版本或量化类型}/来分,每个目录里放权重、配置、tokenizer,再加一个README记下来源和下载日期。推理框架的缓存目录、日志目录、临时文件目录全部分开,别混在一起。有一次我把两个模型的权重放在同一个文件夹,文件名还高度相似,结果启动时加载错了模型,输出完全对不上,查了半天才发现路径写串了。目录清晰这件事,平时看不出价值,出问题的时候能救命。

2.2 CPU/GPU推理环境配置与CUDA适配

环境配置这块,我的原则是版本对齐优先于一切。装之前先确认三件事:显卡驱动版本、CUDA版本、PyTorch版本。NVIDIA官网有兼容表,花五分钟对一下,能省掉后面几小时的报错排查。我通常用conda建独立环境,Python版本选3.10或者3.11,太新太旧都可能碰到轮子不全的问题。装PyTorch的时候别直接pip install torch,去官网拿对应的CUDA版本安装命令,比如cu121或者cu124,装错了就是CPU版本在跑,速度慢得让你怀疑人生。

验证GPU是否真的可用,我一般跑三行代码:torch.cuda.is_available()、torch.cuda.device_count()、torch.cuda.get_device_name(0)。三个都正常才算过关。有一次环境里装了CPU版PyTorch,is_available()返回False,我以为是驱动问题,重装驱动折腾半天,最后发现就是装错了包。还有个细节是CUDA_VISIBLE_DEVICES这个环境变量,多卡机器上用它指定用哪张卡,避免模型默认吃满所有显存。

CPU推理也不能忽视。有些场景就是没有GPU,比如小团队的内网机器、边缘设备。这时候用llama.cpp或者ONNX Runtime的CPU版本更合适,配合量化模型效果能接受。我自己的经验是CPU推理要关注线程数设置,OMP_NUM_THREADS和框架自己的线程参数都调一下,设成物理核心数通常比较稳。内存带宽往往是瓶颈,而不是算力,所以别指望加内存能线性提速,合理预期很重要。

2.3 模型量化、显存优化与启动参数调优

量化是本地部署的必修课,尤其是显存不宽裕的时候。我常用的是4bit量化,GPTQ和AWQ都试过,效果损失在可接受范围内,显存占用能降到原来的三分之一左右。GGUF格式的Q4_K_M也是我常用的选项,CPU和混合推理都友好。量化不是越狠越好,2bit有时候输出会明显退化,特别是涉及推理和代码生成的场景。我的做法是先用4bit跑,效果不够再考虑8bit,别一上来就追求极限压缩。

显存优化有几个实操点。推理框架一般都有gpu_memory_utilization这类参数,vLLM里默认0.9,我通常调到0.85留点余量,避免跟其他进程抢显存导致OOM。max_model_len也要根据实际需求设,设太大显存预分配就多,设太小长文本会被截断。我见过有人把上下文拉到32K,结果一张24G的卡连7B模型都跑不动,调回8K就顺畅了。批处理大小max_num_seqs同理,并发和显存是跷跷板,得按业务实际来。

启动参数调优我习惯做小步对比。同一个模型,换不同的tensor_parallel_size、不同的dtype、开不开flash attention,性能差别能到百分之几十。我的方法是固定一个测试prompt集,每次只改一个参数,记录首token延迟和吞吐,找到适合自己硬件的组合。有一次我把dtype从float16改成bfloat16,显存没怎么变但数值稳定性好了不少,长文本生成时的乱码问题消失了。这种细节只有自己试过才知道。

2.4 本地API服务封装、调用与功能测试

模型能跑起来只是第一步,封装成API才算能给别人用。我常用的方案是vLLM自带的OpenAI兼容接口,启动时加--api-key和--host 0.0.0.0 --port 8000,前端或者脚本直接用openai的SDK就能调,省掉自己写服务层的功夫。需要更灵活的控制时,我会用FastAPI包一层,加自定义的预处理、日志和鉴权逻辑。接口设计上我建议至少提供/v1/chat/completions和/health两个端点,前者干活,后者给监控用。

调用测试我会分三层做。第一层用curl发个最简单的请求,确认服务通、返回结构对。第二层用Python脚本跑多轮对话,看上下文拼接和stop token是否正常。第三层才是压测,用工具模拟并发,观察延迟和错误率。我见过接口单次调用没问题,一并发就超时,原因是框架的默认worker数太少。功能测试里我还会专门测边界情况:空输入、超长输入、特殊字符、多语言混合,这些在真实使用中都会碰到。

封装的时候有个细节容易被忽略:超时和重试。模型推理有时候会卡住,尤其是长文本生成,客户端不设超时就会一直等。我在服务端和客户端都设了超时,服务端用框架的request timeout,客户端用自己的连接超时,再配一个有限次数的重试。日志里记录请求ID、耗时、token数,出问题的时候能快速定位。这些工程上的小事,决定了你的服务是玩具还是能用的东西。

2.5 常见报错排查与性能基准测试

报错排查我总结了一个顺序:先看日志,再看显存,再看版本。OOM是最常见的,日志里会写CUDA out of memory,这时候先降max_model_len或者batch size,再考虑量化。加载失败常见的是文件缺失或者格式不对,报错里一般会点名哪个文件,顺着找就行。还有一类是版本不匹配,比如undefined symbol这种,基本就是PyTorch和CUDA或者某个扩展库版本对不上,重装对应版本通常能解决。

端口冲突和权限问题也经常遇到。Address already in use就换个端口或者把占用的进程杀掉,Permission denied检查一下是不是用了1024以下的端口。我碰到过一次模型服务启动正常但外部访问不了,最后发现是防火墙规则没放行,这种问题不看网络层根本想不到。我的习惯是每解决一个报错就在笔记里记一条,时间长了就是自己的排错手册,比任何教程都贴合自己的环境。

性能基准测试我一般看四个指标:首token延迟、生成速度(tokens/s)、并发吞吐、显存峰值。测试用固定的prompt集,输入长度覆盖短中长三档,输出长度固定,多跑几轮取稳定值。工具上我用过locust和wrk,简单场景自己写个asyncio脚本也够用。基准数据要跟硬件配置一起记下来,换了卡或者换了框架版本再测一次对比,心里才有数。我自己的一张卡在不同量化方案下生成速度能差一倍多,不测根本不知道哪个配置最划算。 FROM pytorch/pytorch:2.1.2-cuda12.1-cudnn8-runtime AS base ENV PYTHONDONTWRITEBYTECODE=1 \

PYTHONUNBUFFERED=1 \
HF_HOME=/cache/huggingface

WORKDIR /app COPY requirements-core.txt . RUN pip install --no-cache-dir -r requirements-core.txt -i https://pypi.tuna.tsinghua.edu.cn/simple COPY requirements-app.txt . RUN pip install --no-cache-dir -r requirements-app.txt -i https://pypi.tuna.tsinghua.edu.cn/simple COPY ./app /app EXPOSE 8000 CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

4.1 将模型服务封装为REST/gRPC API

模型能跑起来,和模型能对外提供服务,中间隔着的是一整个工程化的距离。我早期的一个项目,模型在notebook里跑得好好的,直接扔给业务方用,结果对方第一句话就问“接口文档在哪”,我当时就愣住了。那一刻我才意识到,模型本身只是一个函数,外面得包一层协议、一层错误处理、一层并发控制,才能算一个服务。

REST 和 gRPC 的选择,我现在的判断标准很简单:给前端或者第三方调,用 REST;内部服务之间高频互调,尤其是流式传输场景,gRPC 更合适。REST 的好处是调试方便,curl 一下就能测,文档用 OpenAPI 自动生成,接入方不用学新东西。gRPC 的强项是二进制协议加 HTTP/2,延迟低、吞吐高,流式输出原生支持,缺点是调试得靠 grpcurl 或者专门工具,对不熟悉 protobuf 的团队来说有学习成本。我做过一个对比测试,同样的推理服务,gRPC 在长连接场景下的吞吐大概比 REST 高 30% 左右,但短请求场景差距没那么明显。

接口设计上我踩过最大的坑是超时。模型推理的耗时波动很大,短请求几百毫秒,长生成能到几十秒,如果网关或者客户端设了 5 秒超时,业务侧看到的就是一堆莫名其妙的失败。我现在的做法是把接口拆开,同步接口只做短推理,长生成走异步任务加轮询,或者直接上 SSE 流式返回。流式这个事我要多讲一句,OpenAI 兼容的 /v1/chat/completions 加上 stream: true,前端体验会好很多,用户能看着字一个个蹦出来,不会盯着转圈等到怀疑人生。

错误码也得认真设计。我见过接口出问题一律返回 500 的实现,排查的时候根本不知道是模型加载失败、显存不够、还是请求参数不对。我现在的约定是,参数问题返 400,鉴权失败返 401,限流返 429,模型内部异常返 503,每种错误带上一个 request_id 和一句人话描述。这个 request_id 特别重要,后面日志排查全靠它把一次请求串起来,没有它,日志里几百条记录你根本分不清哪条是出问题的那次。

4.2 反向代理、身份鉴权与访问限流

我在内网做过一次渗透测试,把一个裸奔的模型服务暴露在测试网段,结果第二天就有同事拿脚本刷了几万次请求,GPU 直接被打满。那次之后我定了一条规矩:模型服务永远不直接对外,前面必须有一层反向代理。nginx 也好,traefik 也好,Caddy 也好,选哪个不重要,重要的是这层是流量的唯一入口,你在这里能做很多事。

身份鉴权我是从最朴素的 API Key 开始的。服务端校验 Authorization: Bearer <token>,token 存在环境变量或者密钥管理服务里,不同业务方发不同的 key,谁用超了能追溯到人。这套方案简单到有点土,但真的够用,大部分内部场景不需要上 OAuth2 或者 JWT,那些方案维护成本高,出了问题排查链路还长。JWT 我一般用在对无状态要求比较高的场景,比如多区域部署、服务之间互信,用非对称密钥签,网关只验签不查库。

限流这块,我推荐在反代层和服务层都做。nginx 的 limit_req 按 IP 或者按 key 做粗粒度限流,挡住明显的异常流量;服务内部用令牌桶做细粒度控制,按用户配额、按模型实例数来分配。我做过一个配置,单实例 QPS 上限设成 20,突发允许 40,超过就返 429 加 Retry-After 头,客户端能自己退避重试,体验比直接掐断好很多。并发连接数也要限制,长连接多了不限制,几千个请求挂在那等推理,内存直接爆。

还有个小细节,nginx 的 proxy_buffering 对 SSE 流式接口是致命的。默认开着的时候,nginx 会把响应缓冲起来,前端看到的流式效果变成了一段一段的,甚至等半天才出来。我现在的做法是给流式接口单独配一个 location,关掉 buffering,加 proxy_read_timeout 到 300 秒,再配上 X-Accel-Buffering: no 响应头,这套组合拳打下来流式才真的顺。

4.3 服务监控指标、日志采集与告警配置

监控这件事,我的经验是别一上来就想着大而全,先把几个要命的指标盯住。模型服务我最关心的三个数字:请求 P99 延迟、GPU 显存占用率、排队请求数。P99 能反映用户体验,显存占用能提前预警 OOM,排队数能看出来是不是该扩容了。这三个指标掉一个,服务基本上就处于不健康状态了,不用等用户投诉才知道。

指标采集我用 Prometheus 加 Grafana 的组合,服务端用 prometheus_client 暴露 /metrics 端点,把请求计数、延迟直方图、错误率这些埋进去。GPU 指标用 nvidia_gpu_exporter 或者 DCGM Exporter 拉,显存、利用率、温度都能拿到。Grafana 面板我一般按服务分,每个面板顶部放一排数字卡,延迟、QPS、错误率、显存,一眼就能看出问题。面板做得太多太细反而没人看,我自己就吃过这个亏,花两天搭了二十个图,一个月后打开的次数一只手数得过来。

日志采集我推结构化输出,别再用 print 打那种人类可读但机器难解析的文本了。用 JSON 格式,每个请求一行,带上时间戳、request_id、用户标识、模型名、输入 token 数、输出 token 数、耗时、状态码。这套格式一旦统一,后面不管是接 Loki、ELK 还是云厂商的日志服务,都是配置的事。告警我设在几个关键位置:错误率五分钟内超过 5%、P99 延迟超过阈值、GPU 显存超过 90%、健康检查连续失败三次。告警渠道用钉钉或者企业微信机器人,别用邮件,邮件在手机上的存在感和骚扰短信差不多,看到了也不会马上处理。

告警的阈值要压着实际负载调。我一开始把错误率阈值设成 1%,结果模型偶尔吐个长文本超时,告警响个不停,一周后整个团队都麻木了。后来改成持续五分钟超过 5% 才报,误报少了很多,真出事的时候大家才会认真看。监控这个东西,信噪比比灵敏度重要得多。

4.4 模型更新、版本回滚与数据持久化

模型更新是运维里最容易出事的一环。我有一次直接把新权重覆盖到旧目录,容器重启的时候加载失败,旧权重也回不去了,服务停了两个小时。那次之后我给自己定了规矩:模型文件只增不改,每个版本一个独立目录,/data/models/qwen-7b/v1.0、/data/models/qwen-7b/v1.1,配置文件里通过环境变量指向具体版本,切换就是改一个变量重启。

更新方式我喜欢蓝绿部署。新版本起一个新容器,用另一个端口跑起来,健康检查通过之后,把 nginx 的 upstream 切过去,观察一段时间没问题,再把旧容器下掉。这个过程里两个版本同时占着显存,得提前算好 GPU 够不够。GPU 紧张的时候用滚动更新,先停旧再起新,代价是有几十秒的服务中断,得挑业务低峰期做。灰度发布也可以用 nginx 的 split_clients 做,按百分比把流量导到新版本,5% 跑一天没问题再全量,这个方案对显存最友好。

版本回滚的关键是旧版本别急着删。我一般保留最近三个版本的权重和镜像,配置里写好回滚脚本,一条命令切回去。回滚速度取决于镜像拉取和模型加载时间,冷启动可能要几分钟,这个时间窗口得在运维文档里写清楚,让业务方有心理预期。热备方案成本高,小团队一般不做,真要做到秒级回滚,就得让旧版本容器保持运行,显存和成本都要翻倍。

数据持久化这块,模型本身是无状态的,需要持久化的是会话历史、向量库、缓存和日志。会话历史我放在 Redis 或者 PostgreSQL,加个过期策略,用户几天不活跃就清掉,不然存储会越滚越大。向量库用 Milvus 或者 Qdrant 的容器,挂载数据卷,备份策略跟普通数据库一样,定时快照加异地同步。缓存目录前面讲过要挂出来,换容器不丢 tokenizer 缓存,重启启动速度快不少。数据库的备份我要求每周至少一次全量,每天增量,恢复演练每个季度做一次,不做演练的备份跟没有备份区别不大。

4.5 本地部署与Docker部署的安全加固

安全加固这件事,我的理解是先做网络隔离,再做容器加固,最后才是密钥管理,顺序反了效果打折。网络层面,模型服务只监听 127.0.0.1 或者内网网卡,公网入口只有 nginx 那一层,防火墙规则只放行 80 和 443。数据库、Redis 这些一律不对外,容器之间用自定义网络通信,别用默认的 bridge 网络,默认网络里所有容器互通,一个被攻破就全暴露了。

容器加固我从几个地方下手。基础镜像用非 root 用户跑,Dockerfile 里加 USER appuser,这个改动很小但收益很大,容器逃逸的利用门槛会高不少。文件系统设成只读,需要写的地方单独挂 tmpfs 或者数据卷,--read-only 这个参数加上之后,往容器里写 webshell 的路就被堵了。Capabilities 也要裁剪,模型推理用不到 NET_ADMIN、SYS_PTRACE 这些,--cap-drop all 再加回必要的那几个,能砍掉一大批潜在攻击面。no-new-privileges 也加上,防止容器内提权。

镜像供应链安全最近两年越来越被重视。我现在的流程是,基础镜像只用官方或者公司内部镜像仓库的,每个镜像构建完跑一次 trivy 扫描,高危漏洞直接阻断发布。镜像打上 digest 而不是只用 tag,image@sha256:xxx 这种方式能保证部署的就是扫描过的那份,tag 可以被覆盖,digest 不会。内部 registry 加上签名验证,cosign 这类工具配起来稍微有点麻烦,但确实能防住镜像被替换的风险。

密钥管理是最容易糊弄的地方。我见过太多把 API Key 写进 docker-compose.yml 然后提交到 git 的项目,这种项目一旦仓库权限管理不严,密钥等于公开。生产环境我用 Docker secret 或者外部密钥管理服务,.env 文件只放非敏感的配置,真正敏感的走 secret 挂载。审计日志也要开,谁在什么时候调了哪个接口、用了哪个 key,这些记录保留至少三个月。真出了安全事故,审计日志是唯一能还原现场的东西。安全这件事平时看不出价值,出事的时候才知道平时花的那些功夫都是在给自己兜底。

5.1 案例:本地部署开源大模型并开放API

我拿前段时间做过的一个内部知识助手项目来拆,硬件是一台戴尔 R750,两张 A100 40G,Ubuntu 22.04,模型选的是 Qwen2.5-14B-Instruct。选它的理由很实在——中文表现够用,社区活跃,vLLM 官方支持好,14B 这个尺寸在双卡上跑 FP16 刚好,量化之后还能留出并发余量。目录我规划成 /data/models/qwen2.5-14b,里面再分 hf/ 放原始权重、vllm/ 放转换后的格式、logs/ 放推理日志,一个目录一件事,后来回滚和迁移都省心不少。

下载我用的 huggingface-cli download,加 --local-dir 指定目录,再跑一遍 sha256sum 对着官方清单核一遍。这一步很多人跳过,我建议大家别省,我有一次下载中断导致权重文件不完整,加载时报了个特别奇怪的 tensor shape 错误,排查了大半天才定位到是文件损坏。CUDA 版本我对齐到 12.1,驱动用 550 系列,vLLM 装的 0.6.x,conda 环境单独建一个,pip install 之前先冻结一份 requirements.txt,后面换机器复现全靠它。

启动命令我记得是这样的:python -m vllm.entrypoints.openai.api_server --model /data/models/qwen2.5-14b/vllm --tensor-parallel-size 2 --dtype bfloat16 --max-model-len 8192 --gpu-memory-utilization 0.9 --port 8000。tensor-parallel-size 跟卡数对齐,gpu-memory-utilization 我一开始设的 0.95,跑长上下文的时候偶尔 OOM,后来压到 0.9 就稳了。max-model-len 设 8192 是权衡过的,再往上 KV cache 占得太多,并发数掉得厉害,业务侧实际很少一次问那么长。

服务起来之后第一件事是 curl 一下 /v1/models,确认模型注册成功,再发一条 chat 请求看返回结构对不对。生产用的时候我在前面套了 nginx,把 /v1/ 路径反代到 8000,配了 API Key 鉴权,key 存环境变量,业务方申请的时候按人发。这套下来一个完整的本地 API 服务就能对外了,从裸机到能调,我那次大概花了大半天,其中一半时间耗在下载权重上。

5.2 案例:Docker一键部署AI模型服务

本地跑通之后,下一步就是容器化。我见过太多人手写一堆 docker run 参数,换台机器就跑不起来,Docker Compose 就是来解决这个问题的。我做的那套 docker-compose.yml 大概长这样:一个 vllm 服务用官方镜像 vllm/vllm-openai:latest,runtime: nvidia 打开 GPU,模型目录挂进容器,端口映射 8000:8000,environment 里放 HF_TOKEN 和 VLLM_USE_MODELSCOPE,command 里写启动参数。一个文件搞定,别人 clone 下来 docker compose up -d 就能跑。

镜像我建议别用 latest,生产上固定版本 tag 甚至 digest,我踩过一次最新的镜像把接口字段改了,业务侧直接报错。GPU 容器要装 nvidia-container-toolkit,装完 docker run --rm --gpus all nvidia/cuda:12.1-base nvidia-smi 验一下,能出卡信息才算环境通了。shm-size 也得设,默认 64M 太小,vLLM 多进程通信会挂,我一般设 shm-size: 16g,这个坑我踩过不止一次。

模型文件挂载有两种做法,一种是直接把宿主机目录 volumes 挂进去,好处是更新模型不用重打镜像,坏处是镜像和模型耦合松散,换台机器容易漏配。另一种是构建镜像的时候把模型 COPY 进去,镜像会变得很大(十几 G 起步),但部署简单、版本一致。我现在的选择是前者用于开发迭代,后者用于交付到客户现场,交付场景下镜像自包含最省事。

我还在 compose 里加了个小的 nginx 服务做反代,和 vLLM 放在同一个自定义网络里,depends_on 写上前者,这样一套 compose 起来就是一个完整的对外服务。日志用 docker compose logs -f vllm 看,健康检查加了一个 curl /health 的 healthcheck 配置,容器挂了 compose 能自动重启。这套模板我后来复用到好几个项目上,改改模型路径和端口就能用。

5.3 多模型路由与推理加速实践

单模型服务做多了,就会遇到一个新的问题:一个业务场景里往往要同时用小模型做分类、大模型做生成、embedding 模型做检索,三个模型三个地址,业务方接入起来很难受。我后来搭了一个轻量的路由网关,用 FastAPI 写,按 model 字段转发到不同的后端实例,业务方只认一个地址。路由表放配置文件里,改完热加载,不用重启网关。

路由策略上我有几个模式。按模型名直连是最基本的,model: qwen-14b 就打到 14B 那台,model: bge-m3 打到 embedding 服务。再往上一层可以做按任务路由,请求里带 task: classify 就走小模型,task: chat 走大模型,业务侧不用关心具体是哪个模型在干活。我还试过按负载路由,网关这边拉各实例的 /metrics,排队数少的优先,这个方案在流量不均的时候效果很明显,实现成本也不算高。

推理加速这块,vLLM 的 PagedAttention 和连续批处理已经帮了大忙,我实测下来吞吐比裸 transformers 高好几倍。再往上压榨,可以试试几个方向。投机解码我是用一个小模型做 draft,大模型做 verify,能接受一定精度损失的话加速比能到 1.5 倍左右,前提是 draft 模型和主模型分布接近,不然接受率低反而拖慢。量化方面 AWQ 和 GPTQ 我都跑过,4bit 量化显存直接砍到四分之一,吞吐还能再涨一截,代价是长尾问题的回答质量会掉一点,得拿业务数据评估一下。

还有个小技巧是前缀缓存。系统提示词很长的时候,给 vLLM 加上 --enable-prefix-caching,相同前缀的请求能复用 KV cache,我们那个知识助手系统提示词有两千多 token,开了这个之后首 token 延迟降了差不多百分之四十。这个参数默认好像没开,得手动加,效果立竿见影。

5.4 从单机部署到云边协同的演进思路

单机部署能满足的场景其实很有限。我们那个知识助手刚开始就一台机器,用户多了之后明显扛不住,排队越来越长,后来不得不扩到两台做负载均衡。再往后走,就是往云上和边缘两个方向演进,走哪条路取决于业务形态。数据敏感到不能出内网的走私有云,用户分散在全国各地的走边缘节点,两边各有各的坑。

从单机到多机的第一步是把无状态和有状态分开。模型推理本身是无状态的,会话历史那部分抽出去放 Redis,向量检索放独立的 Milvus 集群,这样推理节点可以随意扩缩容,加一台机器就是改一下 nginx 的 upstream。这一步做完之后,才能真正谈弹性伸缩。

往云上走,我用过的方案有两种。一种是自建 K8s 集群,vLLM 打成 Deployment,配 HPA 按 GPU 利用率或者排队数扩缩容,这个方案可控性最强,运维成本也最高。另一种是直接用云厂商的 GPU 实例,按小时计费,配合对象存储放模型权重,启动的时候拉下来,适合流量波动大的场景。云上要注意的就是成本,GPU 实例贵得吓人,HPA 的扩缩容策略得压着真实指标调,不然账单能让人怀疑人生。

往边缘走,思路反过来。边缘节点硬件弱、带宽小,大模型放边缘不现实,我的做法是把小模型(1.5B 到 3B 这个量级)部署到边缘做意图识别和简单问答,复杂的请求回传到中心节点。边缘节点用 Docker Compose 部署就够了,不用上 K8s,运维复杂度能降一大截。模型更新走中心下发,边缘节点定时拉一次,拉之前先校验 hash,校验不通过不替换。这套架构说白了就是分层推理,简单的本地解决,复杂的往上送,用户体验和成本能兼顾。

5.5 教程总结、排错清单与学习资源

这套教程写到这基本就完整了。从环境准备、本地部署、Docker 容器化、服务化监控安全,到案例和进阶,走的是一个从零到能上生产的完整链路。我特别想强调的是,部署这件事没有标准答案,每个团队的环境、预算、业务要求都不一样,教程里给的是我踩过坑之后觉得比较稳的路,具体参数和方案一定要结合自己的场景调。

排错这块我整理了一份清单,当个检查表用。nvidia-smi 看不到卡,先查驱动和 toolkit;模型加载报 OOM,先降 gpu-memory-utilization 或者缩 max-model-len;首 token 特别慢,看是不是没开前缀缓存、上下文太长或者没走批处理;接口超时一大片,先查网关超时设置和流式配置;Docker 起不来报 unknown runtime: nvidia,重装 toolkit 然后 systemctl restart docker;容器里 CUDA 版本不对,对一下镜像 CUDA 和宿主机驱动的兼容矩阵。这份清单我贴在自己项目的 README 里,新同事上手很快。

学习资源我推荐几个。vLLM 官方文档必看,参数解释很详细;Hugging Face 的 text-generation-inference 文档也值得翻,两种框架的设计思路对照着看会有收获;Docker 官方的 GPU 支持和 compose 文档基础但准确;nginx 的 proxy_buffering 和 split_clients 这两个配置项文档我建议单独看一遍,流式接口和灰度发布全靠它。社区方面,vLLM 的 GitHub issues 里搜报错信息命中率很高,Hugging Face 的论坛也活跃。

最后想说一句掏心窝子的话。部署这件事,看十篇教程不如自己动手跑一遍,跑的过程中会冒出一堆教程没讲到的问题,那些问题才是真正长本事的地方。我做的第一个部署项目,前后折腾了两周,中间重装了三次系统、换了两次驱动版本,最后跑通的那一刻,比写完任何一篇文档都有成就感。教程给的是地图,路得自己走。

赞0
踩0
☆收藏0
版权声明
文章版权声明:除非注明,否则均为ZBLOG原创文章,转载或复制请以超链接形式并注明出处。
分享到
chuanbook

链接已复制到剪贴板