语言大模型


语言大模型

概念

  • 基于海量文本数据训练的深度学习模型,能够理解和生成自然语言
  • 每个词都是向量,语义相近的词,向量也较接近
  • 根据概率,依次生成下一个词

开始部署

下载

环境

# NVIDIA显卡,查看驱动版本
nvidia-smi
# 查看CUDA版本,没有就去NVIDIA官网安装CUDA
nvcc --version
# 查看CUDA版本对应的pytorch版本
https://pytorch.org/get-started/locally
# 安装
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu126
pip install transformers
# 验证
print(f"PyTorch版本: {torch.__version__}")
print(f"CUDA是否可用: {torch.cuda.is_available()}")

使用

pip install accelerate
# 环境
python:3.13
accelerate:1.13.0
torch:2.10.0+cu126
tokenizers:0.22.2
transformers:5.3.0
peft:0.18.1
datasets:4.8.2
# demo
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM

# 装了模型所有文件的上级文件夹路径
model_name = "./model"
# 加载分词器
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 加载模型AutoModelForCausalLM会自动识别模型类型并加载对应的因果语言模型
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    dtype=torch.float16,
    device_map="auto"
)
if "cuda" in str(model.device):
    print(f"模型运行在GPU上: {model.device}")
    print(f"显存使用量: {torch.cuda.memory_allocated() / 1024 ** 3} GB")
else:
    print("模型运行在CPU上")
# 准备输入
text = "星期四要吃什么?"
inputs = tokenizer(text, return_tensors="pt").to(model.device)
# 生成,禁用梯度计算
with torch.no_grad():
    outputs = model.generate(
        **inputs,
        num_return_sequences=3,
        max_new_tokens=999,  # 最大生成token数量
        do_sample=True,      # 使用采样而不是贪婪解码
        temperature=0.5,     # 采样温度,控制随机性
        top_p=0.9            # 核采样参数,控制生成多样性
    )
# 解码并打印结果
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(generated_text)

LoRA微调

微调

# lora微调
import torch
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    TrainingArguments,
    Trainer,
    DataCollatorForLanguageModeling
)
from peft import LoraConfig, get_peft_model, TaskType
from datasets import Dataset
# 加载lora权重,PeftModel.from_pretrained(base_model, "./lora_qwen_output/checkpoint-24")
from peft import PeftModel
# 查看训练日志:tensorboard --logdir ./logs
import tensorboard

# 模型路径
model_name_or_path = "./model"
# 训练输出目录
output_dir = "./lora_qwen_output"

# 配置Lora超参数
lora_config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,    # 因果语言模型任务
    r=8,
    lora_alpha=32,
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],     # 要应用LoRA的模块
    lora_dropout=0.1,                # Dropout概率
    bias="none"                      # 不训练bias
)

# 训练参数
epochs = 3                 # 训练轮数
batch_size = 1             # 每GPU批次大小
gradient_accumulation_steps = 4  # 梯度累积步数
learning_rate = 2e-4       # 学习率
max_length = 512           # 输入最大长度
logging_steps = 10         # 日志打印间隔
save_steps = 50            # 每多少步保存一次

training_args = TrainingArguments(
    output_dir=output_dir,
    num_train_epochs=epochs,
    max_steps=120,
    per_device_train_batch_size=batch_size,
    gradient_accumulation_steps=gradient_accumulation_steps,
    learning_rate=learning_rate,
    logging_steps=logging_steps,
    logging_dir="./logs",
    save_steps=save_steps,
    save_total_limit=2,                    # 只保留最近2个checkpoint
    fp16=True,                             # 使用混合精度训练(与模型半精度一致)
    remove_unused_columns=False,           # 保留所有列,因为collator需要"input_ids"等
    report_to="tensorboard",               # 不报告到外部工具(如wandb),保持精简
)

tokenizer = AutoTokenizer.from_pretrained(
    model_name_or_path,
    trust_remote_code=True
)
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token

# 训练数据
dialogs = []
for i in range(5):
    dialogs.append({
        "messages": [
            {"role": "user", "content": "你好!我是秦始皇!"},
            {"role": "assistant", "content": "陛下!"}
        ]
    })
    dialogs.append({
        "messages": [
            {"role": "user", "content": "什么是语言大模型?"},
            {"role": "assistant", "content": "就是接龙"}
        ]
    })
    dialogs.append({
        "messages": [
            {"role": "user", "content": "阿姆罗的宿敌是谁?"},
            {"role": "assistant", "content": "夏亚"}
        ]
    })


