Skip to content

[Kernels] Vectorize misaligned SiLU inputs with a per-SM cutoff - #163

Draft
bjf-frz wants to merge 6 commits into
InferMatrixLab:mainfrom
bjf-frz:perf/silu-unaligned-adaptive
Draft

bjf-frz wants to merge 6 commits into
InferMatrixLab:mainfrom
bjf-frz:perf/silu-unaligned-adaptive

Conversation

@bjf-frz

@bjf-frz bjf-frz commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Required human contributor answers

The author's public answers in #160, “Required human contributor answers” remain the intent for this follow-up; this PR improves that merged operator rather than introducing a new framework or changing its arithmetic contract.

1. What outcome are you trying to achieve?

Reuse the author's answer in #160, question 1: develop native CUDA operators with a complete, reusable build, integration and validation path. This follow-up improves the first operator's supported misaligned-input path.

2. What is the highest-leverage variable for that outcome?

Reuse #160, question 2: activation memory movement, kernel launches and efficient parallel computation while preserving the numerical contract. Here the changed variable is device latency for independently shifted contiguous inputs.

3. How will this work move that variable rather than create busywork?

Reuse #160, question 3: vectorized fused computation, preserved rounding, numerical checks and matched performance measurements. The concrete extension is safe aligned reads for misaligned inputs, with a measured scalar fallback where reconstruction costs more.

4. Who is the customer or user, and what do they actually need?

Reuse #160, question 4: the author developing native CUDA operators needs a complete implementation/build/validation path that can support subsequent operators.

PR necessity and bugfix evidence

The author explicitly requested a PR for this follow-up. It requires native load/dispatch changes; an existing configuration cannot enable vector processing of these inputs. This is a measured performance optimization, not a claim that the previous scalar implementation returned incorrect results.

Purpose

The existing SiLU-and-Mul operator uses scalar accesses when contiguous gate or up views have independently misaligned addresses. For a sufficiently large input this PR loads aligned 16-byte input vectors and reconstructs the requested logical vectors in registers. Input-view boundaries use scalar loads, with no out-of-view read. The aligned and misaligned paths share the vector compute and 16-byte output-store loop; output elements before the first aligned vector and after the last complete vector are scalar. No input packing or additional kernel launch is introduced.

Small misaligned inputs retain the original scalar path. The cutoff uses the input GPU's actual SM count and 16 bytes per vector:

threshold_elements = SM_count * 160 reference blocks/SM * 256 threads/block * (16 / sizeof(dtype))

That is 8 elements for BF16/FP16 and 4 for FP32. On the 148-SM B300, the thresholds are 48,496,640 and 24,248,320 elements, respectively. The 160 reference blocks/SM determine the cutoff; the launched grid is calculated separately and capped. Aligned inputs retain the direct vector path. This cutoff reflects the requested B300-based heuristic, not a demonstrated optimum on other hardware.

The operator preserves the existing arithmetic contract: compute SiLU in FP32, round it to the input dtype, then multiply by up. It also retains the existing fresh output, contiguous-input, stream, validation, and empty-input behavior.

Test Plan

vllm-rlt Version / Commit: base 989921ac1d3897c5c43e27ec8cddbc91fa953194, tested PR head 21c706e646ae350ab20844ec24bd6dc63ed6e27b.

  • Built the head for SM80 and SM103; ran on B300 SXM6 AC, 148 SMs. Python 3.12.3, torch 2.13.0+cu130, CUDA toolkit 13.4 and driver 610.43.02.
  • On the final head, pre-commit run --all-files passed Ruff lint, Ruff formatting and CPU regression tests.
  • On the final head, 41 selected GPU tests passed (152 deselected).
  • In the benchmark below, all 126 finite random-input cases produce elementwise-equal values across all three implementations (torch.equal; signed zero is not distinguished). This is not an exhaustive numerical proof.
  • Before the access-mode rename, the same GPU test suite passed 241 tests at fe71d62; Compute Sanitizer memcheck passed 24 cases with 0 errors. The subsequent source difference to the current head is the enum name and documentation only; current-head targeted tests are reported separately above.

