为大型语言模型(LLM)选择合适的部署工具对项目成败至关重要。
开源工具给予更多控制权,但需要投入更多工作;托管服务则更易搭建和扩展,但通常成本较高。HuggingFace 是一个流行的开源工具和数据仓库,内含大量预训练模型及辅助任务的工具,如分词、微调和数据处理。
你选择的商业模式会影响收入、成本和用户体验,从而影响部署决策。通过了解用户需求、评估成本和考虑竞争情况,可以选出满足需求、为用户创造价值的商业模式。主要选项包括:
基础设施即服务(IaaS)
适合想自行构建和部署LLM应用但不愿管理底层基础设施的组织。
IaaS允许快速灵活地配置计算资源,无需大量前期投入,支持定制和优化环境以满足特定需求。
适合具备管理应用和基础设施能力的团队,但相较其他模式,需要更高的技术和管理水平。
平台即服务(PaaS)
适合希望快速简便地构建和部署LLM应用,而无需关注底层基础设施的组织。
PaaS简化了开发与部署流程,无需大量前期投入或技术专长,帮助快速上线应用。
适合追求速度的团队,但在灵活性和控制力上可能不及其他模式。
软件即服务(SaaS)
用户通过网页界面或API访问LLM功能,无需大量前期投入或技术能力。
提供简化且流畅的用户体验,便于快速获取LLM能力。
适合无技术背景或无需管理的用户,但灵活性和控制力有限。
目前多数企业在API层面介于IaaS和SaaS之间,集成过程相对简单。
本章逐步介绍部署流程,并提供关于API、知识图谱、延迟和优化的实用建议。
快速推荐
如果构建复杂工作流(如RAG应用),可能需要更多工具,比如向量数据库。
- Pinecone 提供快速低延迟的向量检索及托管服务。
- Weaviate 是强大的语义搜索工具。
- Milvus 和 Qdrant 适合大规模高性能相似度搜索。
如果应用需要结构化数据关系,图数据库如Neo4j能建模交互和依赖。
资源描述框架(RDF)存储,如Virtuoso或Blazegraph,适用于高级语义推理。
预处理工具推荐:
- LangChain 简化链式提示、记忆添加和基于代理的系统构建。
- Haystack 适合文档检索和问答流水线。
- LlamaIndex 高效对接外部数据源与LLM。
模型服务与优化工具:
- Seldon和KServe 支持Kubernetes环境下的LLM部署,强调可扩展性和易管理性。
- ZenML和MLflow 助力实验跟踪与无缝模型服务。
- Ray 高效支持训练和推理时的分布式任务扩展。
若偏好极简配置且能承担费用,托管服务是理想选择:
- Google Cloud Vertex AI 提供训练、调优和部署工具。
- AWS SageMaker 具备类似功能,集成Data Wrangler做预处理。
- Snowflake Data Cloud 适合结合数据存储、检索与ML流程处理。
- Databricks 在大规模微调和优化LLM方面表现出色。
- Microsoft Azure 平台全面,从基于GPU的虚拟机基础设施,到预训练模型及其部署支持一应俱全。
部署你的模型
通过云服务部署大型语言模型(LLM)非常简单。例如,使用OpenAI部署模型的步骤如下:
- 访问OpenAI官网,注册账号。
- 进入API密钥页面,创建新的API密钥。
- 妥善保存API密钥。
- 使用命令
pip install openai安装OpenAI Python库。 - 在代码中导入OpenAI库并调用客户端:
import pandas as pd
import numpy as np
import random
from statistics import mean, stdev
import os
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
client = OpenAI(
api_key=os.environ.get("OPENAI_API_KEY")
)
# 定义测试用的提示
PROMPT_A = "Is the following email spam? Respond with spam if the email is spam or ham if the email is not spam. Use only spam or ham as the answers, nothing else.\n\nSubject: {subject}\n\nMessage: {message}"
PROMPT_B = "After considering it very carefully, do you think it's likely that the email below is spam? Respond with spam if the email is spam or ham if the email is not spam. Use only spam or ham as the answers, nothing else.\n\nSubject: {subject}\n\nMessage: {message}"
# 加载数据集并抽样
df = pd.read_csv("enron_spam_data.csv")
spam_df = df[df['Spam/Ham'] == 'spam'].sample(n=30)
ham_df = df[df['Spam/Ham'] == 'ham'].sample(n=30)
sampled_df = pd.concat([spam_df, ham_df])
# 定义评估函数
# 运行并显示结果
本章假设你希望部署自己的模型。虽然MLOps原则部分适用,但LLMOps需针对大规模模型的独特挑战进行调整。
根据应用不同,LLMOps工作流可能包含预处理与后处理、模型链式调用、推理优化及集成外部系统(如知识库或API)。此外,还需处理大规模文本数据、向量化嵌入,并经常采用RAG技术提升预测上下文。
下面以示例项目演示如何操作。假设你已有模型my-llm-model,下一步是部署它。
步骤1:环境搭建
确保安装所需工具,推荐:
- Jenkins:自动化CI/CD流水线
- Docker:容器化模型及依赖
- Kubernetes:编排可扩展、容错的部署
- ZenML或MLFlow:复杂工作流编排
步骤2:容器化LLM
容器化确保模型及依赖跨环境可移植且一致。项目目录下创建Dockerfile:
# DOCKERFILE
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "serve_model.py"]
构建镜像并本地测试:
docker build -t my-llm-model .
docker run -p 5000:5000 my-llm-model
步骤3:用Jenkins自动化流水线
自动化部署流水线确保流程可靠、可复用。推荐用Jenkins实现CI/CD:
- 安装Jenkins并连接代码仓库。
- 创建Jenkinsfile定义流水线阶段,示例如下:
pipeline {
agent any
stages {
stage('Build Image') {
steps {
sh 'docker build -t my-llm-model .'
}
}
stage('Push Image') {
steps {
sh 'docker tag my-llm-model myregistry/my-llm-model:latest'
sh 'docker push myregistry/my-llm-model:latest'
}
}
stage('Deploy to Kubernetes') {
steps {
sh 'kubectl apply -f deployment.yaml'
}
}
}
}
步骤4:工作流编排
对于复杂流程,ZenML和MLFlow支持定义模块化步骤并管理依赖。安装ZenML示例:
from zenml.pipelines import pipeline
from zenml.steps import step
@step
def preprocess_data():
print("Preprocessing data for LLM training or inference.")
@step
def deploy_model():
print("Deploying the containerized LLM to Kubernetes.")
@pipeline
def llm_pipeline(preprocess_data, deploy_model):
preprocess_data()
deploy_model()
pipeline_instance = llm_pipeline(preprocess_data=preprocess_data(), deploy_model=deploy_model())
pipeline_instance.run()
步骤5:监控搭建
部署完成后,监控是保证LLM应用按预期运行的关键。可用Prometheus和Grafana监测模型延迟、系统资源和错误率,也可用专为LLM设计的工具如Log10.io。
了解了如何部署LLM后,你可能想在不开源模型的情况下,向其他用户提供访问服务。下一节将介绍针对LLM的API相关内容。
为大型语言模型(LLM)开发API
API为用户提供了一种标准化的方式,使客户端能够与其LLM交互,也让开发者能访问并使用来自各种来源的LLM服务和模型。遵循LLMOps的最佳实践(本节将展示)能帮助你打造安全、可靠、易用的API,确保它们满足基于LLM的应用所需的功能和性能。
API起源于20世纪60至70年代,最初用于系统级编程,帮助单一操作系统内部的不同组件间通信。随着90年代互联网兴起,API开始应用于基于网络的应用。
网络API使不同网站和网页应用能基于软件开发的两大核心原则——高内聚与低耦合——实现数据通信和交换。高内聚意味着API组件紧密相关,专注于单一任务,易于理解和维护;低耦合则指组件相互独立,允许部分变化不影响整体,提高灵活性、减少依赖。
如今,网络API是现代网页应用的重要组成部分,支持开发者打造强大、集成度高、随时随地可访问的系统。LLM应用常用的API类型包括自然语言处理(NLP)API和LLM即服务(LLMs-as-APIs)。
-
NLP API 提供分词、词性标注、命名实体识别等功能,常见工具有Hugging Face和spaCy。
-
LLM即服务API 允许通过用户提示访问LLM并获取预测结果,分为两大类:
- LLM平台API,支持开发者构建、训练和部署LLM模型,如Google Cloud LLM、Amazon SageMaker、Microsoft Azure ML等。
- LLM模型API,提供预训练模型接口,用于文本生成、分类、翻译等任务,包括OpenAI、Cohere、Anthropic、Ollama等专有模型API。
LLM平台API提供端到端的工具和服务,涵盖数据准备、模型训练、部署与监控。其最大优势是允许复用现有模型和服务,降低开发新应用的时间和成本。比如,Google Studio(及其Gemini模型系列)提供一套完整的LLM服务,方便开发者构建和部署模型。
API驱动的架构策略
API驱动架构是一种利用API部署基于LLM应用的设计思路,旨在打造可扩展、灵活、可复用的复杂集成系统,支持随时随地访问,并能处理大规模数据和流量。它通过API暴露不同系统和服务的功能与数据。
网络API分为两类:
- 有状态(Stateful)API:维护并管理客户端或用户会话状态。服务器跟踪用户状态,基于此提供个性化和上下文相关的响应,提升用户体验。有状态API还可提供安全认证,防止未授权访问。典型有购物车API、用户认证API、内容管理API和实时通信API。
- 无状态(Stateless)API:不存储任何先前请求信息,每个请求独立且包含完成处理所需所有数据。请求失败不影响其他请求,适合跨环境或平台使用,无需担心会话连续性。
REST API
REST API本身既非严格有状态,也非完全无状态,具体表现取决于使用方式和技术。
REST(表现层状态转移)是一种遵循RESTful架构风格的网络API。它本质上是无状态的,每个请求携带所有完成请求所需信息。但通过会话、cookie或令牌等技术,REST API仍可维护和管理客户端状态。
使用REST API,你可以构建可扩展、灵活、可复用的系统,支持处理海量数据和流量,满足现代网页应用的功能和性能需求。
API 实现
下面介绍如何实现一个API。
步骤1:定义API端点
常见端点包括:
/generate:用于生成文本/summarize:用于摘要任务/embed:用于获取向量嵌入
步骤2:选择API开发框架
本示例使用FastAPI——一个简化API开发且支持异步操作的Python框架。示例代码如下:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class TextRequest(BaseModel):
text: str
@app.post("/generate")
async def generate_text(request: TextRequest):
# 示例响应,实际应用中替换为LLM推理逻辑
generated_text = f"Generated text based on: {request.text}"
return {"input": request.text, "output": generated_text}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
步骤3:测试API
使用命令 python app.py 启动FastAPI服务器。
创建API后,必须有效管理以保证安全性、可靠性和性能。API管理包括监控、维护和持续改进API。开发前就应考虑API管理策略。良好的API管理能降低安全风险,提供宝贵的使用数据,使API成为为组织和用户创造价值的重要资产。
API管理活动包括:
- 监控性能
- 错误处理
- 安全措施实施
- 定期更新维护
针对基于LLM的应用,API管理涵盖以下高层步骤(非详尽):
- 明确应用核心功能,定义访问这些功能的API端点。例如,生成文本、获取模型信息、管理用户账户等。
- 决定API设计方案,比如选择RESTful或GraphQL,使用的数据格式(如JSON)。遵循最佳实践,如端点命名清晰、文档简洁、HTTP状态码合理。
- 使用Web框架实现API(Python可用Flask或Django,Node.js可用Express),注意优雅处理错误,验证输入数据,实施安全机制(认证、限流等)。
- 将LLM集成入API,通过封装LLM库或API,负责输入/输出格式转换、错误处理及其他必要功能。
- 利用自动化测试工具(如PyTest或Jest)彻底测试API,覆盖所有端点、输入校验、错误处理及性能。
- 将API部署到生产环境,选用云服务商(AWS、Google Cloud、Azure等),采用持续集成/持续部署(CI/CD)、性能监控、安全防护(防火墙、访问控制)等最佳实践。
- 持续监控API性能、错误和安全漏洞,实施日志记录与告警,及时发现并处理问题。定期更新依赖、修复漏洞并根据需求新增功能。
凭证管理
凭证管理是API管理中最容易被忽视但又极其关键的环节。凭证包括API密钥、认证令牌、用户密码等用于访问应用或API的敏感信息。为有效管理凭证,应确保安全存储,比如使用安全的密钥库或加密方式。避免将凭证硬编码在代码或配置文件中,因为这会增加泄露风险。推荐使用环境变量或不纳入版本控制的安全配置文件来存储凭证。
同时,应实施访问控制以限制凭证访问权限。可以采用基于角色的访问控制(RBAC)或基于属性的访问控制(ABAC)来限制对敏感信息的访问。
最后,定期更换凭证以降低泄露风险,包括设置API密钥或令牌的过期时间,或要求用户定期更改密码。
API网关
API网关是基于LLM应用中的关键组件,作为所有API请求的统一入口,负责请求路由、负载均衡、认证,有时还负责缓存和日志记录,充当客户端与微服务之间的中间层。
搭建LLM应用的API网关步骤:
- 选择符合需求的API网关服务商,考虑功能、扩展性和成本。
- 定义API,包括端点、方法、请求/响应格式。确保端点命名有意义,提供清晰简洁的文档和合理的HTTP状态码。
- 实施认证与授权机制,如OAuth或JWT,确保只有授权用户能访问API。
- 实施限流以防止滥用(如拒绝服务攻击),确保公平使用。限流策略可设置每分钟或每小时最大请求数,或采用更高级算法。
- 监控和记录API活动,及时发现并响应安全威胁、性能问题和错误。包括设置日志和告警机制。
- 彻底测试API,确保满足功能和非功能需求。
- 使用AWS、Google Cloud或Azure等云平台将API部署到生产环境。
API网关优势:
- 提供统一入口,便于管理和监控API流量。
- 快速识别并响应安全和性能问题。
- 处理认证和授权,如验证API密钥或令牌,执行访问控制。
- 记录并监控API活动,洞察应用使用情况。
- 实施限流,防止滥用并保证公平使用。
API版本管理与生命周期管理
API版本管理指维护多个API版本,确保向后兼容,最大限度减少变更对现有用户的影响。
版本管理方法:
- 在API端点或请求头中包含版本号,方便识别使用的API版本。
- 使用语义化版本号表示兼容性级别,帮助用户理解变更影响并合理规划升级。
- 详细记录各版本间的变更,包括破坏性变更和弃用功能,帮助用户顺利迁移。可提供升级工具或脚本协助用户更新代码或配置。
生命周期管理不仅限于版本控制,还需涵盖API从设计、开发、部署到退役的全过程。关键步骤包括:
- 治理模型:明确角色职责,定义流程与工作流,确定可接受的工具与技术。
- 变更管理流程:确保未来API变更得到充分规划、测试并有效通知用户。
- 监控与告警:搭建监控和告警系统,及时发现和应对性能问题、安全威胁和错误。多数API部署平台提供相关服务,如Azure Application Insights,可监控API调用每一步耗时并自动报警。
- 退役流程:制定并记录API退役流程,包括通知用户、提供迁移方案和归档数据等,确保API不再需要时安全下线。
大型语言模型(LLM)部署架构
软件应用和基于LLM的应用最常见的两种部署架构是模块化架构和单体架构。
模块化架构与单体架构
两种架构各有优势和适用场景,都需谨慎规划。
- 模块化架构将系统拆分为多个组件,便于更新和扩展,适合需要灵活性的应用。
- 单体架构在单一框架内处理所有功能,简单且工作流紧密集成。
在模块化系统中,你会独立训练检索器(retriever)、重排序器(re-ranker)、生成器(generator)等组件,专注于各模块优化。关键在于明确定义模块间通信,模块通信不当是常见问题。相比之下,单体架构多采用端到端训练,简化依赖关系,但计算资源需求更高。
训练完成后,应以支持架构的格式保存模型,例如使用ONNX等开放格式实现互操作,或用PyTorch、TensorFlow等本地格式支持自定义流程。两者都需验证模型性能;模块化系统需针对各组件做兼容性和性能测试,单体架构则需全面端到端评估确保稳健性。
微服务架构实现
假设你决定为LLM应用采用微服务架构,这是一种模块化架构风格,将大型应用拆解为多个独立服务,服务间通过API通信。其优势包括提升扩展性、灵活性和可维护性。
在微服务架构中,API是不同服务间的连接器。每个服务公开API供其他服务交互,API解耦服务,使它们能独立演进,避免单个服务变更影响全局,降低破坏风险。
API还支持服务独立扩展,实现资源更高效分配。例如,语言翻译服务可以独立扩展,而不影响文本转语音服务。使用API还允许不同服务用不同技术和语言开发,提升开发速度,降低技术债务。
微服务架构中API设计要点:
- 明确定义每个服务的API,包含输入输出格式、认证授权机制及错误处理。
- 使用标准通信协议,如HTTP或gRPC,确保服务间兼容与互操作。
- 实施安全机制,如OAuth或JWT,进行API请求认证和授权。
- 部署监控和日志机制,跟踪API使用,快速发现并解决问题,提升用户体验。
- 实施版本管理,合理管理API变更,减少对现有应用和用户的影响。
此方法助你构建可扩展、灵活、易维护的多API LLM应用,满足用户需求并支持大规模分布式功能。下面详细介绍微服务架构的实现步骤。
步骤1:拆解应用为组件
- 预处理服务:对输入文本进行分词和清洗
- 推理服务:执行LLM推理
- 后处理服务:格式化或丰富模型输出
示例预处理服务代码:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class PreprocessRequest(BaseModel):
text: str
@app.post("/preprocess")
async def preprocess(request: PreprocessRequest):
# 基础预处理逻辑
preprocessed_text = request.text.lower().strip()
return {"original": request.text, "processed": preprocessed_text}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8001)
步骤2:建立服务间通信
可选择简单的HTTP协议,或性能更优的gRPC协议。
为实现异步通信,可引入消息中间件,如RabbitMQ或Kafka。
步骤3:协调微服务,保持工作流顺畅
你可以使用Consul或Eureka等工具,实现服务的动态注册与发现,或者部署API网关(如Kong或NGINX),将客户端请求路由到对应的微服务。下面是一个NGINX配置示例:
# nginx.conf
server {
listen 80;
location /preprocess {
proxy_pass http://localhost:8001;
}
location /generate {
proxy_pass http://localhost:8002;
}
}
如果计划使用MLFlow或BentoML等工具管理服务依赖和任务执行,也可以在此步骤实现。
步骤4:为每个微服务创建Dockerfile
以下是基于Python的示例:
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8001"]
下面是一个部署到Kubernetes的示例配置:
apiVersion: apps/v1
kind: Deployment
metadata:
name: preprocessing-service
spec:
replicas: 2
selector:
matchLabels:
app: preprocessing
template:
metadata:
labels:
app: preprocessing
spec:
containers:
- name: preprocessing
image: myregistry/preprocessing-service:latest
ports:
- containerPort: 8001
最后,测试你的Kubernetes部署:
kubectl apply -f preprocessing-deployment.yaml
自动化RAG检索-重排序管道
构建高效的检索器-重排序器管道是实现RAG流水线工作流的关键步骤。检索器负责根据查询抓取相关上下文,重排序器则对结果按相关性排序,供LLM输入使用。正如本书多次提到的,自动化对于确保系统的可扩展性和可靠性至关重要。本节将介绍如何利用LangChain和LlamaIndex等框架简化这一过程。
首先是检索器,它基于查询获取相关数据。通常使用密集向量嵌入,并存储于向量数据库,如Pinecone或Milvus。检索到结果后,重排序器依据相关度重新排序。LangChain提供模块化组件,方便无缝集成这两个步骤,帮助你构建自动化的检索与排序管道,减少人工干预。LlamaIndex则增强了检索系统与结构化数据源的整合,提升知识管理的灵活性。
自动化确保检索-重排序管道时刻保持最新,特别适合处理动态数据(如用户生成内容或频繁更新的知识库)。定期验证和重新训练可不断提升管道准确率。
以下示例展示了如何实现文档检索、重排序并将最相关的上下文传入LLM(示例6-1):
import os
from langchain.vectorstores import Pinecone
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.chains import RetrievalQA
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from pinecone import init, Index
# 第1步,设置API密钥的环境变量
os.environ["OPENAI_API_KEY"] = "your_openai_api_key"
os.environ["PINECONE_API_KEY"] = "your_pinecone_api_key"
os.environ["PINECONE_ENV"] = "your_pinecone_environment"
# 第2步,初始化Pinecone
init(api_key=os.environ["PINECONE_API_KEY"], environment=os.environ["PINECONE_ENV"])
index_name = "your_index_name"
# 确认索引存在
if index_name not in Pinecone.list_indexes():
print(f"索引 '{index_name}' 未找到,请在Pinecone控制台创建。")
exit()
# 第3步,设置检索器
embedding_model = OpenAIEmbeddings()
retriever = Pinecone(index_name=index_name, embedding=embedding_model.embed_query)
# 第4步,定义重排序函数
def rerank_documents(documents, query):
"""
使用简单的嵌入相似度评分对文档进行重排序。
"""
reranked_docs = sorted(
documents,
key=lambda doc: embedding_model.similarity(query, doc.page_content),
reverse=True,
)
return reranked_docs[:5] # 返回前5篇文档
# 第5步,设置LLM与提示模板
llm = OpenAI(model="gpt-4")
prompt_template = """
You are my hero. Use the following context to answer the user's question:
Context: {context}
Question: {question}
Answer:
"""
prompt = PromptTemplate(template=prompt_template, input_variables=["context", "question"])
步骤2中,Pinecone基于查询向量获取top-k相关文档;步骤4用简单函数根据语义相似度对文档重排序。
更优方案可替换为T5或BERT等神经网络重排序器;可为管道增加记忆模块以支持多轮对话;还可通过定时任务自动更新数据库,应对动态内容。
自动化知识图谱更新
保持知识图谱(KG)数据实时更新对于维持准确洞察至关重要。自动化简化了实体链接和图嵌入生成等任务,减少人工干预,提高准确性,保证知识图谱的可靠性。
- 实体链接确保新信息正确关联到KG中的对应节点。例如,“Paris”一词需判定指城市还是人名。自动化管道结合神经自然语言处理模型与现有图结构,利用嵌入理解实体间关系和上下文。spaCy和专门的实体解析库有助构建稳健链接系统。
- 图嵌入是节点、边及其关系的数值表达,支持图搜索、推荐和推理。自动化生成与更新图嵌入,确保知识图谱反映最新数据,管道可根据新数据定时执行更新。PyTorch Geometric和DGL(深度图形库)提供图嵌入工具。定期校验管道,防止错误扩散。
以下示例演示如何使用Python构建自动化知识图谱更新管道,使用spaCy进行实体链接,PyTorch Geometric和DGL生成图嵌入,Neo4j作为图数据库。
先安装依赖:
pip install spacy torch torchvision dgl neo4j pandas
python -m spacy download en_core_web_sm
示例代码:
# 第1步:导入相关库
import spacy
import torch
import dgl
import pandas as pd
from neo4j import GraphDatabase
from spacy.matcher import PhraseMatcher
from torch_geometric.nn import GCNConv
from torch_geometric.data import Data
nlp = spacy.load("en_core_web_sm")
# 第2步:连接Neo4j管理知识图谱
uri = "bolt://localhost:7687"
username = "neo4j"
password = "your_neo4j_password"
driver = GraphDatabase.driver(uri, auth=(username, password))
# 第3步:定义实体链接并更新知识图谱函数
def link_entities_and_update_kg(text, graph):
# 使用spaCy提取实体
doc = nlp(text)
entities = set([ent.text for ent in doc.ents])
# 将新实体合并进KG
with graph.session() as session:
for entity in entities:
session.run(f"MERGE (e:Entity {{name: '{entity}'}})")
print(f"已在KG中链接并更新实体: {entities}")
# 第4步:用图卷积网络(GCN)生成图嵌入
def update_graph_embeddings(graph):
edges = [(0, 1), (1, 2), (2, 0)] # 示例图边
x = torch.tensor([[1, 2], [2, 3], [3, 4]], dtype=torch.float)
edge_index = torch.tensor(edges, dtype=torch.long).t().contiguous()
data = Data(x=x, edge_index=edge_index)
gcn = GCNConv(in_channels=2, out_channels=2)
# 前向传播计算GCN输出
output = gcn(data.x, data.edge_index)
print("更新后的图嵌入:", output)
# 第5步:自动化KG更新流程
def automate_kg_update(text):
link_entities_and_update_kg(text, driver)
update_graph_embeddings(driver)
步骤3中,link_entities_and_update_kg()函数使用spaCy抽取输入文本中的命名实体,并用Neo4j的MERGE语句将实体节点合并到知识图谱中,确保不重复创建。
步骤4利用PyTorch Geometric的GCN层计算图嵌入,示例中手动定义了节点和边。
步骤5的automate_kg_update()函数合并了实体链接与图嵌入更新步骤。可通过cron任务或Celery等任务调度器定期调用此函数,实现自动化更新。
以上介绍了如何自动化实现RAG的检索-重排序管道和知识图谱的动态维护,助力构建高效、实时更新的LLM辅助系统。
部署延迟优化
减少延迟是部署大型语言模型(LLM)时最重要的考虑因素之一。延迟直接影响系统性能和响应速度。某些应用如聊天机器人、搜索引擎和实时决策系统,对低延迟有极高要求,因此必须尽可能缩短系统返回结果的时间。
一种有效方法是使用Triton推理服务器,这是一个开源平台,专门为高性能模型推理设计。它支持包括TensorFlow、PyTorch、ONNX等多种模型格式。Triton显著优化了LLM的执行效率,使其能处理多个并发推理请求且延迟极低。
其优势主要有:
- 支持模型并发运行,可利用GPU进行加速。
- 可根据需求动态加载和卸载模型,适合对延迟敏感的场景。
- 支持批处理,将多个推理请求合并成单次操作,提高吞吐量,降低响应时间。
使用Triton部署LLM的步骤:
- 安装Triton:
docker pull nvcr.io/nvidia/tritonserver:latest
- 准备模型目录,确保模型以Triton支持的格式保存,如TensorFlow SavedModel或PyTorch TorchScript:
model_repository/
├── my_model/
│ ├── 1/
│ │ └── model.pt
- 从终端启动Triton服务器:
docker run --gpus all --rm -p 8000:8000 -p 8001:8001 -p 8002:8002 \
-v /path/to/model_repository:/models nvcr.io/nvidia/tritonserver:latest \
tritonserver --model-repository=/models
- 使用客户端库(如tritonclient)发送推理请求:
import tritonclient.grpc
from tritonclient.grpc import service_pb2, service_pb2_grpc
# 创建Triton客户端
triton_client = tritonclient.grpc.InferenceServerClient(url="localhost:8001")
# 准备输入数据
input_data = some_input_data()
# 发送推理请求
response = triton_client.infer(model_name="my_model", inputs=[input_data])
print(response)
多模型编排
为了在需要多个模型协同工作的系统中实现高效和良好的响应速度,需要采用多模型编排,将模型拆分为微服务,分别独立部署,通过API或消息队列交互。
市面上已有多种成熟的编排器,例如AWS的Multi-Agent Orchestrator,LiteLLM代理工具可切换多个模型和API。但正如软件开发中常见的,依赖越多,故障排查复杂度越高,尤其是在关键推理任务中。
例如,你可以为不同处理阶段设置独立模型:文本预处理模型、文本转语音模型、响应生成模型。编排器确保各阶段并发且高效执行,减少瓶颈,提高整体速度。
你可以使用Kubernetes或Docker Compose等容器编排工具管理运行中的多模型微服务。以下是docker-compose.yml示例:
version: '3'
services:
model1:
image: model1_image
ports:
- "5001:5001"
model2:
image: model2_image
ports:
- "5002:5002"
model3:
image: model3_image
ports:
- "5003:5003"
利用消息队列(如RabbitMQ)或直接API调用协调模型间通信。每个服务监听输入,按需顺序或并发处理。
需要配置Kubernetes或Docker Swarm来管理模型实例数量,并实现请求的负载均衡。Kubernetes通过Service将请求路由至合适Pod,Docker Swarm使用内置负载均衡自动分配流量。
假设你有一个运行模型的Docker容器(镜像名为model_image),想部署多个实例并使用Kubernetes实现流量均衡:
创建Kubernetes部署文件model-deployment.yaml,定义模型容器及副本数:
apiVersion: apps/v1
kind: Deployment
metadata:
name: model-deployment
spec:
replicas: 3 # 副本数量
selector:
matchLabels:
app: model
template:
metadata:
labels:
app: model
spec:
containers:
- name: model-container
image: model_image:latest # 你的Docker镜像
ports:
- containerPort: 5000
此配置会部署3个副本。Kubernetes会管理运行这些Pod,并自动实现流量均衡。为暴露服务创建Service资源:
apiVersion: v1
kind: Service
metadata:
name: model-service
spec:
selector:
app: model
ports:
- protocol: TCP
port: 80 # 对外端口
targetPort: 5000 # 模型容器监听端口
type: LoadBalancer
Service会将请求分发到3个模型副本的80端口,实现负载均衡。
部署到Kubernetes集群:
kubectl apply -f model-deployment.yaml
kubectl apply -f model-service.yaml
kubectl get deployments
kubectl get services
模块化架构优势:
- 可根据各模型任务需求独立扩展实例数,例如文本生成请求多,可单独扩容文本生成模型,不影响其他模型。
- 某个模型出现故障时,其他模型仍可继续服务,保障系统可用性。
- 方便替换单个模型升级版本或替换为不同模型以提升性能。
以上即为部署延迟优化和多模型编排的完整实践方案,助力构建高效、稳定且灵活的LLM生产系统。
选择 Kubernetes 还是 Docker Swarm
Kubernetes 的自愈能力在应用部署和扩展管理方面具有显著优势。它能够自动检测故障,并在无需人工干预的情况下恢复应用的期望状态。如果某个 Pod 崩溃或状态不健康,Kubernetes 的 ReplicaSet 控制器会自动启动新的 Pod 来替代,保证副本数量保持稳定。Pod 生命周期控制器会对 Pod 内的容器执行健康检查,若不符合要求则终止并替换该 Pod。
虽然 Docker 是一个优秀的容器化工具,但它不具备 Kubernetes 那样的编排和自动化管理能力。Docker 主要专注于单个容器的管理,并提供了一些基础的多容器管理功能,但它本身并不具备管理大规模分布式系统复杂性的机制。因此,对于需要持续运行且人工干预最小的生产环境,Kubernetes 更加适合。
优化RAG流水线
优化RAG流水线对于提高信息检索和文本生成任务的效率和降低延迟至关重要。其性能高度依赖于检索流水线的优化效果。本节将介绍几种显著提升RAG性能的技术。
异步查询
异步查询是一种强大的优化技术,允许多个查询并发处理,减少单个查询的等待时间。传统的同步检索系统中,查询是顺序处理的,当多个请求同时发起时会导致延迟。异步查询通过允许系统同时向向量存储发送多个查询,并并行等待响应,从而解决了这一瓶颈。
Python实现异步查询示例:
import asyncio
import faiss
import numpy as np
async def retrieve_from_faiss(query_vector, index):
# 模拟FAISS查询
return index.search(np.array([query_vector]), k=5)
async def batch_retrieve(query_vectors, index):
tasks = [retrieve_from_faiss(qv, index) for qv in query_vectors]
results = await asyncio.gather(*tasks)
return results
dimension = 128 # 向量维度示例
index = faiss.IndexFlatL2(dimension) # 使用L2距离进行相似度搜索
query_vectors = np.random.rand(10, dimension).astype('float32') # 生成随机查询向量
results = asyncio.run(batch_retrieve(query_vectors, index))
print(results)
以上代码中,asyncio.gather()同时向Facebook AI Similarity Search (FAISS)发送所有查询,并异步等待响应,使系统能并行处理多个查询,降低整体延迟。
密集检索与稀疏检索结合
密集检索利用嵌入向量表示查询和文档,实现基于向量距离的语义搜索;稀疏检索如TF-IDF依赖基于关键词的匹配,捕获更细粒度的关键词相关性。密集检索适合语义相关性,稀疏检索则擅长精确关键词匹配。两者结合,可兼顾准确性和全面性。
示例代码:
from whoosh.index import create_in
from whoosh.fields import Schema, TEXT
import faiss
import numpy as np
# 初始化密集检索索引
dimension = 128
dense_index = faiss.IndexFlatL2(dimension)
# 使用Whoosh模拟稀疏检索
schema = Schema(content=TEXT(stored=True))
ix = create_in("index", schema)
writer = ix.writer()
writer.add_document(content="This is a test document.")
writer.add_document(content="Another document for retrieval.")
writer.commit()
def retrieve_dense(query_vector):
return dense_index.search(np.array([query_vector]), k=5)
def retrieve_sparse(query):
searcher = ix.searcher()
results = searcher.find("content", query)
return [hit['content'] for hit in results]
query_vector = np.random.rand(1, dimension).astype('float32')
sparse_query = "document"
dense_results = retrieve_dense(query_vector)
sparse_results = retrieve_sparse(sparse_query)
combined_results = dense_results + sparse_results
print("Combined results:", combined_results)
这里FAISS负责密集向量检索,Whoosh负责稀疏关键词搜索,结果结合后,兼顾语义和关键词匹配,提升整体系统准确性和完整性。
缓存嵌入向量
对于频繁查询的数据,缓存嵌入向量以避免重复计算,可以大幅缩短响应时间并提升效率。若缓存中已有查询对应的嵌入,直接读取,否则计算后存储备用。
示例代码:
import joblib
import numpy as np
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('MiniLM')
def get_embeddings(query):
cache_file = "embedding_cache.pkl"
try:
embeddings_cache = joblib.load(cache_file)
except FileNotFoundError:
embeddings_cache = {}
if query not in embeddings_cache:
embedding = model.encode([query])
embeddings_cache[query] = embedding
joblib.dump(embeddings_cache, cache_file)
return embeddings_cache[query]
query = "What is the capital of France?"
embedding = get_embeddings(query)
print("Embedding for the query:", embedding)
该方法显著减少重复计算,提升系统响应速度和资源利用率。
键值缓存(Key–Value Caching)
键值缓存的工作原理与嵌入缓存类似。它存储键值对的结果,其中键是查询或中间结果,值是对应的响应或计算结果。这使得系统能够直接检索预计算的结果,而不是每次处理重复查询时都重新计算。键值缓存能够加速检索和生成过程,尤其适用于大规模、高流量的系统。
在RAG系统中,键值缓存通常应用于检索阶段,以加快查询-响应循环。在生成阶段,模型可能使用缓存的文档版本或响应片段来构建最终输出。
下面是用Python实现键值缓存的示例:
import redis
import numpy as np
from sentence_transformers import SentenceTransformer
# 步骤1:初始化Redis客户端
r = redis.Redis(host='localhost', port=6379, db=0)
# 步骤2:初始化句子嵌入模型
model = SentenceTransformer('paraphrase-MiniLM-L6-v2')
# 步骤3:获取嵌入并缓存到Redis的函数
def get_embeddings_from_cache_or_compute(query):
cache_key = f"embedding:{query}" # 用于存储查询嵌入的键
# 检查缓存中是否已有该查询的嵌入
cached_embedding = r.get(cache_key)
if cached_embedding:
print("缓存命中,返回缓存的嵌入")
return np.frombuffer(cached_embedding, dtype=np.float32)
else:
print("缓存未命中,计算并存储嵌入")
embedding = model.encode([query])
r.set(cache_key, embedding.tobytes()) # 以字节形式存储嵌入到Redis
return embedding
# 步骤4:执行查询
query = "What is the capital of France?"
embedding = get_embeddings_from_cache_or_compute(query)
print("Embedding:", embedding)
说明:
- 第一步连接到本地运行的Redis实例,用于快速存储和查找键值对。
- 第三步中,代码接收到查询后,会检查Redis中是否存在对应的嵌入缓存(键为
embedding:<query>)。如果存在(缓存命中),直接读取返回;如果不存在(缓存未命中),则使用SentenceTransformer计算嵌入,并以字节形式存储到Redis中,保证后续可正确读取。
通过减少重复计算嵌入或模型响应,键值缓存能够降低计算成本,缓解检索和生成组件的压力,保证系统即使在高负载下也能保持响应速度。
可扩展性与复用性
可扩展性和复用性是应对高流量系统的关键能力。在大规模环境中,高效扩展基础设施的能力尤为重要。分布式推理编排允许系统在流量增加时将负载分散到多个节点,每个节点处理部分请求,从而降低单机过载的风险。
通常使用Kubernetes管理扩展过程,通过自动化任务分配和动态调整资源来实现。
复用组件使得扩展和管理管道更加便捷。这些组件可以快速复制到不同服务或项目中,无需大量修改,特别适合不断更新和迭代的环境。ZenML等编排工具支持创建可复用的流水线,使你在新增模型或任务时,能复用现有组件,保持一致性并缩短开发时间。
分布式推理编排与复用组件相辅相成,确保系统既具备良好的可扩展性又易于维护。当流量激增或出现新用例时,你可以依赖现有基础设施应对需求,提升系统弹性和适应新挑战的敏捷性。
可扩展性与复用性不仅是锦上添花的功能,更是高流量LLM系统的必备特性。分布式推理编排保证系统能按需扩展,复用组件则简化维护和扩展,使大规模LLM部署更加高效、稳定。
总结
合适的技术栈取决于你的项目目标。开源工具适合需要灵活性且具备技术资源的团队;托管服务则适合注重速度和简便性的团队。务必在选择技术栈前仔细评估需求,正确的选择能节省时间、提升性能,助你更高效地完成部署。