尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

vllm部署qwen3.6-27B(autodl)

vllm部署qwen3.6-27B(autodl) 目录一、下载模型二、推理测试三、vllm部署环境准备部署问题1、(EngineCore pid6851) RuntimeError: FlashInfer requires GPUs with sm75 or higher2、ValueError: max_num_seqs (1024) exceeds available Mamba cache blocks (604).Each decode sequence requires one Mamba cache block,so CUDA graph capture cannot proceed.Please lower max_num_seqs to at most 604 or increase gpu_memory_utilization.启动服务并本地测试返回可供调用的地址这次的任务如题并且不只是单纯部署还返回了一个可供他人调用的公网地址下面开始。因为流程和我上篇那个实验几乎相同所以这次写得没这么详细重点在最后一步返回调用地址其他详细步骤具体可看上篇租的显卡是Pro600096G的。一、下载模型这个就不说了直接从modelscope下载但是强调一下虽然我租用的是96G的显卡但要实际下载整个模型的话看的是数据盘 autodl-tmp 的容量我没注意一开始只有50G所以下载不成功终端会报错error搞忘截图侧边模型文件也会显示这个权重文件没下载完成解决办法是扩容数据盘然后关机重启实例苯人多扩了20G这次全部下载完成没报错二、推理测试transformers单次推理响应Qwen3.6-27B 本地模型性能测试工具。 import threading import time from typing import Optional, Generator, Tuple, Any import torch from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer # 配置 class Config: MODEL_PATH /root/autodl-tmp/Base_model PROMPT 请你生成一份关于订单交付结果的周报数据可随意捏造但格式要严谨要求400字左右。 MAX_NEW_TOKENS 512 ENABLE_THINKING False tokenizer AutoTokenizer.from_pretrained(Config.MODEL_PATH) model AutoModelForCausalLM.from_pretrained( Config.MODEL_PATH, torch_dtypeauto, device_mapauto, ) model.eval() if tokenizer.pad_token_id is None: tokenizer.pad_token tokenizer.eos_token def print_section(title: str, width: int 20) - None: 打印分隔线 print(\n * width title * width) # 核心唯一负责生成解析输出的地方 def stream_chunks( include_usage: bool False, prompt: str Config.PROMPT, ) - Generator[Tuple[str, Any], None, None]: 统一的本地流式生成器向外吐出 (event_type, payload) 事件。 messages [{role: user, content: prompt}] text tokenizer.apply_chat_template( messages, tokenizeFalse, add_generation_promptTrue, enable_thinkingConfig.ENABLE_THINKING, ) inputs tokenizer(text, return_tensorspt).to(model.device) streamer TextIteratorStreamer( tokenizer, skip_promptTrue, skip_special_tokensTrue, ) generation_kwargs { **inputs, streamer: streamer, max_new_tokens: Config.MAX_NEW_TOKENS, do_sample: False, pad_token_id: tokenizer.pad_token_id, } worker threading.Thread(targetmodel.generate, kwargsgeneration_kwargs) worker.start() generated_text for chunk in streamer: generated_text chunk yield (content, chunk) worker.join() if include_usage: completion_tokens len(tokenizer.encode(generated_text, add_special_tokensFalse)) prompt_tokens inputs[input_ids].shape[1] yield ( usage, { prompt_tokens: prompt_tokens, completion_tokens: completion_tokens, total_tokens: prompt_tokens completion_tokens, }, ) # 测试1: Token消耗量 def check_token_usage() - Optional[dict]: 只关心消耗了多少token不关心速度 print_section(思考过程) is_answering False usage_info None try: for event_type, payload in stream_chunks(include_usageTrue): if event_type usage: usage_info payload elif event_type reasoning: print(payload, end, flushTrue) elif event_type content: if not is_answering: print_section(完整回复) is_answering True print(payload, end, flushTrue) print_section(Token消耗统计) if usage_info: print(f输入tokens : {usage_info[prompt_tokens]}) print(f输出tokens : {usage_info[completion_tokens]}) print(f总计tokens : {usage_info[total_tokens]}) return { prompt_tokens: usage_info[prompt_tokens], completion_tokens: usage_info[completion_tokens], total_tokens: usage_info[total_tokens], } print(未获取到usage信息) return None except Exception as e: print(f本地模型生成异常: {e}) return None # 测试2: 精确速度tokens/s def check_speed_precise() - Optional[dict]: 从第一个字(思考或回复)出现开始计时到结束为止用真实completion_tokens算平均速度 print_section(思考过程) start_time time.time() first_token_time: Optional[float] None is_answering False usage_info None try: for event_type, payload in stream_chunks(include_usageTrue): if event_type usage: usage_info payload continue if first_token_time is None: first_token_time time.time() # 不管思考还是回复第一次出字就开始计时 if event_type reasoning: print(payload, end, flushTrue) elif event_type content: if not is_answering: print_section(完整回复) is_answering True print(payload, end, flushTrue) end_time time.time() print_section(精确速度统计) if usage_info and first_token_time: ttft first_token_time - start_time gen_duration end_time - first_token_time speed usage_info[completion_tokens] / gen_duration if gen_duration 0 else 0 print(f首字延迟(TTFT) : {ttft:.3f}秒) print(f生成耗时 : {gen_duration:.3f}秒) print(f输出token数 : {usage_info[completion_tokens]}) print(f平均生成速度 : {speed:.2f} tokens/s) return { ttft: ttft, gen_duration: gen_duration, completion_tokens: usage_info[completion_tokens], speed: speed, } print(未获取到完整数据) return None except Exception as e: print(f本地模型生成异常: {e}) return None # 测试3: 实时打字机效果字符/s仅体验参考 def check_speed_live() - Optional[Tuple[int, float]]: 实时刷新字符输出速度注意字符数不等于token数仅用于直观感受节奏 print_section(实时输出, width10) first_token_time: Optional[float] None char_count 0 try: for event_type, payload in stream_chunks(include_usageFalse): if event_type not in (reasoning, content): continue if first_token_time is None: first_token_time time.time() char_count len(payload) elapsed time.time() - first_token_time speed char_count / elapsed if elapsed 0 else 0 print( f\r已输出{char_count}字符 | 用时{elapsed:.2f}s | 约{speed:.1f}字符/s, end, flushTrue, ) print() if first_token_time: return char_count, time.time() - first_token_time return None except Exception as e: print(f本地模型生成异常: {e}) return None # 主程序 if __name__ __main__: print(Qwen3.6-27B 本地模型性能测试工具\n) results {} print( * 30 测试1: Token消耗量 * 30) results[token] check_token_usage() print(\n\n * 30 测试2: 精确生成速度 * 30) results[speed] check_speed_precise() print(\n\n * 30 测试3: 实时打字机效果 * 30) results[live] check_speed_live() print(\n\n * 50) print(测试汇总) print( * 50) for key, value in results.items(): print(f{key}: {value})运行结果没截图但是内容是这样三、vllm部署环境准备创建单独的环境比较好conda create -n vllm python3.12 -y --override-channels -c defaults这个命令长这样是因为第一次运行 conda create -n vllm python3.12 -y 的时候报错了然后因为要通过 uv 下载vllm所以先下载 uvpip install -U uv接着下载vllmuv pip install vllm --torch-backendauto说明一下通过这个 uv 下载是因为官方示例是这样uv 就相当于一个更现代的Python包负责安装Python依赖下载命令里的--torch-backendauto则自动选择合适的PyTorch backend。下载中间报了一个小错问了copilot在它进行检查后发现不是磁盘不够的问题是下载链路断开了执行它给出的这条命令UV_HTTP_TIMEOUT300 \ UV_HTTP_RETRIES10 \ UV_CONCURRENT_DOWNLOADS2 \ /root/miniconda3/envs/vllm/bin/uv pip install vllm --torch-backendauto通过降低并发增大超时增加重试等方法再重新下载且 uv 会继续处理缺失的依赖不会重新下载所有的东西。但是我这次不知道为啥下载速度巨慢。。然后我问了ChatGPT还好这个下载可以中断且自动恢复按Ctrlc中断后关机等第二天来了重新连接服务器激活vllm环境后再次运行上面那条命令uv 会继续利用缓存好是这样用 uv 实在是太慢了在我下载了好几天都没下载完之后还是选择了用 pip 的方式挂的中科大的镜像源PIP_DEFAULT_TIMEOUT300 pip install vllm \ -i https://mirrors.ustc.edu.cn/pypi/simple下得飞快这样显得我前几天的等待很搞笑(TT)下载完成后不急着部署先执行几个检查vllm版本vllm --versionpytorch详细信息python -c import torch; print(torch:, torch.__version__); print(torch cuda:, torch.version.cuda); print(cuda available:, torch.cuda.is_available()); print(gpu:, torch.cuda.get_device_name(0))vllm能不能正常importpython -c import vllm; print(vllm:, vllm.__version__)都正常输出的话就可以进入部署了这里我没截图但是输出应该类似vllm: 0.27.1torch: 2.13.0torch cuda: 13.xcuda available: Truegpu: NVIDIA RTX PRO 6000 Blackwell Server Edition部署问题我一开始的命令是这样当然没有后面的注释vllm serve /root/autodl-tmp/Base_model \ #模型地址 --host 0.0.0.0 \ --port 6006 \ #启动端口号 autodl指定可以映射公网地址的端口号之一 另一个是6008 --max-model-len 8192 \ #上下文最大长度 --gpu-memory-utilization 0.90但是遇到了几个问题1、(EngineCore pid6851) RuntimeError: FlashInfer requires GPUs with sm75 or higherGPT给出的解释是这样但是还好官方代码提供关闭 FlashInfer sampler 的开关关闭以后vLLM 会退回 PyTorch 原生的 Top-K / Top-P samplerVLLM_USE_FLASHINFER_SAMPLER0 \ vllm serve /root/autodl-tmp/Base_model \ --host 0.0.0.0 \ --port 6006 \ --max-model-len 8192 \ --gpu-memory-utilization 0.90事实上这次报错不是模型推理能力有问题刚刚的脚本测试已经证明而是实际启动服务与设备还有显存资源之间的兼容性问题很典型了马上下一个问题就来了。2、ValueError: max_num_seqs (1024) exceeds available Mamba cache blocks (604).Each decode sequence requires one Mamba cache block,so CUDA graph capture cannot proceed.Please lower max_num_seqs to at most 604 or increase gpu_memory_utilization.好消息是上一个错误已经被绕过然后关于这个报错GPT的解释是这样所以修改启动命令VLLM_USE_FLASHINFER_SAMPLER0 \ vllm serve /root/autodl-tmp/Base_model \ --host 0.0.0.0 \ --port 6006 \ --max-model-len 8192 \ --gpu-memory-utilization 0.90 \ --max-num-seqs 32顺便说一下GPT不建议直接用604是因为我现在不是在做压测我猜应该是压力测试我的目标是先跑起来32个并发sequence序列对第一版api验证已经绰绰有余而且反正这个每次都可以调。到这里可以看出生产推理框架比单纯 transformers 推理更复杂vllm不只是把模型加载起来然后直接 generate 就行了它还要考虑请求调度、并发、KV Cache、attention backend等等我这次由于目标很简单所以很多方面还不涉及要做好以后过程中会有很多问题的准备(๑❛︶❛๑)启动服务并本地测试这次成功启动了但是运行命令长这样VLLM_USE_FLASHINFER_SAMPLER0 \ vllm serve /root/autodl-tmp/Base_model \ --host 0.0.0.0 \ --port 6006 \ --served-model-name scs_model \ --max-model-len 8192 \ --gpu-memory-utilization 0.90 \ --max-num-seqs 32多了一行保存模型名字一开始是去掉这行的也成功启动了但是后面负责人问能不能改个名字于是加了这个命令重新启动了下次启动服务的时候还是要指定模型名一样的命令没保存模型名字之前是默认的路径名终端测试命令长这样curl http://localhost:6006/v1/chat/completions \ -H Content-Type: application/json \ -d { model: /root/autodl-tmp/Base_model, messages: [ { role: user, content: 你好呀终于见面咯 } ], chat_template_kwargs: { enable_thinking: false }, max_tokens: 512, temperature: 0 }能成功回复就说明部署没问题接下来就是返回公共调用的地址了。这里顺带说一下上一篇里的测试命令长这样curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: alpaca-zh, messages: [{role: user, content: 计算长为10厘米宽为5厘米的矩形面积}], max_tokens: 200 }这次的测试命令里多的 chat_template_kwargs 参数是vllm自带的简单来说是把额外参数传给模型最常见的用途就是控制 thinking/reasoning 模式不同模型控制思考的key可能不一样有的用 enable_thinking有的用 thinking具体要看模型官方文档。千问系列的是enable_thinking我上篇里没管这个1.5B好像也没有思考模式吧(・・)反正本质上都是调用的 /v1/chat/completions 这个接口。还有就是一些单独检查的测试命令如看返回的详细信息curl http://localhost:6006/v1/models比如不确定模型名字的话就执行上面的命令看返回的 id 是什么然后把测试命令的model改成那个值就行。还有这个据说健康检查curl http://localhost:6006/health这次没有运行这个下次启动的时候试试。返回可供调用的地址最重要的一集这次把端口起在6006就是因为autodl的公网映射功能限制了两个端口号https://u1094167-8dae-3a6734e4.westd.seetacloud.com:8443/v1 就是api所以终端测试命令里的地址换成这样也可以模型名之前保存的curl https://u1094167-8dae-3a6734e4.westd.seetacloud.com:8443/v1/chat/completions \ -H Content-Type: application/json \ -d { model: scs_model, messages: [ { role: user, content: 你还在不在 } ], chat_template_kwargs: { enable_thinking: false }, max_tokens: 512, temperature: 0 }python脚本测试from openai import OpenAI api_key EMPTY client OpenAI( api_keyapi_key, base_urlhttps://u1094167-8dae-3a6734e4.westd.seetacloud.com:8443/v1, ) def chat_with_qwen27b(messages): response client.chat.completions.create( modelscs_model, messagesmessages, streamTrue, max_tokens512, temperature0, extra_body{ chat_template_kwargs: {enable_thinking: False} }, ) full_response for chunk in response: if not chunk.choices: continue content chunk.choices[0].delta.content or if content: print(content, end, flushTrue) full_response content print() return full_response conversation [ {role: system, content: 你是一个聪明的AI} ] while True: user_input input(You: ) if user_input.lower() 退出: print(Assistant: 再见) break conversation.append({role: user, content: user_input}) print(Assistant: , end, flushTrue) assistant_message chat_with_qwen27b(conversation) conversation.append({role: assistant, content: assistant_message})运行结果让同事尝试调用也成功解释一下为什么终端测试里的地址后面跟了 /chat/completions脚本测试里没有两者其实是同一套接口只是调用方式不同curl 测试直接把完整路径写死而脚本测试里 OpenAI 兼容接口的标准约定就是 base_url 指向 /v1或服务根路径我的代码里调用的方法是client.chat.completions.create(...)对应的子路径就是 /chat/completionsgrok 指出不同的方法对应不同的子路径例如这样只需要改一个 base_url 就能切换不同的后端虽然目前为止还没有用过其他的后端。还有一点就是关于 api_key现在没有开启 api 认证可以随便填开启了之后就需要换成真实的这一步也叫“鉴权”。以上就是整个过程有问题可以指出 (๑•̀ㅂ•́)و✧
返回列表