Skip to content

Commit df2c5fc

Browse files
authored
vLLM Gemma Benchmarks (GPU) (#35809)
* add vLLM benchmarks * update base image version * apply yapf * update vLLM workflow * fix Dockerfile & add sdkLocationOverride flag to gradle * minor adjustments to vLLM gemma benchmark * apply rat,yapf,pylint * rm newline
1 parent 55f80c8 commit df2c5fc

14 files changed

Lines changed: 457 additions & 26 deletions

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# This is a composite action to build and push a Docker image.
2+
name: 'Docker Build and Push'
3+
description: 'Builds and pushes a Docker image to a container registry.'
4+
5+
inputs:
6+
dockerfile_path:
7+
description: 'Path to the Dockerfile'
8+
required: true
9+
image_name:
10+
description: 'Base name for the Docker image (e.g., gcr.io/my-project/my-app)'
11+
required: true
12+
image_tag:
13+
description: 'Tag for the Docker image (e.g., latest, or a git sha)'
14+
required: true
15+
build_context:
16+
description: 'The build context for the Docker build command'
17+
required: false
18+
default: '.'
19+
20+
outputs:
21+
image_url:
22+
description: "The full URL of the pushed image, including the tag"
23+
value: ${{ steps.build-push.outputs.image_url }} # the value is set from a step's output
24+
25+
runs:
26+
using: "composite"
27+
steps:
28+
- name: Configure Docker to use Google Cloud credentials
29+
shell: bash
30+
run: gcloud auth configure-docker --quiet
31+
32+
- name: Build and Push Docker Image
33+
id: build-push # give the step an ID to reference its output
34+
shell: bash
35+
run: |
36+
# Construct the full image URL from the inputs
37+
FULL_IMAGE_URL="${{ inputs.image_name }}:${{ inputs.image_tag }}"
38+
echo "Building image: $FULL_IMAGE_URL"
39+
40+
# Build the image
41+
docker build -t $FULL_IMAGE_URL -f ${{ inputs.dockerfile_path }} ${{ inputs.build_context }}
42+
# Push the image
43+
docker push $FULL_IMAGE_URL
44+
# Set the output value for this action
45+
echo "image_url=$FULL_IMAGE_URL" >> $GITHUB_OUTPUT

.github/workflows/beam_Inference_Python_Benchmarks_Dataflow.yml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
(github.event_name == 'schedule' && github.repository == 'apache/beam') ||
5656
github.event.comment.body == 'Run Inference Benchmarks'
5757
runs-on: [self-hosted, ubuntu-20.04, main]
58-
timeout-minutes: 900
58+
timeout-minutes: 1000
5959
name: ${{ matrix.job_name }} (${{ matrix.job_phrase }})
6060
strategy:
6161
matrix:
@@ -72,7 +72,12 @@ jobs:
7272
- name: Setup Python environment
7373
uses: ./.github/actions/setup-environment-action
7474
with:
75+
java-version: default
7576
python-version: '3.10'
77+
- name: Package Python SDK using Gradle
78+
run: ./gradlew :sdks:python:sdist -PpythonVersion=3.10
79+
- name: Configure Docker for Artifact Registry
80+
run: gcloud auth configure-docker us-docker.pkg.dev
7681
- name: Prepare test arguments
7782
uses: ./.github/actions/test-arguments-action
7883
with:
@@ -86,9 +91,28 @@ jobs:
8691
${{ github.workspace }}/.github/workflows/load-tests-pipeline-options/beam_Inference_Python_Benchmarks_Dataflow_Pytorch_Imagenet_Classification_Resnet_152_Tesla_T4_GPU.txt
8792
${{ github.workspace }}/.github/workflows/load-tests-pipeline-options/beam_Inference_Python_Benchmarks_Dataflow_Pytorch_Sentiment_Streaming_DistilBert_Base_Uncased.txt
8893
${{ github.workspace }}/.github/workflows/load-tests-pipeline-options/beam_Inference_Python_Benchmarks_Dataflow_Pytorch_Sentiment_Batch_DistilBert_Base_Uncased.txt
94+
${{ github.workspace }}/.github/workflows/load-tests-pipeline-options/beam_Inference_Python_Benchmarks_Dataflow_VLLM_Gemma_Batch.txt
8995
# The env variables are created and populated in the test-arguments-action as "<github.job>_test_arguments_<argument_file_paths_index>"
9096
- name: get current time
9197
run: echo "NOW_UTC=$(date '+%m%d%H%M%S' --utc)" >> $GITHUB_ENV
98+
- name: Build VLLM Development Image
99+
id: build_vllm_image
100+
uses: ./.github/actions/build-push-docker-action
101+
with:
102+
dockerfile_path: 'sdks/python/apache_beam/ml/inference/test_resources/vllm.dockerfile'
103+
image_name: 'us-docker.pkg.dev/apache-beam-testing/beam-temp/beam-vllm-gpu-base'
104+
image_tag: ${{ github.sha }}
105+
- name: Run VLLM Gemma Batch Test
106+
uses: ./.github/actions/gradle-command-self-hosted-action
107+
timeout-minutes: 180
108+
with:
109+
gradle-command: :sdks:python:apache_beam:testing:load_tests:run
110+
arguments: |
111+
-PloadTest.mainClass=apache_beam.testing.benchmarks.inference.vllm_gemma_benchmarks \
112+
-Prunner=DataflowRunner \
113+
-PsdkLocationOverride=false \
114+
-PpythonVersion=3.10 \
115+
-PloadTest.requirementsTxtFile=apache_beam/ml/inference/vllm_tests_requirements.txt '-PloadTest.args=${{ env.beam_Inference_Python_Benchmarks_Dataflow_test_arguments_8 }} --mode=batch --job_name=benchmark-tests-vllm-with-gemma-2b-it-batch-${{env.NOW_UTC}} --sdk_container_image=${{ steps.build_vllm_image.outputs.image_url }}'
92116
- name: run Pytorch Sentiment Streaming using Hugging Face distilbert-base-uncased model
93117
uses: ./.github/actions/gradle-command-self-hosted-action
94118
timeout-minutes: 180
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to You under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
--runner=DataflowRunner
17+
--region=us-central1
18+
--temp_location=gs://temp-storage-for-perf-tests/loadtests
19+
--staging_location=gs://temp-storage-for-perf-tests/loadtests
20+
--input=gs://apache-beam-ml/testing/inputs/sentences_50k.txt
21+
--machine_type=n1-standard-8
22+
--worker_zone=us-central1-b
23+
--disk_size_gb=50
24+
--input_options={}
25+
--num_workers=8
26+
--max_num_workers=25
27+
--autoscaling_algorithm=THROUGHPUT_BASED
28+
--publish_to_big_query=true
29+
--sdk_location=container
30+
--output_table=apache-beam-testing.beam_run_inference.result_gemma_vllm_batch
31+
--metrics_dataset=beam_run_inference
32+
--metrics_table=gemma_vllm_batch
33+
--influx_measurement=gemma_vllm_batch
34+
--model_gcs_path=gs://apache-beam-ml/models/gemma-2b-it
35+
--dataflow_service_options=worker_accelerator=type:nvidia-tesla-t4;count:1;install-nvidia-driver
36+
--experiments=use_runner_v2

.test-infra/tools/refresh_looker_metrics.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
("80", ["253", "254", "255", "256", "257"]), # PyTorch Resnet 152 Tesla T4
4343
("82", ["263", "264", "265", "266", "267"]), # PyTorch Sentiment Streaming DistilBERT base uncased
4444
("85", ["268", "269", "270", "271", "272"]), # PyTorch Sentiment Batch DistilBERT base uncased
45+
("86", ["284", "285", "286", "287", "288"]), # VLLM Batch Gemma
4546
]
4647

