13 KiB
KServe with Fluid on LLM (Large Language Model) Acceleration
In this example we will show how to use Fluid to improve autoscaling performance on LLM (Large Language Model) in KServe.
DISCLAIMER
This Proof of Concept (PoC) is a collaborative effort between KServe and Fluid communities to integrate KServe with Fluid. The purpose of this PoC is to experiment with the use of Fluid to improve the autoscaling performance on LLMs.
It is important to note that this PoC has not been tested in a production environment and is intended for experimental purposes only. Therefore, if you decide to try it out, please carefully validate it in your use case before deploying it in a production environment.
Context
With the increasing popularity of large language models (LLMs), many companies are now deploying them via KServe. However, due to their typical size ranging from a few GBs to several hundred GBs, it can take a considerable amount of time for the KServe Storage Initializer to pull them from remote storage. As a result, the start-up time for an inference service can take anywhere from a few minutes to over 30 minutes due to the lengthy model downloading and loading times. This makes the serverless autoscaling function of KServe less effective for LLM use cases. To improve performance, it becomes crucial to cache the model artifacts rather than pulling them from remote storage during the autoscaling process. Since large language models are often served on GPU instances, which can be expensive, autoscaling becomes a helpful tool for reducing costs by allowing resources to be scaled up or down based on demand.
Prerequisite
-
-
Please follow this guideline to deploy KServe from
masterbranch -
Enable node selector in KServe InferenceService via Knative
kubectl patch configmap/config-features \ --namespace knative-serving \ --type merge \ --patch '{"data":{"kubernetes.podspec-nodeselector":"enabled", "kubernetes.podspec-tolerations":"enabled"}}'
-
-
-
Deploy Fluid
# Add Fluid repository to Helm repos and keep it up-to-date helm repo add fluid https://fluid-cloudnative.github.io/charts helm repo update # Deploy Fluid with Helm helm upgrade --install fluid --create-namespace --namespace=fluid-system fluid/fluid
-
Prepare Demo Application
The demo application uses KServe to serve a LLM (Large Language Model). In this tutorial Llama3.1-8B-Instruct model is used as an example.
Prerequisite
Checkout KServe repo and go to the example:
git clone https://github.com/kserve/kserve.git
cd kserve/docs/samples/fluid
Prepare Model Artifact
Download model from HuggingFace, Llama3.1-8B-Instruct model is using in this tutorial.
python -m venv .venv
source .venv/bin/activate
pip3 install --upgrade pip
pip3 install "huggingface_hub[hf_transfer]"
# create models folder
mkdir -p models
# to download Llama models, you need accept the Llama 3 community license agreement and set HF_TOKEN
export HF_TOKEN="xxxxxxxx"
HF_HUB_ENABLE_HF_TRANSFER=1 python3 download_model.py --model_name="meta-llama/Meta-Llama-3.1-8B-Instruct" --model_dir="./models"
# export output_dir=models/models--meta-llama--Meta-Llama-3.1-8B-Instruct/snapshots/main
Structure of the model artifact:
❯ tree ${output_dir}
models/models--meta-llama--Meta-Llama-3.1-8B-Instruct/snapshots/main
├── config.json
├── generation_config.json
├── model-00001-of-00004.safetensors
├── model-00002-of-00004.safetensors
├── model-00003-of-00004.safetensors
├── model-00004-of-00004.safetensors
├── model.safetensors.index.json
├── original
│ ├── params.json
│ └── tokenizer.model
├── special_tokens_map.json
├── tokenizer.json
└── tokenizer_config.json
Upload the model to S3 bucket:
# update the path accordingly
aws s3 cp --recursive ${output_dir} s3://${bucket}/models/meta-llama--Meta-Llama-3.1-8B-Instruct
Demo
Let's start our demo.
Create Demo Namespace
kubectl create ns kserve-fluid-demo
Create S3 Credentials Secret And Service Account
# please update the s3 credentials to yours
$ kubectl create -f s3creds.yaml -n kserve-fluid-demo
Create Dataset And Runtime
Create Dataset and Runtime (In this tutorial you will use JindoFS as the runtime, there are other runtimes like JuiceFS and Alluxio etc).
# please update the `mountPoint` and `options`
$ kubectl create -f jindo.yaml -n kserve-fluid-demo
dataset.data.fluid.io/s3-data created
jindoruntime.data.fluid.io/s3-data created
Check the status:
$ kubectl get po -n kserve-fluid-demo
NAME READY STATUS RESTARTS AGE
s3-data-jindofs-master-0 1/1 Running 0 5m18s
s3-data-jindofs-worker-0 1/1 Running 0 3m53s
s3-data-jindofs-worker-1 1/1 Running 0 3m52s
s3-data-jindofs-worker-2 1/1 Running 0 3m52s
$ kubectl get jindoruntime,dataset -n kserve-fluid-demo
NAME MASTER PHASE WORKER PHASE FUSE PHASE AGE
jindoruntime.data.fluid.io/s3-data Ready Ready Ready 9m17s
NAME UFS TOTAL SIZE CACHED CACHE CAPACITY CACHED PERCENTAGE PHASE AGE
dataset.data.fluid.io/s3-data 14.97GiB 12.11MiB 300.00GiB 0.1% Bound 9m19s
A PVC is created to mount the dataset into the application:
$ kubectl get pvc -n kserve-fluid-demo
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
s3-data Bound kserve-fluid-demo-s3-data 100Pi ROX fluid <unset> 9m57s
Preload The Data
Preload the data into workers to improve the performance:
# please update the `path` under `target`
$ kubectl create -f dataload.yaml -n kserve-fluid-demo
Check the dataload status:
$ kubectl get dataload -n kserve-fluid-demo
NAME DATASET PHASE AGE DURATION
s3-dataload s3-data Complete 6m1s 2m51s
$ kubectl get dataset -n kserve-fluid-demo
NAME UFS TOTAL SIZE CACHED CACHE CAPACITY CACHED PERCENTAGE PHASE AGE
s3-data 14.97GiB 44.92GiB 300.00GiB 100.0% Bound 12m
Data are cached successfully.
Deploy Cluster Serving Runtime
From my testing, the safetensors weights are loaded randomly which causing low performance when reading from remote cache, so I modify the huggingfaceserver servingruntime to load the safetensors weights sequentially to warm up the cache before running the application server.
# deploy the custom huggingfaceserver servingruntime
$ kubectl create -f kserve-huggingfaceserver.yaml
Deploy The Inference Service
Create the InferenceService with Fluid:
# please update the `storageUri`.
$ kubectl create -f fluid-isvc.yaml -n kserve-fluid-demo
# run this command if you want to use the KServe Storage Initializer to download from cloud storage, update the `storageUri` as well. Please use the original huggerface server servingruntime.
$ kubectl create -f kserve-isvc.yaml -n kserve-fluid-demo
Test The Demo Application
Follow this guide to get the ingress host and port:
export MODEL_NAME="llama-31-8b-instruct"
export SERVICE_HOSTNAME=$(kubectl get inferenceservice ${MODEL_NAME} -n kserve-fluid-demo -o jsonpath='{.status.url}' | cut -d "/" -f 3)
export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].port}')
$ curl "http://${INGRESS_HOST}:${INGRESS_PORT}/openai/v1/completions" \
-H "content-type: application/json" \
-H "Host: ${SERVICE_HOSTNAME}" \
-d '{"model": "'"$MODEL_NAME"'", "prompt": "Write a poem about colors", "stream":false, "max_tokens": 30}'
Output:
$ curl "http://${INGRESS_HOST}:${INGRESS_PORT}/openai/v1/completions" \
-H "content-type: application/json" \
-H "Host: ${SERVICE_HOSTNAME}" \
-d '{"model": "'"$MODEL_NAME"'", "prompt": "Write a poem about colors", "stream":false, "max_tokens": 30}'
{"id":"4e170959-8112-4a68-9415-4cb10239a2a5","choices":[{"finish_reason":"length","index":0,"logprobs":null,"text":". Many different things in life can be described by colors. Colors evoke emotions and memories. Use sensory details to bring your poem to life.\nSat in"}],"created":1724823045,"model":"llama-31-8b-instruct","system_fingerprint":null,"object":"text_completion","usage":{"completion_tokens":30,"prompt_tokens":6,"total_tokens":36}}
Performance Benchmark
To conduct a performance benchmark, we will compare the scaling time of the inference service with Storage initializer and Fluid across different model artifacts, measuring the time it takes for the service to scale from 0 to 1.
We will use the following machine types for our tests:
- Data: m5n.xlarge (EBS GP3)
- Workload: g5.8xlarge (NVIDIA A10G)
We will use the following Fluid runtimes for our tests:
- JindoFS
For the purposes of our benchmark, we will assume that nodes are readily available and that images are cached in the nodes, so we will not be factoring in node provisioning time or image pulling time.
Other assumptions:
- S3 bucket and Cluster are in the same region (eu-central-1)
- Data are preloaded in workers
- fuse.cleanPolicy: OnDemand
Command to measure the total time:
# Total time includes: pod initialization and running + download model (storage initializer) + load model + inference + network
$ curl --connect-timeout 3600 --max-time 3600 -o /dev/null -s -w 'Total: %{time_total}s\n' -H "Content-Type: application/json" -H "Host: ${SERVICE_HOSTNAME}" "http://${INGRESS_HOST}:${INGRESS_PORT}/openai/v1/completions" -d '{"model": "'"$MODEL_NAME"'", "prompt": "Write a poem about colors", "stream":false, "max_tokens": 30}'
# Total time: 53.037210s, status code: 200
| Model Name | Model Size (Snapshot) | Machine Type | KServe + Storage Initializer | KServe + Fluid(JindoFS) |
|---|---|---|---|---|
| meta-llama/Meta-Llama-3.1-8B-Instruct | 15GB | g5.8xlarge | total: 265.253s | total: 50.111s |
NOTE:
totalis the response time of sending an inference request to a serverless inference service (scale from 0), which includes pod startup, model download, model load, model inference and network time. The above results are for reference only and may vary depending on the environment and the specific use case.
As can be observed, Fluid has demonstrated notable improvements in the autoscaling performance of KServe on LLM.
Limitations
One potential limitation of this integration is the use of an additional Fuse-sidecar, which may lead to increased resource consumption. However, in the use case of LLMs, where the instance size is typically very large and the pod consumes the entire instance, the resource usage of the Fuse-sidecar is expected to be negligible. Nonetheless, it is important to carefully consider the resource impact of the Fuse-sidecar in your specific use case to ensure that it remains within acceptable limits.
Clean Up
kubectl delete -f fluid-isvc.yaml -n kserve-fluid-demo
kubectl delete -f kserve-isvc.yaml -n kserve-fluid-demo
kubectl delete -f dataload.yaml -n kserve-fluid-demo
kubectl delete -f jindo.yaml -n kserve-fluid-demo
kubectl delete -f s3creds.yaml -n kserve-fluid-demo
# uninstall Fluid
helm delete fluid -n fluid-system
kubectl get crd | grep data.fluid.io | awk '{ print $1 }' | xargs kubectl delete crd
kubectl delete ns fluid-system