chore: add an unified server arg for multimodal inputs preprocess config(#12149)
Co-authored-by: bianfeng <bianfeng@pinduoduo.com> Co-authored-by: Xinyuan Tong <115166877+JustinTong0323@users.noreply.github.com>
This commit is contained in:
co-authored by
bianfeng
Xinyuan Tong
parent
aa8ecbda7a
commit
f1be8aa0f2
@@ -156,6 +156,7 @@ Please consult the documentation below and [server_args.py](https://github.com/s
|
|||||||
| `--base-gpu-id` | The base GPU ID to start allocating GPUs from. Useful when running multiple instances on the same machine. | `0` | Type: int |
|
| `--base-gpu-id` | The base GPU ID to start allocating GPUs from. Useful when running multiple instances on the same machine. | `0` | Type: int |
|
||||||
| `--gpu-id-step` | The delta between consecutive GPU IDs that are used. For example, setting it to 2 will use GPU 0,2,4,... | `1` | Type: int |
|
| `--gpu-id-step` | The delta between consecutive GPU IDs that are used. For example, setting it to 2 will use GPU 0,2,4,... | `1` | Type: int |
|
||||||
| `--sleep-on-idle` | Reduce CPU usage when sglang is idle. | `False` | bool flag (set to enable) |
|
| `--sleep-on-idle` | Reduce CPU usage when sglang is idle. | `False` | bool flag (set to enable) |
|
||||||
|
| `--mm-process-config` | A JSON string for multimodal preprocessing configuration. It can contain keys: `image`, `video`, `audio`. | `{}` |
|
||||||
|
|
||||||
## Logging
|
## Logging
|
||||||
| Argument | Description | Defaults | Options |
|
| Argument | Description | Defaults | Options |
|
||||||
|
|||||||
@@ -101,3 +101,9 @@ For multimodal models, you can use the `--keep-mm-feature-on-device` flag to opt
|
|||||||
- **With `--keep-mm-feature-on-device`**: Feature tensors remain on GPU, reducing device-to-host copy overhead and improving latency, but consuming more GPU memory
|
- **With `--keep-mm-feature-on-device`**: Feature tensors remain on GPU, reducing device-to-host copy overhead and improving latency, but consuming more GPU memory
|
||||||
|
|
||||||
Use this flag when you have sufficient GPU memory and want to minimize latency for multimodal inference.
|
Use this flag when you have sufficient GPU memory and want to minimize latency for multimodal inference.
|
||||||
|
|
||||||
|
### Multimodal Inputs Limitation
|
||||||
|
|
||||||
|
- **Use `--mm-process-config '{"image":{"max_pixels":1048576},"video":{"fps":3,"max_pixels":602112,"max_frames":60}}'`**: To set `image`, `video`, and `audio` input limits.
|
||||||
|
|
||||||
|
This can reduce GPU memory usage, improve inference speed, and help to avoid OOM, but may impact model performance, thus set a proper value based on your specific use case. Currently, only `qwen_vl` supports this config. Please refer to [qwen_vl processor](https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/multimodal/processors/qwen_vl.py) for understanding the meaning of each parameter.
|
||||||
|
|||||||
@@ -145,37 +145,44 @@ def smart_nframes(
|
|||||||
async def preprocess_video(
|
async def preprocess_video(
|
||||||
vr,
|
vr,
|
||||||
image_factor: int = IMAGE_FACTOR,
|
image_factor: int = IMAGE_FACTOR,
|
||||||
# vr: VideoReader, image_factor: int = IMAGE_FACTOR
|
video_config: dict = {},
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
entry_time = time.perf_counter()
|
entry_time = time.perf_counter()
|
||||||
ele = {}
|
|
||||||
total_frames, video_fps = len(vr), vr.get_avg_fps()
|
total_frames, video_fps = len(vr), vr.get_avg_fps()
|
||||||
nframes = smart_nframes({}, total_frames=total_frames, video_fps=video_fps)
|
nframes = smart_nframes(
|
||||||
|
video_config, total_frames=total_frames, video_fps=video_fps
|
||||||
|
)
|
||||||
idx = np.linspace(0, total_frames - 1, num=nframes, dtype=np.int64)
|
idx = np.linspace(0, total_frames - 1, num=nframes, dtype=np.int64)
|
||||||
idx = np.unique(idx)
|
idx = np.unique(idx)
|
||||||
video_np = vr.get_batch(idx).asnumpy()
|
video_np = vr.get_batch(idx).asnumpy()
|
||||||
video = torch.from_numpy(video_np).pin_memory()
|
video = torch.from_numpy(video_np).pin_memory()
|
||||||
video = video.permute(0, 3, 1, 2) # Convert to TCHW format
|
video = video.permute(0, 3, 1, 2) # Convert to TCHW format
|
||||||
|
|
||||||
nframes, _, height, width = video.shape
|
nframes, _, height, width = video.shape
|
||||||
min_pixels = ele.get("min_pixels", VIDEO_MIN_PIXELS)
|
min_pixels = video_config.get("min_pixels", VIDEO_MIN_PIXELS)
|
||||||
total_pixels = ele.get("total_pixels", VIDEO_TOTAL_PIXELS)
|
total_pixels = video_config.get("total_pixels", VIDEO_TOTAL_PIXELS)
|
||||||
max_pixels = max(
|
max_pixels = max(
|
||||||
min(VIDEO_MAX_PIXELS, total_pixels / nframes * FRAME_FACTOR),
|
min(
|
||||||
|
video_config.get("max_pixels", VIDEO_MAX_PIXELS),
|
||||||
|
total_pixels / nframes * FRAME_FACTOR,
|
||||||
|
),
|
||||||
int(min_pixels * 1.05),
|
int(min_pixels * 1.05),
|
||||||
)
|
)
|
||||||
|
|
||||||
get_batch_time = time.perf_counter()
|
get_batch_time = time.perf_counter()
|
||||||
|
|
||||||
max_pixels_supposed = ele.get("max_pixels", max_pixels)
|
max_pixels_supposed = video_config.get("max_pixels", max_pixels)
|
||||||
|
|
||||||
if max_pixels_supposed > max_pixels:
|
if max_pixels_supposed > max_pixels:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"The given max_pixels[{max_pixels_supposed}] exceeds limit[{max_pixels}]."
|
f"The given max_pixels[{max_pixels_supposed}] exceeds limit[{max_pixels}]."
|
||||||
)
|
)
|
||||||
max_pixels = min(max_pixels_supposed, max_pixels)
|
max_pixels = min(max_pixels_supposed, max_pixels)
|
||||||
if "resized_height" in ele and "resized_width" in ele:
|
if "resized_height" in video_config and "resized_width" in video_config:
|
||||||
resized_height, resized_width = smart_resize(
|
resized_height, resized_width = smart_resize(
|
||||||
ele["resized_height"],
|
video_config["resized_height"],
|
||||||
ele["resized_width"],
|
video_config["resized_width"],
|
||||||
factor=image_factor,
|
factor=image_factor,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -236,6 +243,9 @@ class QwenVLImageProcessor(SGLangBaseProcessor):
|
|||||||
self.audio_start_token_id = getattr(hf_config, "audio_start_token_id", None)
|
self.audio_start_token_id = getattr(hf_config, "audio_start_token_id", None)
|
||||||
self.audio_token_id = getattr(hf_config, "audio_token_id", None)
|
self.audio_token_id = getattr(hf_config, "audio_token_id", None)
|
||||||
|
|
||||||
|
self.image_config = server_args.mm_process_config.get("image", {})
|
||||||
|
self.video_config = server_args.mm_process_config.get("video", {})
|
||||||
|
|
||||||
self.mm_tokens = MultimodalSpecialTokens(
|
self.mm_tokens = MultimodalSpecialTokens(
|
||||||
image_token="<|vision_start|><|image_pad|><|vision_end|>",
|
image_token="<|vision_start|><|image_pad|><|vision_end|>",
|
||||||
image_token_id=hf_config.image_token_id,
|
image_token_id=hf_config.image_token_id,
|
||||||
@@ -269,7 +279,8 @@ class QwenVLImageProcessor(SGLangBaseProcessor):
|
|||||||
video_metadata = None
|
video_metadata = None
|
||||||
if base_output.videos:
|
if base_output.videos:
|
||||||
videos_processed = [
|
videos_processed = [
|
||||||
await preprocess_video(video) for video in base_output.videos
|
await preprocess_video(video, video_config=self.video_config)
|
||||||
|
for video in base_output.videos
|
||||||
]
|
]
|
||||||
base_output.videos, video_metadata = map(list, zip(*videos_processed))
|
base_output.videos, video_metadata = map(list, zip(*videos_processed))
|
||||||
|
|
||||||
|
|||||||
@@ -297,6 +297,7 @@ class ServerArgs:
|
|||||||
base_gpu_id: int = 0
|
base_gpu_id: int = 0
|
||||||
gpu_id_step: int = 1
|
gpu_id_step: int = 1
|
||||||
sleep_on_idle: bool = False
|
sleep_on_idle: bool = False
|
||||||
|
mm_process_config: Optional[Dict[str, Any]] = None
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
log_level: str = "info"
|
log_level: str = "info"
|
||||||
@@ -682,6 +683,8 @@ class ServerArgs:
|
|||||||
self.device = get_device()
|
self.device = get_device()
|
||||||
if self.random_seed is None:
|
if self.random_seed is None:
|
||||||
self.random_seed = random.randint(0, 1 << 30)
|
self.random_seed = random.randint(0, 1 << 30)
|
||||||
|
if self.mm_process_config is None:
|
||||||
|
self.mm_process_config = {}
|
||||||
|
|
||||||
def _handle_gpu_memory_settings(self, gpu_mem):
|
def _handle_gpu_memory_settings(self, gpu_mem):
|
||||||
"""
|
"""
|
||||||
@@ -2351,6 +2354,12 @@ class ServerArgs:
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="Reduce CPU usage when sglang is idle.",
|
help="Reduce CPU usage when sglang is idle.",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--mm-process-config",
|
||||||
|
type=json.loads,
|
||||||
|
default=ServerArgs.mm_process_config,
|
||||||
|
help="Multimodal preprocessing config, a json config contains keys: `image`, `video`, `audio`",
|
||||||
|
)
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
|||||||
Reference in New Issue
Block a user