Skip to content

Add streaming filter support for conv1d, resample_poly, channelize_poly - #1209

Merged
tbensonatl merged 2 commits into
mainfrom
tbenson/add-streaming-support-for-filters
Jul 24, 2026
Merged

Add streaming filter support for conv1d, resample_poly, channelize_poly#1209
tbensonatl merged 2 commits into
mainfrom
tbenson/add-streaming-support-for-filters

Conversation

@tbensonatl

Copy link
Copy Markdown
Collaborator

Add Conv1DStream, ResamplePolyStream, and ChannelizePolyStream objects that filter arbitrarily long signals delivered in segments. Segments of any size are supplied via feed() and a final flush() emits the trailing outputs and ends the stream. The concatenated outputs match the corresponding one-shot transform. Objects are created with the make_*_stream() factories and support CUDA and host executors.

Each object retains only a small history buffer between calls, and feed()/flush() write into a caller-provided output buffer sized once via max_output(), so no allocation occurs during streaming. conv1d supports FULL/SAME/VALID modes. To support the internal windowed one-shot calls, the resample_poly and channelize_poly transforms and kernels gain output-window offset parameters.

See examples/streaming.cu and the API Reference section Signal and Image Processing / Streaming for usage and per-transform caveats. Tests cover one-shot equivalence across filter/segment configurations, varying segment schedules, end-of-stream semantics, and host executors.

Add Conv1DStream, ResamplePolyStream, and ChannelizePolyStream objects
that filter arbitrarily long signals delivered in segments. Segments of
any size are supplied via feed() and a final flush() emits the trailing
outputs and ends the stream. The concatenated outputs match the
corresponding one-shot transform. Objects are created with the
make_*_stream() factories and support CUDA and host executors.

Each object retains only a small history buffer between calls, and
feed()/flush() write into a caller-provided output buffer sized once
via max_output(), so no allocation occurs during streaming. conv1d
supports FULL/SAME/VALID modes. To support the internal windowed
one-shot calls, the resample_poly and channelize_poly transforms and
kernels gain output-window offset parameters.

See examples/streaming.cu and the API Reference section Signal and
Image Processing / Streaming for usage and per-transform caveats.
Tests cover one-shot equivalence across filter/segment configurations,
varying segment schedules, end-of-stream semantics, and host executors.

Signed-off-by: Thomas Benson <tbenson@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Jul 23, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds Conv1DStream, ResamplePolyStream, and ChannelizePolyStream streaming wrapper objects that filter arbitrarily long signals supplied in variable-sized segments. Each object retains only a small history buffer between calls, using a ping-pong allocation to avoid aliasing; outputs are written into a caller-provided buffer sized once via max_output(), so no dynamic allocation occurs during streaming. The underlying one-shot transforms gain an out_elem_offset parameter that lets a windowed call compute only the output elements owned by one feed.

  • Conv1DStream wraps direct-method conv1d in FULL/SAME/VALID modes, retaining L−1 samples and computing a mode-appropriate startup skip and flush tail so the concatenated outputs match a one-shot call for total signal length ≥ L.
  • ResamplePolyStream and ChannelizePolyStream use a buffer-alignment invariant (retain-history start kept a multiple of dr or lcm(M,D)) so the local output grid coincides with the global grid, enabling a windowed one-shot call to compute exactly the outputs owned by each segment.
  • All kernel variants for channelize_poly and resample_poly receive the window-offset parameter; the unused ResamplePoly1D_PhaseBlock kernel (previously compiled but never dispatched) is removed as dead code.

Confidence Score: 5/5

Safe to merge. The streaming wrappers are purely additive and the windowed one-shot changes are backward-compatible (offset defaults to 0). All changed kernels default to prior behavior when out_elem_offset == 0.

The streaming wrappers are purely additive and the windowed one-shot changes are backward-compatible. All kernel modifications are mechanical index shifts that leave the zero-offset case identical to the pre-PR behavior. The new tests cover windowed one-shot equivalence for every kernel dispatch variant, multiple segment schedules, short-stream edge cases, and host executors. The only findings are style-level: a duplicated helper function and a structural asymmetry between two streaming classes.

No files require special attention for correctness. Reviewers interested in the alignment math may want to check the channelize_plan and resample_plan helper functions in the streaming headers.

Important Files Changed

