Skip to content

Commit 488efd7

Browse files
speed mm
1 parent 7f124b5 commit 488efd7

1 file changed

Lines changed: 13 additions & 131 deletions

File tree

fastdeploy/engine/common_engine.py

Lines changed: 13 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -30,106 +30,12 @@
3030
import time
3131
import traceback
3232
import weakref
33-
from bisect import bisect_right
3433
from concurrent.futures import ThreadPoolExecutor
3534
from pathlib import Path
3635
from typing import Dict, List, Optional, Tuple
3736

3837
import numpy as np
3938
import paddle
40-
41-
42-
def _balanced_mm_chunks(input_ids_np, grid_thw_np, image_patch_id, target):
43-
"""
44-
Globally balanced partition for multimodal prefill chunks.
45-
46-
Replaces the local-greedy `get_mm_split_fuse` kernel: given the full
47-
input_ids and image grid_thw, choose chunk boundaries so that the
48-
maximum chunk length is minimized while respecting image atomicity
49-
(no chunk boundary may fall inside an image patch span).
50-
51-
Returns (chunk_image_num_list, chunk_seq_len_list) matching the kernel's
52-
contract (lists of Python ints).
53-
"""
54-
n = int(len(input_ids_np))
55-
if n == 0:
56-
return [0], [0]
57-
58-
is_patch = input_ids_np == image_patch_id
59-
60-
# A position p in [0..N] is "splittable" iff it is not strictly inside
61-
# an image patch span: not (is_patch[p-1] and is_patch[p]).
62-
splittable_mask = np.ones(n + 1, dtype=bool)
63-
if n >= 2:
64-
inside = is_patch[:-1] & is_patch[1:]
65-
splittable_mask[1:n] = ~inside
66-
splittable = np.flatnonzero(splittable_mask).tolist()
67-
68-
# Lower bound on L: the largest atomic gap (an image span we cannot split).
69-
sp_arr = np.asarray(splittable)
70-
max_atomic = int(np.diff(sp_arr).max()) if len(sp_arr) >= 2 else n
71-
k_target = max(1, (n + target - 1) // target)
72-
73-
def feasible(max_chunk_len):
74-
cur = 0
75-
cuts = 0
76-
while cur < n:
77-
j = bisect_right(splittable, cur + max_chunk_len) - 1
78-
if j < 0 or splittable[j] <= cur:
79-
return False
80-
cur = splittable[j]
81-
cuts += 1
82-
if cuts > k_target:
83-
return False
84-
return cur == n
85-
86-
lo = max(max_atomic, (n + k_target - 1) // k_target)
87-
hi = n
88-
while lo < hi:
89-
mid = (lo + hi) // 2
90-
if feasible(mid):
91-
hi = mid
92-
else:
93-
lo = mid + 1
94-
chosen_l = lo
95-
96-
# Build the actual partition with chosen_l.
97-
cur = 0
98-
cuts_pos = [0]
99-
while cur < n:
100-
j = bisect_right(splittable, cur + chosen_l) - 1
101-
cur = splittable[j]
102-
cuts_pos.append(cur)
103-
104-
chunk_seq_lens = [cuts_pos[i + 1] - cuts_pos[i] for i in range(len(cuts_pos) - 1)]
105-
106-
# Compute per-chunk image count (number of grid rows fully covered).
107-
image_token_sum = np.zeros(n + 1, dtype=np.int64)
108-
image_token_sum[1:] = np.cumsum(is_patch.astype(np.int64))
109-
if grid_thw_np is None or len(grid_thw_np) == 0:
110-
per_img = np.array([], dtype=np.int64)
111-
else:
112-
# Token count contributed by each row: h * w / 4 (matches kernel).
113-
per_img = (grid_thw_np[:, 1].astype(np.int64) * grid_thw_np[:, 2].astype(np.int64)) // 4
114-
115-
chunk_image_num = []
116-
last_ib = 0
117-
img_total = int(len(per_img))
118-
for i in range(len(chunk_seq_lens)):
119-
chunk_img_token = int(image_token_sum[cuts_pos[i + 1]] - image_token_sum[cuts_pos[i]])
120-
cnt = 0
121-
acc = 0
122-
ib = last_ib
123-
while ib < img_total and acc < chunk_img_token:
124-
acc += int(per_img[ib])
125-
ib += 1
126-
cnt += 1
127-
chunk_image_num.append(cnt)
128-
last_ib = ib
129-
130-
return chunk_image_num, chunk_seq_lens
131-
132-
13339
import zmq
13440
from tqdm import tqdm
13541

@@ -831,43 +737,19 @@ def update_mm_requests_chunk_size(self, requests):
831737

832738
from fastdeploy.model_executor.ops.gpu import get_mm_split_fuse
833739

834-
# mm 切分专用 step:默认与 partial_chunked_tokens[1] 一致;
835-
# 可通过 FD_MM_CHUNK_STEP 放宽(建议 20480~24576),用于减少
836-
# 因图像 patch 边界回退而产生的小 chunk(< chunk_step 的尾段)。
837-
mm_chunk_step = int(os.getenv("FD_MM_CHUNK_STEP", str(self.partial_chunked_tokens[1])))
838-
mm_chunk_step = mm_chunk_step // self.cfg.cache_config.block_size * self.cfg.cache_config.block_size
839-
if mm_chunk_step <= 0:
840-
mm_chunk_step = self.partial_chunked_tokens[1]
841-
842-
# 方案2:全局均衡切分(Python 层)。开启后绕过 kernel get_mm_split_fuse,
843-
# 用二分 + 贪心在所有可切点中选 K 个切点,使最大 chunk 长度最小。
844-
# 通过 FD_MM_BALANCED_CHUNKING=1 启用,默认关。
845-
use_balanced = os.getenv("FD_MM_BALANCED_CHUNKING", "0") == "1"
846-
if use_balanced:
847-
input_ids_np = np.asarray(inputs["input_ids"], dtype=np.int64)
848-
grid_thw_np = (
849-
grid_thw.numpy().reshape([-1, 3]) if grid_thw.shape[0] > 0 else np.zeros((0, 3), dtype=np.int64)
850-
)
851-
chunk_image_num, chunk_seq_len = _balanced_mm_chunks(
852-
input_ids_np,
853-
grid_thw_np,
854-
int(self.data_processor.image_patch_id),
855-
int(mm_chunk_step),
856-
)
857-
else:
858-
chunk_image_num, chunk_seq_len = get_mm_split_fuse(
859-
input_ids,
860-
image_type_ids,
861-
image_token_sum,
862-
grid_thw,
863-
self.data_processor.image_patch_id,
864-
len(grid_thw),
865-
0,
866-
len(input_ids),
867-
0,
868-
mm_chunk_step,
869-
2048,
870-
)
740+
chunk_image_num, chunk_seq_len = get_mm_split_fuse(
741+
input_ids,
742+
image_type_ids,
743+
image_token_sum,
744+
grid_thw,
745+
self.data_processor.image_patch_id,
746+
len(grid_thw),
747+
0,
748+
len(input_ids),
749+
0,
750+
self.partial_chunked_tokens[1],
751+
2048,
752+
)
871753

872754
grid_thw = grid_thw.numpy().reshape([-1, 3])
873755
num_chunks = len(chunk_image_num)

0 commit comments

Comments
 (0)