Skip to content

Commit bfde1f7

Browse files
Merge branch 'flagos-ai:main' into gcu_fix
2 parents 0af6ea3 + f4ebc25 commit bfde1f7

52 files changed

Lines changed: 3109 additions & 202 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/configs/cuda.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ runner_labels:
2929
- gpu-8
3030
- cuda130
3131

32+
# Device-specific runner labels override the platform defaults for E2E jobs.
33+
device_runner_labels:
34+
h20:
35+
- vllm-plugin-ci-h20
36+
3237
# Runner labels for this hardware (Platform hosted)
3338
# runner_labels:
3439
# - ci-vllm-plugin-fl

.github/scripts/load_config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def load_platform_config(platform: str) -> dict:
4545

4646
# Ensure the platform key is present
4747
config.setdefault("platform", platform)
48+
config.setdefault("device_runner_labels", {})
4849
return config
4950

5051

.github/workflows/_platform_test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ jobs:
140140
device: ${{ matrix.test.device }}
141141
cases: ${{ matrix.test.cases }}
142142
ci_image: ${{ matrix.test.image || fromJson(needs.setup.outputs.config).ci_image }}
143-
runner_labels: ${{ toJson(fromJson(needs.setup.outputs.config).runner_labels) }}
143+
runner_labels: ${{ toJson(fromJson(needs.setup.outputs.config).device_runner_labels[matrix.test.device] || fromJson(needs.setup.outputs.config).runner_labels) }}
144144
container_volumes: ${{ toJson(fromJson(needs.setup.outputs.config).container_volumes) }}
145145
container_options: ${{ fromJson(needs.setup.outputs.config).container_options }}
146146
timeout: ${{ matrix.test.timeout || 60 }}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ In theory, vllm-plugin-FL can support all models available in vLLM, as long as n
9999
```sh
100100
git clone https://github.com/flagos-ai/FlagCX.git
101101
cd FlagCX
102-
git checkout -b v0.9.0
102+
git checkout -b v0.13.0
103103
git submodule update --init --recursive
104104
```
105105

examples/disaggregated_serving_xpyd/router.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,11 @@ async def lifespan(app):
124124