Test Result: performance

Measured on one idle B300, with no other compute process detected on that device during the run. The other GPUs were in use. These are operator-level measurements, not model throughput. Each point reports the median of 5 shuffled rounds of unprofiled CUDA Graph timing, 32 calls/graph, 30 graph replays per round (8 at the threshold); each function has 10 warmups. Clocks were not locked. Inputs use fixed seed 42, gate sampled from normal(std=3), up from normal(std=1). Gate/up are separate contiguous allocations; offsets in the table are in elements. The GPU results are limited to this hardware and workload.

The three code versions are:

  1. PR: 21c706e, the restored output-prefix implementation.
  2. Main: upstream main 989921a, compiled from the same SiLU source in a separate operator namespace; misaligned inputs use scalar access.
  3. vLLM: local vLLM 2b678ac739166bd0356883048673bbd1c34a3b0e, compiled directly from its original csrc/activation_kernels.cu. Its interface consumes one packed [gate | up] tensor, so it is shown in two timing modes:
    • vLLM packed: packing and output allocation are done before the timed graph. This measures the prepacked vLLM kernel and its packed input starts aligned; the original independent gate/up offsets no longer apply to this input.
    • vLLM + cat: torch.cat((gate, up), dim=-1), output allocation, and the vLLM kernel are inside the timed graph. CUDA Graph timings measure device work; host-side allocation overhead is not included. This column reflects adapting separate inputs to the vLLM interface.

Representative latency, in microseconds (lower is better):

Shape gate/up offsets PR path PR Main vLLM packed vLLM + cat
1 × 5632 (1,3) scalar 1.282 1.282 1.986 3.585
16 × 5632 (1,3) scalar 1.474 1.474 1.986 3.970
64 × 5632 (1,3) scalar 2.434 2.434 2.050 4.547
128 × 5632 (1,3) scalar 3.732 3.729 2.114 5.314
256 × 5632 (1,3) scalar 5.417 5.442 3.106 7.886
1024 × 5632 (1,3) scalar 10.715 10.703 7.053 20.879
128 × 5632 (0,0) aligned vec 1.988 1.986 2.074 3.970
128 × 5633 (1,3) scalar 3.715 3.734 3.522 7.362
1024 × 5633 (1,3) scalar 10.719 10.720 14.470 29.473

All rows above are BF16. For the 120 common-shape measurements (BF16/FP16/FP32, 1–1024 rows, widths 5632/5633, aligned and independently shifted inputs), the PR/main latency ratio has a median of approximately 1.00; the misaligned inputs are below the cutoff and use the scalar path. For the 60 aligned cases, the observed main/PR speedup ranges from 0.960× to 1.015×. Small differences at these microsecond times are workload-specific; no blanket aligned-path speedup is claimed.

The cutoff is easier to see with fixed width 5632 and neighboring row counts. In each dtype the first row is just below the cutoff and the second is just above it:

dtype / shape gate/up offsets PR path PR Main vLLM packed vLLM + cat
BF16 8610 × 5632 (1,3) scalar 116.207 116.201 51.196 219.484
BF16 8611 × 5632 (1,3) misaligned vec 64.196 115.832 51.240 218.988
FP16 8610 × 5632 (1,3) scalar 117.404 117.420 49.540 218.096
FP16 8611 × 5632 (1,3) misaligned vec 63.284 118.460 49.452 217.633
FP32 4305 × 5632 (1,3) scalar 67.632 67.640 43.206 141.276
FP32 4306 × 5632 (1,3) misaligned vec 53.576 67.704 43.256 141.172

Immediately above the cutoff, the PR is 1.80× faster for BF16, 1.87× for FP16, and 1.26× for FP32 than main on these shapes. Below it, the PR and main remain on the scalar path and have nearly identical latencies. For the above-cutoff examples, the PR's five-round ranges were 64.171–64.220 µs (BF16), 63.244–63.292 µs (FP16), and 53.564–53.592 µs (FP32); main's corresponding ranges were 115.800–115.840, 118.436–118.484, and 67.688–67.720 µs. These are repetitions of one benchmark run, not independent multi-run confidence intervals.