def format_dialog(example):
    text = tokenizer.apply_chat_template(
        example["messages"],
        tokenize=False,              # 不进行tokenize,只返回字符串
        add_generation_prompt=False  # 训练时不添加生成提示,让模型学习生成assistant部分
    )
    return {"text": text}


# 创建HuggingFace Dataset对象
dataset = Dataset.from_list(dialogs)
dataset = dataset.map(format_dialog, remove_columns=["messages"])


# 对文本进行tokenize
def tokenize_function(examples):
    return tokenizer(
        examples["text"],
        truncation=True,
        max_length=max_length,
        padding=False,            # 不需要padding,DataCollator会动态处理
        return_tensors=None       # 返回list,由collator处理
    )


dataset = dataset.map(tokenize_function, batched=True, remove_columns=["text"])
# 此时dataset包含"input_ids"和"attention_mask"字段

# 以半精度加载模型
model = AutoModelForCausalLM.from_pretrained(
    model_name_or_path,
    dtype=torch.float16,        # 半精度,显存减半
    device_map=None,
    trust_remote_code=True
)
model = model.cuda()

# 加载lora权重
# model = PeftModel.from_pretrained(model, "./lora_qwen_output/checkpoint-27")
# 启用梯度检查点(可选,进一步节省显存,但会减慢速度)
# model.gradient_checkpointing_enable()

# 将Lora应用到模型上
model = get_peft_model(model, lora_config)
# 打印可训练参数数量,确认Lora生效
model.print_trainable_parameters()

# 创建数据收集器 因果语言建模的数据收集器,会自动对批次进行padding,并创建labels(与input_ids相同)
data_collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer,
    mlm=False,            # 不是掩码语言模型,而是因果LM
)

# 开始训练
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=dataset,
    data_collator=data_collator,
)
# 从头训练
trainer.train(resume_from_checkpoint=None)
# 恢复训练
# trainer.train(resume_from_checkpoint="./lora_qwen_output/checkpoint-114")
# 保存模型
model.save_pretrained(output_dir)
# 保存分词器
tokenizer.save_pretrained(output_dir)
print(f"训练完成!保存到 {output_dir}")

使用

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel

# 基座模型路径
base_model_path = "./model"  # 或本地路径
lora_weights_path = "./lora_qwen_output"  # 你保存LoRA权重的目录

# 加载基座模型和分词器
tokenizer = AutoTokenizer.from_pretrained(base_model_path, trust_remote_code=True)
base_model = AutoModelForCausalLM.from_pretrained(
    base_model_path,
    torch_dtype=torch.float16,
    device_map=None,
    trust_remote_code=True
)
base_model = base_model.cuda()

# 加载LoRA权重并合并或直接使用PeftModel
lora_model = PeftModel.from_pretrained(base_model, lora_weights_path)
# 如果想永久合并权重(变成普通模型):
# merged_model = lora_model.merge_and_unload()


# 推理函数
def generate_response(model, prompt):
    messages = [{"role": "user", "content": prompt}]
    text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
    inputs = tokenizer(text, return_tensors="pt").to(model.device)
    outputs = model.generate(
        **inputs,
        max_new_tokens=128,
        do_sample=True,
        temperature=0.7
    )
    response = tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
    return response


# 测试
test_prompts = [
    "你好!我是秦始皇!",
    # "什么是语言大模型?",
    # "阿姆罗的宿敌是谁?"
]
print("=== 微调前模型 ===")
for p in test_prompts:
    print(f"in: {p}")
    print(f"out: {generate_response(base_model, p)}\n")

print("=== 微调后模型 ===")
for p in test_prompts:
    print(f"in: {p}")
    print(f"out: {generate_response(lora_model, p)}\n")

生产部署

环境

ubuntu-22.04
# 内核:5.15.0-173-generic
# 查看驱动
nvidia-smi
# 如果内核和nvidia驱动不兼容,卸载nvidia驱动
/usr/bin/nvidia-uninstall
# 安装当前内核的头文件(驱动编译时需要)
apt install linux-headers-$(uname -r)
# 添加NVIDIA 驱动仓库
add-apt-repository ppa:graphics-drivers/ppa
# 查看推荐驱动
ubuntu-drivers devices
# 安装
apt install nvidia-driver-580
reboot
# 确认
nvidia-smi
# CUDA Version: 13.0
# 确认 DKMS(更新内核时,自动重新编译)
dkms status | grep nvidia