Filename Overview
include/matx/streaming/conv1d_stream.h New streaming wrapper for 1D FIR convolution. Mode-specific skip/flush logic is correct; ping-pong retain buffer avoids aliasing; max_output safely covers all modes including flush. L=1 degenerate case handled explicitly.
include/matx/streaming/resample_poly_stream.h New streaming resampler; buffer-alignment invariant (dr-aligned retain start) ensures per-feed output grid matches global grid. The retain copy in the retain_len_==0 path lacks the retain_len_next>0 guard present in ChannelizePolyStream, though retain_len_next>=1 always holds here. nonneg_mod duplicated from ChannelizePolyStream.
include/matx/streaming/channelize_poly_stream.h New streaming channelizer; lcm(M,D)-alignment invariant is mathematically sound and well-documented. Edge cases (history_len_=0 for L=1/M=D=1, zero-block flush, retain_len_next guard) are all handled correctly.
include/matx/streaming/stream_detail.h New shared helpers: StreamSlicePlan struct and SegmentLifecycleGuard RAII wrapper. Guard correctly handles PostRun on intermediate exceptions (after PreRun).
include/matx/kernels/channelize_poly.cuh All four kernel variants correctly introduce out_elem_offset — global index g = t + out_elem_offset drives input-footprint and phase calculations while write row stays local t. Changes are mechanical and symmetric across kernels.
include/matx/kernels/resample_poly.cuh Adds out_offset to ElemBlock and WarpCentric kernels; up_ind = (out_ind + out_offset) * down correctly shifts polyphase math while write position stays local. Dead ResamplePoly1D_PhaseBlock kernel removed.
include/matx/transforms/channelize_poly.h All internal dispatch helpers and both public channelize_poly_impl overloads (CUDA and host) correctly thread out_elem_offset through. Assertion relaxed from exact-match to window-fits-grid, correctly documented.
include/matx/transforms/resample_poly.h Both CUDA and host overloads of matxResamplePoly1DInternal gain out_offset; validation correctly requires window fits within full grid. PhaseBlock dispatch path removed (was unreachable).
include/matx/operators/conv.h Promotes MAX_MIN_DIMENSION_DIRECT to namespace-level CONV1D_MAX_MIN_DIMENSION_DIRECT, allowing Conv1DStream's constructor to enforce the 1024-tap direct-method limit.
test/00_transform/StreamingConv.cu Good coverage: one-shot equivalence, varying segment schedules, all three modes (FULL/SAME/VALID), host executor, and edge cases (single-sample segments, length-1 filter).
test/00_transform/StreamingResample.cu Covers one-shot equivalence for multiple up/down ratios (coprime and non-coprime), varying segment sizes, host executor, and even-length filters.
test/00_transform/StreamingChannelize.cu Covers maximally-decimated, integer-oversampled, and rational-oversampled configurations, multiple segment schedules, host executor, and the degenerate M=D=1 single-channel case.

Sequence Diagram

sequenceDiagram
    participant User
    participant StreamObj as Conv1DStream / ResamplePolyStream / ChannelizePolyStream
    participant RetainBuf as Retain Buffer (ping-pong)
    participant OneShot as One-shot Transform

    User->>StreamObj: "make_*_stream(filter, params, exec)"
    StreamObj->>RetainBuf: allocate 2xhistory_len (zeros)
    StreamObj->>OneShot: materialize filter once

    loop for each segment
        User->>StreamObj: feed(new_samples, out_buf)
        StreamObj->>StreamObj: compute plan (lo, cnt) from sizes only
        StreamObj->>StreamObj: validate out_buf before lifecycle
        StreamObj->>RetainBuf: read cur_retain (half A)
        StreamObj->>OneShot: "one-shot([retain|new], out_elem_offset=lo, cnt)"
        OneShot-->>User: cnt outputs written to out_buf[0:cnt]
        StreamObj->>RetainBuf: write next_retain (half B, disjoint)
        StreamObj->>StreamObj: flip ping-pong, update retain_len
        StreamObj-->>User: return cnt
    end

    User->>StreamObj: flush(out_buf)
    StreamObj->>RetainBuf: read cur_retain (final tail)
    StreamObj->>OneShot: "one-shot(retain, out_elem_offset=lo, cnt)"
    OneShot-->>User: trailing outputs written to out_buf[0:cnt]
    StreamObj-->>User: "return cnt, mark flushed=true"
Loading

Reviews (2): Last reviewed commit: "Return count rather than slice from feed..." | Re-trigger Greptile

Comment thread include/matx/streaming/resample_poly_stream.h
@tbensonatl

Copy link
Copy Markdown
Collaborator Author

/build

@tbensonatl tbensonatl self-assigned this Jul 23, 2026
@tbensonatl
tbensonatl requested a review from cliffburdick July 23, 2026 22:27
Comment thread docs_input/api/signalimage/streaming/channelize_poly_stream.rst Outdated
Comment thread docs_input/api/signalimage/streaming/index.rst Outdated
MatX does not support 0-sized slices, but there are some cases in which
feed()/flush() return no new samples. Rather than return a slice of the
user-provided output, instead return the count of newly generated output
samples. The user then creates their own slice, or otherwise consumes
the output, if count > 0.

Signed-off-by: Thomas Benson <tbenson@nvidia.com>
@tbensonatl

Copy link
Copy Markdown
Collaborator Author

The initial version of this PR returned a slice of the user-provided output tensor from the feed()/flush() methods. There are cases in which there are zero new output samples, but MatX does not allow for zero-sized slices, so that approach failed in such cases. The API has been updated so that feed()/flush() instead return the count of output samples that have been written to the user-provided output tensor/operator. The user can then slice (if count > 0) or otherwise consume as needed.

@tbensonatl

Copy link
Copy Markdown
Collaborator Author

/build

@cliffburdick cliffburdick left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work!

@coveralls

Copy link
Copy Markdown

Coverage Status

Coverage is 93.659%tbenson/add-streaming-support-for-filters into main. No base build found for main.

@tbensonatl
tbensonatl merged commit d9e618e into main Jul 24, 2026
2 checks passed
@tbensonatl
tbensonatl deleted the tbenson/add-streaming-support-for-filters branch August 13, 2026 16:13
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.

3 participants