The vLLM packed column can be faster: at BF16 [8611, 5632] it takes 51.240 µs versus the PR's 64.196 µs, while adapting separate inputs with cat takes 218.988 µs. The packed layout is different, so that comparison does not isolate the cost of independent misalignment. For width 5633 the local vLLM dispatch falls back to scalar because the hidden dimension does not divide its vector width; the RLT operator handles the separately allocated tensors as flat contiguous arrays. Short ops-only and full profiles confirm the scalar and vector branches and show a separate cat kernel for the final column; profiled timings are not used as latency evidence.

All 126 measured cases (CUDA Graph median, µs)

BF16

Shape gate/up offsets PR path PR Main vLLM packed vLLM + cat
1 × 5632 (0,0) aligned vec 1.570 1.545 1.986 3.202
2 × 5632 (0,0) aligned vec 1.540 1.542 1.986 3.266
4 × 5632 (0,0) aligned vec 1.602 1.602 1.986 3.266
8 × 5632 (0,0) aligned vec 1.602 1.540 1.986 3.330
16 × 5632 (0,0) aligned vec 1.579 1.602 1.986 3.394
32 × 5632 (0,0) aligned vec 1.602 1.602 1.988 3.397
64 × 5632 (0,0) aligned vec 1.732 1.730 2.050 3.526
128 × 5632 (0,0) aligned vec 1.988 1.986 2.074 3.970
256 × 5632 (0,0) aligned vec 2.562 2.561 3.085 5.500
1024 × 5632 (0,0) aligned vec 6.186 6.170 7.046 13.120
1 × 5632 (1,3) scalar 1.282 1.282 1.986 3.585
2 × 5632 (1,3) scalar 1.284 1.282 1.986 3.650
4 × 5632 (1,3) scalar 1.346 1.346 1.986 3.650
8 × 5632 (1,3) scalar 1.355 1.353 1.986 3.713
16 × 5632 (1,3) scalar 1.474 1.474 1.986 3.970
32 × 5632 (1,3) scalar 1.794 1.794 1.988 4.067
64 × 5632 (1,3) scalar 2.434 2.434 2.050 4.547
128 × 5632 (1,3) scalar 3.732 3.729 2.114 5.314
256 × 5632 (1,3) scalar 5.417 5.442 3.106 7.886
1024 × 5632 (1,3) scalar 10.715 10.703 7.053 20.879
1 × 5633 (0,0) aligned vec 1.922 1.922 3.395 4.643
2 × 5633 (0,0) aligned vec 1.922 1.922 3.422 5.121
4 × 5633 (0,0) aligned vec 1.922 1.922 3.423 4.997
8 × 5633 (0,0) aligned vec 1.564 1.576 3.439 5.114
16 × 5633 (0,0) aligned vec 1.602 1.602 3.458 5.375
32 × 5633 (0,0) aligned vec 1.602 1.602 3.458 5.509
64 × 5633 (0,0) aligned vec 1.794 1.781 3.458 6.019
128 × 5633 (0,0) aligned vec 1.986 1.986 3.484 6.812
256 × 5633 (0,0) aligned vec 2.524 2.522 5.121 9.663
1024 × 5633 (0,0) aligned vec 6.170 6.173 14.473 27.249
1 × 5633 (1,3) scalar 1.282 1.282 3.351 5.105
2 × 5633 (1,3) scalar 1.282 1.284 3.455 5.778
4 × 5633 (1,3) scalar 1.346 1.346 3.418 5.763
8 × 5633 (1,3) scalar 1.353 1.353 3.458 5.815
16 × 5633 (1,3) scalar 1.474 1.474 3.427 6.031
32 × 5633 (1,3) scalar 1.803 1.796 3.458 6.168
64 × 5633 (1,3) scalar 2.434 2.445 3.520 6.609
128 × 5633 (1,3) scalar 3.715 3.734 3.522 7.362
256 × 5633 (1,3) scalar 5.441 5.440 5.121 10.314
1024 × 5633 (1,3) scalar 10.719 10.720 14.470 29.473
8610 × 5632 (1,3) scalar 116.207 116.201 51.196 219.484
8611 × 5632 (1,3) misaligned vec 64.196 115.832 51.240 218.988

