Repository navigation
[WS1][CUDA][MiniMax-H3] timestep_sinusoid_h3: FP32 timestep features + shared H3 harness (RFC #420 stack 1/10) #483
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Flink-ddd
merged 28 commits into
RL-Align:test-h3
from
fusheng-ji:feat/h3-timestep-sinusoid
Oct 28, 2026
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
0522865
[WS1][CUDA][MiniMax-H3] timestep_sinusoid_h3: FP32 timestep features …
fusheng-ji 23a530e
[WS1][MiniMax-H3] timestep_sinusoid_h3: B200 evidence report and figure
fusheng-ji e0f9dcb
fix(h3): validate native timesteps and extracted weight contracts
fusheng-ji edca559
feat(scripts): H3 prior-art runner: batch invariance, accuracy and la…
fusheng-ji 0d3794d
fix(scripts): keep h3_prior_art mode results next to the report, not …
fusheng-ji 4b01e4f
fix(scripts): name the failing check in the H3 prior-art figure
fusheng-ji d10034c
fix(scripts): shorter batch-invariance panel title in the H3 prior-ar…
fusheng-ji 3146af1
docs(h3): timestep_sinusoid vs existing implementations (B200, 7a82917)
fusheng-ji 2e120f2
fix(scripts): readable H3 prior-art figure when errors are close or l…
fusheng-ji 919b67d
Merge test-h3 refactor and migrate H3 timestep validation
fusheng-ji bf902e4
fix(h3): keep timestep_sinusoid_h3 out of fast-math builds
fusheng-ji 0c5d8ed
Merge branch 'test-h3' into feat/h3-timestep-sinusoid
fusheng-ji b4b58b8
build(h3): gate every H3 bitwise kernel on one fast-math guard
fusheng-ji 231de16
test(h3): skip the CUDA chain e2e when the H3 kernels are not built
fusheng-ji be6a94a
tools(h3): direct bitwise comparison of reuse candidates
fusheng-ji 367883a
evidence(h3): timestep_sinusoid direct bitwise comparison on B200
fusheng-ji 9ba6ce6
docs: move the measurement outputs out of the pull request
fusheng-ji 674a0dc
Merge branch 'test-h3' into feat/h3-timestep-sinusoid
Flink-ddd e504029
docs: point commit hashes at the rewritten history
fusheng-ji 5445e9b
Merge test-h3 into feat/h3-timestep-sinusoid
fusheng-ji 95df2a9
fix(h3): time competing callables in a shuffled order and fix the sin…
fusheng-ji c19a6e5
fix(h3): time candidate and provider with the rotating harness of the…
fusheng-ji c80704f
fix(h3): use one timing harness across the stacked PRs
fusheng-ji 7bb3092
docs(h3): parameterize the sinusoid numerics and say which commits wr…
fusheng-ji c0b4cd8
fix(build): decide the H3 sources and bindings from one predicate, an…
fusheng-ji 2caf0c8
fix(h3): read every spelling of relaxed nvcc math, and keep sinusoid …
fusheng-ji ac225c7
fix(build): read nvcc options files, say why a build left the H3 kern…
fusheng-ji 562d3fa
fix(h3): read quoted options-file names as nvcc does, fail closed on …
fusheng-ji File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # Copyright (c) 2026 RL-Kernel Contributors | ||
|
|
||
| """Benchmark the MiniMax-H3 conditioning ops (RFC #420) against the provider path. | ||
|
|
||
| CUDA-event medians, bandwidth and peak memory per case, plus the backend | ||
| the registry dispatched. Cases live in ``rl_engine/validation/models/h3_report.py``. | ||
|
|
||
| python benchmarks/models/benchmark_h3_conditioning.py --op timestep_sinusoid_h3 | ||
| python benchmarks/models/benchmark_h3_conditioning.py --op all --json out.json | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import json | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| sys.path.insert(0, str(Path(__file__).resolve().parents[2])) | ||
|
|
||
| import torch # noqa: E402 | ||
|
|
||
| from rl_engine.runtime.registry import KernelRegistry # noqa: E402 | ||
| from rl_engine.validation.models.h3_chain import environment # noqa: E402 | ||
| from rl_engine.validation.models.h3_report import PERF_CASES, TIMED_KEYS, measure # noqa: E402 | ||
|
|
||
|
|
||
| def main() -> None: | ||
| parser = argparse.ArgumentParser(description=__doc__) | ||
| parser.add_argument("--op", default="all", choices=["all", *PERF_CASES]) | ||
| parser.add_argument("--warmup", type=int, default=20) | ||
| parser.add_argument("--iters", type=int, default=200) | ||
| parser.add_argument("--json", type=Path, default=None) | ||
| args = parser.parse_args() | ||
| if not torch.cuda.is_available(): | ||
| raise SystemExit("needs a CUDA device") | ||
| torch.backends.cuda.matmul.allow_tf32 = False | ||
|
|
||
| registry = KernelRegistry() | ||
| env = environment() | ||
| print(f"device={env['gpu']} torch={env['torch']} cuda={env['cuda']}") | ||
| results = [] | ||
| for name in list(PERF_CASES) if args.op == "all" else [args.op]: | ||
| for case in PERF_CASES[name](registry): | ||
| row = measure(case, args.warmup, args.iters) | ||
| results.append(row) | ||
| parts = [ | ||
| f"{key}={row[f'{key}_us']:.2f}us ({row[f'{key}_gbps']:.1f} GB/s, " | ||
| f"peak {row[f'{key}_peak_mib']:.2f} MiB)" | ||
| for key in TIMED_KEYS | ||
| if f"{key}_us" in row | ||
| ] | ||
| print(f"{name} {row['case']} [{row['backend']}]: " + "; ".join(parts)) | ||
| if args.json is not None: | ||
| args.json.write_text(json.dumps({"environment": env, "results": results}, indent=2) + "\n") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // Copyright (c) 2026 RL-Kernel Contributors | ||
| // | ||
| // MiniMax-H3 sinusoidal timestep features (RFC #420 `timestep_sinusoid_h3`). | ||
| // | ||
| // freq[k] = expf((c * k) * (1 / half)) c = (float)(-ln(max_period)) | ||
| // arg[t, k] = t[t] * freq[k] | ||
| // out[t] = [cosf(arg) | sinf(arg)] (T, 2 * half) FP32 | ||
| // | ||
| // Every value is produced by the same FP32 operation sequence as diffusers' | ||
| // get_timestep_embedding on CUDA (scalar * arange, multiply by the reciprocal | ||
| // of the CPU-scalar divisor, exp, multiply, cos/sin), so the output is meant | ||
| // to be bitwise equal to that path. Each element depends only on (t, k): | ||
| // batch-, position- and repeat-invariant by construction. Build without | ||
| // --use_fast_math: expf/sinf/cosf must stay the precise libdevice functions. | ||
| // nvcc defines no macro for fast math, so build_tools/extensions.py lists this | ||
| // file in H3_BITWISE_SOURCES, which KERNEL_ALIGN_USE_FAST_MATH=1 builds leave | ||
| // out; the op then falls back to the PyTorch reference. | ||
|
|
||
| #include <torch/extension.h> | ||
| #include <ATen/cuda/CUDAContext.h> | ||
| #include <c10/cuda/CUDAGuard.h> | ||
| #include <c10/cuda/CUDAException.h> | ||
|
|
||
| #include <algorithm> | ||
| #include <cmath> | ||
| #include <cstdint> | ||
|
|
||
| namespace { | ||
|
|
||
| __global__ void h3_timestep_sinusoid_kernel( | ||
| const float* __restrict__ timestep, | ||
| float* __restrict__ out, | ||
| int64_t num_timesteps, | ||
| int64_t half, | ||
| float neg_log_max_period, | ||
| float inv_half) { | ||
| const int64_t total = num_timesteps * half; | ||
| for (int64_t idx = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x; idx < total; | ||
| idx += static_cast<int64_t>(gridDim.x) * blockDim.x) { | ||
| const int64_t row = idx / half; | ||
| const int64_t k = idx - row * half; | ||
| // __fmul_rn keeps each product a separately rounded FP32 multiply (no FMA | ||
| // contraction), matching the two elementwise torch kernels it replays. | ||
| const float exponent = __fmul_rn(__fmul_rn(neg_log_max_period, static_cast<float>(k)), inv_half); | ||
| const float freq = expf(exponent); | ||
| const float arg = __fmul_rn(timestep[row], freq); | ||
| float* out_row = out + row * 2 * half; | ||
| out_row[k] = cosf(arg); | ||
| out_row[half + k] = sinf(arg); | ||
| } | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| torch::Tensor h3_timestep_sinusoid_forward(torch::Tensor timestep, int64_t num_channels, | ||
| double max_period, bool check_range) { | ||
| TORCH_CHECK(timestep.is_cuda(), "timestep must be a CUDA tensor"); | ||
| TORCH_CHECK(timestep.scalar_type() == at::kFloat, "timestep must be float32, got ", | ||
| timestep.scalar_type()); | ||
| TORCH_CHECK(timestep.dim() == 1, "timestep must be 1-D (num_timesteps,)"); | ||
| TORCH_CHECK(timestep.numel() > 0, "timestep must hold at least one timestep"); | ||
| TORCH_CHECK(num_channels > 0 && num_channels % 2 == 0, | ||
| "num_channels must be a positive even number, got ", num_channels); | ||
| TORCH_CHECK(max_period > 0.0, "max_period must be positive"); | ||
|
|
||
| const c10::cuda::CUDAGuard device_guard(timestep.device()); | ||
| auto t = timestep.contiguous(); | ||
| // Check at the native boundary, including direct extension calls. The explicit | ||
| // opt-out is for already-validated inputs and kernel-only profiling. | ||
| if (check_range) { | ||
| TORCH_CHECK_VALUE(((t >= 0) & (t <= 1)).all().item<bool>(), | ||
| "timestep must be finite and lie in [0, 1]: H3 consumes t = 1 - sigma " | ||
| "unscaled"); | ||
| } | ||
| const int64_t num_timesteps = t.size(0); | ||
| const int64_t half = num_channels / 2; | ||
| auto out = torch::empty({num_timesteps, num_channels}, t.options()); | ||
|
|
||
| // torch evaluates `-math.log(max_period) * arange` with the Python double | ||
| // rounded to the FP32 opmath type, and `x / half` as x * (1 / half) in FP32. | ||
| const float neg_log_max_period = static_cast<float>(-std::log(max_period)); | ||
| const float inv_half = 1.0f / static_cast<float>(half); | ||
|
|
||
| const int threads = 256; | ||
| const int64_t total = num_timesteps * half; | ||
| const int64_t blocks = std::min<int64_t>((total + threads - 1) / threads, 65535); | ||
| auto stream = at::cuda::getCurrentCUDAStream(); | ||
| h3_timestep_sinusoid_kernel<<<static_cast<unsigned int>(blocks), threads, 0, stream>>>( | ||
| t.data_ptr<float>(), out.data_ptr<float>(), num_timesteps, half, neg_log_max_period, | ||
| inv_half); | ||
| C10_CUDA_KERNEL_LAUNCH_CHECK(); | ||
| return out; | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The kernel replaces the reference's FP32 divide
exponent / halfwith a multiply byinv_half = 1.0f / half. The reference (NativeH3TimestepSinusoidOp.forwardline 87:exponent = exponent / half) performs a true FP32 division. Forhalfvalues that are not powers of two,1.0f/halfis inexact andx * inv_half != x / halfin FP32, so the claimed bitwise parity breaks. The kernel accepts any evennum_channels(num_channels % 2 == 0), and the test suite exercisesnum_channels=96,320etc., i.e. non-power-of-twohalf(48, 160). Either keep the divide (exponent / static_cast<float>(half)) or restrictnum_channels/2to powers of two.Note: the Python reference
frequencies_fp32usesexponent / halftoo and the backward relies on it, so the same divergence applies there.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not changed. PyTorch evaluates
x / halfwith a scalar divisor asx * (1 / half)in FP32, and-math.log(max_period)as an FP32 scalar times the arange, which is what the kernel does (see the comment aboveinv_half). Checked against the repo's op-for-op replay of diffusers'get_timestep_embedding(provider_time_proj) on the same inputs (2^20 uniform timesteps, a 100001-point grid and every 997th FP32 bit pattern in [0, 1]): 0 differing elements for num_channels 96, 250, 256, 320 and 1000 (half = 48, 125, 128, 160, 500, i.e. not only powers of two); 212,845,056 to 2,217,136,000 elements per width. A true__fdiv_rnwould break that parity.