4748

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
from __future__ import annotations
19+
20+
import logging
21+
import os
22+
import tempfile
23+
24+
import apache_beam as beam
25+
from apache_beam.io.filesystems import FileSystems
26+
from apache_beam.ml.inference.base import RunInference
27+
from apache_beam.ml.inference.vllm_inference import VLLMCompletionsModelHandler
28+
from apache_beam.ml.inference.vllm_inference import _VLLMModelServer
29+
from apache_beam.options.pipeline_options import PipelineOptions
30+
from apache_beam.options.pipeline_options import SetupOptions
31+
32+
33+
class GemmaVLLMOptions(PipelineOptions):
34+
"""Custom pipeline options for the Gemma vLLM batch inference job."""
35+
@classmethod
36+
def _add_argparse_args(cls, parser):
37+
parser.add_argument(
38+
"--input",
39+
dest="input_file",
40+
required=True,
41+
help="Input file gs://path containing prompts.",
42+
)
43+
parser.add_argument(
44+
"--output_table",
45+
required=True,
46+
help="BigQuery table to write to in the form project:dataset.table.",
47+
)
48+
parser.add_argument(
49+
"--model_gcs_path",
50+
required=True,
51+
help="GCS path to the directory containing model files.",
52+
)
53+
54+
55+
class FormatOutput(beam.DoFn):
56+
def process(self, element):
57+
prompt = element.example
58+
comp = element.inference
59+
60+
if hasattr(comp, 'choices'):
61+
completion = comp.choices[0].text
62+
# fallback to a single .text field
63+
elif hasattr(comp, 'text'):
64+
completion = comp.text
65+
# final fallback
66+
else:
67+
completion = str(comp)
68+
69+
yield {'prompt': prompt, 'completion': completion}
70+
71+
72+
class GcsVLLMCompletionsModelHandler(VLLMCompletionsModelHandler):
73+
def __init__(self, model_name, vllm_server_kwargs=None):
74+
super().__init__(model_name, vllm_server_kwargs)
75+
self._local_model_dir = None
76+
77+
def _download_gcs_directory(self, gcs_path: str, local_path: str):
78+
logging.info("Downloading model from %s to %s…", gcs_path, local_path)
79+
matches = FileSystems.match([os.path.join(gcs_path, "**")])[0].metadata_list
80+
for md in matches:
81+
rel = os.path.relpath(md.path, gcs_path)
82+
dst = os.path.join(local_path, rel)
83+
os.makedirs(os.path.dirname(dst), exist_ok=True)
84+
with FileSystems.open(md.path) as src, open(dst, "wb") as dstf:
85+
dstf.write(src.read())
86+
logging.info("Download complete.")
87+
88+
def load_model(self) -> _VLLMModelServer:
89+
uri = self._model_name
90+
if uri.startswith("gs://"):
91+
self._local_model_dir = tempfile.mkdtemp(prefix="vllm_model_")
92+
self._download_gcs_directory(uri, self._local_model_dir)
93+
logging.info("Loading vLLM from local dir %s", self._local_model_dir)
94+
return _VLLMModelServer(self._local_model_dir, self._vllm_server_kwargs)
95+
else:
96+
logging.info("Loading vLLM from HF hub: %s", uri)
97+
return super().load_model()
98+
99+
100+
def run(argv=None, save_main_session=True, test_pipeline=None):
101+
# Build pipeline options
102+
opts = PipelineOptions(argv)
103+
104+
gem = opts.view_as(GemmaVLLMOptions)
105+
opts.view_as(SetupOptions).save_main_session = save_main_session
106+
107+
logging.info("Pipeline starting with model path: %s", gem.model_gcs_path)
108+
handler = GcsVLLMCompletionsModelHandler(
109+
model_name=gem.model_gcs_path,
110+
vllm_server_kwargs={"served-model-name": gem.model_gcs_path})
111+
112+
with (test_pipeline or beam.Pipeline(options=opts)) as p:
113+
_ = (
114+
p
115+
| "Read" >> beam.io.ReadFromText(gem.input_file)
116+
| "InferBatch" >> RunInference(handler, inference_batch_size=32)
117+
| "FormatForBQ" >> beam.ParDo(FormatOutput())
118+
| "WriteToBQ" >> beam.io.WriteToBigQuery(
119+
gem.output_table,
120+
schema="prompt:STRING,completion:STRING",
121+
write_disposition=beam.io.BigQueryDisposition.WRITE_APPEND,
122+
create_disposition=beam.io.BigQueryDisposition.CREATE_IF_NEEDED,
123+
method=beam.io.WriteToBigQuery.Method.FILE_LOADS,
124+
))
125+
return p.result
126+
127+
128+
if __name__ == "__main__":
129+
logging.getLogger().setLevel(logging.INFO)
130+
run()

