Skip to content

[BackendBench] Add Helion DSL kernel generation support - #200

Merged
jiannanWang merged 1 commit into
mainfrom
karthickai/stack/1
Nov 11, 2025
Merged

[BackendBench] Add Helion DSL kernel generation support#200
jiannanWang merged 1 commit into
mainfrom
karthickai/stack/1

Conversation

@karthickai

@karthickai karthickai commented Nov 10, 2025

Copy link
Copy Markdown
Contributor

Stacked PRs:


This PR adds support for Helion DSL to BackendBench and fixes #31

High-level view: Helion is a higher-level abstraction over Triton that:

  • Uses PyTorch-style syntax with tiling primitives (hl.tile())
  • Automates grid/block configuration and memory access patterns
  • Provides better autotuning capabilities over many Triton configurations

Added --dsl helion flag it enables helion DSL for kernel generation.
Added --daemon/--no-daemon flag to control worker process daemon mode (default daemon=True).
--no-daemon must be set for helion because helion autotuning spawns subprocesses.

helion autotuning can take significant time so set HELION_AUTOTUNE_EFFORT=none env variable to disable autotuning for faster execution.

# BackendBench Run Summary (ops: add, HELION_AUTOTUNE_EFFORT=none)

## Command

python -m BackendBench.scripts.main --suite torchbench --topn 5 --backend llm-relay --ops add --dsl helion

## Results

| Metric | Value |
|--------|-------|
| Correctness Score | 1.00 |
| Performance Score (geomean speedup) | 0.77 |
| Perf@1.0 Score | 0.20 |

### Metric Descriptions

- **Correctness Score**: Mean pass rate over all operators
- **Performance Score**: Geometric mean speedup over all operators
- **Perf@1.0 Score**: Rate of correct samples with a speedup greater than 1.0

## Output Files

The following files are saved in this directory:

- `full_results.json`: Complete test results for all operators
- `operator_summary.csv`: Operator-level summary statistics
- `failed_tests.json`: Log of failed tests (if any)
- `OVERALL_SUMMARY.md`: This file
### Operator Speedups vs Eager in Descending Order

| Operator | Correctness Ratio | Speedup vs Eager |
|----------|-----------|----------------|
| addcmul.default | 100.0000% | 1.2080x|
| addmm.default | 100.0000% | 0.9985x|
| add.Scalar | 100.0000% | 0.9306x|
| add_.Tensor | 100.0000% | 0.5689x|
| add.Tensor | 100.0000% | 0.4234x|

# BackendBench Run Summary  (ops: relu, HELION_AUTOTUNE_EFFORT=none)

## Command
python -m BackendBench.scripts.main --suite torchbench --topn 5 --backend llm-relay --ops relu --dsl helion


## Results

| Metric | Value |
|--------|-------|
| Correctness Score | 1.00 |
| Performance Score (geomean speedup) | 0.94 |
| Perf@1.0 Score | 0.50 |

### Metric Descriptions

- **Correctness Score**: Mean pass rate over all operators
- **Performance Score**: Geometric mean speedup over all operators
- **Perf@1.0 Score**: Rate of correct samples with a speedup greater than 1.0

## Output Files

The following files are saved in this directory:

- `full_results.json`: Complete test results for all operators
- `operator_summary.csv`: Operator-level summary statistics
- `failed_tests.json`: Log of failed tests (if any)
- `OVERALL_SUMMARY.md`: This file
### Operator Speedups vs Eager in Descending Order

| Operator | Correctness Ratio | Speedup vs Eager |
|----------|-----------|----------------|
| relu_.default | 100.0000% | 1.0044x|
| relu.default | 100.0000% | 1.0022x|
| leaky_relu_.default | 100.0000% | 0.9960x|
| leaky_relu.default | 100.0000% | 0.7693x|

stack-info: PR: #200, branch: karthickai/stack/1
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Nov 10, 2025
@jiannanWang

Copy link
Copy Markdown
Contributor

Thank you for adding Helion support! I have a question about how Helion uses PyTorch operators to generate Triton kernels. Specifically, I'm concerned about the potential for infinite loops in BackendBench. If the Helion kernel implementation (e.g., add__Tensor_kernel_impl) calls torch.add, and BackendBench registers the Helion kernel to replace the PyTorch eager op (torch.ops.aten.add.Tensor), wouldn't that result in a recursive loop?

@karthickai

karthickai commented Nov 11, 2025

Copy link
Copy Markdown
Contributor Author

Thank you for adding Helion support! I have a question about how Helion uses PyTorch operators to generate Triton kernels. Specifically, I'm concerned about the potential for infinite loops in BackendBench. If the Helion kernel implementation (e.g., add__Tensor_kernel_impl) calls torch.add, and BackendBench registers the Helion kernel to replace the PyTorch eager op (torch.ops.aten.add.Tensor), wouldn't that result in a recursive loop?

