diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..10b731c --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..e2a5931 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..6a9e3aa --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..504e620 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vllm-r9700-container.iml b/.idea/vllm-r9700-container.iml new file mode 100644 index 0000000..2a15c21 --- /dev/null +++ b/.idea/vllm-r9700-container.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1d94a13 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,172 @@ +FROM registry.fedoraproject.org/fedora:43 + +# 1. System Base & Build Tools +RUN dnf -y install --setopt=install_weak_deps=False --nodocs \ + python3.13 python3.13-devel git rsync libatomic bash ca-certificates curl \ + gcc gcc-c++ binutils make ffmpeg-free \ + cmake ninja-build aria2c tar xz vim nano \ + libdrm-devel zlib-devel openssl-devel jq \ + numactl-devel gperftools-libs procps-ng \ + && dnf clean all && rm -rf /var/cache/dnf/* + +# 2. Install "TheRock" ROCm SDK (Tarball Method) +WORKDIR /tmp +ARG ROCM_MAJOR_VER=7 +ARG GFX=gfx120X-all +RUN set -euo pipefail; \ + BASE="https://therock-nightly-tarball.s3.amazonaws.com"; \ + PREFIX="therock-dist-linux-${GFX}-${ROCM_MAJOR_VER}"; \ + KEY="$(curl -s "${BASE}?list-type=2&prefix=${PREFIX}" \ + | tr '<' '\n' \ + | grep -o "therock-dist-linux-${GFX}-${ROCM_MAJOR_VER}\..*\.tar\.gz" \ + | sort -V | tail -n1)"; \ + echo "Downloading Latest Tarball: ${KEY}"; \ + aria2c -x 16 -s 16 -j 16 --file-allocation=none "${BASE}/${KEY}" -o therock.tar.gz; \ + mkdir -p /opt/rocm; \ + tar xzf therock.tar.gz -C /opt/rocm --strip-components=1; \ + rm therock.tar.gz + +# 3. Configure Global ROCm Environment +RUN export ROCM_PATH=/opt/rocm && \ + BITCODE_PATH=$(find /opt/rocm -type d -name bitcode -print -quit) && \ + printf '%s\n' \ + "export ROCM_PATH=/opt/rocm" \ + "export HIP_PLATFORM=amd" \ + "export HIP_PATH=/opt/rocm" \ + "export HIP_CLANG_PATH=/opt/rocm/llvm/bin" \ + "export HIP_DEVICE_LIB_PATH=$BITCODE_PATH" \ + "export PATH=$ROCM_PATH/bin:$ROCM_PATH/llvm/bin:\$PATH" \ + "export LD_LIBRARY_PATH=$ROCM_PATH/lib:$ROCM_PATH/lib64:$ROCM_PATH/llvm/lib:\$LD_LIBRARY_PATH" \ + "export ROCBLAS_USE_HIPBLASLT=1" \ + "export TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1" \ + "export VLLM_TARGET_DEVICE=rocm" \ + "export HIP_FORCE_DEV_KERNARG=1" \ + "export RAY_EXPERIMENTAL_NOSET_ROCR_VISIBLE_DEVICES=1" \ + "export LD_PRELOAD=/usr/lib64/libtcmalloc_minimal.so.4" \ + > /etc/profile.d/rocm-sdk.sh && \ + chmod 0644 /etc/profile.d/rocm-sdk.sh + +# 4. Python Venv Setup +RUN /usr/bin/python3.13 -m venv /opt/venv +ENV VIRTUAL_ENV=/opt/venv +ENV PATH=/opt/venv/bin:$PATH +ENV PIP_NO_CACHE_DIR=1 +RUN printf 'source /opt/venv/bin/activate\n' > /etc/profile.d/venv.sh +RUN python -m pip install --upgrade pip wheel packaging "setuptools<80.0.0" + +# 5. Install PyTorch (TheRock Nightly) and PyYAML +RUN python -m pip install \ + --index-url https://rocm.nightlies.amd.com/v2-staging/gfx120X-all/ \ + --pre torch torchaudio torchvision && \ + python -m pip install pyyaml + +# Flash-Attention +WORKDIR /opt +ENV FLASH_ATTENTION_TRITON_AMD_ENABLE="TRUE" + +RUN git clone https://github.com/ROCm/flash-attention.git &&\ \ + cd flash-attention &&\ + git checkout main_perf &&\ + python setup.py install && \ + cd /opt && rm -rf /opt/flash-attention + +# 6. Clone vLLM +RUN git clone https://github.com/vllm-project/vllm.git /opt/vllm +WORKDIR /opt/vllm + +# --- PATCHING --- +RUN echo "import sys, re" > patch_vllm.py && \ + echo "from pathlib import Path" >> patch_vllm.py && \ + echo "p = Path('vllm/platforms/__init__.py')" >> patch_vllm.py && \ + echo "txt = p.read_text()" >> patch_vllm.py && \ + echo "txt = txt.replace('import amdsmi', '# import amdsmi')" >> patch_vllm.py && \ + echo "txt = re.sub(r'is_rocm = .*', 'is_rocm = True', txt)" >> patch_vllm.py && \ + echo "txt = re.sub(r'if len\(amdsmi\.amdsmi_get_processor_handles\(\)\) > 0:', 'if True:', txt)" >> patch_vllm.py && \ + echo "txt = txt.replace('amdsmi.amdsmi_init()', 'pass')" >> patch_vllm.py && \ + echo "txt = txt.replace('amdsmi.amdsmi_shut_down()', 'pass')" >> patch_vllm.py && \ + echo "p.write_text(txt)" >> patch_vllm.py && \ + echo "p = Path('vllm/platforms/rocm.py')" >> patch_vllm.py && \ + echo "txt = p.read_text()" >> patch_vllm.py && \ + echo "header = 'import sys\nfrom unittest.mock import MagicMock\nsys.modules[\"amdsmi\"] = MagicMock()\n'" >> patch_vllm.py && \ + echo "txt = header + txt" >> patch_vllm.py && \ + echo "txt = re.sub(r'device_type = .*', 'device_type = \"rocm\"', txt)" >> patch_vllm.py && \ + echo "txt = re.sub(r'device_name = .*', 'device_name = \"gfx1201\"', txt)" >> patch_vllm.py && \ + echo "txt += '\n def get_device_name(self, device_id: int = 0) -> str:\n return \"AMD-gfx1201\"\n'" >> patch_vllm.py && \ + echo "p.write_text(txt)" >> patch_vllm.py && \ + echo "print('Successfully patched vLLM for R9700')" >> patch_vllm.py && \ + python patch_vllm.py + +# 7. Build vLLM (Wheel Method) with CLANG Host Compiler +RUN python -m pip install --upgrade cmake ninja packaging wheel numpy "setuptools-scm>=8" "setuptools<80.0.0" scikit-build-core pybind11 +ENV ROCM_HOME="/opt/rocm" +ENV HIP_PATH="/opt/rocm" +ENV VLLM_TARGET_DEVICE="rocm" +ENV PYTORCH_ROCM_ARCH="gfx1201" +ENV HIP_ARCHITECTURES="gfx1201" +ENV AMDGPU_TARGETS="gfx1201" +ENV MAX_JOBS="4" + +# --- FIX FOR SEGFAULT --- +ENV CC="/opt/rocm/llvm/bin/clang" +ENV CXX="/opt/rocm/llvm/bin/clang++" + +RUN export HIP_DEVICE_LIB_PATH=$(find /opt/rocm -type d -name bitcode -print -quit) && \ + echo "Compiling with Bitcode: $HIP_DEVICE_LIB_PATH" && \ + export CMAKE_PREFIX_PATH="/opt/venv/lib64/python3.13/site-packages/torch/share/cmake:/opt/rocm" && \ + export CMAKE_ARGS="-DROCM_PATH=/opt/rocm -DHIP_PATH=/opt/rocm -DAMDGPU_TARGETS=gfx1201 -DHIP_ARCHITECTURES=gfx1201 -DCMAKE_PREFIX_PATH=/opt/venv/lib64/python3.13/site-packages/torch/share/cmake:/opt/rocm" && \ + python -m pip wheel --no-build-isolation --no-deps -w /tmp/dist -v . && \ + python -m pip install /tmp/dist/*.whl + +# --- bitsandbytes (ROCm) --- +WORKDIR /opt +RUN git clone -b rocm_enabled_multi_backend https://github.com/ROCm/bitsandbytes.git +WORKDIR /opt/bitsandbytes + +# Explicitly set HIP_PLATFORM (Docker ENV, not /etc/profile) +ENV HIP_PLATFORM="amd" +ENV CMAKE_PREFIX_PATH="/opt/rocm" + +# Force CMake to use the System ROCm Compiler (/opt/rocm/llvm/bin/clang++) +RUN cmake -S . \ + -DGPU_TARGETS="gfx1201" \ + -DBNB_ROCM_ARCH="gfx1201" \ + -DCOMPUTE_BACKEND=hip \ + -DCMAKE_HIP_COMPILER=/opt/rocm/llvm/bin/clang++ \ + -DCMAKE_CXX_COMPILER=/opt/rocm/llvm/bin/clang++ \ + && \ + make -j$(nproc) && \ + python -m pip install --no-cache-dir . --no-build-isolation --no-deps + +# 8. Final Cleanup & Runtime +WORKDIR /opt +RUN chmod -R a+rwX /opt && \ + find /opt/venv -type f -name "*.so" -exec strip -s {} + 2>/dev/null || true && \ + find /opt/venv -type d -name "__pycache__" -prune -exec rm -rf {} + && \ + rm -rf /root/.cache/pip || true && \ + dnf clean all && rm -rf /var/cache/dnf/* + +# Create vLLM configuration directory +RUN mkdir -p /etc/vllm + +# Create default YAML configuration file +RUN printf '# model_config.yaml - 多模型配置\n# 默认启动的模型\ndefault: "deepseek_r1_distill_qwen_32b_awq"\n\n# 模型配置\nmodels:\n deepseek_r1_distill_qwen_14b:\n path: "/app/vllm/models/DeepSeek-R1-Distill-Qwen-14B"\n name: "DeepSeek-R1-Distill-Qwen-14B"\n max_model_len: 8192\n gpu_memory_utilization: 0.9\n port: 2001\n dtype: "float16"\n quantization: "awq"\n tensor_parallel_size: 1\n enforce_eager: true\n api_key: "sk-14b-20240101-abcdef123456"\n \n deepseek_r1_distill_qwen_32b_awq:\n path: "/app/vllm/models/DeepSeek-R1-Distill-Qwen-32B-AWQ"\n name: "DeepSeek-R1-Distill-Qwen-32B-AWQ"\n \n # 模型性能参数\n max_model_len: 32768\n gpu_memory_utilization: 0.95\n enforce_eager: true\n max_num_seqs: 2\n max_num_batched_tokens: 1024\n block_size: 16\n tensor_parallel_size: 1\n swap_space: 0\n \n # 新增:采样参数默认值\n sampling_defaults:\n temperature: 0.6\n max_tokens: 4096\n top_p: 0.9\n frequency_penalty: 0.0\n presence_penalty: 0.0\n stop:\n - "用户:" + - "助手:" + - "###" + - "问题:" + - "回答:" + \n # 其他配置\n dtype: "auto"\n quantization: "awq"\n port: 2001\n api_key: "sk-32b-20240101-ghijk789012"\n \n glm_4_7_flash_awq:\n path: "/app/vllm/models/GLM-4.7-Flash-AWQ"\n name: "GLM-4.7-Flash-AWQ"\n \n # 模型性能参数\n max_model_len: 32768\n gpu_memory_utilization: 0.9\n enforce_eager: false\n max_num_seqs: 2\n max_num_batched_tokens: 1024\n block_size: 16\n tensor_parallel_size: 1\n swap_space: 0\n \n # 新增:采样参数默认值\n sampling_defaults:\n temperature: 0.6\n max_tokens: 4096\n top_p: 0.9\n frequency_penalty: 0.0\n presence_penalty: 0.0\n stop:\n - "用户:" + - "助手:" + - "###" + - "问题:" + - "回答:" + \n # 其他配置\n dtype: "auto"\n quantization: "awq"\n port: 2001\n api_key: "sk-32b-20240101-ghijk789012"\n \n qwen3_vl_32b_instruct_awq:\n path: "/app/vllm/models/Qwen3-VL-32B-Instruct-AWQ"\n name: "Qwen3-VL-32B-Instruct-AWQ"\n max_model_len: 32768\n gpu_memory_utilization: 0.7\n port: 2001\n dtype: "auto"\n quantization: "awq"\n tensor_parallel_size: 1\n enforce_eager: true\n api_key: "sk-glm-20240101-lmnop345678"\n\n# 服务器通用设置\nserver:\n host: "0.0.0.0"\n log_level: "info"\n # 全局管理员密钥(拥有所有模型的访问权限)\n admin_key: "sk-admin-20240101-xyz789"\n # 允许的请求头名称(支持多个,按顺序检查)\n api_key_headers: ["Authorization", "X-API-Key", "api-key"]\n # 是否允许通过查询参数传递密钥\n allow_query_param: true\n # 查询参数名称\n api_key_param: "api_key"' > /etc/vllm/model_config.yaml + +# Copy necessary scripts +COPY scripts/01-rocm-envs.sh /etc/profile.d/01-rocm-envs.sh +COPY scripts/99-toolbox-banner.sh /etc/profile.d/99-toolbox-banner.sh +COPY scripts/zz-venv-last.sh /etc/profile.d/zz-venv-last.sh +COPY scripts/start_vllm.py /usr/local/bin/start-vllm +RUN chmod 0644 /etc/profile.d/*.sh && chmod +x /usr/local/bin/start-vllm +RUN printf 'ulimit -S -c 0\n' > /etc/profile.d/90-nocoredump.sh && chmod 0644 /etc/profile.d/90-nocoredump.sh + +CMD ["/bin/bash"] \ No newline at end of file diff --git a/README.md b/README.md index a7892c6..c8c0f71 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,444 @@ -# vllm-r9700-container +# 自定义 vLLM 容器 +这是一个基于 Fedora 的 Docker/Podman 容器,专为在 AMD Radeon R9700 (gfx1201) GPU 上运行 vLLM 而设计。 + +## 特性 + +- 基于 Fedora 43 +- 使用最新的 TheRock ROCm 7.x SDK +- 包含 PyTorch 预发布版本(ROCm 支持) +- 内置 Flash-Attention(ROCm 版本) +- 支持多种大型语言模型 +- 基于配置文件的启动方式 +- 仅支持本地模型(无网络下载功能) +- 多模型配置支持 +- **提供标准的 OpenAI API 服务** + +## 快速开始 + +### 1. 构建并运行(推荐) + +```bash +# 赋予脚本执行权限 +chmod +x build_and_run.sh + +# 一键构建并运行 +./build_and_run.sh +``` + +### 2. 手动构建和运行 + +```bash +# 构建镜像 +docker build -t custom-vllm-r9700:latest . + +# 运行容器 +docker run -it --device /dev/dri --device /dev/kfd \ + --group-add video --group-add render --security-opt seccomp=unconfined \ + -v /path/to/models:/models \ + -v /path/to/model_config.yaml:/etc/vllm/model_config.yaml \ + -e LOCAL_MODEL_DIR=/models \ + custom-vllm-r9700:latest +``` + +## 构建容器 + +在项目目录中运行: + +```bash +docker build -t custom-vllm-r9700:latest . +``` + +## 使用方法 + +### 使用 Docker/Podman + +```bash +docker run -it --device /dev/dri --device /dev/kfd \ + --group-add video --group-add render --security-opt seccomp=unconfined \ + -v /path/to/models:/models \ + -v /path/to/model_config.yaml:/etc/vllm/model_config.yaml \ + -e LOCAL_MODEL_DIR=/models \ + custom-vllm-r9700:latest +``` + +### 使用 Toolbx(Fedora) + +```bash +toolbox create vllm-custom \ + --image custom-vllm-r9700:latest \ + -- --device /dev/dri --device /dev/kfd \ + --group-add video --group-add render --security-opt seccomp=unconfined + +toolbox enter vllm-custom +``` + +### 使用 Distrobox(Ubuntu) + +```bash +distrobox create -n vllm-custom \ + --image custom-vllm-r9700:latest \ + --additional-flags "--device /dev/kfd --device /dev/dri --group-add video --group-add render --security-opt seccomp=unconfined" + +distrobox enter vllm-custom +``` + +## 配置文件 + +容器使用 YAML 格式的配置文件来设置 vLLM 服务器参数。默认配置文件位于 `/etc/vllm/model_config.yaml`。项目根目录中提供了配置文件示例 `model_config.yaml.example`,您可以参考它来创建自己的配置文件。 + +### 配置文件示例 + +```yaml +# model_config.yaml - 多模型配置示例 +# 默认启动的模型 +default: "deepseek_r1_distill_qwen_32b_awq" + +# 模型配置 +models: + deepseek_r1_distill_qwen_14b: + path: "/app/vllm/models/DeepSeek-R1-Distill-Qwen-14B" + name: "DeepSeek-R1-Distill-Qwen-14B" + max_model_len: 8192 + gpu_memory_utilization: 0.9 + port: 2001 + dtype: "float16" + quantization: "awq" + tensor_parallel_size: 1 + enforce_eager: true + api_key: "sk-14b-20240101-abcdef123456" + + deepseek_r1_distill_qwen_32b_awq: + path: "/app/vllm/models/DeepSeek-R1-Distill-Qwen-32B-AWQ" + name: "DeepSeek-R1-Distill-Qwen-32B-AWQ" + + # 模型性能参数 + max_model_len: 32768 + gpu_memory_utilization: 0.95 + enforce_eager: true + max_num_seqs: 2 + max_num_batched_tokens: 1024 + block_size: 16 + tensor_parallel_size: 1 + swap_space: 0 + + # 新增:采样参数默认值 + sampling_defaults: + temperature: 0.6 + max_tokens: 4096 + top_p: 0.9 + frequency_penalty: 0.0 + presence_penalty: 0.0 + stop: + - "用户:" + - "助手:" + - "###" + - "问题:" + - "回答:" + + # 其他配置 + dtype: "auto" + quantization: "awq" + port: 2001 + api_key: "sk-32b-20240101-ghijk789012" + + glm_4_7_flash_awq: + path: "/app/vllm/models/GLM-4.7-Flash-AWQ" + name: "GLM-4.7-Flash-AWQ" + + # 模型性能参数 + max_model_len: 32768 + gpu_memory_utilization: 0.9 + enforce_eager: false + max_num_seqs: 2 + max_num_batched_tokens: 1024 + block_size: 16 + tensor_parallel_size: 1 + swap_space: 0 + + # 新增:采样参数默认值 + sampling_defaults: + temperature: 0.6 + max_tokens: 4096 + top_p: 0.9 + frequency_penalty: 0.0 + presence_penalty: 0.0 + stop: + - "用户:" + - "助手:" + - "###" + - "问题:" + - "回答:" + + # 其他配置 + dtype: "auto" + quantization: "awq" + port: 2001 + api_key: "sk-32b-20240101-ghijk789012" + + qwen3_vl_32b_instruct_awq: + path: "/app/vllm/models/Qwen3-VL-32B-Instruct-AWQ" + name: "Qwen3-VL-32B-Instruct-AWQ" + max_model_len: 32768 + gpu_memory_utilization: 0.7 + port: 2001 + dtype: "auto" + quantization: "awq" + tensor_parallel_size: 1 + enforce_eager: true + api_key: "sk-glm-20240101-lmnop345678" + +# 服务器通用设置 +server: + host: "0.0.0.0" + log_level: "info" + # 全局管理员密钥(拥有所有模型的访问权限) + admin_key: "sk-admin-20240101-xyz789" + # 允许的请求头名称(支持多个,按顺序检查) + api_key_headers: ["Authorization", "X-API-Key", "api-key"] + # 是否允许通过查询参数传递密钥 + allow_query_param: true + # 查询参数名称 + api_key_param: "api_key" +``` + +### 配置参数说明 + +- `default`:默认启动的模型名称 +- `models`:模型配置列表 + - 每个模型包含: + - `path`:模型路径 + - `name`:模型名称 + - `max_model_len`:最大模型上下文长度 + - `gpu_memory_utilization`:GPU 内存利用率 + - `port`:服务器端口 + - `dtype`:数据类型 + - `quantization`:量化方式 + - `tensor_parallel_size`:张量并行度 + - `enforce_eager`:是否强制使用 eager 模式 + - `max_num_seqs`:最大并发请求数 + - `max_num_batched_tokens`:最大批量 tokens 数 + - `block_size`:块大小 + - `swap_space`:交换空间大小 + - `sampling_defaults`:采样参数默认值 + - `api_key`:API 密钥(用于 OpenAI 兼容模式) +- `server`:服务器通用设置 + - `host`:服务器主机地址 + - `log_level`:日志级别 + - `admin_key`:全局管理员密钥 + - `api_key_headers`:允许的请求头名称 + - `allow_query_param`:是否允许通过查询参数传递密钥 + - `api_key_param`:查询参数名称 + +## 环境变量 + +- `LOCAL_MODEL_DIR`:本地模型目录路径(必须设置) +- `VLLM_CONFIG_FILE`:配置文件路径(默认:/etc/vllm/model_config.yaml) + +## 启动 vLLM 服务器 + +### 使用构建和部署脚本(推荐) + +项目提供了 `build_and_run.sh` 脚本,用于一键构建镜像并部署运行: + +```bash +# 赋予执行权限 +chmod +x build_and_run.sh + +# 构建并运行(默认后台运行) +./build_and_run.sh + +# 仅构建镜像 +./build_and_run.sh -b + +# 重新构建镜像(不使用缓存) +./build_and_run.sh -r + +# 仅停止并删除容器 +./build_and_run.sh -s + +# 指定端口运行 +./build_and_run.sh -p 8080 + +# 指定配置文件和模型目录 +./build_and_run.sh -c /path/to/config.yaml -m /path/to/models + +# 交互式运行(前台运行) +./build_and_run.sh -i +``` + +**脚本选项:** + +- `-b, --build-only`:仅构建镜像,不运行容器 +- `-r, --rebuild`:重新构建镜像(不使用缓存) +- `-s, --stop`:仅停止并删除容器 +- `-d, --detach`:后台运行容器(默认) +- `-i, --interactive`:交互式运行容器 +- `-c, --config FILE`:指定配置文件路径 +- `-m, --models DIR`:指定模型目录路径 +- `-p, --port PORT`:指定服务端口 +- `-h, --help`:显示帮助信息 + +### 手动部署 + +```bash +# 1. 构建镜像 +docker build -t custom-vllm-r9700:latest . + +# 2. 运行容器 +docker run -it --device /dev/dri --device /dev/kfd \ + --group-add video --group-add render --security-opt seccomp=unconfined \ + -v /path/to/models:/models \ + -v /path/to/model_config.yaml:/etc/vllm/model_config.yaml \ + -e LOCAL_MODEL_DIR=/models \ + custom-vllm-r9700:latest +``` + +### 容器内运行 + +进入容器后,可以使用以下命令启动 vLLM 服务器: + +```bash +# 使用默认模型启动 +start-vllm + +# 或指定模型名称启动 +start-vllm deepseek_r1_distill_qwen_14b + +# 或直接指定模型路径 +vllm serve /models/model-name --tensor-parallel-size 2 --max-model-len 128000 +``` + +## 测试 API(OpenAI 兼容) + +vLLM 提供与 OpenAI API 完全兼容的服务接口。 + +### 1. 使用 curl 测试 + +```bash +# 聊天补全接口 +curl -X POST http://localhost:8000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer sk-32b-20240101-ghijk789012" \ + -d '{ + "model": "deepseek_r1_distill_qwen_32b_awq", + "messages": [ + {"role": "user", "content": "你好,请介绍一下你自己"} + ], + "temperature": 0.6, + "max_tokens": 4096, + "top_p": 0.9 + }' + +# 文本补全接口 +curl -X POST http://localhost:8000/v1/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer sk-32b-20240101-ghijk789012" \ + -d '{ + "model": "deepseek_r1_distill_qwen_32b_awq", + "prompt": "Once upon a time", + "max_tokens": 100 + }' + +# 列出可用模型 +curl http://localhost:8000/v1/models \ + -H "Authorization: Bearer sk-32b-20240101-ghijk789012" +``` + +### 2. 使用 Python OpenAI SDK + +```python +from openai import OpenAI + +# 初始化客户端 +client = OpenAI( + base_url="http://localhost:8000/v1", + api_key="sk-32b-20240101-ghijk789012" +) + +# 聊天补全 +response = client.chat.completions.create( + model="deepseek_r1_distill_qwen_32b_awq", + messages=[ + {"role": "user", "content": "你好,请介绍一下你自己"} + ], + temperature=0.6, + max_tokens=4096 +) + +print(response.choices[0].message.content) + +# 文本补全 +response = client.completions.create( + model="deepseek_r1_distill_qwen_32b_awq", + prompt="Once upon a time", + max_tokens=100 +) + +print(response.choices[0].text) +``` + +### 3. 使用其他 OpenAI 兼容工具 + +由于提供标准的 OpenAI API,您可以使用任何支持 OpenAI 的工具和库,例如: + +- LangChain +- LlamaIndex +- AutoGen +- FastChat +- 等等 + +只需将 `base_url` 设置为 `http://localhost:8000/v1`,并使用配置的 API 密钥即可。 + +## 本地模型目录结构 + +确保本地模型目录包含以下文件之一: +- `config.json` +- `pytorch_model.bin` +- `model.safetensors` + +正确的目录结构示例: + +``` +/models/ + ├── deepseek_r1_distill_qwen_14b/ + │ ├── config.json + │ └── model.safetensors + ├── deepseek_r1_distill_qwen_32b_awq/ + │ ├── config.json + │ └── pytorch_model.bin + ├── glm_4_7_flash_awq/ + │ ├── config.json + │ └── model.safetensors + └── qwen3_vl_32b_instruct_awq/ + ├── config.json + └── pytorch_model.bin +``` + +## 注意事项 + +- 确保您的 AMD Radeon R9700 GPU 驱动已正确安装 +- 容器需要访问 GPU 设备,因此运行时需要添加 `--device /dev/dri --device /dev/kfd` 参数 +- 首次启动时,vLLM 会编译计算图,可能需要较长时间 +- 如果遇到内存不足的问题,可以调整 `gpu_memory_utilization` 参数 +- API 密钥在配置文件的每个模型中单独配置,用于 OpenAI 兼容模式的认证 + +## OpenAI API 兼容性 + +本容器提供的服务完全兼容 OpenAI API 标准,包括: + +- **聊天补全**:`/v1/chat/completions` +- **文本补全**:`/v1/completions` +- **模型列表**:`/v1/models` +- **嵌入**:`/v1/embeddings`(如支持) + +所有端点都支持标准的 OpenAI 请求格式和参数,您可以无缝切换使用。 + +## 项目文件说明 + +- `Dockerfile` - Docker 镜像构建文件 +- `README.md` - 项目说明文档 +- `model_config.yaml.example` - 配置文件示例 +- `params_config.yaml` - 参数配置文件(定义各类参数的类型和验证规则) +- `build_and_run.sh` - 构建和部署脚本 +- `scripts/start_vllm.py` - vLLM 启动脚本 diff --git a/build_and_run.sh b/build_and_run.sh new file mode 100644 index 0000000..cd205cf --- /dev/null +++ b/build_and_run.sh @@ -0,0 +1,291 @@ +#!/bin/bash + +# vLLM R9700 Container 构建和部署脚本 +# 用于一键构建镜像并部署运行 + +set -e + +# 配置变量 +IMAGE_NAME="custom-vllm-r9700" +IMAGE_TAG="latest" +CONTAINER_NAME="custom-vllm-r9700" +MODEL_DIR="${MODEL_DIR:-/opt/models}" +CONFIG_FILE="${CONFIG_FILE:-/opt/model_config.yaml}" +LOCAL_MODEL_DIR="${LOCAL_MODEL_DIR:-/models}" + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 打印带颜色的消息 +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# 显示使用帮助 +show_usage() { + echo "用法:$0 [选项]" + echo "" + echo "选项:" + echo " -b, --build-only 仅构建镜像,不运行容器" + echo " -r, --rebuild 重新构建镜像(不使用缓存)" + echo " -s, --stop 仅停止并删除容器" + echo " -d, --detach 后台运行容器(默认)" + echo " -i, --interactive 交互式运行容器" + echo " -c, --config FILE 指定配置文件路径" + echo " -m, --models DIR 指定模型目录路径" + echo " -p, --port PORT 指定服务端口" + echo " -h, --help 显示此帮助信息" + echo "" + echo "示例:" + echo " $0 # 构建并运行" + echo " $0 -b # 仅构建镜像" + echo " $0 -r # 重新构建并运行" + echo " $0 -s # 停止并删除容器" + echo " $0 -p 8080 # 使用端口 8080 运行" + echo " $0 -c /path/to/config.yaml # 使用指定配置文件" + echo " $0 -m /path/to/models # 使用指定模型目录" + exit 0 +} + +# 解析命令行参数 +BUILD_ONLY=false +REBUILD=false +STOP_ONLY=false +DETACH=true +CUSTOM_CONFIG="" +CUSTOM_MODELS="" +CUSTOM_PORT="" + +while [[ $# -gt 0 ]]; do + case $1 in + -b|--build-only) + BUILD_ONLY=true + shift + ;; + -r|--rebuild) + REBUILD=true + shift + ;; + -s|--stop) + STOP_ONLY=true + shift + ;; + -d|--detach) + DETACH=true + shift + ;; + -i|--interactive) + DETACH=false + shift + ;; + -c|--config) + CUSTOM_CONFIG="$2" + shift 2 + ;; + -m|--models) + CUSTOM_MODELS="$2" + shift 2 + ;; + -p|--port) + CUSTOM_PORT="$2" + shift 2 + ;; + -h|--help) + show_usage + ;; + *) + print_error "未知选项:$1" + show_usage + ;; + esac +done + +# 停止并删除容器 +stop_container() { + print_info "停止并删除容器..." + + if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + docker stop "${CONTAINER_NAME}" 2>/dev/null || true + print_success "容器已停止" + + docker rm "${CONTAINER_NAME}" 2>/dev/null || true + print_success "容器已删除" + else + print_warning "容器不存在" + fi +} + +# 如果只需要停止容器,直接退出 +if [ "$STOP_ONLY" = true ]; then + stop_container + exit 0 +fi + +# 检查 Docker 是否运行 +check_docker() { + if ! docker info > /dev/null 2>&1; then + print_error "Docker 未运行或无权限访问" + print_error "请确保 Docker 服务正在运行,并且当前用户有 Docker 访问权限" + exit 1 + fi + print_success "Docker 检查通过" +} + +# 构建镜像 +build_image() { + print_info "开始构建 Docker 镜像..." + print_info "镜像名称:${IMAGE_NAME}:${IMAGE_TAG}" + + BUILD_ARGS="--no-cache" + if [ "$REBUILD" = false ]; then + BUILD_ARGS="" + fi + + if docker build ${BUILD_ARGS} -t "${IMAGE_NAME}:${IMAGE_TAG}" .; then + print_success "镜像构建成功" + docker images "${IMAGE_NAME}:${IMAGE_TAG}" --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}" + else + print_error "镜像构建失败" + exit 1 + fi +} + +# 运行容器 +run_container() { + print_info "开始部署容器..." + + # 设置卷挂载参数 + VOLUME_ARGS="" + + # 模型目录 + if [ -n "$CUSTOM_MODELS" ]; then + MODEL_DIR="$CUSTOM_MODELS" + fi + + if [ -d "$MODEL_DIR" ]; then + VOLUME_ARGS="$VOLUME_ARGS -v ${MODEL_DIR}:${LOCAL_MODEL_DIR}" + print_info "模型目录:${MODEL_DIR} -> ${LOCAL_MODEL_DIR}" + else + print_warning "模型目录不存在:${MODEL_DIR}" + print_info "创建模型目录..." + mkdir -p "$MODEL_DIR" + VOLUME_ARGS="$VOLUME_ARGS -v ${MODEL_DIR}:${LOCAL_MODEL_DIR}" + fi + + # 配置文件 + if [ -n "$CUSTOM_CONFIG" ]; then + CONFIG_FILE="$CUSTOM_CONFIG" + fi + + if [ -f "$CONFIG_FILE" ]; then + VOLUME_ARGS="$VOLUME_ARGS -v ${CONFIG_FILE}:/etc/vllm/model_config.yaml" + print_info "配置文件:${CONFIG_FILE}" + else + print_warning "配置文件不存在:${CONFIG_FILE}" + print_info "将使用容器内默认配置" + fi + + # 端口设置 + PORT_ARG="-p 8000:8000" + if [ -n "$CUSTOM_PORT" ]; then + PORT_ARG="-p ${CUSTOM_PORT}:8000" + fi + + # 运行模式 + RUN_MODE="-d" + if [ "$DETACH" = false ]; then + RUN_MODE="-it" + fi + + # 构建运行命令 + print_info "启动容器..." + + docker run ${RUN_MODE} \ + --name "${CONTAINER_NAME}" \ + --device /dev/dri \ + --device /dev/kfd \ + --group-add video \ + --group-add render \ + --security-opt seccomp=unconfined \ + ${VOLUME_ARGS} \ + ${PORT_ARG} \ + -e LOCAL_MODEL_DIR="${LOCAL_MODEL_DIR}" \ + "${IMAGE_NAME}:${IMAGE_TAG}" + + if [ $? -eq 0 ]; then + print_success "容器启动成功" + + if [ "$DETACH" = true ]; then + echo "" + print_info "容器信息:" + echo " 容器名称:${CONTAINER_NAME}" + echo " 镜像:${IMAGE_NAME}:${IMAGE_TAG}" + echo " 状态:运行中" + echo " API 地址:http://localhost:8000" + echo "" + print_info "常用命令:" + echo " 查看日志:docker logs -f ${CONTAINER_NAME}" + echo " 停止容器:docker stop ${CONTAINER_NAME}" + echo " 启动容器:docker start ${CONTAINER_NAME}" + echo " 删除容器:docker rm -f ${CONTAINER_NAME}" + echo " 进入容器:docker exec -it ${CONTAINER_NAME} /bin/bash" + fi + else + print_error "容器启动失败" + exit 1 + fi +} + +# 主流程 +main() { + echo "==========================================" + echo "vLLM R9700 Container 构建和部署" + echo "==========================================" + echo "开始时间:$(date '+%Y-%m-%d %H:%M:%S')" + echo "" + + # 检查 Docker + check_docker + + # 停止旧容器 + stop_container + + # 构建镜像 + build_image + + # 如果仅构建,退出 + if [ "$BUILD_ONLY" = true ]; then + print_success "镜像构建完成" + print_info "运行以下命令启动容器:" + echo " $0" + exit 0 + fi + + # 运行容器 + run_container + + echo "" + echo "==========================================" + print_success "部署完成" + echo "==========================================" + echo "完成时间:$(date '+%Y-%m-%d %H:%M:%S')" +} + +# 执行主流程 +main diff --git a/model_config.yaml.example b/model_config.yaml.example new file mode 100644 index 0000000..2b2a37b --- /dev/null +++ b/model_config.yaml.example @@ -0,0 +1,110 @@ +# model_config.yaml - 多模型配置示例 +# 默认启动的模型 +default: "deepseek_r1_distill_qwen_32b_awq" + +# 模型配置 +models: + deepseek_r1_distill_qwen_14b: + path: "/app/vllm/models/DeepSeek-R1-Distill-Qwen-14B" + name: "DeepSeek-R1-Distill-Qwen-14B" + max_model_len: 8192 + gpu_memory_utilization: 0.9 + port: 2001 + dtype: "float16" + quantization: "awq" + tensor_parallel_size: 1 + enforce_eager: true + api_key: "sk-14b-20240101-abcdef123456" + + deepseek_r1_distill_qwen_32b_awq: + path: "/app/vllm/models/DeepSeek-R1-Distill-Qwen-32B-AWQ" + name: "DeepSeek-R1-Distill-Qwen-32B-AWQ" + + # 模型性能参数 + max_model_len: 32768 + gpu_memory_utilization: 0.95 + enforce_eager: true + max_num_seqs: 2 + max_num_batched_tokens: 1024 + block_size: 16 + tensor_parallel_size: 1 + swap_space: 0 + + # 新增:采样参数默认值 + sampling_defaults: + temperature: 0.6 + max_tokens: 4096 + top_p: 0.9 + frequency_penalty: 0.0 + presence_penalty: 0.0 + stop: + - "用户:" + - "助手:" + - "###" + - "问题:" + - "回答:" + + # 其他配置 + dtype: "auto" + quantization: "awq" + port: 2001 + api_key: "sk-32b-20240101-ghijk789012" + + glm_4_7_flash_awq: + path: "/app/vllm/models/GLM-4.7-Flash-AWQ" + name: "GLM-4.7-Flash-AWQ" + + # 模型性能参数 + max_model_len: 32768 + gpu_memory_utilization: 0.9 + enforce_eager: false + max_num_seqs: 2 + max_num_batched_tokens: 1024 + block_size: 16 + tensor_parallel_size: 1 + swap_space: 0 + + # 新增:采样参数默认值 + sampling_defaults: + temperature: 0.6 + max_tokens: 4096 + top_p: 0.9 + frequency_penalty: 0.0 + presence_penalty: 0.0 + stop: + - "用户:" + - "助手:" + - "###" + - "问题:" + - "回答:" + + # 其他配置 + dtype: "auto" + quantization: "awq" + port: 2001 + api_key: "sk-32b-20240101-ghijk789012" + + qwen3_vl_32b_instruct_awq: + path: "/app/vllm/models/Qwen3-VL-32B-Instruct-AWQ" + name: "Qwen3-VL-32B-Instruct-AWQ" + max_model_len: 32768 + gpu_memory_utilization: 0.7 + port: 2001 + dtype: "auto" + quantization: "awq" + tensor_parallel_size: 1 + enforce_eager: true + api_key: "sk-glm-20240101-lmnop345678" + +# 服务器通用设置 +server: + host: "0.0.0.0" + log_level: "info" + # 全局管理员密钥(拥有所有模型的访问权限) + admin_key: "sk-admin-20240101-xyz789" + # 允许的请求头名称(支持多个,按顺序检查) + api_key_headers: ["Authorization", "X-API-Key", "api-key"] + # 是否允许通过查询参数传递密钥 + allow_query_param: true + # 查询参数名称 + api_key_param: "api_key" diff --git a/params_config.yaml b/params_config.yaml new file mode 100644 index 0000000..fd3da40 --- /dev/null +++ b/params_config.yaml @@ -0,0 +1,269 @@ +# vLLM 参数配置文件 +# 用于定义和识别 vLLM 服务器的各类参数 + +# 模型参数配置 +model_params: + # 必需参数 + required: + - path # 模型路径 + + # 可选参数 + optional: + - name # 模型名称 + - dtype # 数据类型 (auto, float16, float32, bfloat16) + - quantization # 量化方式 (awq, gptq, squeezellm) + - trust_remote # 是否信任远程代码 + +# 性能参数配置 +performance_params: + # GPU 相关 + gpu: + - tensor_parallel_size # 张量并行度 + - pipeline_parallel_size # 流水线并行度 + - gpu_memory_utilization # GPU 内存利用率 (0.0-1.0) + - swap_space # CPU 交换空间大小 (GB) + - max_num_batched_tokens # 最大批量 tokens 数 + - max_num_seqs # 最大并发请求数 + - num_scheduler_steps # 调度器步数 + + # 内存管理 + memory: + - block_size # 块大小 (8, 16, 32) + - max_model_len # 最大模型上下文长度 + - max_logprobs # 最大 logprobs 数 + - disable_sliding_window # 禁用滑动窗口 + +# 服务参数配置 +server_params: + # 网络配置 + network: + - host # 服务器主机地址 + - port # 服务器端口 + - ssl_keyfile # SSL 密钥文件 + - ssl_certfile # SSL 证书文件 + - ssl_ca_certs # SSL CA 证书 + - ssl_keyfile_password # SSL 密钥密码 + + # API 配置 + api: + - api_key # API 密钥 + - allowed_origins # 允许的源 + - timeout_keep_alive # 保持连接超时时间 + +# 采样参数配置 +sampling_params: + # 温度控制 + - temperature # 温度 (0.0-2.0) + - top_p # 核采样参数 (0.0-1.0) + - top_k # Top-K 采样 + - min_p # 最小概率 + + # 惩罚参数 + - frequency_penalty # 频率惩罚 (-2.0 到 2.0) + - presence_penalty # 存在惩罚 (-2.0 到 2.0) + - repetition_penalty # 重复惩罚 + + # 生成长度 + - max_tokens # 最大生成 tokens 数 + - min_tokens # 最小生成 tokens 数 + - stop # 停止词列表 + - stop_token_ids # 停止 token IDs + + # 其他采样选项 + - seed # 随机种子 + - use_beam_search # 使用束搜索 + - best_of # 束搜索的最佳候选数 + - length_penalty # 长度惩罚 + - early_stopping # 早期停止 + - ignore_eos # 忽略 EOS token + - skip_special_tokens # 跳过特殊 tokens + - spaces_between_special_tokens # 特殊 tokens 之间的空格 + +# 日志参数配置 +logging_params: + - log_level # 日志级别 (debug, info, warning, error) + - log_requests # 是否记录请求 + - log_responses # 是否记录响应 + +# 高级参数配置 +advanced_params: + # 执行模式 + - enforce_eager # 强制使用 eager 模式 + - cuda_graphs # CUDA 图 + - use_v2_block_manager # 使用 V2 块管理器 + + # 注意力后端 + - use_rocm_attn # 使用 ROCm 注意力后端 + - attention_backend # 注意力后端类型 + + # 分布式 + - distributed_executor_backend # 分布式执行器后端 + - ray_workers_use_nsight # Ray workers 使用 nsight + + # 其他 + - load_format # 加载格式 + - download_dir # 下载目录 + - revision # 模型版本 + - code_revision # 代码版本 + - tokenizer_revision # tokenizer 版本 + +# 参数类型映射 +param_types: + # 整数类型 + integer: + - tensor_parallel_size + - pipeline_parallel_size + - max_num_batched_tokens + - max_num_seqs + - block_size + - max_model_len + - max_tokens + - min_tokens + - top_k + - best_of + - seed + - num_scheduler_steps + - max_logprobs + - swap_space + - timeout_keep_alive + + # 浮点数类型 + float: + - gpu_memory_utilization + - temperature + - top_p + - min_p + - frequency_penalty + - presence_penalty + - repetition_penalty + - length_penalty + + # 布尔类型 + boolean: + - trust_remote + - enforce_eager + - use_rocm_attn + - use_beam_search + - early_stopping + - ignore_eos + - skip_special_tokens + - spaces_between_special_tokens + - log_requests + - log_responses + - disable_sliding_window + - use_v2_block_manager + + # 字符串类型 + string: + - path + - name + - dtype + - quantization + - host + - port + - api_key + - ssl_keyfile + - ssl_certfile + - ssl_ca_certs + - ssl_keyfile_password + - log_level + - attention_backend + - load_format + - download_dir + - revision + - code_revision + - tokenizer_revision + + # 列表类型 + list: + - stop + - stop_token_ids + - allowed_origins + +# 参数默认值 +param_defaults: + host: "0.0.0.0" + port: 8000 + dtype: "auto" + gpu_memory_utilization: 0.9 + max_model_len: 8192 + tensor_parallel_size: 1 + max_num_seqs: 256 + block_size: 16 + temperature: 0.7 + top_p: 0.9 + max_tokens: 256 + enforce_eager: false + trust_remote: false + log_level: "info" + +# 参数验证规则 +param_validation: + gpu_memory_utilization: + min: 0.0 + max: 1.0 + + temperature: + min: 0.0 + max: 2.0 + + top_p: + min: 0.0 + max: 1.0 + + min_p: + min: 0.0 + max: 1.0 + + frequency_penalty: + min: -2.0 + max: 2.0 + + presence_penalty: + min: -2.0 + max: 2.0 + + tensor_parallel_size: + min: 1 + max: 8 + + block_size: + allowed_values: [8, 16, 32] + +# 参数分组(用于配置文件组织) +param_groups: + basic: + name: "基础配置" + params: + - path + - name + - port + - api_key + + performance: + name: "性能配置" + params: + - tensor_parallel_size + - gpu_memory_utilization + - max_model_len + - max_num_seqs + - block_size + + sampling: + name: "采样配置" + params: + - temperature + - top_p + - max_tokens + - frequency_penalty + - presence_penalty + - stop + + advanced: + name: "高级配置" + params: + - dtype + - quantization + - enforce_eager + - trust_remote + - swap_space diff --git a/scripts/01-rocm-envs.sh b/scripts/01-rocm-envs.sh new file mode 100644 index 0000000..eada83f --- /dev/null +++ b/scripts/01-rocm-envs.sh @@ -0,0 +1,4 @@ +export TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 +export FLASH_ATTENTION_TRITON_AMD_ENABLE="TRUE" +export VLLM_TARGET_DEVICE=rocm +export VLLM_USE_TRITON_AWQ=1 \ No newline at end of file diff --git a/scripts/99-toolbox-banner.sh b/scripts/99-toolbox-banner.sh new file mode 100644 index 0000000..67085ea --- /dev/null +++ b/scripts/99-toolbox-banner.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# Lightweight banner with machine/GPU and ROCm version (vLLM edition) +# No Triton env sourcing, same info/format as the image/video banner. + +# Only show for interactive shells +case $- in *i*) ;; *) return 0 ;; esac + +oem_info() { + local v="" m="" d lv lm + for d in /sys/class/dmi/id /sys/devices/virtual/dmi/id; do + [[ -r "$d/sys_vendor" ]] && v=$(<"$d/sys_vendor") + [[ -r "$d/product_name" ]] && m=$(<"$d/product_name") + [[ -n "$v" || -n "$m" ]] && break + done + # ARM/SBC fallback + if [[ -z "$v" && -z "$m" && -r /proc/device-tree/model ]]; then + tr -d '\0' /dev/null 2>&1; then + name=$(rocm-smi --showproductname --csv 2>/dev/null | tail -n1 | cut -d, -f2) + [[ -z "$name" ]] && name=$(rocm-smi --showproductname 2>/dev/null | grep -m1 -E 'Product Name|Card series' | sed 's/.*: //') + fi + if [[ -z "$name" ]] && command -v rocminfo >/dev/null 2>&1; then + name=$(rocminfo 2>/dev/null | awk -F': ' '/^[[:space:]]*Name:/{print $2; exit}') + fi + if [[ -z "$name" ]] && command -v lspci >/dev/null 2>&1; then + name=$(lspci -nn 2>/dev/null | grep -Ei 'vga|display|gpu' | grep -i amd | head -n1 | cut -d: -f3-) + fi + # trim + name=$(printf '%s' "$name" | sed -e 's/^[[:space:]]\+//' -e 's/[[:space:]]\+$//' -e 's/[[:space:]]\{2,\}/ /g') + printf '%s\n' "${name:-Unknown AMD GPU}" +} + +rocm_version() { + # Prefer the PyTorch HIP version from the venv, fallback to rocm pkg metadata + local PY="/torch-therock/.venv/bin/python" + [[ -x "$PY" ]] || PY="python" + "$PY" - <<'PY' 2>/dev/null || true +try: + import torch + v = getattr(getattr(torch, "version", None), "hip", "") or "" + if v: + print(v) + else: + raise Exception("no torch.version.hip") +except Exception: + try: + import importlib.metadata as im + try: + print(im.version("_rocm_sdk_core")) + except Exception: + print(im.version("rocm")) + except Exception: + print("") +PY +} + +MACHINE="$(oem_info)" +GPU="$(gpu_name)" +ROCM_VER="$(rocm_version)" + +echo +cat <<'ASCII' + _____ _____ ______ ____ _ _ ___ ______ ___ ___ +| __ \ /\ | __ \| ____/ __ \| \ | | / _ \____ / _ \ / _ \ +| |__) | / \ | | | | |__ | | | | \| | | (_) | / / | | | | | | +| _ / / /\ \ | | | | __|| | | | . ` | \__, | / /| | | | | | | +| | \ \ / ____ \| |__| | |___| |__| | |\ | / / / / | |_| | |_| | +|_| \_\/_/ \_\_____/|______\____/|_| \_| /_/ /_/ \___/ \___/ + + _____ _____ _____ ____ + /\ |_ _| | __ \| __ \ / __ \ + / \ | | | |__) | |__) | | | | + / /\ \ | | | ___/| _ /| | | | + / ____ \ _| |_ | | | | \ \| |__| | + /_/ \_\_____| |_| |_| \_\\____/ + + v L L M +ASCII +echo +printf 'AMD R9700 — vLLM Toolbox (gfx1201, ROCm via TheRock)\n' +[[ -n "$ROCM_VER" ]] && printf 'ROCm nightly: %s\n' "$ROCM_VER" +echo +printf 'Machine: %s\n' "$MACHINE" +printf 'GPU : %s\n\n' "$GPU" +printf 'Repo : https://github.com/kyuz0/amd-r9700-vllm-toolboxes\n' +printf 'Image : docker.io/kyuz0/vllm-therock-gfx1201:latest\n\n' +printf 'Included:\n' +printf ' - %-16s → %s\n' "start-vllm (TUI)" "Interactive launcher: Model select, Multi-GPU & Cache handling" +printf ' - %-16s → %s\n' "vLLM server" "vllm serve meta-llama/Meta-Llama-3.1-8B-Instruct" +printf ' - %-16s → %s\n' "API test" "curl localhost:8000/v1/chat/completions" +echo +printf 'SSH tip: ssh -L 8000:localhost:8000 user@host\n\n' + +unset PROMPT_COMMAND +PS1='\u@\h:\w\$ ' diff --git a/scripts/start_vllm.py b/scripts/start_vllm.py new file mode 100644 index 0000000..a5d8e81 --- /dev/null +++ b/scripts/start_vllm.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +import sys +import os +import json +import yaml +import shutil +import subprocess +from pathlib import Path + +# Add benchmarks dir to path to import config +SCRIPT_DIR = Path(__file__).parent.resolve() +OPT_DIR = Path("/opt") + +# Required environment variable pointing to a local models directory +LOCAL_MODEL_DIR = os.getenv("LOCAL_MODEL_DIR") +if not LOCAL_MODEL_DIR: + print("Error: LOCAL_MODEL_DIR environment variable is required.") + sys.exit(1) + +# Configuration file path +CONFIG_FILE = os.getenv("VLLM_CONFIG_FILE", "/etc/vllm/model_config.yaml") + +# Default configuration +DEFAULT_CONFIG = { + "default": "", + "models": {}, + "server": { + "host": "0.0.0.0", + "log_level": "info" + } +} + +def detect_gpus(): + """Detects AMD GPUs via rocm-smi or /dev/dri.""" + try: + # Try rocm-smi first + res = subprocess.run(["rocm-smi", "--showid", "--csv"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + if res.returncode == 0: + count = res.stdout.count("GPU") + if count > 0: return count + except: pass + + # Fallback to /dev/dri/render* + try: + return len(list(Path("/dev/dri").glob("renderD*"))) + except: + return 1 + +def load_config(): + """Load configuration from YAML file.""" + config = DEFAULT_CONFIG.copy() + + if Path(CONFIG_FILE).exists(): + try: + with open(CONFIG_FILE, "r", encoding="utf-8") as f: + user_config = yaml.safe_load(f) + if user_config: + config.update(user_config) + except Exception as e: + print(f"Warning: Failed to load config file: {e}") + print("Using default configuration.") + else: + print(f"Warning: Config file not found at {CONFIG_FILE}") + print("Using default configuration.") + + return config + +def nuke_vllm_cache(): + """Removes vLLM cache directory to fix potential graph/incompatibility issues.""" + cache = Path.home() / ".cache" / "vllm" + if cache.exists(): + try: + print(f"Clearing vLLM cache at {cache}...", end="", flush=True) + subprocess.run(["rm", "-rf", str(cache)], check=True) + cache.mkdir(parents=True, exist_ok=True) + print(" Done.") + except Exception as e: + print(f" Failed: {e}") + +def get_local_model_path(model_name): + """Get local model path from LOCAL_MODEL_DIR.""" + # Try multiple paths: + # 1) LOCAL_MODEL_DIR/model_name + # 2) case-insensitive match in LOCAL_MODEL_DIR + + # Exact match + candidate_exact = os.path.join(LOCAL_MODEL_DIR, model_name) + if os.path.isdir(candidate_exact): + return candidate_exact + + # Case-insensitive match + try: + for entry in os.listdir(LOCAL_MODEL_DIR): + if entry.lower() == model_name.lower(): + entry_path = os.path.join(LOCAL_MODEL_DIR, entry) + if os.path.isdir(entry_path): + return entry_path + except Exception as e: + print(f"Error searching for model: {e}") + + return None + +def verify_model_path(model_path): + """Verify that the model path contains required files.""" + required = ["config.json", "pytorch_model.bin", "model.safetensors"] + found = any(os.path.isfile(os.path.join(model_path, f)) for f in required) + if not found: + print(f"Error: local model dir {model_path} missing expected files {required}") + return False + return True + +def main(): + # Load configuration + config = load_config() + + # Get model name from command line or use default + model_name = None + if len(sys.argv) > 1: + model_name = sys.argv[1] + + if not model_name: + model_name = config.get("default", "") + if not model_name: + print("Error: Either specify a model name as argument or set 'default' in config.") + sys.exit(1) + + # Get model configuration + model_config = config.get("models", {}).get(model_name) + if not model_config: + print(f"Error: Model '{model_name}' not found in configuration.") + sys.exit(1) + + # Get model path + model_path = model_config.get("path") + if not model_path: + # Fallback to LOCAL_MODEL_DIR if path not specified + model_path = get_local_model_path(model_name) + if not model_path: + print(f"Error: model '{model_name}' not found under LOCAL_MODEL_DIR={LOCAL_MODEL_DIR}") + sys.exit(1) + + # Verify model path + if not verify_model_path(model_path): + sys.exit(1) + + # Detect GPU count + gpu_count = detect_gpus() + + # Get server configuration + server_config = config.get("server", {}) + host = server_config.get("host", "0.0.0.0") + + # Build command + cmd = ["vllm", "serve", model_path] + + # Add server parameters + cmd.extend(["--host", host]) + if "port" in model_config: + cmd.extend(["--port", str(model_config["port"])]) + + # Add API key for OpenAI compatibility + api_key = model_config.get("api_key") + if api_key: + cmd.extend(["--api-key", api_key]) + + # Add model parameters + if "tensor_parallel_size" in model_config: + tp_size = min(model_config["tensor_parallel_size"], gpu_count) + cmd.extend(["--tensor-parallel-size", str(tp_size)]) + + if "max_num_seqs" in model_config: + cmd.extend(["--max-num-seqs", str(model_config["max_num_seqs"])]) + + if "max_model_len" in model_config: + cmd.extend(["--max-model-len", str(model_config["max_model_len"])]) + + if "gpu_memory_utilization" in model_config: + cmd.extend(["--gpu-memory-utilization", str(model_config["gpu_memory_utilization"])]) + + if "dtype" in model_config: + cmd.extend(["--dtype", model_config["dtype"]]) + else: + cmd.extend(["--dtype", "auto"]) + + if "max_num_batched_tokens" in model_config: + cmd.extend(["--max-num-batched-tokens", str(model_config["max_num_batched_tokens"])]) + + if "block_size" in model_config: + cmd.extend(["--block-size", str(model_config["block_size"])]) + + if "swap_space" in model_config: + cmd.extend(["--swap-space", str(model_config["swap_space"])]) + + if model_config.get("enforce_eager", False): + cmd.append("--enforce-eager") + + if model_config.get("trust_remote", False): + cmd.append("--trust-remote-code") + + # Set environment variables + env = os.environ.copy() + + if model_config.get("use_rocm_attn", False): + env["VLLM_V1_USE_PREFILL_DECODE_ATTENTION"] = "1" + env["VLLM_USE_TRITON_FLASH_ATTN"] = "0" + + # Clear cache if requested + if model_config.get("clear_cache", False): + nuke_vllm_cache() + + # Print configuration + print("\n" + "="*60) + print(f" Launching: {model_name}") + print(f" Model Path: {model_path}") + print(f" Host: {host}") + if "port" in model_config: + print(f" Port: {model_config['port']}") + if api_key: + print(f" API Key: {api_key[:8]}...{api_key[-4:]}") + if "tensor_parallel_size" in model_config: + print(f" TP Size: {tp_size}") + if "max_num_seqs" in model_config: + print(f" Max Seqs: {model_config['max_num_seqs']}") + if "max_model_len" in model_config: + print(f" Max Ctx: {model_config['max_model_len']}") + if "gpu_memory_utilization" in model_config: + print(f" GPU Util: {model_config['gpu_memory_utilization']}") + print(f" Command: {' '.join(cmd)}") + print("="*60 + "\n") + + # Launch vLLM server + os.execvpe("vllm", cmd, env) + +if __name__ == "__main__": + main() diff --git a/scripts/zz-venv-last.sh b/scripts/zz-venv-last.sh new file mode 100644 index 0000000..eee3228 --- /dev/null +++ b/scripts/zz-venv-last.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Ensure /opt/venv/bin is first even if ~/.local/bin or ~/.cargo/bin prepend themselves via user dotfiles. + +_venv_path_fix() { + # remove any existing /opt/venv/bin entries, then prepend one + local newpath + newpath="$(printf '%s' "$PATH" | awk -v RS=: -v ORS=: '$0!="/opt/venv/bin"{print}')" + PATH="/opt/venv/bin:${newpath%:}" +} + +# run once after shell init; don't duplicate +case "$PROMPT_COMMAND" in + *_venv_path_fix*) : ;; + *) PROMPT_COMMAND="_venv_path_fix${PROMPT_COMMAND:+;$PROMPT_COMMAND}" ;; +esac +