python环境

# 进入虚拟环境
apt install python3-pip
# 镜像源
pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip3 install torch torchvision
pip install transformers
# 验证
print(f"PyTorch版本: {torch.__version__}")
print(f"CUDA是否可用: {torch.cuda.is_available()}")

运行demo

pip install accelerate
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch


model_name = "./dfdfdf/dfdf/baichuan"
model = AutoModelForCausalLM.from_pretrained(
  model_name,
  device_map="auto",
  dtype=torch.float16,
  trust_remote_code=True
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 2. Input prompt text
prompt = "今天重庆天气怎么样?"
# 3. Encode the input text for the model
messages = [
    {"role": "user", "content": prompt}
]
text = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True,
    thinking_mode='off' # on/off/auto 
)
model_inputs = tokenizer([text], return_tensors="pt").to(model.device)

# 4. Generate text
generated_ids = model.generate(
    **model_inputs,
    max_new_tokens=1024
)
output_ids = [
    output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
][0].tolist()
# 5. parsing thinking content
try:
    # rindex finding 151668 (</think>)
    index = len(output_ids) - output_ids[::-1].index(151668)
except ValueError:
    index = 0

# thinking_content = tokenizer.decode(output_ids[:index], skip_special_tokens=True).strip("")
content = tokenizer.decode(output_ids[index:], skip_special_tokens=True).strip("")

# print("thinking content:", thinking_content)
print(content)