It will not cause infinite recursion because Helion uses make_fx in symbolic tracing mode. During compilation, make_fx records torch.add() as a node but does not execute it. At runtime, only the compiled Triton kernel executes.

for example

import torch
import helion
import helion.language as hl

os.environ['HELION_AUTOTUNE_EFFORT'] = 'none'

# llm generated kernel I modified to torch.add instead of +
@helion.kernel()
def my_add_kernel(x, y):
    x, y = torch.broadcast_tensors(x, y)
    out = torch.empty(x.shape, dtype=torch.promote_types(x.dtype, y.dtype), device=x.device)
    out_flat = out.view(-1)
    x_flat = x.contiguous().view(-1)
    y_flat = y.contiguous().view(-1)

    for tile in hl.tile(out_flat.size()):
        out_flat[tile] = torch.add(x_flat[tile], y_flat[tile])
    return out

x = torch.randn(10, device='cuda')
y = torch.randn(10, device='cuda')

lib = torch.library.Library("aten", "IMPL")
lib.impl("add.Tensor", my_add_kernel, "CUDA")
out = torch.add(x, y)

output

tensor([-1.1052, -0.9066, -0.1886, -2.5633,  2.8374, -0.8006,  0.7526,  2.2208,
         0.2794,  3.7469], device='cuda:0')

@jiannanWang

Copy link
Copy Markdown
Contributor

Thank you! Just a small request. Could you please change the name of the wrapper function from add_kernel_impl to add__Tensor_kernel_impl? This would align better with our current naming standard.

@karthickai

Copy link
Copy Markdown
Contributor Author

Thank you! Just a small request. Could you please change the name of the wrapper function from add_kernel_impl to add__Tensor_kernel_impl? This would align better with our current naming standard.

I believe it is generating correctlyadd___Tensor_kernel_impl. for example below is full llm generated kernel

import torch
import helion
import helion.language as hl

@helion.kernel()
def add___Tensor_helion_kernel(input_tensor: torch.Tensor, other: torch.Tensor) -> torch.Tensor:
    input_tensor, other = torch.broadcast_tensors(input_tensor, other)
    out = torch.empty(
        input_tensor.shape,
        dtype=torch.promote_types(input_tensor.dtype, other.dtype),
        device=input_tensor.device
    )

    input_flat = input_tensor.contiguous().view(-1)
    other_flat = other.contiguous().view(-1)
    out_flat = out.view(-1)

    for tile in hl.tile(out_flat.size()):
        out_flat[tile] = torch.add(input_flat[tile], other_flat[tile])

    return out

def add___Tensor_kernel_impl(*args, **kwargs) -> torch.Tensor:
    if len(args) >= 2:
        input_tensor = args[0]
        other = args[1]
    elif len(args) == 1 and 'other' in kwargs:
        input_tensor = args[0]
        other = kwargs['other']
    elif 'input' in kwargs and 'other' in kwargs:
        input_tensor = kwargs['input']
        other = kwargs['other']
    else:
        raise ValueError("add_ requires 'input' and 'other' arguments")

    if 'alpha' in kwargs:
        alpha = kwargs['alpha']
        other = other * alpha

    original_input = args[0]
    original_device = input_tensor.device

    if not input_tensor.is_cuda:
        if not torch.cuda.is_available():
            raise RuntimeError("CUDA is not available")
        input_tensor = input_tensor.cuda()

    if torch.is_tensor(other) and not other.is_cuda:
        if not torch.cuda.is_available():
            raise RuntimeError("CUDA is not available")
        other = other.cuda()

    result = add___Tensor_helion_kernel(input_tensor, other)

    if original_device.type == 'cpu':
        result = result.cpu()

    if result.shape == original_input.shape:
        original_input.copy_(result)
    else:

        try:
            original_input.copy_(result)
        except RuntimeError as e:
            raise RuntimeError(f"Cannot perform in-place addition with broadcasting: "
                             f"input shape {original_input.shape} vs result shape {result.shape}. "
                             f"Original error: {e}")

    return original_input

Comment thread BackendBench/prompts.py
def exp(x: torch.Tensor) -> torch.Tensor:
out = torch.empty_like(x)
for tile in hl.tile(x.size()):
out[tile] = torch.exp(x[tile])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this a typical pattern in Helion kernels? In BackendBench, the goal is to avoid falling back to PyTorch, since that would allow the LLM to bypass generating the actual kernel code. However, I'm not sure if Helion handles PyTorch operations differently.

Comment thread BackendBench/prompts.py
return out

# WRAPPER TEMPLATE - Helion kernels are called like Python functions
def add_kernel_impl(*args, **kwargs) -> torch.Tensor:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We are using overload names. Please use something like add__Tensor_kernel_impl here.

@jiannanWang
jiannanWang merged commit 7b15936 into main Nov 11, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

More DSLs

2 participants