diff --git a/docs/10-tutorials/01-vLLM-stepbysteb.md b/docs/10-tutorials/01-vLLM-stepbysteb.md new file mode 100644 index 0000000..21754cc --- /dev/null +++ b/docs/10-tutorials/01-vLLM-stepbysteb.md @@ -0,0 +1,225 @@ +--- +title: vLLM 入门教程:零基础分步指南 +--- + +在本教程中,将逐步展示如何配置和运行 vLLM,提供从安装到启动的完整入门指南。 + +[在线运行此教程](https://openbayes.com/console/public/tutorials/rXxb5fZFr29) + + +## 目录 +- [一、安装 vLLM](#一、安装vLLM) +- [二、开始使用](#二、开始使用) + - [2.1 模型准备](#2.1模型准备) + - [2.2 离线推理](#2.2离线推理) +- [三、启动 vLLM 服务器](#三、启动vLLM服务器) + - [3.1 主要参数设置](#3.1主要参数设置) + - [3.2 启动命令行](#3.2启动命令行) +- [四、发出请求](#四、发出请求) + - [4.1 使用 OpenAI 客户端](#4.1使用OpenAI客户端) + - [4.2 使用 Curl 命令请求](#4.2使用Curl命令请求) + +## 一、安装 vLLM + +该教程基于 OpenBayes 云平台操作,该平台已完成 vllm==0.5.4 的安装。如果您在平台上操作,请跳过此步骤。如果您在本地部署,请按照以下步骤进行安装。 + +安装 vLLM 非常简单: + +```bash +pip install vllm +``` + +请注意,vLLM 是使用 CUDA 12.1 编译的,因此您需要确保机器运行的是该版本的 CUDA。 + +检查 CUDA 版本,运行: + +```bash +nvcc --version +``` + +如果您的 CUDA 版本不是 12.1,您可以安装与您当前 CUDA 版本兼容的 vLLM 版本(更多信息请参考安装说明),或者安装 CUDA 12.1。 + +## 二、开始使用 + +### 2.1 模型准备 + +#### 方法一:使用平台公共模型 + +首先,我们可以检查平台的公共模型是否已经存在。如果模型已上传到公共资源库,您可以直接使用。如果没有找到,则请参考方法二进行下载。 + +例如,平台已存放了 `Qwen-1_8B-Chat` 模型。以下是绑定模型的步骤(本教程已将此模型捆绑)。 + +![图片](/img/docs/02-toturials/model.png) + +![图片](/img/docs/02-toturials/id.png) + +![图片](/img/docs/02-toturials/bangding.png) + +#### 方法二:从 HuggingFace下载 或者 联系客服帮忙上传平台 + +大多数主流模型都可以在 HuggingFace 上找到,vLLM 支持的模型列表请参见官方文档: [vllm-supported-models](https://vllm.io/docs/supported_models)。 + +本教程将使用 `Qwen-1_8B-Chat` 进行测试。请按照以下步骤使用 Git LFS 下载模型: + +```bash +apt install git-lfs -y +git lfs install +# 下载模型,将模型文件下载到当前文件夹 +cd /input0/ +git clone https://huggingface.co/Qwen/Qwen-1_8B-Chat +``` + +### 2.2 离线推理 + +vLLM 作为一个开源项目,可以通过其 Python API 执行 LLM 推理。以下是一个简单的示例,请将代码保存为 `offline_infer.py` 文件: + +```python +from vllm import LLM, SamplingParams + +# 输入几个问题 +prompts = [ + "你好,你是谁?", + "法国的首都在哪里?", +] + +# 设置初始化采样参数 +sampling_params = SamplingParams(temperature=0.8, top_p=0.95, max_tokens=100) + +# 加载模型,确保路径正确 +llm = LLM(model="/input0/Qwen-1_8B-Chat/", trust_remote_code=True, max_model_len=4096) + +# 展示输出结果 +outputs = llm.generate(prompts, sampling_params) + +# 打印输出结果 +for output in outputs: + prompt = output.prompt + generated_text = output.outputs[0].text + print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}") +``` + +然后运行脚本: + +```bash +python offline_infer.py +``` + +模型加载后,您将看到以下输出: + +```python +Processed prompts: 100%|██████████| 2/2 [00:00<00:00, 10.23it/s, est. speed input: 56.29 toks/s, output: 225.14 toks/s] +Prompt: '你好,你是谁?', Generated text: '我是来自阿里云的大规模语言模型,我叫通义千问。' +Prompt: '法国的首都在哪里?', Generated text: '法国的首都是巴黎。' +``` + +## 三、启动 vLLM 服务器 + +要使用 vLLM 提供在线服务,您可以启动与 OpenAI API 兼容的服务器。成功启动后,您可以像使用 GPT 一样使用部署的模型。 + +### 3.1 主要参数设置 + +以下是启动 vLLM 服务器时常用的一些参数: + +- `--model`:要使用的 HuggingFace 模型名称或路径(默认值:`facebook/opt-125m`)。 +- `--host` 和 `--port`:指定服务器地址和端口。 +- `--dtype`:模型权重和激活的精度类型。可能的值:`auto`、`half`、`float16`、`bfloat16`、`float`、`float32`。默认值:`auto`。 +- `--tokenizer`:要使用的 HuggingFace 标记器名称或路径。如果未指定,默认使用模型名称或路径。 +- `--max-num-seqs`:每次迭代的最大序列数。 +- `--max-model-len`:模型的上下文长度,默认值自动从模型配置中获取。 +- `--tensor-parallel-size`、`-tp`:张量并行副本数量(对于 GPU)。默认值:`1`。 +- `--distributed-executor-backend=ray`:指定分布式服务的后端,可能的值:`ray`、`mp`。默认值:`ray`(当使用超过一个 GPU 时,自动设置为 `ray`)。 + +### 3.2 启动命令行 + +创建兼容 OpenAI API 接口的服务器。运行以下命令启动服务器: + +```bash +python3 -m vllm.entrypoints.openai.api_server --model /input0/Qwen-1_8B-Chat/ --host 0.0.0.0 --port 8080 --dtype auto --max-num-seqs 32 --max-model-len 4096 --tensor-parallel-size 1 --trust-remote-code +``` + +成功启动后,您将看到类似以下的输出: + +![图片](/img/docs/02-toturials/start.png) + +vLLM 现在可以作为实现 OpenAI API 协议的服务器进行部署,默认情况下它将在 `http://localhost:8080` 启动服务器。您可以通过 `--host` 和 `--port` 参数指定其他地址。 + +## 四、发出请求 + +在本教程中启动的 API 地址是 `http://localhost:8080`,您可以通过访问该地址来使用 API。`localhost` 指平台本机,`8080` 是 API 服务监听的端口号。 + +在工作空间右侧,API 地址将转发到本地 8080 服务,可以通过真实主机进行请求,如下图所示: + +![图片](/img/docs/02-toturials/api_path.png) + +### 4.1 使用 OpenAI 客户端 + +在第四步中启动 vLLM 服务后,您可以通过 OpenAI 客户端调用 API。以下是一个简单的示例: + +```python +# 注意:请先安装 openai +# pip install openai +from openai import OpenAI + +# 设置 OpenAI API 密钥和 API 基础地址 +openai_api_key = "EMPTY" # 请替换为您的 API 密钥 +openai_api_base = "http://localhost:8080/v1" # 本地服务地址 + +client = OpenAI(api_key=openai_api_key, base_url=openai_api_base) + +models = client.models.list() +model = models.data[0].id +prompt = "描述一下北京的秋天" + +# Completion API 调用 +completion + + = client.completions.create(model=model, prompt=prompt) +res = completion.choices[0].text.strip() +print(f"Prompt: {prompt}\nResponse: {res}") +``` + +执行命令: + +```bash +python api_infer.py +``` + +您将看到如下输出结果: + +![图片](/img/docs/02-toturials/res_api.png) + +### 4.2 使用 Curl 命令请求 + +您也可以使用以下命令直接发送请求。在平台上访问时,输入以下命令: + +```bash +curl http://localhost:8080/v1/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "/input0/Qwen-1_8B-Chat/", + "prompt": "描述一下北京的秋天", + "max_tokens": 512 + }' +``` + +您将得到如下响应: + +![图片](/img/docs/02-toturials/curl_res.png) + +如果您使用的是 OpenBayes 平台,输入以下命令: + +```bash +curl https://hyperai-tutorials-****.gear-c1.openbayes.net/v1/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "/input0/Qwen-1_8B-Chat/", + "prompt": "描述一下北京的秋天", + "max_tokens": 512 + }' +``` + +响应结果如下: + +![图片](/img/docs/02-toturials/curl_res_local.png) + + diff --git a/docs/10-tutorials/02-infer-34b-with-vllm.md b/docs/10-tutorials/02-infer-34b-with-vllm.md new file mode 100644 index 0000000..306e7c0 --- /dev/null +++ b/docs/10-tutorials/02-infer-34b-with-vllm.md @@ -0,0 +1,211 @@ +--- +title: 使用 vLLM 对 Qwen2.5 推理 +--- + +[在线运行此教程](https://openbayes.com/console/hyperai-tutorials/containers/wmGLO8o5IiV) + +本教程演示了如何在短短 5 小时内推理 LLM 3B 模型。 + +## 目录 +- [1.安装 vllm](#1.安装vllm) +- [2.使用vLLM加载Qwen量化模型](#2.使用vLLM加载Qwen量化模型) +- [3.加载测试数据](#3.加载测试数据) +- [4.提示工程](#4.提示工程) +- [5.Infer测试](#5.Infer测试) +- [6.提取推理概率](#6.提取推理概率) +- [7.创建提交CSV](#7.创建提交CSV) +- [8.计算CV分数](#8.计算CV分数) + +## 1. 安装 vLLM + +该教程基于 OpenBayes 云平台操作,该平台已完成 vllm==0.5.4 的安装。如果您在平台上操作,请跳过此步骤。如果您在本地部署,请按照以下步骤进行安装。 + +安装 vLLM 非常简单: + +``` +pip install vllm +``` + +本教程已经安装 vllm==0.5.4,如需更新 vllm 请取消下行注释 + +``` +!pip install -U vllm +``` + +## 2. 使用 vLLM 加载 Qwen 量化模型 +``` +import os, math, numpy as np +os.environ["CUDA_VISIBLE_DEVICES"]="0" +``` + +``` +# 我们将在此处加载并使用 Qwen2.5-3B-Instruct-AWQ + +import vllm + +llm = vllm.LLM( + "/input0/Qwen2.5-3B-Instruct-AWQ", + quantization="awq", + tensor_parallel_size=1, + gpu_memory_utilization=0.95, + trust_remote_code=True, + dtype="half", + enforce_eager=True, + max_model_len=512, + #distributed_executor_backend="ray", +) +tokenizer = llm.get_tokenizer() +``` + +## 3. 加载测试数据 + +在提交期间,我们加载 128 行 train 来计算 CV 分数,加载测试数据。 + +``` +import pandas as pd +VALIDATE = 128 + +test = pd.read_csv("./lmsys-chatbot-arena/test.csv") +if len(test)==3: + test = pd.read_csv("./lmsys-chatbot-arena/train.csv") + test = test.iloc[:VALIDATE] +print( test.shape ) +test.head(1) +``` + +## 4. 提示工程 +如果我们想提交零次 LLM,我们需要尝试不同的系统提示来提高 CV 分数。如果我们对模型进行微调,那么系统就不那么重要了,因为无论我们使用哪个系统提示,模型都会从目标中学习该做什么。 + +我们使用 logits 处理器强制模型输出我们感兴趣的 3 个标记。 + +``` +from typing import Any, Dict, List +from transformers import LogitsProcessor +import torch + +choices = ["A","B","tie"] + +KEEP = [] +for x in choices: + c = tokenizer.encode(x,add_special_tokens=False)[0] + KEEP.append(c) +print(f"Force predictions to be tokens {KEEP} which are {choices}.") + +class DigitLogitsProcessor(LogitsProcessor): + def __init__(self, tokenizer): + self.allowed_ids = KEEP + + def __call__(self, input_ids: List[int], scores: torch.Tensor) -> torch.Tensor: + scores[self.allowed_ids] += 100 + return scores +``` + +``` +sys_prompt = """Please read the following prompt and two responses. Determine which response is better. +If the responses are relatively the same, respond with 'tie'. Otherwise respond with 'A' or 'B' to indicate which is better.""" +``` + +``` +SS = "#"*25 + "\n" +``` + +``` +all_prompts = [] +for index,row in test.iterrows(): + + a = " ".join(eval(row.prompt, {"null": ""})) + b = " ".join(eval(row.response_a, {"null": ""})) + c = " ".join(eval(row.response_b, {"null": ""})) + + prompt = f"{SS}PROMPT: "+a+f"\n\n{SS}RESPONSE A: "+b+f"\n\n{SS}RESPONSE B: "+c+"\n\n" + + formatted_sample = sys_prompt + "\n\n" + prompt + + all_prompts.append( formatted_sample ) +``` + +## 5. Infer 测试 +现在使用 vLLM 推断测试。我们要求 vLLM 输出第一个 Token 中被认为预测的前 5 个 Token 的概率。并将预测限制为 1 个 token,以提高推理速度。 + +根据推断 128 个训练样本所需的速度,可以推断出 25000 个测试样本需要多长时间。 + +``` +from time import time +start = time() + +logits_processors = [DigitLogitsProcessor(tokenizer)] +responses = llm.generate( + all_prompts, + vllm.SamplingParams( + n=1, # Number of output sequences to return for each prompt. + top_p=0.9, # Float that controls the cumulative probability of the top tokens to consider. + temperature=0, # randomness of the sampling + seed=777, # Seed for reprodicibility + skip_special_tokens=True, # Whether to skip special tokens in the output. + max_tokens=1, # Maximum number of tokens to generate per output sequence. + logits_processors=logits_processors, + logprobs = 5 + ), + use_tqdm = True +) + +end = time() +elapsed = (end-start)/60. #minutes +print(f"Inference of {VALIDATE} samples took {elapsed} minutes!") +``` + +``` +submit = 25_000 / 128 * elapsed / 60 +print(f"Submit will take {submit} hours") +``` + +## 6. 提取推理概率 +``` +results = [] +errors = 0 + +for i,response in enumerate(responses): + try: + x = response.outputs[0].logprobs[0] + logprobs = [] + for k in KEEP: + if k in x: + logprobs.append( math.exp(x[k].logprob) ) + else: + logprobs.append( 0 ) + print(f"bad logits {i}") + logprobs = np.array( logprobs ) + logprobs /= logprobs.sum() + results.append( logprobs ) + except: + #print(f"error {i}") + results.append( np.array([1/3., 1/3., 1/3.]) ) + errors += 1 + +print(f"There were {errors} inference errors out of {i+1} inferences") +results = np.vstack(results) +``` + +## 7. 创建提交 CSV +``` +sub = pd.read_csv("./lmsys-chatbot-arena/sample_submission.csv") + +if len(test)!=VALIDATE: + sub[["winner_model_a","winner_model_b","winner_tie"]] = results + +sub.to_csv("submission.csv",index=False) +sub.head() +``` + +## 8. 计算 CV 分数 +``` +if len(test)==VALIDATE: + true = test[['winner_model_a','winner_model_b','winner_tie']].values + print(true.shape) +``` + +``` +if len(test)==VALIDATE: + from sklearn.metrics import log_loss + print(f"CV loglosss is {log_loss(true,results)}" ) +``` diff --git a/docs/10-tutorials/03-few-shot-w-qwen2-5.md b/docs/10-tutorials/03-few-shot-w-qwen2-5.md new file mode 100644 index 0000000..67b90c4 --- /dev/null +++ b/docs/10-tutorials/03-few-shot-w-qwen2-5.md @@ -0,0 +1,394 @@ +--- +title: 使用 vLLM 加载 AWQ 量化 Qwen2.5-3B-Instruct 进行少样本学习 (Few shot) +--- + +[在线运行此教程](https://openbayes.com/console/hyperai-tutorials/containers/1HFARLMLJXL) + +该教程为在 RTX4090 上使用 vLLM 加载 AWQ 量化 Qwen2.5-3B-Instruct。 + +- 对于每个测试问题,我们使用训练数据检索一组「支持」它的类似问题。 + - 考虑「construct」和「subject」等内容 +- 使用一组类似的问题,我们创建了一个可以馈送到我们的模型的对话 + - 在对话中使用最近支持的 chat() 功能 + - 生成温度略高的 n 个响应,以创建不同的输出 + +- 对于每个问题/答案对,我们现在有 n 个推断的误解,对于每个误解,我们使用 BGE 嵌入检索前 25 个误解。 +- 对于每个问题/答案对的 n 个推断错误中的每一个的 25 个最接近的误解,现在可以使用 Borda Ranking 进行组合,这有点像最简单的集成形式。 + +## 目录 +- [1. 导入相关的库](#1.导入相关的库) +- [2. 加载数据](#2.加载数据) +- [3. 使用 vLLM 启动 Qwen2.5-3B-Instruct-AWQ](#3.使用vLLM启动Qwen2.5-3B-Instruct-AWQ) +- [4. 后处理数据](#4.后处理数据) +- [5. 辅助函数](#5.辅助函数) +- [6. 使用llm.chat](#6.使用llm.chat) +- [7. 找到最相似的误解](#7.找到最相似的误解) +- [8. 提交](#8.提交) + +## 1. 导入相关的库 + +``` +import os +import gc +import ctypes +import numpy as np +import pandas as pd + +from random import sample +from tqdm.auto import tqdm +from eedi_metrics import mapk, apk +from scipy.spatial.distance import cdist +from sklearn.metrics.pairwise import cosine_similarity + +import torch +from vllm import LLM, SamplingParams +from transformers import AutoTokenizer, AutoModel +``` + + +``` +os.environ["CUDA_VISIBLE_DEVICES"] = "0" +os.environ["TOKENIZERS_PARALLELISM"] = "false" + +def clean_memory(deep=False): + gc.collect() + if deep: + ctypes.CDLL("libc.so.6").malloc_trim(0) + torch.cuda.empty_cache() +``` + +## 2. 加载数据 + +``` +k = 3 + +train_eval = True +n_train_eval_rows = 100 + +comp_dir = './eedi-mining-misconceptions-in-mathematics' + +llm_model_pth = '/input0/Qwen2.5-3B-Instruct-AWQ' + +embed_model_pth = '/input0/nomic-embed-text-v1.5' + + +if os.getenv("KAGGLE_IS_COMPETITION_RERUN"): + train_eval = False +``` + +``` +if train_eval: + test = pd.read_csv(f'{comp_dir}/train.csv').sample(n_train_eval_rows, random_state=3) + test = test.sort_values(['QuestionId'], ascending=True).reset_index(drop=True) +else: + test = pd.read_csv(f'{comp_dir}/test.csv') + +train = pd.read_csv(f'{comp_dir}/train.csv') +sample_sub = pd.read_csv(f'{comp_dir}/sample_submission.csv') +misconceptions = pd.read_csv(f'{comp_dir}/misconception_mapping.csv') + +len(train), len(test), len(misconceptions) +``` + +## 3. 使用 vLLM 启动 Qwen2.5-3B-Instruct-AWQ + +如果出现 OOM 错误,将 max_num_seqs减少到 4 或 8 甚至 1 可能会有所帮助(默认值为 256)。 + +``` +llm = LLM( + llm_model_pth, + trust_remote_code=True, + dtype="half", max_model_len=4096, + tensor_parallel_size=1, gpu_memory_utilization=0.95, +) + +tokenizer = llm.get_tokenizer() +``` + +## 4. 后处理数据 + +``` +answer_cols = ["AnswerAText", "AnswerBText", "AnswerCText", "AnswerDText"] +misconception_cols = ["MisconceptionAId", "MisconceptionBId", "MisconceptionCId", "MisconceptionDId"] + +keep_cols = ["QuestionId", "CorrectAnswer", "ConstructName", "SubjectName", "QuestionText" ] + +def wide_to_long(df: pd.DataFrame) -> pd.DataFrame: + + # Melt the answer columns + answers_df = pd.melt( + id_vars=keep_cols, + frame=df[keep_cols + answer_cols], + var_name='Answer', value_name='Value' + ).sort_values(["QuestionId", "Answer"]).reset_index(drop=True) + if misconception_cols[0] not in df.columns: # If test set + return answers_df + + # Melt the misconception columns + misconceptions_df = pd.melt( + id_vars=keep_cols, + frame=df[keep_cols + misconception_cols], + var_name='Misconception', value_name='MisconceptionId' + ).sort_values(["QuestionId", "Misconception"]).reset_index(drop=True) + + answers_df[['Misconception', 'MisconceptionId']] = misconceptions_df[['Misconception', 'MisconceptionId']] + + return answers_df +test = wide_to_long(test) +train = wide_to_long(train) + +test['AnswerId'] = test.Answer.str.replace('Answer', '').str.replace('Text', '') +train['AnswerId'] = train.Answer.str.replace('Answer', '').str.replace('Text', '') + +train = pd.merge(train, misconceptions, on='MisconceptionId', how='left') +if train_eval: + test = pd.merge(test, misconceptions, on='MisconceptionId', how='left') +``` + + +``` +train.head(3) +``` + +``` +test.head(3) +``` + + +## 5. 辅助函数 +### 在给定 subject 和 construct 的情况下获取最相似的 question_ids' + +以下函数首先通过检查结构top_k subject 相似的问题来返回问题 ID 的数量。 + +如果这没有达到top_k,则选择具有相似主题或结构的问题。如果我们仍然缺少问题 ID',我们会为剩余的 top_k 选择随机问题。 + + +``` +def get_topk_similar_rows(question_id: int, construct: str, subject: str, top_k: int) -> list[int]: + """ Gets the top n ids of questions that most similar to the given construct and subject """ + + # Rows with similar construct and subject + similar_cs_rows = train[(train.ConstructName == construct) & (train.SubjectName == subject)] + similar_cs_qids = list(set(similar_cs_rows.QuestionId.values.tolist())) + + if train_eval and question_id in similar_cs_qids: + similar_cs_qids.remove(question_id) + + if len(similar_cs_qids) >= top_k: + k_similar_cs_qids = sample(similar_cs_qids, top_k) + return k_similar_cs_qids + # Rows with similar construct or subject for remainder of top_k + similar_s_rows = train[(train.ConstructName != construct) & (train.SubjectName == subject)] + similar_c_rows = train[(train.ConstructName == construct) & (train.SubjectName != subject)] + similar_c_or_s_qids = list(set(similar_s_rows.QuestionId.values.tolist() + similar_c_rows.QuestionId.values.tolist())) + + if train_eval and question_id in similar_c_or_s_qids: + similar_c_or_s_qids.remove(question_id) + + if len(similar_c_or_s_qids) >= top_k - len(similar_cs_qids): + n_similar_c_or_s_qids = sample(similar_c_or_s_qids, top_k - len(similar_cs_qids)) + return similar_cs_qids + n_similar_c_or_s_qids + # Random rows for remainder of top_k + not_so_similar_rows = train[(train.ConstructName != construct) & (train.SubjectName != subject)] + not_so_similar_rows_qids = list(set(not_so_similar_rows.QuestionId.values.tolist())) + + if train_eval and question_id in not_so_similar_rows_qids: + not_so_similar_rows_qids.remove(question_id) + + n_not_so_similar_rows_qids = sample(not_so_similar_rows_qids, top_k - len(similar_c_or_s_qids)) + return similar_c_or_s_qids + n_not_so_similar_rows_qids +``` + +### 获取每个问题的聊天对话 + + +``` +def get_conversation_msgs(question, correct_ans, incorrect_ans, misconception): + msgs = [ + {'role': 'user', 'content': 'Question: ' + question.strip()}, + {'role': 'assistant', 'content': 'Provide me with the correct answer for a baseline.'}, + {'role': 'user', 'content': 'Correct Answer: ' + correct_ans.strip()}, + {'role': 'assistant', 'content': 'Now provide the incorrect answer and I will anaylze the difference to infer the misconception.'}, + {'role': 'user', 'content': 'Incorrect Answer: ' + incorrect_ans.strip()}, + ] + + if misconception is not None: + msgs += [{'role': 'assistant', 'content': 'Misconception for incorrect answer: ' + misconception}] + + return msgs +``` + +## 6. 使用 llm.chat +注意:llm() 是最近才推出的,仅在后续版本中可用 + +我们生成 n 个输出,使用更高的温度来创建输出的多样化表示,然后可以稍后用于对结果进行排名。 + +``` +sampling_params = SamplingParams( + n=10, # 对于每个提示,返回的输出序列数量。Number of output sequences to return for each prompt. + # top_p=0.5, # 控制考虑的顶部标记的累积概率的浮点数。Float that controls the cumulative probability of the top tokens to consider. + temperature=0.7, # 采样的随机性。randomness of the sampling + seed=1, # +用于可重复性的种子。Seed for reprodicibility + skip_special_tokens=True, # 是否在输出中跳过特殊标记。Whether to skip special tokens in the output. + max_tokens=64, # 每个输出序列生成的最大标记数。Maximum number of tokens to generate per output sequence. + stop=['\n\n', '. '], # 当生成的文本中包含这些字符串时,将停止生成过程的字符串列表。List of strings that stop the generation when they are generated. +) +``` + +``` +submission = [] +for idx, row in tqdm(test.iterrows(), total=len(test)): + + if idx % 50: + clean_memory() + clean_memory() + + if row['CorrectAnswer'] == row['AnswerId']: continue + if train_eval and not row['MisconceptionId'] >= 0: continue + + context_qids = get_topk_similar_rows(row['QuestionId'], row['ConstructName'], row['SubjectName'], k) + correct_answer = test[(test.QuestionId == row['QuestionId']) & (test.CorrectAnswer == test.AnswerId)].Value.tolist()[0] + + messages = [] + for qid in context_qids: + correct_option = train[(train.QuestionId == qid) & (train.CorrectAnswer == train.AnswerId)] + incorrect_options = train[(train.QuestionId == qid) & (train.CorrectAnswer != train.AnswerId)] + for idx, incorrect_option in incorrect_options.iterrows(): + if type(incorrect_option['MisconceptionName']) == str: # Filter out NaNs + messages += get_conversation_msgs( + question = correct_option.QuestionText.tolist()[0], + correct_ans = correct_option.Value.tolist()[0], + incorrect_ans = incorrect_option['Value'], + misconception = incorrect_option['MisconceptionName'], + ) + + # 对话对于错误答案以获取误解的原因。Coversation for Incorrect answer to get misconception for + messages += get_conversation_msgs( + question = row['QuestionText'], + correct_ans = correct_answer, + incorrect_ans = row['Value'], + misconception = None, + ) + + output = llm.chat(messages, sampling_params, use_tqdm=False) + inferred_misconceptions = [imc.text.split(':')[-1].strip() for imc in output[0].outputs] + if not train_eval: + submission.append([f"{row['QuestionId']}_{row['AnswerId']}", inferred_misconceptions]) + else: + submission.append([ + f"{row['QuestionId']}_{row['AnswerId']}", + inferred_misconceptions, + context_qids, + [int(row['MisconceptionId'])], + row['MisconceptionName'] + ]) +submission = pd.DataFrame(submission, columns=['QuestionId_Answer', 'InferredMisconception', 'TopKQuestionIDs', + 'MisconceptionIdGT', 'MisconceptionNameGT'][:len(submission[0])]) + +len(submission) +``` + +``` +submission.head() +``` + +## 7. 找到最相似的误解 + +删除模型并清理内存以加载嵌入模型 +``` +del llm + +clean_memory(deep=True) +clean_memory(deep=True) +``` + +``` +tokenizer = AutoTokenizer.from_pretrained(embed_model_pth, trust_remote_code=True) +embed_model = AutoModel.from_pretrained(embed_model_pth, trust_remote_code=True).to("cuda:0") +``` + +``` +def generate_embeddings(texts, batch_size=8): + all_embeddings = [] + for i in range(0, len(texts), batch_size): + batch_texts = texts[i:i+batch_size] + inputs = tokenizer(batch_texts, padding=True, truncation=True, return_tensors="pt", max_length=1024).to('cuda:0') + with torch.no_grad(): + outputs = embed_model(**inputs) + embeddings = outputs.last_hidden_state[:, 0, :] # CLS token + embeddings = torch.nn.functional.normalize(embeddings, p=2, dim=1) + all_embeddings.append(embeddings.cpu().numpy()) + + return np.concatenate(all_embeddings, axis=0) +``` + +``` +all_ctx_vector = generate_embeddings(list(misconceptions.MisconceptionName.values)) + +all_ctx_vector.shape +``` + +``` +n_results = [] + +for results in tqdm(pd.DataFrame(submission.InferredMisconception.values.tolist()).T.values): + all_text_vector = generate_embeddings(list(results)) + cosine_similarities = cosine_similarity(all_text_vector, all_ctx_vector) + test_sorted_indices = np.argsort(-cosine_similarities, axis=1) + n_results.append(test_sorted_indices) + +n_results = np.array(n_results) +n_results.shape +``` + +``` +n_results = np.transpose(n_results, (1, 0, 2)) +n_results.shape +``` + +### 合并每个问题的每个生成输出的排名 +Borda count 是一种非常简单的排名机制 +``` +def borda_count(rankings): + scores = {} + num_elements = len(next(iter(rankings))) + + for model_ranking in rankings: + for idx, item in enumerate(model_ranking): + points = num_elements - idx + scores[item] = scores.get(item, 0) + points + + # 根据总分排序误解。Sort the misconceptions based on total points + final_ranking = sorted(scores.items(), key=lambda x: x[1], reverse=True) + ranked_results = [r for r, score in final_ranking] + return ranked_results + +# 计算最终排名。Compute the final ranking +final_rankings = np.array([borda_count(result) for result in n_results]) + +final_rankings.shape +``` + +``` +submission['MisconceptionId'] = final_rankings[:, :25].tolist() +``` + + +## 8. 提交 +``` +if train_eval: + submission['apk@25'] = submission.apply(lambda row: apk(row['MisconceptionIdGT'], row['MisconceptionId']), axis=1) + submission.to_csv('submission_debug.csv', index=False) + + print(submission['apk@25'].mean()) +``` + +``` +submission["MisconceptionId"] = submission["MisconceptionId"].apply(lambda x: ' '.join(map(str, x))) +submission[['QuestionId_Answer', 'MisconceptionId']].to_csv('submission.csv', index=False) +``` + +``` +submission.head(25) +``` diff --git a/docs/10-tutorials/04-vllm-langchain-tutorial.md b/docs/10-tutorials/04-vllm-langchain-tutorial.md new file mode 100644 index 0000000..c569c6d --- /dev/null +++ b/docs/10-tutorials/04-vllm-langchain-tutorial.md @@ -0,0 +1,198 @@ +--- +title: 将 LangChain 与 vLLM 结合使用:完整教程 +--- + +[在线运行此教程](https://openbayes.com/console/hyperai-tutorials/containers/ODfeIHjjXbW) + +LangChain 是提供构建复杂操作链的工具,而 vLLM 专注于高效的模型推理。两者结合应用可以简化并加速智能 LLM 应用程序的开发。 + +在本教程中,我们将介绍如何将 LangChain 与 vLLM 结合使用,从设置到分布式推理和量化的所有内容。 + +## 目录 +- [1. 安装和设置 vLLM](#1.安装和设置vLLM) +- [2. 配置 vLLM 以与 LangChain 配合使用](#2.配置vLLM以与LangChain配合使用) +- [3. 使用 LangChain 和 vLLM 创建链](#3.使用LangChain和vLLM创建链) +- [4. 利用多 GPU 推理进行扩展](#4.利用多GPU推理进行扩展) +- [5. 利用量化提高效率](#5.利用量化提高效率) +- [6. 结论](#6.结论) + +## 1. 安装和设置 vLLM + +vLLM 配置要求: + +操作系统: Linux + +Python 版本: Python >= 3.8 + +GPU 要求:计算能力 >= 7.0 的 GPU(例如 V100、T4、RTX20xx、A100、L4、H100)。 + +CUDA 版本: vLLM 使用 CUDA 12.1 编译。请确保您的系统正在运行此版本。 + +如果您没有运行 CUDA 12.1,您可以安装为您的 CUDA 版本编译的 vLLM 版本或将您的 CUDA 升级到版本 12.1。 + +在继续之前,建议执行一些基本检查以确保一切都安装正确。您可以通过运行以下命令来验证 PyTorch 是否与 CUDA 一起使用: + +```python +# Ensure torch is working with CUDA, this should print: True +python -c 'import torch; print(torch.cuda.is_available())' +``` +vLLM 是一个 Python 库,还包含预编译的 C++ 和 CUDA (12.1) 二进制文件。但是,如果您需要 CUDA 11.8,则可以使用以下命令安装兼容版本: + +```python +# Install vLLM with CUDA 11.8 +export VLLM_VERSION=0.6.1.post1 +export PYTHON_VERSION=310 +pip install https://github.com/vllm-project/vllm/releases/download/v${VLLM_VERSION}/vllm-${VLLM_VERSION}+cu118-cp${PYTHON_VERSION}-cp${PYTHON_VERSION}-manylinux1_x86_64.whl --extra-index-url https://download.pytorch.org/whl/cu118 +``` + +### Docker 安装 +对于那些在构建 vLLM 或处理 CUDA 兼容性时遇到问题的人,建议使用 NVIDIA PyTorch Docker 映像。它提供了一个预配置的环境,其中包含正确版本的 CUDA 和其他依赖项: + +```python +# Use `--ipc=host` to ensure the shared memory is sufficient +docker run --gpus all -it --rm --ipc=host nvcr.io/nvidia/pytorch:23.10-py3 +``` +集成过程最终从安装所需的软件包开始。我们建议将 vLLM 升级到最新版本,以避免兼容性问题并受益于最新的改进和功能。 + +```python +pip install --upgrade --quiet vllm -q +pip install langchain langchain_community -q +``` +本教程已经安装 vllm==0.6.4,只需将 langchain 相关包安装完毕。 + +``` +!pip install -U langchain langchain_community -q +``` + + +## 2. 配置 vLLM 以与 LangChain 配合使用 +现在依赖项已安装完毕,我们可以设置 vLLM 并将其连接到 LangChain。为此,我们将从 LangChain 社区集成中导入 VLLM。下面的示例演示了如何使用 vLLM 库初始化模型并将其与 LangChain 集成。 + + +``` +import gc +import ctypes +import torch +def clean_memory(deep=False): + gc.collect() + if deep: + ctypes.CDLL("libc.so.6").malloc_trim(0) + torch.cuda.empty_cache() +``` + +``` +from langchain_community.llms import VLLM + +# Initializing the vLLM model +llm = VLLM( + model="/input0/Qwen2.5-1.5B-Instruct", + trust_remote_code=True, # mandatory for Hugging Face models + max_new_tokens=128, + top_k=10, + top_p=0.95, + temperature=0.8, +) + +# Running a simple query +print(llm.invoke("What are the most popular Halloween Costumes?")) +``` + +以下是使用 vLLM 与 LangChain 时需要考虑的参数列表: + +| 参数名称 | 描述 | +|---------------------|----------------------------------------------------------------------------------------------------| +| 模型 | 要使用的 Hugging Face Transformers 模型的名称或路径。 | +| top_k | 将采样池限制为前 k 个 token,以提高多样性。默认值为 -1。 | +| top_p | 使用累积概率来确定要考虑哪些标记,从而支持更一致的输出。默认值为 1.0。 | +| 信任远程代码 | 允许模型执行远程代码,对某些 Hugging Face 模型有用。默认值为 False。 | +| 温度 | 控制采样的随机性,值越高,输出越多样化。默认值为 1.0。 | +| 最大新令牌数 | 指定每个输出序列生成的最大标记数。默认值为 512。 | +| 回调 | 添加到运行跟踪的回调,对于在生成期间添加日志记录或监控功能很有用。 | +| 标签 | 添加到运行跟踪的标签可以方便进行分类和调试。 | +| tensor_parallel_size | 用于分布式张量并行执行的 GPU 数量。默认值为 1。 | +| 使用光束搜索 | 是否使用集束搜索而不是采样来生成更优化的序列。默认值为 False。 | +| 复制代码 | 保存对 vLLM LLM 调用有效的未明确指定的附加参数。 | + +在此示例中,我们加载 `Qwen2.5-1.5B-Instruct` 模型并配置`max_new_tokens`、`top_k`和 等参数`temperature`。这些设置会影响模型生成文本的方式。 + +## 3. 使用 LangChain 和 vLLM 创建链 + +LangChain 的核心功能之一是能够创建操作链,从而实现更复杂的交互。我们可以轻松地将 vLLM 模型集成到 LLMChain 中,从而提供更大的灵活性。 + + +``` +from langchain.chains import LLMChain +from langchain_core.prompts import PromptTemplate + +# Defining a prompt template for our LLMChain +template = """Question: {question} + +Answer: Let's think step by step.""" +prompt = PromptTemplate.from_template(template) + +# Creating an LLMChain with vLLM +llm_chain = LLMChain(prompt=prompt, llm=llm) + +# Testing the LLMChain +question = "Who was the US president in the year the first Pokemon game was released?" +print(llm_chain.invoke(question)) +``` + +## 4. 利用多 GPU 推理进行扩展 +如果您正在使用本地托管的大型模型,则可能需要利用多个 GPU 进行推理。特别是对于需要同时处理许多请求的高吞吐量系统。vLLM 允许这样做:分布式张量并行推理,以帮助扩展操作。 + +要运行多 GPU 推理,请 tensor_parallel_size 在初始化 VLLM 类时使用该参数。 + +``` +del llm + +clean_memory(deep=True) +``` + +``` +from langchain_community.llms import VLLM + +# Running inference on multiple GPUs +llm = VLLM( + model="/input0/Qwen2.5-1.5B-Instruct", + tensor_parallel_size=1, # using 1 GPUs + trust_remote_code=True, +) + +print(llm.invoke("What is the future of AI?")) +``` + +对于较大的模型,强烈建议使用此方法,因为它的计算量很大,而且在单个 GPU 上运行速度太慢。 + +## 5. 利用量化提高效率 +量化是一种通过减少内存使用和加快计算来提高语言模型性能的有效技术。 + +vLLM 支持 AWQ 量化格式。要启用它,请通过参数传递量化选项 vllm_kwargs。量化允许在资源受限的环境(例如边缘设备或较旧的 GPU)中部署 LLM,而不会牺牲太多准确性。 + +``` +del llm + +clean_memory(deep=True) +``` + +``` +llm_q = VLLM( + model="/input0/Qwen2.5-3B-Instruct-AWQ", + trust_remote_code=True, + max_new_tokens=512, + vllm_kwargs={"quantization": "awq"}, +) +# Running a simple query +print(llm_q.invoke("What are the most popular Halloween Costumes?")) +``` + +在此示例中,Qwen2.5-3B-Instruct-AWQ模型已量化以实现最佳性能。在将应用程序部署到生产环境(成本和资源效率至关重要)时,此功能尤其有用。 + +## 6. 结论 +通过利用分布式 GPU 支持、先进的量化技术和保持 API 兼容性,您可以创建不仅提供卓越性能而且还能灵活满足不同业务需求的系统。 + +当您继续使用 LangChain 和 vLLM 进行大型语言模型研究时,请务必记住,持续优化和监控是实现最佳效率的关键。 + +例如,vLLM 的 CUDA 优化内核和连续批处理策略可以显著减少响应时间。 + +然而,在生产系统中,特别是面向用户的系统(如聊天机器人)中,监控实时推理延迟至关重要。 diff --git a/static/img/docs/02-tutorials/api_path.png b/static/img/docs/02-tutorials/api_path.png new file mode 100644 index 0000000..1cd5b80 Binary files /dev/null and b/static/img/docs/02-tutorials/api_path.png differ diff --git a/static/img/docs/02-tutorials/bangding.png b/static/img/docs/02-tutorials/bangding.png new file mode 100644 index 0000000..dca510c Binary files /dev/null and b/static/img/docs/02-tutorials/bangding.png differ diff --git a/static/img/docs/02-tutorials/curl_res.png b/static/img/docs/02-tutorials/curl_res.png new file mode 100644 index 0000000..604991b Binary files /dev/null and b/static/img/docs/02-tutorials/curl_res.png differ diff --git a/static/img/docs/02-tutorials/curl_res_local.png b/static/img/docs/02-tutorials/curl_res_local.png new file mode 100644 index 0000000..c08e327 Binary files /dev/null and b/static/img/docs/02-tutorials/curl_res_local.png differ diff --git a/static/img/docs/02-tutorials/id.png b/static/img/docs/02-tutorials/id.png new file mode 100644 index 0000000..8a3ccd5 Binary files /dev/null and b/static/img/docs/02-tutorials/id.png differ diff --git a/static/img/docs/02-tutorials/model.png b/static/img/docs/02-tutorials/model.png new file mode 100644 index 0000000..29129c8 Binary files /dev/null and b/static/img/docs/02-tutorials/model.png differ diff --git a/static/img/docs/02-tutorials/res_api.png b/static/img/docs/02-tutorials/res_api.png new file mode 100644 index 0000000..3611119 Binary files /dev/null and b/static/img/docs/02-tutorials/res_api.png differ diff --git a/static/img/docs/02-tutorials/start.png b/static/img/docs/02-tutorials/start.png new file mode 100644 index 0000000..91aee9a Binary files /dev/null and b/static/img/docs/02-tutorials/start.png differ