Skip to content

Speed up Conv1D/Conv2D with large dilation_rate (closes #132) - #458

Merged
Dobiasd merged 1 commit into
masterfrom
fix-dilated-convolution-perf
Apr 27, 2026
Merged

Speed up Conv1D/Conv2D with large dilation_rate (closes #132)#458
Dobiasd merged 1 commit into
masterfrom
fix-dilated-convolution-perf

Conversation

@Dobiasd

@Dobiasd Dobiasd commented Apr 27, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the long-standing slowdown for Conv1D/Conv2D with large dilation_rate reported in #132.

The previous implementation inflated the filter with zeros at construction time (dilate_filterdilate_tensor). For a 3-tap kernel with dilation_rate=64, this produced a 129-tap kernel where 126 of the 129 entries are zero — but the Eigen GEMM still multiplied through every one of them.

This PR keeps filters un-dilated and threads dilation_rate through convolve(). In the strides=(1,1) fast path, the loop now iterates over the original (y_filt, x_filt) positions and runs one GEMM per position, indexing the padded input at (y_filt * dil_y, x_filt * dil_x). The existing output_temp wraparound trick keeps working as long as out_width_temp = out_width + (f_width - 1) * dil_x (which equals the padded input width).

Benchmark

Stacked-WaveNet style net (3 levels × 7 branches at dilations 1, 2, 4, 8, 16, 32, 64; 16 channels; input shape (18000, 3)):

Forward pass
master 159 ms
this PR 49 ms
same model with all dilation_rate=1 46 ms

So the dilation tax drops from ~3.5× to ~6 %.

Scope

  • conv_2d_layer (covers Conv1D + Conv2D) is updated.
  • conv_2d_transpose_layer, depthwise_conv_2d_layer, separable_conv_2d_layer are unchanged — they continue to pre-dilate filters where applicable. Those paths are not the bottleneck reported in the issue.
  • Conv3D's dilate_filter_3d could get the same treatment in a follow-up; left out of this PR to keep it focused.

Test plan

  • test_model_exhaustive.json — passes within 1e-5 tolerance (covers Conv1D dilation_rate=2 causal, Conv2D dilation_rate=(2,3) same, Conv3D dilation_rate=(2,2,3) same).
  • WaveNet model from the benchmark — passes within 1e-5 against the Keras reference.
  • Mini 2D dilated model with padding='same' and asymmetric dilation (2, 3) — passes.
  • Forward pass on VGG19 (no dilation, exercises the unchanged s1x1 and strided paths) — runs.

🤖 Generated with Claude Code

Previously, Conv1D/Conv2D with dilation_rate > 1 inflated the filter with
zeros at construction time (dilate_filter -> dilate_tensor). For example, a
3-tap kernel with dilation_rate=64 became a 129-tap kernel with 126 zero
entries, and the Eigen GEMM still multiplied through every entry.

Keep filters un-dilated and pass dilation_rate down to convolve(). For the
strides=(1,1) fast path, loop over the un-dilated (y_filt, x_filt) positions
and do one GEMM per position, indexing the input at (y_filt*dil_y,
x_filt*dil_x). The s1x1 output_temp wraparound trick still works as long as
out_width_temp = out_width + (f_width-1)*dil_x.

Benchmark (3-stack WaveNet, 16 channels, input 18000x3, dilations 1..64):
  master:    159 ms
  this PR:    49 ms (3.3x faster; matches the no-dilation baseline of 46 ms)

convolve_transposed and depthwise/separable conv keep pre-dilating filters
(separate code paths, not the bottleneck reported in #132). Conv3D is left
for a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Dobiasd
Dobiasd merged commit 431a215 into master Apr 27, 2026
4 checks passed
@Dobiasd
Dobiasd deleted the fix-dilated-convolution-perf branch April 27, 2026 12:32
@Dobiasd Dobiasd mentioned this pull request Apr 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant