-
Notifications
You must be signed in to change notification settings - Fork 179
228 lines (203 loc) · 8.25 KB
/
Copy pathperf.yml
File metadata and controls
228 lines (203 loc) · 8.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
name: Perf Benchmark
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
inputs:
concurrency:
description: "aiperf --concurrency"
default: "10"
request_count:
description: "aiperf --request-count"
default: "200"
# Cancel superseded PR runs; keep main builds.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
jobs:
proxy-perf:
name: Proxy overhead (local stub backend)
runs-on: ubuntu-latest
env:
PROXY_PORT: 4000
STUB_PORT: 9000
# A local zero-latency stub replaces the removed noop route; the proxy
# forwards to it over loopback, which adds a small constant overhead.
PERF_MODEL: mock-model
# gpt2 tokenizer is ~500 KB and downloads in < 2 s on Actions runners.
# It is only used for dataset-manager token counting; actual inference
# token counts come from the server (--use-server-token-count).
PERF_TOKENIZER: gpt2
PERF_CONCURRENCY: ${{ inputs.concurrency || '10' }}
PERF_REQUEST_COUNT: ${{ inputs.request_count || '200' }}
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
with:
python-version: "3.12"
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Install switchyard (default extras only)
run: uv sync
- name: Install aiperf
run: uv pip install aiperf
# ------------------------------------------------------------------
# Start a local zero-latency mock OpenAI upstream. This replaces the
# removed noop route: the proxy serves a real `type: model` chain that
# forwards to this loopback stub, which returns a fixed completion
# instantly. The extra loopback hop adds a small constant overhead.
# ------------------------------------------------------------------
- name: Start local mock OpenAI upstream
run: |
cat > perf_stub.py <<'PY'
"""Zero-latency OpenAI-compatible chat.completions stub (loopback only)."""
import json
import os
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
_COMPLETION = {
"id": "chatcmpl-perf-stub",
"object": "chat.completion",
"created": 1700000000,
"model": "mock-model",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "4"},
"finish_reason": "stop",
}
],
"usage": {"prompt_tokens": 5, "completion_tokens": 1, "total_tokens": 6},
}
_CHUNK = {
"id": "chatcmpl-perf-stub",
"object": "chat.completion.chunk",
"created": 1700000000,
"model": "mock-model",
"choices": [
{"index": 0, "delta": {"content": "4"}, "finish_reason": "stop"}
],
"usage": {"prompt_tokens": 5, "completion_tokens": 1, "total_tokens": 6},
}
class Handler(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
def do_POST(self):
length = int(self.headers.get("content-length", "0"))
body = json.loads(self.rfile.read(length) or b"{}")
if body.get("stream"):
payload = (
f"data: {json.dumps(_CHUNK)}\n\n".encode()
+ b"data: [DONE]\n\n"
)
content_type = "text/event-stream"
else:
payload = json.dumps(_COMPLETION).encode()
content_type = "application/json"
self.send_response(200)
self.send_header("content-type", content_type)
self.send_header("content-length", str(len(payload)))
self.end_headers()
self.wfile.write(payload)
def log_message(self, *_args):
return None
port = int(os.environ["STUB_PORT"])
ThreadingHTTPServer(("127.0.0.1", port), Handler).serve_forever()
PY
python3 perf_stub.py &
echo "STUB_PID=$!" >> "$GITHUB_ENV"
# ------------------------------------------------------------------
# Start the proxy: a `type: model` route pointed at the local stub.
# ------------------------------------------------------------------
- name: Start switchyard proxy
run: |
cat > bench.yaml <<YAML
defaults:
api_key: dummy
base_url: http://localhost:$STUB_PORT/v1
format: openai
routes:
mock-model:
type: model
model: mock-model
YAML
uv run switchyard --routing-profiles bench.yaml -- serve --port $PROXY_PORT &
echo "PROXY_PID=$!" >> "$GITHUB_ENV"
- name: Wait for proxy to be ready
run: |
for i in $(seq 1 30); do
if curl -sf http://localhost:$PROXY_PORT/health > /dev/null 2>&1; then
echo "Proxy is up after ${i}s"
exit 0
fi
sleep 1
done
echo "Proxy did not become ready in 30s"
exit 1
# ------------------------------------------------------------------
# Pre-build a minimal input dataset so aiperf doesn't need to
# download a HuggingFace tokenizer for synthetic data generation.
# Combined with --use-server-token-count this makes the benchmark
# completely self-contained (no network calls beyond the proxy).
# ------------------------------------------------------------------
- name: Generate perf input dataset
run: |
python3 - <<'PY'
import json
# 300 entries — enough to cycle through for any request-count
with open("perf-input.jsonl", "w") as f:
for _ in range(300):
f.write(json.dumps({"text": "What is 2 + 2?"}) + "\n")
PY
# ------------------------------------------------------------------
# Run the benchmark
# ------------------------------------------------------------------
- name: Run aiperf benchmark (non-streaming)
run: |
mkdir -p perf-results
uv run aiperf profile \
--model "$PERF_MODEL" \
--tokenizer "$PERF_TOKENIZER" \
--url "http://localhost:$PROXY_PORT" \
--endpoint-type chat \
--concurrency "$PERF_CONCURRENCY" \
--request-count "$PERF_REQUEST_COUNT" \
--ui none \
--custom-dataset-type single-turn \
--input-file perf-input.jsonl \
--use-server-token-count \
--output-artifact-dir perf-results/non-streaming
- name: Run aiperf benchmark (streaming)
run: |
uv run aiperf profile \
--model "$PERF_MODEL" \
--tokenizer "$PERF_TOKENIZER" \
--url "http://localhost:$PROXY_PORT" \
--endpoint-type chat \
--streaming \
--concurrency "$PERF_CONCURRENCY" \
--request-count "$PERF_REQUEST_COUNT" \
--ui none \
--custom-dataset-type single-turn \
--input-file perf-input.jsonl \
--use-server-token-count \
--output-artifact-dir perf-results/streaming
# ------------------------------------------------------------------
# Stop proxy and local stub
# ------------------------------------------------------------------
- name: Stop proxy and stub
if: always()
run: |
kill "$PROXY_PID" || true
kill "$STUB_PID" || true
# ------------------------------------------------------------------
# Upload results as a build artifact for comparison over time
# ------------------------------------------------------------------
- name: Upload perf results
uses: actions/upload-artifact@v4
if: always()
with:
name: perf-results-${{ github.sha }}
path: perf-results/
retention-days: 90