Molbap HF Staff commited on
Commit
4ee800d
·
verified ·
1 Parent(s): 3f4e938

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -42,9 +42,8 @@ pixel_values = kir.resize_normalize(
42
  # -> (3, 3, 384, 384) float32, ready for the model
43
  ```
44
 
45
- `trust_remote_code=True` is required because this is a personal namespace (not the trusted
46
- `kernels-community` org). `revision="main"` loads the current code; tag a `v1.0.0` release if
47
- you want `version=1` loading instead.
48
 
49
  `resize_normalize` accepts a stacked `(N, C, H, W)` tensor or a ragged list of CHW
50
  tensors. `resize_normalize_ragged` is the same kernel, list-only.
 
42
  # -> (3, 3, 384, 384) float32, ready for the model
43
  ```
44
 
45
+ Requires `kernels >= 0.15` (published as a `kernel` repo type). `trust_remote_code=True` is needed
46
+ because `Molbap` is a personal namespace, not the auto-trusted `kernels-community` org.
 
47
 
48
  `resize_normalize` accepts a stacked `(N, C, H, W)` tensor or a ragged list of CHW
49
  tensors. `resize_normalize_ragged` is the same kernel, list-only.
build/torch-universal/kernel_image_resize/__init__.py CHANGED
@@ -18,6 +18,7 @@ launch, taps*taps. Both parity <=1e-4 vs torchvision-float.
18
 
19
  from ._fused import fused_resize_normalize
20
  from ._pack import PIL_RESAMPLE_TO_INTERP, as_image_list
 
21
  from ._separable import separable_resize_crop_normalize, separable_resize_normalize
22
 
23
 
@@ -110,4 +111,4 @@ def resize_normalize_ragged(
110
  )
111
 
112
 
113
- __all__ = ["resize_normalize", "resize_normalize_ragged"]
 
18
 
19
  from ._fused import fused_resize_normalize
20
  from ._pack import PIL_RESAMPLE_TO_INTERP, as_image_list
21
+ from ._ragged import resize_normalize_ragged_output
22
  from ._separable import separable_resize_crop_normalize, separable_resize_normalize
23
 
24
 
 
111
  )
112
 
113
 
