[Bugfix][Frontend] Honor output_compression on the image generations route (#7447)
Signed-off-by: hsliu_ustc <hsliu_ustc@noreply.gitcode.com> Signed-off-by: amy-why-3459 <wuhaiyan17@huawei.com> Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
This commit is contained in:
1 parent
f7d9deb45a
commit
01a2f93256
4 files changed
+101
-1
No files matched your search
@@ -452,6 +452,39 @@ curl -s http://localhost:8091/v1/chat/completions \
|
||||
- **Known limitations:** the same single-GPU limitations as Base and Edit apply;
|
||||
CPU offload, Cache-DiT, and multi-GPU parallelism are not yet validated.
|
||||
|
||||
## Output format and compression
|
||||
|
||||
Image requests accept `output_format` (`png`, `jpeg`, or `webp`) and
|
||||
`output_compression` (0-100). The default is `png` with the fastest,
|
||||
least-compressed PNG encode, which is lossless but produces multi-megabyte
|
||||
payloads. For latency-sensitive clients, `output_format: "jpeg"` removes
|
||||
almost the entire response-encoding overhead: on a 1024x1024 Turbo request
|
||||
the client-observed e2e latency drops from ~900 ms to ~766 ms and the
|
||||
payload shrinks from ~3.1 MB to ~0.8 MB. Use `output_compression` to trade
|
||||
encode time against payload size when PNG must stay lossless: lower values
|
||||
(e.g. 1) compress harder and produce smaller payloads at proportionally
|
||||
longer encode times.
|
||||
|
||||
## Attention backend
|
||||
|
||||
The diffusion attention backend defaults to `FLASH_ATTN` (FlashAttention-2).
|
||||
On Hopper-class GPUs (SM90) pass `--diffusion-attention-backend CUDNN_ATTN`
|
||||
for a measured ~12% engine-time reduction on Boogu-Image workloads:
|
||||
|
||||
```bash
|
||||
vllm serve Boogu/Boogu-Image-0.1-Turbo \
|
||||
--omni \
|
||||
--diffusion-attention-backend CUDNN_ATTN \
|
||||
--port 8091
|
||||
```
|
||||
|
||||
Measured on a 143 GB SM90 card (1024x1024, 4 steps, 5-run mean): stage time
|
||||
673 ms with `CUDNN_ATTN` vs 763 ms with the default. Outputs are numerically
|
||||
different from FLASH_ATTN (different attention kernel) but visually
|
||||
equivalent, deterministic per seed, and decode identically. On datacenter
|
||||
Blackwell (SM100 / SM103) use `--diffusion-attention-backend TRTLLM_ATTN`
|
||||
instead; it is rejected on SM90.
|
||||
|
||||
## Performance validation
|
||||
|
||||
The checked-in single-device A40 measurements remain the current Boogu
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
"""L1 tests for the image response encoder's compression contract."""
|
||||
|
||||
import base64
|
||||
import io
|
||||
|
||||
import pytest
|
||||
from PIL import Image
|
||||
|
||||
from vllm_omni.entrypoints.openai.image_api_utils import (
|
||||
encode_image_base64_with_compression,
|
||||
)
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def image():
|
||||
# Photographic-content proxy: noise-plus-gradient content compresses very
|
||||
# differently across PNG compress levels, which is what the mapping under
|
||||
# test controls.
|
||||
import numpy as np
|
||||
|
||||
rng = np.random.default_rng(0)
|
||||
grad = np.linspace(0, 255, 256, dtype=np.float32)
|
||||
arr = np.clip(grad[None, :, None] + rng.integers(-24, 25, (256, 256, 3)), 0, 255).astype(np.uint8)
|
||||
return Image.fromarray(arr, mode="RGB")
|
||||
|
||||
|
||||
def test_png_output_compression_maps_to_compress_level(image):
|
||||
"""output_compression 100 -> compress_level 0, 1 -> compress_level 9."""
|
||||
|
||||
def encode(compression):
|
||||
buffer = io.BytesIO()
|
||||
image.save(buffer, format="PNG", compress_level=max(0, min(9, 9 - compression // 11)))
|
||||
return len(base64.b64encode(buffer.getvalue()))
|
||||
|
||||
fast = encode_image_base64_with_compression(image, format="png", output_compression=100)
|
||||
small = encode_image_base64_with_compression(image, format="png", output_compression=1)
|
||||
|
||||
# Level-0 PNG barely compresses; the mapped level-9 encode is smaller.
|
||||
assert len(small) < len(fast)
|
||||
# The mapping must produce the same sizes as the equivalent compress_level.
|
||||
assert len(fast) == encode(100)
|
||||
assert len(small) == encode(1)
|
||||
|
||||
|
||||
def test_jpeg_output_compression_maps_to_quality(image):
|
||||
high = base64.b64decode(encode_image_base64_with_compression(image, format="jpeg", output_compression=100))
|
||||
low = base64.b64decode(encode_image_base64_with_compression(image, format="jpeg", output_compression=10))
|
||||
|
||||
assert len(high) > len(low)
|
||||
@@ -1605,7 +1605,9 @@ def _build_image_generation_response(
|
||||
output_format = _choose_output_format(request.output_format or "png", None)
|
||||
image_data = [
|
||||
ImageData(
|
||||
b64_json=encode_image_base64_with_compression(image, format=output_format),
|
||||
b64_json=encode_image_base64_with_compression(
|
||||
image, format=output_format, output_compression=request.output_compression
|
||||
),
|
||||
revised_prompt=None,
|
||||
)
|
||||
for image in images
|
||||
|
||||
@@ -175,6 +175,17 @@ class ImageGenerationRequest(BaseModel):
|
||||
default=None,
|
||||
description="Output image format: 'png', 'jpeg', or 'webp'. Defaults to 'png'.",
|
||||
)
|
||||
output_compression: int = Field(
|
||||
default=100,
|
||||
ge=0,
|
||||
le=100,
|
||||
description=(
|
||||
"Compression level 0-100. For 'jpeg'/'webp' this is the encoder "
|
||||
"quality. For 'png' 100 keeps the fastest, least-compressed "
|
||||
"encode and lower values trade encode time for smaller payloads "
|
||||
"(100 -> compress_level 0, 1 -> compress_level 9)."
|
||||
),
|
||||
)
|
||||
return_stage_metrics: bool | None = Field(
|
||||
default=None,
|
||||
description="Return stage metrics for benchmark clients.",
|
||||
|
||||
Reference in new issue
Block a user