项目搭建

  • 项目结构
    -configs/
    --config.yaml
    -models/
    --base/
    --lora/
    -scripts/
    --demo.py
    -data/
    -logs/
  • 配置文件
    model:
      baichuan:
        base_path: "/xx/xxx/models/Baichuan-M2-32B/base"
        lora_save_path: "/xxx/xxx/models/Baichuan-M2-32B/lora"
    
    data:
      processed_path: "/xx/xxx/data/processed/my_dataset"
    
    training:
      log_dir: "/xxx/xxx/logs"
  • tool
    import yaml
    import torch
    from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline,GenerationConfig
    from langchain_huggingface import HuggingFacePipeline, ChatHuggingFace
    from langchain_openai import ChatOpenAI
    
    def get_config():
      with open('../configs/config.yaml','r') as f:
        return yaml.safe_load(f)
      
    def load_model_baichuan():
        model_name = get_config()['model']['baichuan']['base_path']
        tokenizer = AutoTokenizer.from_pretrained(model_name)
        model = AutoModelForCausalLM.from_pretrained(
            model_name,
            dtype=torch.float16,
            device_map="auto",
            trust_remote_code=True
        )
        if "cuda" in str(model.device):
            print(f"模型运行在GPU上: {model.device}")
            print(f"显存使用量: {torch.cuda.memory_allocated() / 1024 ** 3:.2f} GB")
        else:
            print("模型运行在CPU上")
    
        gen_config = GenerationConfig(
          max_new_tokens=1024,
          max_length=None,    # 显式覆盖该值以消除冲突警告
          do_sample=True,
          temperature=0.2
        )
        # 创建pipeline(支持chat模板模型)
        pipe = pipeline(
            "text-generation",
            model=model,
            tokenizer=tokenizer
        )
        llm_pipeline = HuggingFacePipeline(
          pipeline=pipe,
          model_kwargs={
            "generation_config": gen_config
          }
        )
        return ChatHuggingFace(llm=llm_pipeline)
    
    
    def remote_model():
        base_url = "https://xxx.xxx.cn/v1/"
        api_key = "xxxx"
        model_id = "xxxxc"
        llm = ChatOpenAI(
            model=model_id,  # 对应你调用时的 model
            temperature=0.2,
            top_p=0.9,
            frequency_penalty=0,
            openai_api_key=api_key,
            openai_api_base=base_url,
            max_retries=2,
            timeout=60,
        )
        return llm
  • 使用
    # pip install langchain
    from langchain.agents import create_agent
    from langchain.tools import tool
    import langchain
    from langchain_core.prompts import PromptTemplate
    from pub_tool import load_model_baichuan
    
    # 加载大模型
    llm = load_model_baichuan()
    
    
    # ============   chain   ============
    def simple_chain():
        prompt = PromptTemplate.from_template(
            "请为一个新式 {product} 设计一个有创意、有幽默感的名称,解释这样设计的含义"
        )
        chain = prompt | llm
        result = chain.invoke({"product": "智能水杯"})
        print(result.content)
    
    
    # ============   逐一输出   ============
    def stream_print():
        for chunk in llm.stream("为什么夏亚阿兹纳布尔加入扎比家吉翁军?"):
            for char in chunk.content:
                print(char, end="|", flush=True)
    
    
    simple_agent()

    vllm部署

  • 快速、易于使用的 LLM 推理和服务库
  • 快速部署本地大模型
  • 原生支持 OpenAI 兼容的 API 服务器
  • 内置PagedAttention 显存管理、并发请求调度等功能
    # 安装
    pip install vllm
    # 验证
    python -c "import vllm; print(vllm.__version__)"
    # 编写启动脚本,启动
    ./vllm.sh
  • 启动配置脚本
    # vllm.sh
    
    # ================================
    # vLLM OpenAI 服务器启动脚本
    # ================================
    
    # ---------- 基础模型配置 ----------
    BASE_MODEL_PATH="./xxx"                      # 基础模型路径
    SERVED_MODEL_NAME="xxxx"
    DTYPE="float16"                               # 数据类型 half/float16/bfloat16
    GPU_IDS="0,1,2,3"                             # 使用的 GPU,多卡用逗号分隔
    TENSOR_PARALLEL_SIZE=4                        # 张量并行度
    GPU_MEMORY_UTIL=0.95                          # GPU 显存使用比例
    MAX_MODEL_LEN=2048                            # 最大序列长度
    MAX_NUM_SEQS=2                                # 最大并发请求数
    MAX_NUM_BATCHED_TOKENS=1024                   # 批处理最大 token 数
    
    # ---------- LoRA 配置 ----------
    # 格式:lora_name1=/path/to/lora1 lora_name2=/path/to/lora2
    LORA_MODULES=""
    # 是否启用 LoRA
    ENABLE_LORA=true
    MAX_LORA_RANK=64                               # 根据你的 LoRA rank 设置
    MAX_CPU_LORAS=4                                # 最多在 CPU 缓存几个 LoRA
    
    # ---------- 服务器配置 ----------
    API_KEY="cousin"
    PORT=8000
    LOG_DIR="./logs"
    LOG_FILE="${LOG_DIR}/vllm_$(date +%Y%m%d_%H%M%S).log"
    
    # ---------- 其他额外参数 ----------
    EXTRA_ARGS="--enable-auto-tool-choice --tool-call-parser hermes"
    
    # ---------- 检查基础模型路径 ----------
    if [ ! -d "${BASE_MODEL_PATH}" ]; then
        echo "错误:基础模型路径 '${BASE_MODEL_PATH}' 不存在。"
        exit 1
    fi
    
    # ---------- 构建启动命令 ----------
    CMD="python -m vllm.entrypoints.openai.api_server \
        --model ${BASE_MODEL_PATH} \
        --served-model-name ${SERVED_MODEL_NAME} \
        --dtype ${DTYPE} \
        --api-key ${API_KEY} \
        --port ${PORT} \
        --gpu-memory-utilization ${GPU_MEMORY_UTIL} \
        --max-model-len ${MAX_MODEL_LEN} \
        --max-num-seqs ${MAX_NUM_SEQS} \
        --max-num-batched-tokens ${MAX_NUM_BATCHED_TOKENS}"
    
    # 多卡并行
    if [ ${TENSOR_PARALLEL_SIZE} -gt 1 ]; then
        CMD="${CMD} --tensor-parallel-size ${TENSOR_PARALLEL_SIZE}"
    fi
    
    # LoRA 相关参数
    if [ "${ENABLE_LORA}" = true ] && [ -n "${LORA_MODULES}" ]; then
        # 将空格分隔的多个 "name=path" 拼接到参数中
        CMD="${CMD} --enable-lora --lora-modules ${LORA_MODULES}"
        CMD="${CMD} --max-lora-rank ${MAX_LORA_RANK}"
        CMD="${CMD} --max-cpu-loras ${MAX_CPU_LORAS}"
        echo "LoRA 已启用,模块:${LORA_MODULES}"
    fi
    
    # 额外参数
    if [ -n "${EXTRA_ARGS}" ]; then
        CMD="${CMD} ${EXTRA_ARGS}"
    fi
    
    # ---------- 设置 GPU 环境 ----------
    export CUDA_VISIBLE_DEVICES=${GPU_IDS}
    
    # ---------- 启动信息 ----------
    mkdir -p ${LOG_DIR}
    echo "=========================================="
    echo "启动 vLLM 服务器"
    echo "基础模型: ${BASE_MODEL_PATH}"
    echo "LoRA 模块: ${LORA_MODULES:-无}"
    echo "端口: ${PORT} | API Key: ${API_KEY}"
    echo "GPU: ${GPU_IDS} (并行度 ${TENSOR_PARALLEL_SIZE})"
    echo "显存比例: ${GPU_MEMORY_UTIL} | 最大并发: ${MAX_NUM_SEQS}"
    echo "日志: ${LOG_FILE}"
    echo "=========================================="
    echo "命令:"
    echo "${CMD}"
    echo "=========================================="
    
    # 启动(后台运行)
    nohup ${CMD} >> ${LOG_FILE} 2>&1 &
    echo "vLLM 服务已启动,PID: $!"
  • 客户端调用
    def local_chat_baichuan():
        base_url = "http://localhost:8000/v1"
        api_key = "xxxxx"
        model_id = "xxxxx"
        llm = ChatOpenAI(
            model=model_id,
            temperature=0.2,
            top_p=0.9,
            frequency_penalty=0,
            openai_api_key=api_key,
            openai_api_base=base_url,
            max_retries=2,
            timeout=60,
        )
        return llm
    
    # 加载大模型
    llm = local_chat_baichuan()
    llm.invoke("你是什么大模型")

量化版本部署

transformer部署

环境
pip install gptqmodel # 可能报错:'QuantizeConfig' is not defined,依赖不适配,下载对应版本的gptqmodel:https://github.com/ModelCloud/GPTQModel
# pip install ./gptqmodel-4.2.5+cu12.torch2.7-cp310-cp310-linux_x86_64.whl
pip install optimum
pip install auto-gptq
from transformers import AutoTokenizer, AutoModelForCausalLM, GPTQConfig
import torch
import yaml


def get_config():
  with open('../configs/config.yaml','r') as f:
    return yaml.safe_load(f)


model_name = get_config()['model']['baichuan']['int4_path']
tokenizer = AutoTokenizer.from_pretrained(model_name)
gptq_config = GPTQConfig(
    bits=4,           # 量化位数(Int8=8,Int4=4)
    group_size=128,   # 量化分组大小,默认128
    dataset="c4",     # 量化时使用的数据集(不影响加载,填默认值即可)
    tokenizer=tokenizer,
    trust_remote_code=True
)
model = AutoModelForCausalLM.from_pretrained(
  model_name,
  quantization_config=gptq_config,
  device_map="auto",
  trust_remote_code=True
)

# 2. Input prompt text
prompt = "今天重庆天气怎么样?"
# 3. Encode the input text for the model
messages = [
    {"role": "user", "content": prompt}
]
text = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True,
    thinking_mode='off' # on/off/auto 
)
model_inputs = tokenizer([text], return_tensors="pt").to(model.device)

# 4. Generate text
generated_ids = model.generate(
    **model_inputs,
    max_new_tokens=1024
)
output_ids = [
    output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
][0].tolist()
# 5. parsing thinking content
try:
    # rindex finding 151668 (</think>)
    index = len(output_ids) - output_ids[::-1].index(151668)
except ValueError:
    index = 0

# thinking_content = tokenizer.decode(output_ids[:index], skip_special_tokens=True).strip("")
content = tokenizer.decode(output_ids[index:], skip_special_tokens=True).strip("")

# print("thinking content:", thinking_content)
print(content)