114
+ __all__ = ["resize_normalize", "resize_normalize_ragged", "resize_normalize_ragged_output"]
build/torch-universal/kernel_image_resize/_ragged.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Ragged-OUTPUT resize + normalize, for dynamic-resolution VLMs.
2
+
3
+ The classic kernel (`resize_normalize`) takes ragged input and produces ONE fixed output size.
4
+ Recent VLMs do the opposite at the output end: each image is resized to its OWN target size
5
+ (aspect-preserving, bounded by a patch budget) and then patchified — Gemma4, Qwen-style
6
+ `smart_resize` (minimax_m3_vl, glmga), etc. So the output is ragged: image i -> (C, h_i, w_i).
7
+
8
+ This kernel keeps the classic kernel's efficiency (two separable passes, uint8 input, taps+taps,
9
+ TWO launches for the whole batch — no per-image Python loop) but writes into a packed ragged
10
+ output buffer with per-image offsets, and returns a list of per-image (C, h_i, w_i) tensors.
11
+
12
+ The caller computes the per-image target sizes with the model's own rule (Gemma4's
13
+ `get_aspect_ratio_preserving_size`, Qwen's `smart_resize`, ...) and passes them in. Patchify is
14
+ left to the model (it is a cheap reshape/unfold over this kernel's output).
15
+
16
+ EXPERIMENTAL: authored without a GPU; validate parity on CUDA before use.
17
+ """
18
+
19
+ import triton
20
+ import triton.language as tl
21
+
22
+ from ._fused import _resample_weight
23
+ from ._pack import fold_mean_std, pack_images
24
+
25
+
26
+ @triton.jit
27
+ def _ragged_horizontal_kernel(
28
+ input_pixels, intermediate, input_offsets, intermediate_offsets,
29
+ heights, widths, out_widths,
30
+ cubic_coeff,
31
+ CHANNELS: tl.constexpr, BLOCK: tl.constexpr, CUBIC: tl.constexpr, ANTIALIAS: tl.constexpr,
32
+ MAX_TAPS_COL: tl.constexpr,
33
+ ):
34
+ """Resize width per image: uint8 (C, H, W) -> float (C, H, w_i)."""
35
+ image_index = tl.program_id(0)
36
+ block_index = tl.program_id(1)
37
+ in_height = tl.load(heights + image_index)
38
+ in_width = tl.load(widths + image_index)
39
+ out_width = tl.load(out_widths + image_index)
40
+ input_start = tl.load(input_offsets + image_index)
41
+ intermediate_start = tl.load(intermediate_offsets + image_index)
42
+ in_width_f = in_width.to(tl.float32)
43
+
44
+ num_pixels = in_height * out_width
45
+ flat_index = block_index * BLOCK + tl.arange(0, BLOCK)
46
+ active = flat_index < num_pixels
47
+ input_row = flat_index // out_width
48
+ out_col = flat_index % out_width
49
+
50
+ filter_half = 2.0 if CUBIC else 1.0
51
+ col_scale = in_width_f / out_width.to(tl.float32)
52
+ col_filter_scale = tl.maximum(col_scale, 1.0) if ANTIALIAS else 1.0
53
+ col_support = filter_half * col_filter_scale
54
+ col_inv_scale = 1.0 / col_filter_scale
55
+ src_center_col = col_scale * (out_col.to(tl.float32) + 0.5)
56
+ first_tap_col = tl.floor(src_center_col - col_support + 0.5)
57
+
58
+ col_weight_sum = tl.zeros([BLOCK], dtype=tl.float32)
59
+ for tap in tl.static_range(MAX_TAPS_COL):
60
+ tap_col = first_tap_col + tap
61
+ weight = _resample_weight((tap_col - src_center_col + 0.5) * col_inv_scale, cubic_coeff, CUBIC)
62
+ if ANTIALIAS:
63
+ weight = tl.where((tap_col >= 0.0) & (tap_col < in_width_f), weight, 0.0)
64
+ col_weight_sum += weight
65
+
66
+ input_plane = (in_height * in_width).to(tl.int64)
67
+ intermediate_plane = (in_height * out_width).to(tl.int64)
68
+ in_width_i64 = in_width.to(tl.int64)
69
+ out_width_i64 = out_width.to(tl.int64)
70
+ input_row_i64 = input_row.to(tl.int64)
71
+ for channel in tl.static_range(CHANNELS):
72
+ input_row_base = input_start + channel * input_plane + input_row_i64 * in_width_i64
73
+ accumulator = tl.zeros([BLOCK], dtype=tl.float32)
74
+ for tap in tl.static_range(MAX_TAPS_COL):
75
+ tap_col = first_tap_col + tap
76
+ weight = _resample_weight((tap_col - src_center_col + 0.5) * col_inv_scale, cubic_coeff, CUBIC)
77
+ if ANTIALIAS:
78
+ weight = tl.where((tap_col >= 0.0) & (tap_col < in_width_f), weight, 0.0)
79
+ clamped_tap_col = tl.minimum(tl.maximum(tap_col.to(tl.int32), 0), in_width - 1).to(tl.int64)
80
+ pixel = tl.load(input_pixels + input_row_base + clamped_tap_col, mask=active, other=0).to(tl.float32)
81
+ accumulator += weight * pixel
82
+ accumulator = accumulator / col_weight_sum
83
+ write_index = intermediate_start + channel * intermediate_plane + input_row_i64 * out_width_i64 + out_col
84
+ tl.store(intermediate + write_index, accumulator, mask=active)
85
+
86
+
87
+ @triton.jit
88
+ def _ragged_vertical_kernel(
89
+ intermediate, output, intermediate_offsets, output_offsets,
90
+ heights, out_heights, out_widths, means, stds,
91
+ cubic_coeff,
92
+ CHANNELS: tl.constexpr, BLOCK: tl.constexpr, CUBIC: tl.constexpr, ANTIALIAS: tl.constexpr,
93
+ MAX_TAPS_ROW: tl.constexpr,
94
+ ):
95
+ """Resize height per image + normalize: float (C, H, w_i) -> float (C, h_i, w_i)."""
96
+ image_index = tl.program_id(0)
97
+ block_index = tl.program_id(1)
98
+ in_height = tl.load(heights + image_index)
99
+ out_height = tl.load(out_heights + image_index)
100
+ out_width = tl.load(out_widths + image_index)
101
+ intermediate_start = tl.load(intermediate_offsets + image_index)
102
+ output_start = tl.load(output_offsets + image_index)
103
+ in_height_f = in_height.to(tl.float32)
104
+
105
+ num_pixels = out_height * out_width
106
+ flat_index = block_index * BLOCK + tl.arange(0, BLOCK)
107
+ active = flat_index < num_pixels
108
+ out_row = flat_index // out_width
109
+ out_col = flat_index % out_width
110
+
111
+ filter_half = 2.0 if CUBIC else 1.0
112
+ row_scale = in_height_f / out_height.to(tl.float32)
113
+ row_filter_scale = tl.maximum(row_scale, 1.0) if ANTIALIAS else 1.0
114
+ row_support = filter_half * row_filter_scale
115
+ row_inv_scale = 1.0 / row_filter_scale
116
+ src_center_row = row_scale * (out_row.to(tl.float32) + 0.5)
117
+ first_tap_row = tl.floor(src_center_row - row_support + 0.5)
118
+
119
+ row_weight_sum = tl.zeros([BLOCK], dtype=tl.float32)
120
+ for tap in tl.static_range(MAX_TAPS_ROW):
121
+ tap_row = first_tap_row + tap
122
+ weight = _resample_weight((tap_row - src_center_row + 0.5) * row_inv_scale, cubic_coeff, CUBIC)
123
+ if ANTIALIAS:
124
+ weight = tl.where((tap_row >= 0.0) & (tap_row < in_height_f), weight, 0.0)
125
+ row_weight_sum += weight
126
+
127
+ intermediate_plane = (in_height * out_width).to(tl.int64)
128
+ output_plane = (out_height * out_width).to(tl.int64)
129
+ out_width_i64 = out_width.to(tl.int64)
130
+ out_col_i64 = out_col.to(tl.int64)
131
+ out_row_i64 = out_row.to(tl.int64)
132
+ for channel in tl.static_range(CHANNELS):
133
+ channel_base = intermediate_start + channel * intermediate_plane
134
+ accumulator = tl.zeros([BLOCK], dtype=tl.float32)
135
+ for tap in tl.static_range(MAX_TAPS_ROW):
136
+ tap_row = first_tap_row + tap
137
+ weight = _resample_weight((tap_row - src_center_row + 0.5) * row_inv_scale, cubic_coeff, CUBIC)
138
+ if ANTIALIAS:
139
+ weight = tl.where((tap_row >= 0.0) & (tap_row < in_height_f), weight, 0.0)
140
+ clamped_tap_row = tl.minimum(tl.maximum(tap_row.to(tl.int32), 0), in_height - 1).to(tl.int64)
141
+ pixel = tl.load(intermediate + channel_base + clamped_tap_row * out_width_i64 + out_col_i64, mask=active, other=0.0)
142
+ accumulator += weight * pixel
143
+ accumulator = accumulator / row_weight_sum
144
+ mean = tl.load(means + channel)
145
+ std = tl.load(stds + channel)
146
+ accumulator = (accumulator - mean) / std
147
+ write_index = output_start + channel * output_plane + out_row_i64 * out_width_i64 + out_col_i64
148
+ tl.store(output + write_index, accumulator, mask=active)
149
+
150
+
151
+ def _ragged_max_taps(in_sizes, out_sizes, interp, antialias):
152
+ import math
153
+
154
+ interp_half = 2.0 if interp == "bicubic" else 1.0
155
+ worst = 0
156
+ for in_size, out_size in zip(in_sizes, out_sizes):
157
+ scale = in_size / out_size
158
+ eff = max(scale, 1.0) if antialias else 1.0
159
+ worst = max(worst, math.ceil(interp_half * eff) * 2 + 1)
160
+ return worst
161
+
162
+
163
+ def resize_normalize_ragged_output(images, target_sizes, mean, std, rescale, interp, antialias, block: int = 256):
164
+ """Resize each image to its own `target_sizes[i] = (h_i, w_i)` and normalize.
165
+
166
+ Returns a list of per-image `(C, h_i, w_i)` float tensors (views into one packed buffer).
167
+ `target_sizes` come from the model's resize rule (e.g. Gemma4 aspect-ratio-preserving,
168
+ Qwen smart_resize). No cropping — VLMs resize then patchify.
169
+ """
170
+ import torch
171
+
172
+ images = list(images)
173
+ device = images[0].device
174
+ num_images = len(images)
175
+ cubic_coeff = -0.5 if antialias else -0.75
176
+ in_heights = [int(img.shape[1]) for img in images]
177
+ in_widths = [int(img.shape[2]) for img in images]
178
+ out_heights = [int(h) for h, _ in target_sizes]
179
+ out_widths = [int(w) for _, w in target_sizes]
180
+ max_taps_row = _ragged_max_taps(in_heights, out_heights, interp, antialias)
181
+ max_taps_col = _ragged_max_taps(in_widths, out_widths, interp, antialias)
182
+ means, stds = fold_mean_std(mean, std, rescale, device)
183
+
184
+ input_pixels, input_offsets, heights, widths, channels = pack_images(images, dtype=torch.uint8)
185
+
186
+ intermediate_offsets_list, intermediate_total, max_mid = [], 0, 0
187
+ output_offsets_list, output_total, max_out = [], 0, 0
188
+ for h_in, w_out, h_out in zip(in_heights, out_widths, out_heights):
189
+ intermediate_offsets_list.append(intermediate_total)
190
+ intermediate_total += channels * h_in * w_out
191
+ max_mid = max(max_mid, h_in * w_out)
192
+ output_offsets_list.append(output_total)
193
+ output_total += channels * h_out * w_out
194
+ max_out = max(max_out, h_out * w_out)
195
+
196
+ intermediate = torch.empty(intermediate_total, device=device, dtype=torch.float32)
197
+ output = torch.empty(output_total, device=device, dtype=torch.float32)
198
+ intermediate_offsets = torch.tensor(intermediate_offsets_list, device=device, dtype=torch.int64)
199
+ output_offsets = torch.tensor(output_offsets_list, device=device, dtype=torch.int64)
200
+ out_widths_t = torch.tensor(out_widths, device=device, dtype=torch.int32)
201
+ out_heights_t = torch.tensor(out_heights, device=device, dtype=torch.int32)
202
+
203
+ _ragged_horizontal_kernel[(num_images, triton.cdiv(max_mid, block))](
204
+ input_pixels, intermediate, input_offsets, intermediate_offsets, heights, widths, out_widths_t,
205
+ cubic_coeff, CHANNELS=channels, BLOCK=block, CUBIC=(interp == "bicubic"), ANTIALIAS=antialias,
206
+ MAX_TAPS_COL=max_taps_col,
207
+ )
208
+ _ragged_vertical_kernel[(num_images, triton.cdiv(max_out, block))](
209
+ intermediate, output, intermediate_offsets, output_offsets, heights, out_heights_t, out_widths_t,
210
+ means, stds, cubic_coeff, CHANNELS=channels, BLOCK=block, CUBIC=(interp == "bicubic"),
211
+ ANTIALIAS=antialias, MAX_TAPS_ROW=max_taps_row,
212
+ )
213
+
214
+ results = []
215
+ for offset, h_out, w_out in zip(output_offsets_list, out_heights, out_widths):
216
+ results.append(output[offset : offset + channels * h_out * w_out].view(channels, h_out, w_out))
217
+ return results