125125
async def send_to_prefill(prefill_client, endpoint, req_data, request_id):
126126
data = req_data.copy()
127-
data["kv_transfer_params"] = {"do_remote_decode": True, "do_remote_prefill": False}
127+
data["kv_transfer_params"] = {
128+
"do_remote_decode": True,
129+
"do_remote_prefill": False,
130+
"transfer_id": f"fgx-{request_id}",
131+
}
128132
data["stream"] = False
129133
data["max_tokens"] = 1
130134
if "max_completion_tokens" in data:
@@ -153,6 +157,7 @@ async def stream_from_decode(
153157
"do_remote_decode": False,
154158
"remote_host": prefill_client["remote_host"],
155159
"remote_port": prefill_client["side_channel_port"],
160+
"transfer_id": f"fgx-{request_id}",
156161
}
157162
headers = {"X-Request-Id": request_id}
158163
api_key = os.environ.get("OPENAI_API_KEY", "")

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ test = [
6565
"numpy",
6666
"requests",
6767
"openai",
68+
"pillow",
6869
"decorator",
6970
"vllm[audio]==0.20.2",
7071
"modelscope>=1.18.1",

tests/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ generate:
239239
| `serve.endpoints` | list | No | Endpoints to test: `completion`, `chat` |
240240
| `serve.completion_prompt` | str | No | Prompt for `/v1/completions` |
241241
| `serve.chat_messages` | list | No | Messages for `/v1/chat/completions` |
242+
| `serve.chat_cases` | list | No | Named chat requests run against one server; `expected` validates required response substrings and `generated_image: true` creates the local image fixture |
242243
| `serve.max_tokens` | int | No | Max tokens for serving requests (default: 50) |
243244
| `serve.api_key` | str | No | API key for authenticated endpoints |
244245
| `serve.extra_engine` | dict | No | Engine param overrides for serving only |

tests/e2e_tests/serving/test_serving_smoke.py

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
controlled by the ``serve.stream`` flag in the model YAML.
1616
"""
1717

18+
import base64
1819
import os
20+
from io import BytesIO
1921

2022
import pytest
2123
import requests
@@ -138,20 +140,75 @@ def _run_chat(base_url: str, headers: dict) -> None:
138140
Otherwise, uses a plain requests POST.
139141
"""
140142
serve = _CFG.serve
141-
messages = serve.chat_messages or [{"role": "user", "content": "Hello"}]
142-
143-
if serve.stream:
144-
_run_chat_streaming(base_url, serve, messages)
145-
else:
146-
_run_chat_non_streaming(base_url, headers, serve, messages)
143+
chat_cases = serve.chat_cases or [
144+
{
145+
"name": "default",
146+
"messages": serve.chat_messages or [{"role": "user", "content": "Hello"}],
147+
}
148+
]
149+
150+
for chat_case in chat_cases:
151+
case_name = chat_case.get("name", "unnamed")
152+
if chat_case.get("generated_image"):
153+
messages = _generated_image_messages(
154+
chat_case.get("prompt", "Describe this image.")
155+
)
156+
else:
157+
messages = chat_case.get("messages", [])
158+
159+
assert messages, f"Chat case '{case_name}' has no messages"
160+
print(f"\nRunning chat case: {case_name}")
161+
if serve.stream:
162+
response_text = _run_chat_streaming(base_url, serve, messages)
163+
else:
164+
response_text = _run_chat_non_streaming(base_url, headers, serve, messages)
165+
166+
expected = chat_case.get("expected")
167+
if expected:
168+
expected_values = expected if isinstance(expected, list) else [expected]
169+
normalized_response = response_text.casefold()
170+
missing = [
171+
value
172+
for value in expected_values
173+
if value.casefold() not in normalized_response
174+
]
175+
is_correct = not missing
176+
print(f"Answer validation: {'CORRECT' if is_correct else 'INCORRECT'}")
177+
assert is_correct, f"Chat case '{case_name}' is missing expected: {missing}"
178+
179+
180+
def _generated_image_messages(prompt: str) -> list[dict]:
181+
"""Create the local image payload used by the Qwen multimodal smoke case."""
182+
from PIL import Image, ImageDraw
183+
184+
image = Image.new("RGB", (300, 200), color="white")
185+
draw = ImageDraw.Draw(image)
186+
draw.rectangle((50, 50, 250, 150), fill="blue")
187+
draw.text((90, 80), "Hello VLM", fill="yellow")
188+
189+
buffer = BytesIO()
190+
image.save(buffer, format="JPEG")
191+
encoded = base64.b64encode(buffer.getvalue()).decode("utf-8")
192+
return [
193+
{
194+
"role": "user",
195+
"content": [
196+
{
197+
"type": "image_url",
198+
"image_url": {"url": f"data:image/jpeg;base64,{encoded}"},
199+
},
200+
{"type": "text", "text": prompt},
201+
],
202+
}
203+
]
147204

148205

149206
def _run_chat_non_streaming(
150207
base_url: str,
151208
headers: dict,
152209
serve,
153210
messages: list[dict],
154-
) -> None:
211+
) -> str:
155212
"""Non-streaming chat completions via raw requests."""
156213
payload: dict = {
157214
"model": _REQUEST_MODEL,
@@ -177,13 +234,14 @@ def _run_chat_non_streaming(
177234
content = data["choices"][0]["message"]["content"]
178235
assert len(content.strip()) > 0, "Assistant message is empty"
179236
print(f"\nResponse: {content}")
237+
return content
180238

181239

182240
def _run_chat_streaming(
183241
base_url: str,
184242
serve,
185243
messages: list[dict],
186-
) -> None:
244+
) -> str:
187245
"""Streaming chat completions via OpenAI SDK."""
188246
import httpx
189247
from openai import OpenAI
@@ -214,6 +272,7 @@ def _run_chat_streaming(
214272

215273
assert len(text.strip()) > 0, "Streaming response is empty"
216274
print(f"\nStreaming response: {text}")
275+
return text
217276

218277

219278
def _run_embedding(base_url: str, headers: dict) -> None:
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright 2026 FlagOS Contributors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Qwen3.6-27B 262K serving configuration (TP=2).
16+
17+
llm:
18+
model: "/data/models/Qwen/Qwen3.6-27B"
19+
tensor_parallel_size: 2
20+
pipeline_parallel_size: 1
21+
max_model_len: 262144
22+
trust_remote_code: false
23+
24+
serve:
25+
served_model_name: "qwen"
26+
startup_retries: 120
27+
endpoints: ["chat"]
28+
stream: true
29+
max_tokens: 1024
30+
sampling:
31+
temperature: 0.0
32+
chat_cases:
33+
- name: text
34+
messages:
35+
- role: "user"
36+
content: "The capital of France is"
37+
expected: "Paris"
38+
- name: image
39+
generated_image: true
40+
prompt: "Describe all visible text, colors, and shapes in English."
41+
expected:
42+
- "Hello VLM"
43+
- "blue rectangle"
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright 2026 FlagOS Contributors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Qwen3.6-35B-A3B 262K serving configuration (TP=2).
16+
17+
llm:
18+
model: "/data/models/Qwen/Qwen3.6-35B-A3B"
19+
tensor_parallel_size: 2
20+
pipeline_parallel_size: 1
21+
max_model_len: 262144
22+
trust_remote_code: false
23+
24+
serve:
25+
served_model_name: "qwen"
26+
startup_retries: 120
27+
endpoints: ["chat"]
28+
stream: true
29+
max_tokens: 1024
30+
sampling:
31+
temperature: 0.0
32+
chat_cases:
33+
- name: text
34+
messages:
35+
- role: "user"
36+
content: "The capital of France is"
37+
expected: "Paris"
38+
- name: image
39+
generated_image: true
40+
prompt: "Describe all visible text, colors, and shapes in English."
41+
expected:
42+
- "Hello VLM"
43+
- "blue rectangle"

0 commit comments

Comments
 (0)