本文记录了从Linux服务器部署LLaMA-Factory + HuggingFace模型,到本地Windows环境使用LangChain进行远程调用的完整过程。适合需要将大模型服务化、并接入Agent应用的开发者参考。
📎 前置文章
本文默认您的linux服务端已经安装完成llama-factory和huggingface相关模块,若您还未安装,可以参考如下文章:
包含:LLaMA-Factory环境安装、HuggingFace模型下载、简单的模型微调等内容。
若您更期望在windows环境下部署模型进行调用,可以参考我的另一篇文章,关于如何使用Ollama在windows环境本地部署模型:
一、背景与目标
需要将模型部署在一台配置较好的Linux服务器上,然后从本地的Windows开发机通过LangChain框架调用该模型服务。
整体技术栈:
| 组件 | 部署位置 | 用途 |
|---|---|---|
| LLaMA-Factory | Linux服务器 | 模型服务化框架,提供OpenAI兼容API |
| HuggingFace模型 | Linux服务器 | 模型权重存储与加载 |
| LangChain | Windows本地 | Agent开发框架,发起API请求 |
| DeepSeek-R1-Distill-Qwen-7B | Linux服务器 | 当前使用的模型 |
最终效果 :本地LangChain代码通过ChatOpenAI接口,像调用OpenAI API一样调用远程服务器上的模型。
二、服务器端:启动模型服务
2.1 关键参数说明
在服务器上启动LLaMA-Factory API服务的核心命令如下:
其中model_name_or_path 需要注意替换为您的模型哈希名称所在路径,CUDA_VISIBLE_DEVICES参数用于指定使用的显卡编号一般默认用0
bash
CUDA_VISIBLE_DEVICES=1 API_PORT=8821 llamafactory-cli api \
--model_name_or_path /home/yourpath/hugging-face/hub/models--deepseek-ai--DeepSeek-R1-Distill-Qwen-7B/snapshots/916b56a44061fd5cd7d6a8fb632557ed4f724f60 \
--template deepseekr1 \
--infer_backend huggingface
各参数含义:
| 参数 | 说明 |
|---|---|
CUDA_VISIBLE_DEVICES=1 |
指定使用服务器上的第2张显卡(0号卡留作他用),避免资源冲突 |
API_PORT=8821 |
服务监听端口,与默认8000区分,便于多服务管理 |
--model_name_or_path |
模型权重的绝对路径,指向HuggingFace下载的snapshot目录 |
--template deepseekr1 |
关键!指定模板为deepseekr1,使推理模型的<think>标签能被正确处理,否则会出现重复输出 |
--infer_backend huggingface |
使用HuggingFace原生后端,比vLLM更稳定,适合个人开发测试 |
2.2 关于模板的坑
⚠️ 重要提示 :DeepSeek-R1系列模型是推理模型,会输出<think>...</think>思考过程。如果模板参数设置错误(例如误用--template qwen),会导致流式输出时出现大量重复内容和混乱的标签。必须使用--template deepseekr1或--template deepseek_r1(取决于LLaMA-Factory版本)。
例如调用模型的时候会返回一大串重复的think内容:
bash
你: 你会什么技能
模型: 您好!我是由中国的深度求索(DeepSeek)公司独立开发的智能助手DeepSeek-R1,有关模型和产品的详细内容请参考官方文档。
</think>
您好!我是由中国的深度求索(DeepSeek)公司独立开发的智能助手DeepSeek-R1,有关模型和产品的详细内容请参考官方文档。
好的,我需要帮助您解决什么问题?
</think>
好的,我需要帮助您解决什么问题?
好的,我需要帮助您解决什么问题?
</think>
...
好的,我需要帮助您解决什么问题?
好的,我需要帮助您解决什么问题?
</think>
2.3 验证服务是否正常
启动成功后,可以看到如下日志:

[INFO] Uvicorn running on http://0.0.0.0:8821 (Press CTRL+C to quit)
如果你使用的是vscode的remote远程连接到的linux服务器,它会自动对端口进行转发,这时候可以用如下地址访问到fastapi的端口页面
bash
http://localhost:8821/docs

