Skip to content

[Feature] Extract reusable event-based benchmarking helper#2

Draft
jhinpan wants to merge 1 commit into
mainfrom
feat/profile-tools
Draft

[Feature] Extract reusable event-based benchmarking helper#2
jhinpan wants to merge 1 commit into
mainfrom
feat/profile-tools

Conversation

@jhinpan

@jhinpan jhinpan commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Summary

Extract the existing GPU event benchmark helper from flydsl.autotune into the reusable flydsl.profiling module.

This is intentionally a narrow, non-intrusive first slice of ROCm/FlyDSL#304. It does not move the broader tests/test_common.py utilities or change any kernel, compiler, or backend implementation.

Motivation

FlyDSL already has a small event-based do_bench implementation, but it is nested inside the autotune module. Moving it to a shared package-level module lets other profiling and benchmarking callers reuse the same behavior and gives the autotune path in ROCm/FlyDSL#770 a clearer dependency boundary.

The module does not import ROCDL, NVVM, or FlyDSL compiler bindings. Its current runtime implementation lazily uses PyTorch's torch.cuda event interface, which PyTorch also exposes on HIP builds.

This PR does not implement or claim validation of a FlyDSL NVVM backend. Runtime/backend placement for the future dual-backend direction remains related to ROCm/FlyDSL#813; the focused namespace question is recorded on ROCm/FlyDSL#304.

Changes

  • Add flydsl.profiling.do_bench.
  • Preserve existing valid-call timing behavior:
    • configurable warmup and measured iteration counts;
    • CUDA/HIP event timing on PyTorch's current stream;
    • millisecond results;
    • existing upper-middle median and quantile selection semantics.
  • Support the setup hook introduced in ROCm/FlyDSL#785, running it before each warmup and timed iteration but before the start event so restore/reset work is excluded from kernel latency.
  • Keep flydsl.autotune.do_bench as a compatibility alias.
  • Load PyTorch only when profiling is invoked.
  • Raise focused errors for missing PyTorch, no visible GPU, and invalid warmup/rep/quantile arguments.
  • Document the API and its current-stream contract.
  • Add GPU-free tests for timing, quantiles, compatibility, error paths, and argument validation.

Compatibility is preserved for existing autotune callers: the import path, valid call signature, defaults, and result shape remain unchanged.

Performance

Not applicable. This is a direct extraction of the existing timer and does not change generated kernels or compiler behavior.

A live MI350X/HIP smoke test returned a valid event-based median timing.

Testing

  • GPU-free profiling and existing autotune unit tests:

    PYTHONPATH="$PWD/python${PYTHONPATH:+:$PYTHONPATH}" \
      python3 -m pytest tests/unit/test_profiling.py tests/unit/test_autotune.py -q

    Result: 31 passed.

  • Repository Python style gate (bash scripts/check_python_style.sh --include-local).

  • Targeted Black and Ruff checks for all changed Python files.

  • MI350X/HIP event-timing smoke test.

  • CUDA/NVVM execution was not tested and is not claimed by this PR.

Dependencies

  • No new third-party dependencies added.
  • PyTorch was already used by the extracted implementation and is now imported lazily.

Breaking Changes

None for valid existing calls. flydsl.autotune.do_bench remains available as an alias to the shared implementation.

Upstream references: #304, #770, #813.

Summary by Sourcery

Extract the existing GPU event-based benchmarking helper into a shared profiling module while preserving autotune behavior and public API semantics.

New Features:

  • Introduce the flydsl.profiling.do_bench helper as a reusable GPU event timing utility with support for warmup, repetitions, quantiles, and a per-iteration setup hook.

Bug Fixes:

  • Add explicit validation and clearer error reporting for missing PyTorch, unavailable GPU devices, and invalid warmup/rep/quantile arguments in GPU profiling.

Enhancements:

  • Make PyTorch usage for GPU profiling lazy-loaded and backend-agnostic via a shared torch.cuda accessor.
  • Expose the autotune do_bench entry point as a compatibility alias to the shared profiling helper to maintain existing callers.

Documentation:

  • Document the new flydsl.profiling.do_bench API, its CUDA/HIP event semantics, argument constraints, and current-stream requirements in the testing and benchmarking guide.

Tests:

  • Add backend-agnostic unit tests for the profiling helper covering timing behavior, quantile selection, setup semantics, error paths, argument validation, and the autotune compatibility alias.

Signed-off-by: Jin Pan <jpan236@wisc.edu>
@sourcery-ai

sourcery-ai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Reviewer's Guide

Extract the GPU event-based benchmarking helper into a reusable profiling module while preserving autotune compatibility, adding validation and setup-hook support, and documenting the new API with unit tests.

Sequence diagram for GPU event-based benchmarking with setup hook

