|
30 | 30 | import time |
31 | 31 | import traceback |
32 | 32 | import weakref |
33 | | -from bisect import bisect_right |
34 | 33 | from concurrent.futures import ThreadPoolExecutor |
35 | 34 | from pathlib import Path |
36 | 35 | from typing import Dict, List, Optional, Tuple |
37 | 36 |
|
38 | 37 | import numpy as np |
39 | 38 | 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 | | - |
133 | 39 | import zmq |
134 | 40 | from tqdm import tqdm |
135 | 41 |
|
@@ -831,43 +737,19 @@ def update_mm_requests_chunk_size(self, requests): |
831 | 737 |
|
832 | 738 | from fastdeploy.model_executor.ops.gpu import get_mm_split_fuse |
833 | 739 |
|
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 | + ) |
871 | 753 |
|
872 | 754 | grid_thw = grid_thw.numpy().reshape([-1, 3]) |
873 | 755 | num_chunks = len(chunk_image_num) |
|
0 commit comments