FP16

Shape gate/up offsets PR path PR Main vLLM packed vLLM + cat
1 × 5632 (0,0) aligned vec 1.540 1.547 1.922 3.200
2 × 5632 (0,0) aligned vec 1.602 1.598 1.986 3.265
4 × 5632 (0,0) aligned vec 1.602 1.577 1.986 3.266
8 × 5632 (0,0) aligned vec 1.600 1.542 1.986 3.330
16 × 5632 (0,0) aligned vec 1.602 1.602 1.986 3.330
32 × 5632 (0,0) aligned vec 1.602 1.602 1.986 3.395
64 × 5632 (0,0) aligned vec 1.734 1.730 2.007 3.522
128 × 5632 (0,0) aligned vec 1.986 1.986 2.050 3.970
256 × 5632 (0,0) aligned vec 2.529 2.562 3.012 5.421
1024 × 5632 (0,0) aligned vec 6.086 6.087 6.784 12.820
1 × 5632 (1,3) scalar 1.308 1.303 1.926 3.586
2 × 5632 (1,3) scalar 1.284 1.284 1.986 3.648
4 × 5632 (1,3) scalar 1.299 1.282 1.986 3.650
8 × 5632 (1,3) scalar 1.359 1.359 1.986 3.707
16 × 5632 (1,3) scalar 1.474 1.474 1.986 3.934
32 × 5632 (1,3) scalar 1.794 1.794 1.986 4.063
64 × 5632 (1,3) scalar 2.434 2.434 2.010 4.545
128 × 5632 (1,3) scalar 3.718 3.740 2.050 5.313
256 × 5632 (1,3) scalar 5.440 5.441 3.015 7.799
1024 × 5632 (1,3) scalar 10.694 10.699 6.787 20.589
1 × 5633 (0,0) aligned vec 1.922 1.922 3.266 4.547
2 × 5633 (0,0) aligned vec 1.922 1.922 3.364 5.006
4 × 5633 (0,0) aligned vec 1.922 1.922 3.330 4.931
8 × 5633 (0,0) aligned vec 1.602 1.602 3.394 4.998
16 × 5633 (0,0) aligned vec 1.602 1.602 3.394 5.270
32 × 5633 (0,0) aligned vec 1.602 1.602 3.394 5.445
64 × 5633 (0,0) aligned vec 1.730 1.739 3.443 5.958
128 × 5633 (0,0) aligned vec 1.986 1.986 3.436 6.735
256 × 5633 (0,0) aligned vec 2.501 2.500 5.055 9.587
1024 × 5633 (0,0) aligned vec 6.091 6.095 14.267 27.010
1 × 5633 (1,3) scalar 1.282 1.282 3.330 4.991
2 × 5633 (1,3) scalar 1.282 1.284 3.340 5.712
4 × 5633 (1,3) scalar 1.346 1.320 3.394 5.697
8 × 5633 (1,3) scalar 1.410 1.410 3.377 5.761
16 × 5633 (1,3) scalar 1.474 1.474 3.394 5.955
32 × 5633 (1,3) scalar 1.794 1.794 3.394 6.019
64 × 5633 (1,3) scalar 2.435 2.434 3.411 6.535
128 × 5633 (1,3) scalar 3.765 3.776 3.458 7.290
256 × 5633 (1,3) scalar 5.441 5.441 5.057 10.209
1024 × 5633 (1,3) scalar 10.691 10.690 14.244 29.209
8610 × 5632 (1,3) scalar 117.404 117.420 49.540 218.096
8611 × 5632 (1,3) misaligned vec 63.284 118.460 49.452 217.633

FP32