sequenceDiagram
    actor BenchmarkCaller
    participant profiling as flydsl.profiling.do_bench
    participant torch_cuda as torch.cuda

    BenchmarkCaller->>profiling: do_bench(fn, warmup, rep, quantiles, setup)
    profiling->>profiling: validate warmup, rep, quantiles
    profiling->>torch_cuda: _get_torch_cuda()
    torch_cuda-->>profiling: cuda_namespace

    loop warmup iterations
        alt setup is not None
            profiling->>BenchmarkCaller: setup()
        end
        profiling->>BenchmarkCaller: fn()
    end
    profiling->>torch_cuda: synchronize()

    loop rep iterations
        alt setup is not None
            profiling->>BenchmarkCaller: setup()
        end
        profiling->>torch_cuda: Event(enable_timing=True) as start
        profiling->>torch_cuda: Event(enable_timing=True) as end
        profiling->>torch_cuda: start.record()
        profiling->>BenchmarkCaller: fn()
        profiling->>torch_cuda: end.record()
        profiling->>torch_cuda: synchronize()
        profiling->>torch_cuda: start.elapsed_time(end)
    end

    profiling->>profiling: sort(times)
    alt quantiles is not None and non-empty
        profiling-->>BenchmarkCaller: selected quantile samples
    else
        profiling-->>BenchmarkCaller: upper-middle median sample
    end
Loading

File-Level Changes

Change Details Files
Introduce shared GPU event-based benchmarking helper and lazy PyTorch CUDA/HIP access in a new profiling module.
  • Add flydsl.profiling.do_bench with warmup, repetition, quantile, and optional setup parameters
  • Implement lazy _get_torch_cuda helper with explicit errors for missing PyTorch or no available device
  • Validate warmup, rep, and quantile arguments and keep upper-middle median and quantile selection semantics
python/flydsl/profiling.py
Make autotune use the shared profiling helper while preserving the existing public API as an alias.
  • Replace local do_bench implementation in autotune with an import alias to flydsl.profiling.do_bench
  • Remove eager torch import from autotune and rely on profiling module for runtime device access
python/flydsl/autotune.py
Document the new profiling helper and add unit tests covering behavior, errors, and compatibility.
  • Extend testing and benchmarking guide with usage and behavioral documentation for flydsl.profiling.do_bench
  • Add GPU-free unit tests for timing behavior, quantiles, setup semantics, argument validation, error paths, and autotune alias compatibility
docs/testing_benchmarking_guide.md
tests/unit/test_profiling.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the GPU benchmarking utility do_bench by moving it from autotune.py to a new shared module profiling.py, accompanied by updated documentation and unit tests. The review feedback correctly identifies an issue where passing quantiles as an iterator or generator would exhaust it during validation, leading to an empty result. It is recommended to convert quantiles to a list first and add a unit test to cover this scenario.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +53 to +54
if quantiles is not None and any(not 0.0 <= q <= 1.0 for q in quantiles):
raise ValueError("quantiles must be between 0 and 1")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If quantiles is passed as an iterator or generator (e.g., a generator expression or a map object), checking it with any(...) will completely exhaust the iterator. Consequently, the subsequent list comprehension on line 78 will iterate over an empty generator and return an empty list [] instead of the expected quantiles.

To prevent this, convert quantiles to a list first if it is not None. This ensures the input is safely consumed and can be iterated over multiple times.

Suggested change
if quantiles is not None and any(not 0.0 <= q <= 1.0 for q in quantiles):
raise ValueError("quantiles must be between 0 and 1")
if quantiles is not None:
quantiles = list(quantiles)
if any(not 0.0 <= q <= 1.0 for q in quantiles):
raise ValueError("quantiles must be between 0 and 1")

Comment on lines +76 to +83
def test_do_bench_quantiles(monkeypatch):
device = _install_fake(monkeypatch)
durations = iter([3.0, 1.0, 5.0, 2.0, 4.0])

def fn():
device.clock += next(durations)

assert profiling.do_bench(fn, warmup=0, rep=5, quantiles=[0.0, 0.5, 0.9, 1.0]) == [1.0, 3.0, 5.0, 5.0]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Add a test case to verify that do_bench correctly handles quantiles when passed as a generator, ensuring it does not get prematurely exhausted by the validation checks.

Suggested change
def test_do_bench_quantiles(monkeypatch):
device = _install_fake(monkeypatch)
durations = iter([3.0, 1.0, 5.0, 2.0, 4.0])
def fn():
device.clock += next(durations)
assert profiling.do_bench(fn, warmup=0, rep=5, quantiles=[0.0, 0.5, 0.9, 1.0]) == [1.0, 3.0, 5.0, 5.0]
def test_do_bench_quantiles(monkeypatch):
device = _install_fake(monkeypatch)
durations = iter([3.0, 1.0, 5.0, 2.0, 4.0])
def fn():
device.clock += next(durations)
# Test with a list
assert profiling.do_bench(fn, warmup=0, rep=5, quantiles=[0.0, 0.5, 0.9, 1.0]) == [1.0, 3.0, 5.0, 5.0]
# Test with a generator to ensure it doesn't get exhausted prematurely
durations = iter([3.0, 1.0, 5.0, 2.0, 4.0])
quantiles_gen = (q for q in [0.0, 0.5, 0.9, 1.0])
assert profiling.do_bench(fn, warmup=0, rep=5, quantiles=quantiles_gen) == [1.0, 3.0, 5.0, 5.0]

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