Complete refactor, improved overall functionality
This commit is contained in:
+33
-32
@@ -1,53 +1,54 @@
|
||||
# Base image - Set default to CUDA 11.8.0
|
||||
ARG CUDA_VERSION=11.8.0
|
||||
# Base image - Set default to CUDA 11.8
|
||||
ARG WORKER_CUDA_VERSION=11.8
|
||||
FROM runpod/base:0.4.2-cuda${WORKER_CUDA_VERSION}.0 as builder
|
||||
|
||||
# Use different base images based on CUDA_VERSION argument
|
||||
FROM runpod/base:0.4.2-cuda${CUDA_VERSION} as builder
|
||||
ARG WORKER_CUDA_VERSION=11.8 # Required duplicate to keep in scope
|
||||
|
||||
ENV HF_DATASETS_CACHE="/runpod-volume/huggingface-cache/datasets" \
|
||||
# Set Environment Variables
|
||||
ENV WORKER_CUDA_VERSION=${WORKER_CUDA_VERSION} \
|
||||
HF_DATASETS_CACHE="/runpod-volume/huggingface-cache/datasets" \
|
||||
HUGGINGFACE_HUB_CACHE="/runpod-volume/huggingface-cache/hub" \
|
||||
TRANSFORMERS_CACHE="/runpod-volume/huggingface-cache/hub"
|
||||
TRANSFORMERS_CACHE="/runpod-volume/huggingface-cache/hub"
|
||||
|
||||
# Install Python dependencies (Worker Template)
|
||||
|
||||
# Install Python dependencies
|
||||
COPY builder/requirements.txt /requirements.txt
|
||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
python3.11 -m pip install --upgrade pip && \
|
||||
python3.11 -m pip install --upgrade -r /requirements.txt --no-cache-dir && \
|
||||
python3.11 -m pip install --upgrade -r /requirements.txt && \
|
||||
rm /requirements.txt
|
||||
|
||||
# Install specific packages based on CUDA version
|
||||
# RUN if [ "$CUDA_VERSION" == "12.1.0" ]; then \
|
||||
# python3.11 -m pip install vllm==0.2.3; \
|
||||
# else \
|
||||
# python3.11 -m pip install https://github.com/vllm-project/vllm/releases/download/v0.2.4/vllm-0.2.4+cu118-cp311-cp311-manylinux1_x86_64.whl; \
|
||||
# fi
|
||||
# Install torch and vllm based on CUDA version
|
||||
RUN if [[ "${WORKER_CUDA_VERSION}" == 11.8* ]]; then \
|
||||
wget https://github.com/alpayariyak/vllm/releases/download/0.2.4-runpod-11.8/vllm-0.2.4+cu118-cp311-cp311-linux_x86_64.whl && \
|
||||
python3.11 -m pip install vllm-0.2.4+cu118-cp311-cp311-linux_x86_64.whl && \
|
||||
rm vllm-0.2.4+cu118-cp311-cp311-linux_x86_64.whl; \
|
||||
python3.11 -m pip uninstall torch -y; \
|
||||
python3.11 -m pip install torch --upgrade --index-url https://download.pytorch.org/whl/cu118; \
|
||||
python3.11 -m pip uninstall xformers -y; \
|
||||
python3.11 -m pip install --upgrade xformers --index-url https://download.pytorch.org/whl/cu118; \
|
||||
else \
|
||||
python3.11 -m pip install -e git+https://github.com/alpayariyak/vllm.git#egg=vllm; \
|
||||
fi && \
|
||||
rm -rf /root/.cache/pip
|
||||
|
||||
|
||||
# Add source files
|
||||
ADD src .
|
||||
COPY src .
|
||||
|
||||
# Setup for Option 2: Building the Image with the Model included
|
||||
ARG MODEL_NAME=""
|
||||
ARG MODEL_BASE_PATH="/runpod-volume/"
|
||||
ARG HF_TOKEN=""
|
||||
ARG QUANTIZATION=""
|
||||
|
||||
# Conditionally run download_model.py
|
||||
RUN if [ -n "$MODEL_NAME" ]; then \
|
||||
export HF_TOKEN=$HF_TOKEN; \
|
||||
python3.11 /download_model.py --model $MODEL_NAME --download_dir $MODEL_BASE_PATH; \
|
||||
export MODEL_NAME=$MODEL_NAME; \
|
||||
export MODEL_BASE_PATH=$MODEL_BASE_PATH; \
|
||||
fi
|
||||
|
||||
RUN if [ -n "$QUANTIZATION" ]; then \
|
||||
python3.11 /download_model.py --model $MODEL_NAME --download_dir $MODEL_BASE_PATH; \
|
||||
export MODEL_BASE_PATH=$MODEL_BASE_PATH; \
|
||||
export MODEL_NAME=$MODEL_NAME; \
|
||||
fi && \
|
||||
if [ -n "$QUANTIZATION" ]; then \
|
||||
export QUANTIZATION=$QUANTIZATION; \
|
||||
fi
|
||||
|
||||
RUN mkdir inference_engine && \
|
||||
cd inference_engine && \
|
||||
git clone https://github.com/alpayariyak/vllm.git && \
|
||||
cd vllm && \
|
||||
python3.11 -m pip install -e . && \
|
||||
cd ../..
|
||||
|
||||
# Start the handler
|
||||
CMD ["python3.11", "/handler.py"]
|
||||
CMD ["python3.11", "/handler.py"]
|
||||
@@ -11,35 +11,40 @@
|
||||
|
||||
## Setting up the Serverless Worker
|
||||
|
||||
### Option 1: Use Pre-Built Image
|
||||
We now offer a pre-built Docker Image for the vLLM Worker that you can configure entirely with Environment Variables when creating the RunPod Serverless Endpoint: `runpod/worker-vllm`
|
||||
#### Environment Variables
|
||||
Required:
|
||||
- `MODEL_NAME`: the Hugging Face model to use.
|
||||
|
||||
Optional:
|
||||
- `MODEL_BASE_PATH`: directory to store the model in
|
||||
- `HF_TOKEN`: your Hugging Face token to access private or gated models, such as Llama, Falcon, etc.
|
||||
- `NUM_GPU_SHARD`: Number of GPUs to split the model across.
|
||||
- `QUANTIZATION`: `awq` to use AWQ Quantization (Base model must be in AWQ format). `squeezellm` for SqueezeLLM quantization - preliminary
|
||||
- `VLLM_N_CPUS`: due to Serverless Endpoints having CPU-burst enabled, multi-gpu might not work correctly unless the number of CPUs is limited. It is set to 10 by default.
|
||||
- `CONCURRENCY_MODIFIER`: limit of concurrent requests per worker.
|
||||
- `DEFAULT_BATCH_SIZE`: default batch size for token streaming to reduce the number of http calls and speed up streaming. Defaults to 10.
|
||||
- `DISABLE_LOG_STATS`: set to True or False to enable/disable vLLM stats logging.
|
||||
### Option 1:Deploy Any Model Using Pre-Built Docker Image
|
||||
We now offer a pre-built Docker Image for the vLLM Worker that you can configure entirely with Environment Variables when creating the RunPod Serverless Endpoint:
|
||||
|
||||
### Option 2: Build Image with Model Inside
|
||||
<div align="center">
|
||||
|
||||
```runpod/worker-vllm```
|
||||
|
||||
</div>
|
||||
|
||||
#### Environment Variables
|
||||
- **Required**:
|
||||
- `MODEL_NAME`: Hugging Face Model Repository (e.g., `openchat/openchat_3.5`).
|
||||
|
||||
- **Optional**:
|
||||
- `MODEL_BASE_PATH`: Model storage directory (default: `/runpod-volume`).
|
||||
- `HF_TOKEN`: Hugging Face token for private and gated models (e.g., Llama, Falcon).
|
||||
- `NUM_GPU_SHARD`: Number of GPUs to split the model across (default: `1`).
|
||||
- `QUANTIZATION`: AWQ (`awq`) or SqueezeLLM (`squeezellm`) quantization.
|
||||
- `MAX_CONCURRENCY`: Max concurrent requests (default: `100`).
|
||||
- `DEFAULT_BATCH_SIZE`: Token streaming batch size (default: `10`). This reduces the number of HTTP calls, increasing speed 8-10x vs non-batching, matching non-streaming performance.
|
||||
- `DISABLE_LOG_STATS`: Enable (`False`) or disable (`True`) vLLM stats logging.
|
||||
|
||||
### Option 2: Build Docker Image with Model Inside
|
||||
To build an image with the model baked in, you must specify the following docker arguments when building the image:
|
||||
|
||||
Required:
|
||||
- `MODEL_NAME`
|
||||
- `MODEL_BASE_PATH`
|
||||
- **Required**
|
||||
- `MODEL_NAME`
|
||||
- **Optional**
|
||||
- `MODEL_BASE_PATH`: Defaults to `/runpod-volume` for network storage. Use `/models` or for local container storage.
|
||||
- `QUANTIZATION`
|
||||
- `HF_TOKEN`
|
||||
- `WORKER_CUDA_VERSION`: `11.8` or `12.1` (default: `11.8` due to a small amount of workers not having CUDA 12.1 support yet. `12.1` is recommended for optimal performance).
|
||||
|
||||
Optional:
|
||||
- `QUANTIZATION`
|
||||
- `HF_TOKEN`
|
||||
- `CUDA_VERSION`: 11.8.0 or 12.1.0. Defaults to 11.8.0
|
||||
|
||||
#### Example: OpenChat-3.5
|
||||
#### Example: Building an image with OpenChat-3.5
|
||||
`sudo docker build -t username/image:tag --build-arg MODEL_NAME="openchat/openchat_3.5" --build-arg MODEL_BASE_PATH="/models" .`
|
||||
|
||||
### Compatible Models
|
||||
@@ -75,7 +80,6 @@ Ensure that you have Docker installed and properly set up before running the doc
|
||||
| sampling_params | dict | {} | Sampling parameters to control the generation, like temperature, top_p, etc. |
|
||||
| streaming | bool | False | Whether to enable streaming of output. If True, responses are streamed as they are generated. |
|
||||
| batch_size | int | DEFAULT_BATCH_SIZE | The number of responses to generate in one batch. Only applicable
|
||||
| count_tokens | bool | False | Whether to return the number of input and output tokens at the end
|
||||
|
||||
### Sampling Parameters
|
||||
| Argument | Type | Default | Description |
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
DEFAULT_BATCH_SIZE = 10
|
||||
|
||||
MAX_CONCURRENCY = 100
|
||||
|
||||
sampling_param_types = {
|
||||
'n': int,
|
||||
'best_of': int,
|
||||
'presence_penalty': float,
|
||||
'frequency_penalty': float,
|
||||
'repetition_penalty': float,
|
||||
'temperature': float,
|
||||
'top_p': float,
|
||||
'top_k': int,
|
||||
'min_p': float,
|
||||
'use_beam_search': bool,
|
||||
'length_penalty': float,
|
||||
'early_stopping': [bool, str],
|
||||
'stop': [str, list],
|
||||
'stop_token_ids': list,
|
||||
'ignore_eos': bool,
|
||||
'max_tokens': int,
|
||||
'logprobs': int,
|
||||
'prompt_logprobs': int,
|
||||
'skip_special_tokens': bool,
|
||||
'spaces_between_special_tokens': bool,
|
||||
}
|
||||
+20
-39
@@ -1,70 +1,51 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
from typing import Generator
|
||||
import runpod
|
||||
from utils import validate_and_convert_sampling_params, intialize_llm_engine
|
||||
from utils import validate_and_convert_sampling_params, initialize_llm_engine, JobManager, ServerlessConfig
|
||||
from vllm.utils import random_uuid
|
||||
|
||||
# Default batch size, configurable via environment variable set in the Endpoint Template
|
||||
DEFAULT_BATCH_SIZE = int(os.environ.get('DEFAULT_BATCH_SIZE', 10))
|
||||
serverless_config = ServerlessConfig()
|
||||
job_manager = JobManager()
|
||||
llm = initialize_llm_engine()
|
||||
|
||||
# Initialize the vLLM engine
|
||||
llm = intialize_llm_engine()
|
||||
def concurrency_modifier() -> int:
|
||||
return max(0, serverless_config.max_concurrency - job_manager.total_running_jobs)
|
||||
|
||||
async def handler(job: dict) -> Generator[str, None, None]:
|
||||
"""
|
||||
Asynchronous Generator Handler for the vLLM worker.
|
||||
|
||||
Args:
|
||||
job (dict): A dictionary containing job details, including the prompt and other parameters.
|
||||
|
||||
Yields:
|
||||
Generator[str, None, None]: A generator that yields generated text outputs. Format: List[str]
|
||||
"""
|
||||
# Extract the job inputs
|
||||
async def handler(job: dict) -> Generator[dict, None, None]:
|
||||
job_input = job["input"]
|
||||
prompt = job_input["prompt"]
|
||||
streaming = job_input.get("streaming", False)
|
||||
batch_size = job_input.get("batch_size", DEFAULT_BATCH_SIZE)
|
||||
batch_size = job_input.get("batch_size", serverless_config.default_batch_size)
|
||||
sampling_params = job_input.get("sampling_params", {})
|
||||
return_token_counts = job_input.get("count_tokens", False)
|
||||
|
||||
# Validate and convert sampling parameters
|
||||
|
||||
validated_params = validate_and_convert_sampling_params(sampling_params)
|
||||
|
||||
# Generate a unique request ID
|
||||
request_id = random_uuid()
|
||||
|
||||
# Initialize the vLLM generator
|
||||
results_generator = llm.generate(prompt, validated_params, request_id)
|
||||
last_output_text = ""
|
||||
batch = []
|
||||
job_manager.increment_job_count()
|
||||
|
||||
# Process and yield the generated text
|
||||
batch, last_output_text = [], ""
|
||||
async for request_output in results_generator:
|
||||
for output in request_output.outputs:
|
||||
usage = {"input": len(request_output.prompt_token_ids), "output": len(output.token_ids)}
|
||||
|
||||
if streaming:
|
||||
batch.append({"text": output.text[len(last_output_text):]})
|
||||
batch.append({"text": output.text[len(last_output_text):], "usage": usage})
|
||||
if len(batch) >= batch_size:
|
||||
yield batch
|
||||
batch = []
|
||||
last_output_text = output.text
|
||||
|
||||
if not streaming:
|
||||
yield [{"text":last_output_text}]
|
||||
yield [{"text": last_output_text, "usage": usage}]
|
||||
|
||||
if batch and streaming:
|
||||
if batch:
|
||||
yield batch
|
||||
|
||||
if return_token_counts and request_output is not None:
|
||||
token_counts = {"token_counts":{"input": len(request_output.prompt_token_ids),
|
||||
"output": len(output.outputs[-1].token_ids)}}
|
||||
yield token_counts
|
||||
|
||||
# Start the serverless worker
|
||||
job_manager.decrement_job_count()
|
||||
|
||||
runpod.serverless.start({
|
||||
"handler": handler,
|
||||
"concurrency_modifier": lambda _: int(os.environ.get('CONCURRENCY_MODIFIER', 100)),
|
||||
"concurrency_modifier": concurrency_modifier,
|
||||
"return_aggregate_stream": True
|
||||
})
|
||||
})
|
||||
|
||||
+69
-81
@@ -1,99 +1,87 @@
|
||||
import os
|
||||
from typing import Any, Dict, Optional, Union
|
||||
from vllm import AsyncLLMEngine, AsyncEngineArgs, SamplingParams
|
||||
from constants import sampling_param_types, DEFAULT_BATCH_SIZE, MAX_CONCURRENCY
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
class ServerlessConfig:
|
||||
def __init__(self):
|
||||
self._max_concurrency = int(os.environ.get('MAX_CONCURRENCY', DEFAULT_BATCH_SIZE))
|
||||
self._default_batch_size = int(os.environ.get('DEFAULT_BATCH_SIZE', MAX_CONCURRENCY))
|
||||
|
||||
@property
|
||||
def max_concurrency(self):
|
||||
return self._max_concurrency
|
||||
|
||||
@property
|
||||
def default_batch_size(self):
|
||||
return self._default_batch_size
|
||||
|
||||
class EngineConfig:
|
||||
"""
|
||||
Configuration for the vLLM engine.
|
||||
"""
|
||||
def __init__(self, make_dirs=True):
|
||||
self.model_name = os.getenv('MODEL_NAME')
|
||||
if self.model_name is None:
|
||||
raise ValueError("MODEL_NAME environment variable is not set")
|
||||
|
||||
def __init__(self):
|
||||
self.model_name = os.getenv('MODEL_NAME', 'default_model')
|
||||
self.tokenizer = os.getenv('TOKENIZER', self.model_name)
|
||||
self.model_base_path = os.getenv('MODEL_BASE_PATH', "/runpod-volume/")
|
||||
self.num_gpu_shard = int(os.getenv('NUM_GPU_SHARD', 1))
|
||||
self.use_full_metrics = os.getenv('USE_FULL_METRICS', 'True') == 'True'
|
||||
self.quantization = str(os.getenv('QUANTIZATION', None)).lower()
|
||||
self.quantization = self.quantization if self.quantization in ['squeezellm', 'awq'] else None
|
||||
self.quantization = os.getenv('QUANTIZATION', None)
|
||||
self.dtype = "auto" if self.quantization is None else "half"
|
||||
self.disable_log_stats = os.getenv('DISABLE_LOG_STATS', 'True') == 'True'
|
||||
self.gpu_memory_utilization = float(os.getenv('GPU_MEMORY_UTILIZATION', 0.98))
|
||||
if make_dirs and not os.path.exists(self.model_base_path):
|
||||
os.makedirs(self.model_base_path)
|
||||
|
||||
def intialize_llm_engine():
|
||||
"""
|
||||
Initialize the vLLM engine.
|
||||
|
||||
Returns:
|
||||
AsyncLLMEngine: vLLM AsyncLLMEngine
|
||||
"""
|
||||
# Load the configuration for the vLLM engine
|
||||
config = EngineConfig()
|
||||
|
||||
engine_args = AsyncEngineArgs(
|
||||
model=config.model_name,
|
||||
download_dir=config.model_base_path,
|
||||
tokenizer=config.tokenizer,
|
||||
tensor_parallel_size=config.num_gpu_shard,
|
||||
dtype=config.dtype,
|
||||
disable_log_stats=config.disable_log_stats,
|
||||
quantization=config.quantization,
|
||||
gpu_memory_utilization=config.gpu_memory_utilization,
|
||||
)
|
||||
|
||||
# Create the asynchronous vLLM engine
|
||||
return AsyncLLMEngine.from_engine_args(engine_args)
|
||||
os.makedirs(self.model_base_path, exist_ok=True)
|
||||
|
||||
|
||||
# Map of parameter names to their expected types
|
||||
sampling_param_types = {
|
||||
'n': int,
|
||||
'best_of': int,
|
||||
'presence_penalty': float,
|
||||
'frequency_penalty': float,
|
||||
'temperature': float,
|
||||
'top_p': float,
|
||||
'top_k': int,
|
||||
'use_beam_search': bool,
|
||||
'stop': str,
|
||||
'ignore_eos': bool,
|
||||
'max_tokens': int,
|
||||
'logprobs': float,
|
||||
}
|
||||
|
||||
|
||||
# Function to convert sampling parameters to the right types
|
||||
def cast_sampling_param(value, target_type):
|
||||
"""
|
||||
Args:
|
||||
value: The value to cast
|
||||
target_type: The target type to cast to
|
||||
|
||||
Returns:
|
||||
The casted value if it can be casted, otherwise None
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
def initialize_llm_engine() -> AsyncLLMEngine:
|
||||
try:
|
||||
return target_type(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
config = EngineConfig()
|
||||
engine_args = AsyncEngineArgs(
|
||||
model=config.model_name,
|
||||
download_dir=config.model_base_path,
|
||||
tokenizer=config.tokenizer,
|
||||
tensor_parallel_size=config.num_gpu_shard,
|
||||
dtype=config.dtype,
|
||||
disable_log_stats=config.disable_log_stats,
|
||||
quantization=config.quantization,
|
||||
gpu_memory_utilization=config.gpu_memory_utilization,
|
||||
)
|
||||
return AsyncLLMEngine.from_engine_args(engine_args)
|
||||
except Exception as e:
|
||||
logging.error(f"Error initializing vLLM engine: {e}")
|
||||
raise
|
||||
|
||||
class JobManager:
|
||||
def __init__(self):
|
||||
self.total_running_jobs = 0
|
||||
|
||||
# Function to validate and convert sampling parameters
|
||||
def validate_and_convert_sampling_params(sampling_params):
|
||||
"""
|
||||
Args:
|
||||
sampling_params: The sampling parameters to validate and convert
|
||||
def increment_job_count(self):
|
||||
self.total_running_jobs += 1
|
||||
|
||||
Returns:
|
||||
The validated and converted sampling parameters
|
||||
"""
|
||||
def decrement_job_count(self):
|
||||
self.total_running_jobs -= 1
|
||||
|
||||
def validate_and_convert_sampling_params(params: Dict[str, Any]) -> SamplingParams:
|
||||
validated_params = {}
|
||||
for param_name, param_type in sampling_param_types.items():
|
||||
param_value = sampling_params.get(param_name)
|
||||
if param_value is not None:
|
||||
validated_params[param_name] = cast_sampling_param(param_value, param_type)
|
||||
return SamplingParams(**validated_params)
|
||||
|
||||
for key, value in params.items():
|
||||
expected_type = sampling_param_types.get(key)
|
||||
if value is None:
|
||||
validated_params[key] = None
|
||||
continue
|
||||
|
||||
if expected_type is None:
|
||||
continue
|
||||
|
||||
if not isinstance(expected_type, tuple):
|
||||
expected_type = (expected_type,)
|
||||
|
||||
try:
|
||||
validated_params[key] = next(
|
||||
casted_value for t in expected_type
|
||||
if (casted_value := t(value)) or True
|
||||
)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
|
||||
return SamplingParams(**validated_params)
|
||||
|
||||
Reference in New Issue
Block a user