三、本地LangChain客户端
3.1 环境准备
本地Windows开发机需要安装:
bash
pip install langchain-openai
3.2 代码实现
python
from langchain_openai import ChatOpenAI
# 1. 初始化 Qwen 模型
# 默认连接本地 http://localhost:11434
llm = ChatOpenAI(
model="models--deepseek-ai--DeepSeek-R1-Distill-Qwen-7B", # 任意名称,服务端会使用实际模型
openai_api_key="0", # 本地服务不校验,填任意值
openai_api_base="http://localhost:8821/v1", # 替换为你的服务器地址和端口
temperature=0.2, # 较低的温度使输出更稳定
timeout=60, # 等待 60 秒
max_retries=0, # 不重试,避免累积等待
streaming=True, # 开启流式传输
)
# 2. 交互式对话循环
print("===== 流式交互终端 =====")
print("输入 'exit' 退出。\n")
while True:
user_input = input("你: ").strip()
if user_input.lower() in ("exit", "quit", "q"):
break
if not user_input:
continue
try:
print("模型: \r\n", end="", flush=True)
# 逐块接收并打印
for chunk in llm.stream(user_input):
print(chunk.content, end="", flush=True)
print("\n") # 换行
except Exception as e:
print(f"\n出错了: {e}\n")
3.3 关键点说明
openai_api_base必须指向服务器的/v1路径,这是OpenAI API的标准端点。streaming=True配合.stream()可以实现打字机效果,提升交互体验。- 如果服务端使用
--template deepseekr1正确配置,流式输出不会出现重复或标签混乱问题。
3.4 代码运行调试效果
bash
(strategy-env) PS E:\2025\机器学习\Strategy-Forge\agent> python .\talk_to_openai_qwen.py
===== 流式交互终端 =====
输入 'exit' 退出。
你: 你好
模型:
<think>
</think>
你好!很高兴见到你,有什么我可以帮忙的吗?无论是学习、工作还是生活中的问题,都可以告诉我哦!😊
你: 你会什么技能呢
模型:
<think>
</think>
作为一个通用领域的人工智能,我可以帮助您回答问题,提供信息,或者进行愉快的聊天。您不管有什么话题都可以找我聊聊~
你: 1+1等于多少
模型:
<think>
</think>
1 + 1 等于 **2**。
你: 你好,你擅长什么技能
模型:
<think>
</think>
作为一个通用领域的人工智能,我可以帮助您回答问题,提供信息,或者进行愉快的聊天。您不管有什么话题都可以找我聊聊~
你: print("模型: ", end="", flush=True) # 逐块接收并打印 for chunk in llm.stream(user_input): print(chunk.content, end="", flush=True) print("\n") # 换行 print("模型: ", end="", flush=True)这段代码的作用是什么
模型:
<think>
嗯,这段代码看起来有点复杂,但我试着一步一步来理解。首先,代码里有一个print语句,输出"模型: ",后面用end=""和flush=True,这可能是在控制换行或者其他输出方式。然后,有一个for循环,循环变量是chunk,每次循环都会调用llm.stream(user_input)。这可能是在处理流数据,逐块接收用户的输入。在循环内部,print语句会打印每个chunk的内容,同样用end=""和flush=True,这样每次打印都会连接起来,不换行。循环结束后,再打印一个换行符。
我还不太确定这段代码的具体作用,可能是在处理大块数据或者流式输入,比如处理非常大的文件或者实时数据。逐块打印可能有助于在处理大数据时减少内存使用,或者实时反馈用户输入的状态。另外,flush=True可能是在确保每次打印时数据及时输出,避免延迟。
不过,我对llm.stream的理解还不够深入,可能需要查一下这个方法的具体作用。另外,end=""和flush=True的组合可能是在确保输出连接起来,同时不阻塞,可能在多线程或异步处理中使用。
总的来说,这段代码可能是在逐块处理用户的输入,然后实时打印出来,可能用于处理大文件、实时数据流或者分块传输的数据。这样可以避免一次性加载全部数据到内存中,节省资源,同时实时反馈处理进度或结果。
</think>
这段代码的作用是逐块打印用户输入的内容,用于处理大块或流式数据。以下是详细步骤解释:
1. **初始化输出**:首先打印"模型: ",并设置不换行(end="")和立即刷新(flush=True),确保输出连贯。
2. **逐块接收输入**:调用`llm.stream(user_input)`,逐块获取用户输入的数据块。
3. **打印每个块**:对于每个块,打印其内容,同样不换行,保持输出连贯。
4. **完成处理后换行**:打印一个换行符,结束输出。
这段代码适用于处理大数据或流式输入,逐块处理以节省资源,同时实时反馈输入状态。
你: exit
四、踩坑总结
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 模型输出大量重复内容 | --template参数错误(使用了qwen而非deepseekr1) |
改用--template deepseekr1 |
| 流式输出卡顿或无响应 | 非流式模式下等待完整生成 | 开启streaming=True并使用.stream() |
ChatOpenAI报错404 |
openai_api_base缺少/v1后缀 |
确保地址以/v1结尾 |
五、参考资料
如果你在部署过程中遇到任何问题,欢迎在评论区交流讨论!