Shape gate/up offsets PR path PR Main vLLM packed vLLM + cat
1 × 5632 (0,0) aligned vec 1.346 1.346 2.182 3.458
2 × 5632 (0,0) aligned vec 1.346 1.346 2.242 3.459
4 × 5632 (0,0) aligned vec 1.346 1.346 2.242 3.650
8 × 5632 (0,0) aligned vec 1.346 1.346 2.242 3.650
16 × 5632 (0,0) aligned vec 1.361 1.380 2.243 3.754
32 × 5632 (0,0) aligned vec 1.476 1.476 2.244 3.906
64 × 5632 (0,0) aligned vec 1.602 1.619 2.306 4.226
128 × 5632 (0,0) aligned vec 1.986 1.986 2.307 4.800
256 × 5632 (0,0) aligned vec 2.690 2.690 2.882 6.617
1024 × 5632 (0,0) aligned vec 6.856 6.732 7.808 22.994
1 × 5632 (1,3) scalar 1.282 1.282 2.242 3.718
2 × 5632 (1,3) scalar 1.282 1.282 2.242 4.034
4 × 5632 (1,3) scalar 1.301 1.284 2.242 4.034
8 × 5632 (1,3) scalar 1.376 1.357 2.241 4.034
16 × 5632 (1,3) scalar 1.474 1.474 2.242 4.293
32 × 5632 (1,3) scalar 1.794 1.794 2.242 4.417
64 × 5632 (1,3) scalar 2.437 2.443 2.259 4.878
128 × 5632 (1,3) scalar 3.714 3.714 2.306 5.635
256 × 5632 (1,3) scalar 5.379 5.379 2.883 7.489
1024 × 5632 (1,3) scalar 18.208 18.058 7.820 33.839
1 × 5633 (0,0) aligned vec 1.668 1.668 3.217 4.673
2 × 5633 (0,0) aligned vec 1.666 1.666 3.266 5.105
4 × 5633 (0,0) aligned vec 1.346 1.346 3.266 4.928
8 × 5633 (0,0) aligned vec 1.346 1.359 3.266 5.164
16 × 5633 (0,0) aligned vec 1.404 1.348 3.268 5.288
32 × 5633 (0,0) aligned vec 1.474 1.474 3.330 5.684
64 × 5633 (0,0) aligned vec 1.607 1.604 3.302 6.019
128 × 5633 (0,0) aligned vec 1.986 1.986 3.394 6.688
256 × 5633 (0,0) aligned vec 2.690 2.691 4.822 9.538
1024 × 5633 (0,0) aligned vec 6.785 6.720 13.509 31.203
1 × 5633 (1,3) scalar 1.282 1.282 3.212 5.060
2 × 5633 (1,3) scalar 1.282 1.282 3.253 5.552
4 × 5633 (1,3) scalar 1.282 1.301 3.266 5.521
8 × 5633 (1,3) scalar 1.404 1.357 3.266 5.571
16 × 5633 (1,3) scalar 1.476 1.474 3.266 5.824
32 × 5633 (1,3) scalar 1.794 1.794 3.267 5.953
64 × 5633 (1,3) scalar 2.455 2.444 3.330 6.429
128 × 5633 (1,3) scalar 3.714 3.714 3.393 7.169
256 × 5633 (1,3) scalar 5.382 5.379 4.820 10.117
1024 × 5633 (1,3) scalar 18.093 18.179 13.505 40.361
4305 × 5632 (1,3) scalar 67.632 67.640 43.206 141.276
4306 × 5632 (1,3) misaligned vec 53.576 67.704 43.256 141.172

The benchmark reuses the repository's benchmarks/kernels/silu_and_mul.py timing and profiling helpers. The standalone comparison script, all five timing samples including eager timings, native build metadata, profiles, and GPU process-monitor log are retained in the local ignored artifacts/silu-unified-20261103/ directory; they are not committed. The displayed table includes all measured shapes.

AI assistance was used for implementation, measurements and author self-review.

@bjf-frz bjf-frz changed the title [Kernels] Vectorize misaligned SiLU inputs with a measured scalar fallback [Kernels] Vectorize misaligned SiLU inputs with a per-SM cutoff Nov 3, 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