vllm配置

  • 启动脚本
    #!/bin/bash
    
    # ================================
    # vLLM OpenAI 服务器启动脚本
    # ================================
    
    # ---------- 基础模型配置 ----------
    BASE_MODEL_PATH="/xxxx/Baichuan-M2-32B/INT4/"          # 基础模型路径
    SERVED_MODEL_NAME="baichuan"
    DTYPE="auto"                                   # 数据类型 half/float16/bfloat16
    GPU_IDS="2,3"                                    # 使用的 GPU,多卡用逗号分隔
    TENSOR_PARALLEL_SIZE=2                        # 张量并行度
    PIPELINE_PARALLEL_SIZE=1                      # 流水线并行        
    GPU_MEMORY_UTIL=0.95                          # GPU 显存使用比例
    MAX_MODEL_LEN=32768                             # 最大序列长度
    MAX_NUM_SEQS=2                                # 最大并发请求数
    MAX_NUM_BATCHED_TOKENS=2048                    # 批处理最大 token 数
    
    # ---------- LoRA 配置 ----------
    # 格式:lora_name1=/path/to/lora1 lora_name2=/path/to/lora2
    # 如果只有一个 LoRA,按下面示例填写;多个用空格分隔。
    LORA_MODULES=""
    # 是否启用 LoRA(如果上面有值,会自动启用;也可以手动强制 false)
    ENABLE_LORA=true
    MAX_LORA_RANK=64                               # 根据你的 LoRA rank 设置
    MAX_CPU_LORAS=4                                # 最多在 CPU 缓存几个 LoRA
    
    # ---------- 服务器配置 ----------
    API_KEY="xxx"
    PORT=xxxx
    LOG_DIR="/xxxxx"
    LOG_FILE="${LOG_DIR}/vllm_$(date +%Y%m%d_%H%M%S).log"
    
    # ---------- 其他额外参数 ----------
    EXTRA_ARGS="--quantization gptq_marlin --reasoning-parser qwen3 --enable-auto-tool-choice --tool-call-parser hermes"
    # --enforce-eager
    
    # ---------- 检查基础模型路径 ----------
    if [ ! -d "${BASE_MODEL_PATH}" ]; then
        echo "错误:基础模型路径 '${BASE_MODEL_PATH}' 不存在。"
        exit 1
    fi
    
    # ---------- 构建启动命令 ----------
    CMD="python -m vllm.entrypoints.openai.api_server \
        --model ${BASE_MODEL_PATH} \
        --served-model-name ${SERVED_MODEL_NAME} \
        --dtype ${DTYPE} \
        --api-key ${API_KEY} \
        --port ${PORT} \
        --gpu-memory-utilization ${GPU_MEMORY_UTIL} \
        --max-model-len ${MAX_MODEL_LEN} \
        --max-num-seqs ${MAX_NUM_SEQS} \
        --max-num-batched-tokens ${MAX_NUM_BATCHED_TOKENS}"
    
    # 多卡并行
    if [ ${TENSOR_PARALLEL_SIZE} -gt 1 ]; then
        CMD="${CMD} --tensor-parallel-size ${TENSOR_PARALLEL_SIZE}"
    fi
    
    if [ ${PIPELINE_PARALLEL_SIZE} -gt 1 ]; then
        CMD="${CMD} --pipeline-parallel-size ${PIPELINE_PARALLEL_SIZE}"
    fi
    
    # LoRA 相关参数(仅当 ENABLE_LORA=true 且 LORA_MODULES 非空时添加)
    if [ "${ENABLE_LORA}" = true ] && [ -n "${LORA_MODULES}" ]; then
        # 将空格分隔的多个 "name=path" 拼接到参数中
        CMD="${CMD} --enable-lora --lora-modules ${LORA_MODULES}"
        CMD="${CMD} --max-lora-rank ${MAX_LORA_RANK}"
        CMD="${CMD} --max-cpu-loras ${MAX_CPU_LORAS}"
        echo "LoRA 已启用,模块:${LORA_MODULES}"
    fi
    
    # 额外参数
    if [ -n "${EXTRA_ARGS}" ]; then
        CMD="${CMD} ${EXTRA_ARGS}"
    fi
    
    # ---------- 设置 GPU 环境 ----------
    export CUDA_VISIBLE_DEVICES=${GPU_IDS}
    
    # ---------- 打印启动信息 ----------
    mkdir -p ${LOG_DIR}
    echo "=========================================="
    echo "启动 vLLM 服务器"
    echo "基础模型: ${BASE_MODEL_PATH}"
    echo "LoRA 模块: ${LORA_MODULES:-无}"
    echo "端口: ${PORT} | API Key: ${API_KEY}"
    echo "GPU: ${GPU_IDS} (并行度 ${TENSOR_PARALLEL_SIZE})"
    echo "显存比例: ${GPU_MEMORY_UTIL} | 最大并发: ${MAX_NUM_SEQS}"
    echo "日志: ${LOG_FILE}"
    echo "=========================================="
    echo "命令:"
    echo "${CMD}"
    echo "=========================================="
    
    # 启动(后台运行)
    nohup ${CMD} >> ${LOG_FILE} 2>&1 &
    echo "vLLM 服务已启动,PID: $!"

  目录