Shared logic for regular convolution emission. Handles weight transform, input/output transposition, bias, and node emission for all spatial dimensions (1D, 2D, 3D). Weight: [C_out, C_in/G, *K] -> [C_out, *K, C_in/G] Input: (N, C, *spatial) -> (N, *spatial, C) Output: (N, *spat
(
P: MLXProgramBuilder,
n: Node,
x_node: Node,
w_node: Node,
bias_node,
stride: list,
padding: list,
dilation: list,
groups: int,
ndim: int,
)
| 2314 | |
| 2315 | |
| 2316 | def _emit_conv( |
| 2317 | P: MLXProgramBuilder, |
| 2318 | n: Node, |
| 2319 | x_node: Node, |
| 2320 | w_node: Node, |
| 2321 | bias_node, |
| 2322 | stride: list, |
| 2323 | padding: list, |
| 2324 | dilation: list, |
| 2325 | groups: int, |
| 2326 | ndim: int, |
| 2327 | ) -> Slot: |
| 2328 | """Shared logic for regular convolution emission. |
| 2329 | |
| 2330 | Handles weight transform, input/output transposition, bias, and node emission |
| 2331 | for all spatial dimensions (1D, 2D, 3D). |
| 2332 | |
| 2333 | Weight: [C_out, C_in/G, *K] -> [C_out, *K, C_in/G] |
| 2334 | Input: (N, C, *spatial) -> (N, *spatial, C) |
| 2335 | Output: (N, *spatial, C) -> (N, C, *spatial) |
| 2336 | """ |
| 2337 | if ndim == 3 and groups != 1: |
| 2338 | raise ValueError( |
| 2339 | "conv3d with groups != 1 is not supported by MLX. " f"Got groups={groups}." |
| 2340 | ) |
| 2341 | |
| 2342 | # Permutation: channels-first [N, C, *spatial] <-> channels-last [N, *spatial, C] |
| 2343 | ch_first_to_last = [0] + list(range(2, ndim + 2)) + [1] |
| 2344 | ch_last_to_first = [0, ndim + 1] + list(range(1, ndim + 1)) |
| 2345 | |
| 2346 | # Weight: [C_out, C_in/G, *K] -> [C_out, *K, C_in/G] (same permutation) |
| 2347 | w = _emit_channel_last_weight(P, w_node, ch_first_to_last) |
| 2348 | |
| 2349 | x, bias = P.slot_map([x_node, bias_node]) |
| 2350 | |
| 2351 | _, tmp = P.make_tmp_slot() |
| 2352 | P.emit( |
| 2353 | TransposeNode(x=P.slot_to_tid(x), out=P.slot_to_tid(tmp), perm=ch_first_to_last) |
| 2354 | ) |
| 2355 | |
| 2356 | if ndim == 1: |
| 2357 | P.emit( |
| 2358 | Conv1DNode( |
| 2359 | x=P.slot_to_tid(tmp), |
| 2360 | w=P.slot_to_tid(w), |
| 2361 | out=P.slot_to_tid(tmp), |
| 2362 | stride=stride[0], |
| 2363 | padding=padding[0], |
| 2364 | dilation=dilation[0], |
| 2365 | groups=groups, |
| 2366 | ) |
| 2367 | ) |
| 2368 | elif ndim == 2: |
| 2369 | P.emit( |
| 2370 | Conv2DNode( |
| 2371 | x=P.slot_to_tid(tmp), |
| 2372 | w=P.slot_to_tid(w), |
| 2373 | out=P.slot_to_tid(tmp), |
no test coverage detected