sdks/python/apache_beam/ml/inference/test_resources/vllm.dockerfile

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,33 +15,54 @@
1515
# limitations under the License.
1616

1717
# Used for any vLLM integration test
18+
# Dockerfile — Beam dev harness + install dev SDK from LOCAL source package
1819

1920
FROM nvidia/cuda:12.4.1-devel-ubuntu22.04
2021

21-
RUN apt update
22-
RUN apt install software-properties-common -y
23-
RUN add-apt-repository ppa:deadsnakes/ppa
24-
RUN apt update
22+
# 1) Non-interactive + timezone
23+
ENV DEBIAN_FRONTEND=noninteractive \
24+
TZ=Etc/UTC
2525

26-
ARG DEBIAN_FRONTEND=noninteractive
26+
RUN apt-get update && \
27+
apt-get install -y --no-install-recommends \
28+
curl \
29+
tzdata \
30+
software-properties-common \
31+
python3.10-full \
32+
python3.10-distutils \
33+
build-essential \
34+
python3.10-dev \
35+
cython3 && \
36+
ln -fs /usr/share/zoneinfo/$TZ /etc/localtime && \
37+
dpkg-reconfigure --frontend noninteractive tzdata && \
38+
rm -rf /var/lib/apt/lists/*
2739

28-
RUN apt install python3.12 -y
29-
RUN apt install python3.12-venv -y
30-
RUN apt install python3.12-dev -y
31-
RUN rm /usr/bin/python3
32-
RUN ln -s python3.12 /usr/bin/python3
33-
RUN python3 --version
34-
RUN apt-get install -y curl
35-
RUN curl -sS https://bootstrap.pypa.io/get-pip.py | python3.12 && pip install --upgrade pip
40+
# 2) Symlink python3 to 3.10
41+
RUN ln -sf /usr/bin/python3.10 /usr/bin/python3 && \
42+
ln -sf /usr/bin/python3.10 /usr/bin/python
3643

37-
RUN pip install --no-cache-dir -vvv apache-beam[gcp]==2.58.1
38-
RUN pip install openai vllm
44+
# 3) Install pip, setuptools & wheel
45+
RUN curl -sS https://bootstrap.pypa.io/get-pip.py | python3 && \
46+
python3 -m pip install --upgrade pip setuptools wheel
3947

40-
RUN apt install libcairo2-dev pkg-config python3-dev -y
41-
RUN pip install pycairo
48+
# 4) Copy the Beam SDK harness (for Dataflow workers)
49+
COPY --from=gcr.io/apache-beam-testing/beam-sdk/beam_python3.10_sdk:2.68.0.dev \
50+
/opt/apache/beam /opt/apache/beam
4251

43-
# Copy the Apache Beam worker dependencies from the Beam Python 3.12 SDK image.
44-
COPY --from=apache/beam_python3.12_sdk:2.58.1 /opt/apache/beam /opt/apache/beam
52+
# 5) Make sure the harness is discovered first
53+
ENV PYTHONPATH=/opt/apache/beam:$PYTHONPATH
4554

46-
# Set the entrypoint to Apache Beam SDK worker launcher.
47-
ENTRYPOINT [ "/opt/apache/beam/boot" ]
55+
# 6) Install the Beam dev SDK from the local source package.
56+
# This .tar.gz file will be created by GitHub Actions workflow
57+
# and copied into the build context.
58+
COPY ./sdks/python/build/apache-beam.tar.gz /tmp/beam.tar.gz
59+
RUN python3 -m pip install --no-cache-dir "/tmp/beam.tar.gz[gcp]"
60+
61+
# 7) Install vLLM, and other dependencies
62+
RUN python3 -m pip install --no-cache-dir \
63+
openai>=1.52.2 \
64+
vllm>=0.6.3 \
65+
triton>=3.1.0
66+
67+
# 8) Use the Beam boot script as entrypoint
68+
ENTRYPOINT ["/opt/apache/beam/boot"]

0 commit comments

Comments
 (0)