Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
e7bc53a
[WS1][CUDA][MiniMax-H3] adaln_gate_residual: gated residual with in-k…
fusheng-ji Oct 7, 2026
cd688b3
[WS1][MiniMax-H3] adaln_gate_residual: B200 evidence report and figure
fusheng-ji Oct 7, 2026
debde49
Merge inherited RFC 420 review fixes into PR #488
fusheng-ji Oct 7, 2026
7090f00
fix(h3): address native validation and evidence review comments
fusheng-ji Oct 7, 2026
a05952f
Merge inherited RFC 420 review fixes into PR #488
fusheng-ji Oct 8, 2026
be7c6a9
Merge the H3 prior-art runner into PR #488
fusheng-ji Oct 8, 2026
b4c9fd5
Merge the H3 prior-art runner fix into PR #488
fusheng-ji Oct 8, 2026
052f5cb
Merge the H3 prior-art figure fix into PR #488
fusheng-ji Oct 8, 2026
5f4253d
Merge the H3 prior-art figure fix into PR #488
fusheng-ji Oct 8, 2026
dd35c14
docs(h3): gate_residual vs existing implementations (B200, 4010854)
fusheng-ji Oct 8, 2026
2d1b9d0
Merge the H3 prior-art evidence into PR #488
fusheng-ji Oct 8, 2026
1d1e739
Merge the H3 prior-art figure layout fix into PR #488
fusheng-ji Oct 8, 2026
c3fad21
Merge test-h3 refactor and migrate H3 kernels, tools, and tests
fusheng-ji Oct 10, 2026
fdc58d4
Merge migrated H3 dependency PR #487
fusheng-ji Oct 10, 2026
6682e76
Merge migrated validation documentation from PR #487
fusheng-ji Oct 10, 2026
f75ff23
Merge feat/h3-rmsnorm into feat/h3-adaln-gate-residual
fusheng-ji Oct 13, 2026
a746bff
Merge feat/h3-rmsnorm into feat/h3-adaln-gate-residual
fusheng-ji Oct 13, 2026
3bbcca4
evidence(h3): gate_residual direct bitwise comparison on B200
fusheng-ji Oct 13, 2026
3310212
Merge feat/h3-rmsnorm into feat/h3-adaln-gate-residual
fusheng-ji Oct 13, 2026
2c27639
Merge feat/h3-rmsnorm into feat/h3-adaln-gate-residual
fusheng-ji Oct 13, 2026
9c76b32
Merge feat/h3-rmsnorm into feat/h3-adaln-gate-residual
fusheng-ji Oct 13, 2026
81741e9
Merge feat/h3-rmsnorm into feat/h3-adaln-gate-residual
fusheng-ji Oct 13, 2026
d8a4a69
Merge feat/h3-rmsnorm into feat/h3-adaln-gate-residual
fusheng-ji Oct 13, 2026
9592623
Merge feat/h3-rmsnorm into feat/h3-adaln-gate-residual
fusheng-ji Oct 13, 2026
9b95c4a
Merge feat/h3-rmsnorm into feat/h3-adaln-gate-residual
fusheng-ji Oct 13, 2026
a8e5901
docs: move the measurement outputs out of the pull request
fusheng-ji Oct 19, 2026
3801808
docs: point commit hashes at the rewritten history
fusheng-ji Oct 23, 2026
1cacd95
Merge test-h3 into feat/h3-adaln-gate-residual
fusheng-ji Oct 25, 2026
c08d9ee
Merge feat/h3-rmsnorm into feat/h3-adaln-gate-residual
fusheng-ji Oct 26, 2026
9c9ab15
Merge feat/h3-rmsnorm into feat/h3-adaln-gate-residual
fusheng-ji Oct 26, 2026
669513d
fix(h3): keep the gate-residual forward out of the timed backward
fusheng-ji Oct 26, 2026
8e5ae90
Merge feat/h3-rmsnorm into feat/h3-adaln-gate-residual
fusheng-ji Oct 26, 2026
e5318ad
fix(h3): do not ask for the checkpoint when the gate-residual evidenc…
fusheng-ji Oct 26, 2026
7ed723a
Merge feat/h3 parent (#487) into #488
fusheng-ji Oct 26, 2026
4cde743
Merge parent (#487) into #488
fusheng-ji Oct 26, 2026
37f54db
Merge feat/h3-rmsnorm into feat/h3-adaln-gate-residual
fusheng-ji Oct 26, 2026
239ef80
fix(h3): assert the gate row in the native kernels and check the back…
fusheng-ji Oct 26, 2026
a2d0674
docs(h3): gate-residual latencies from the backward-only rerun
fusheng-ji Oct 26, 2026
d46cbe5
Merge feat/h3-rmsnorm into feat/h3-adaln-gate-residual
fusheng-ji Oct 26, 2026
1857fa6
fix(h3): check the gate backward's tile metadata and lengths, and sca…
fusheng-ji Oct 26, 2026
53ba838
Merge feat/h3-rmsnorm into feat/h3-adaln-gate-residual
fusheng-ji Oct 26, 2026
2084242
Merge feat/h3-rmsnorm into feat/h3-adaln-gate-residual
fusheng-ji Oct 27, 2026
ab407b3
fix(h3): reject gate tables whose rows overlap, and state the residua…
fusheng-ji Oct 27, 2026
d20d767
Merge #487 round-5 fixes into #488
fusheng-ji Oct 27, 2026
60e7785
fix(h3): return no residual gradient for a residual that needs none
fusheng-ji Oct 27, 2026
22a3c79
Merge upstream test-h3 into feat/h3-adaln-gate-residual
fusheng-ji Oct 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build_tools/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"csrc/cuda/h3/det_linear.cu",
"csrc/cuda/h3/adaln_row_gather.cu",
"csrc/cuda/h3/rmsnorm_modulate.cu",
"csrc/cuda/h3/gate_residual.cu",
)
H3_KERNELS_DEFINE = "-DKERNEL_ALIGN_WITH_H3_KERNELS"
# nvcc options that relax FP32 math. They can also arrive through the environment, where PyTorch's
Expand Down
15 changes: 15 additions & 0 deletions csrc/bindings/ops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,14 @@ std::vector<torch::Tensor> h3_rmsnorm_backward(
c10::optional<torch::Tensor> index, c10::optional<torch::Tensor> sorted_pos,
c10::optional<torch::Tensor> tile_begin, c10::optional<torch::Tensor> tile_end,
c10::optional<torch::Tensor> seg_first_tile);
torch::Tensor h3_gate_residual_forward(torch::Tensor residual, torch::Tensor y, torch::Tensor gate,
torch::Tensor index);
std::vector<torch::Tensor> h3_gate_residual_backward(torch::Tensor grad, torch::Tensor y,
torch::Tensor gate, torch::Tensor index,
torch::Tensor sorted_pos,
torch::Tensor tile_begin,
torch::Tensor tile_end,
torch::Tensor seg_first_tile);
#endif

// PyBind11 Module Registration
Expand Down Expand Up @@ -825,5 +833,12 @@ PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
py::arg("index") = py::none(), py::arg("sorted_pos") = py::none(),
py::arg("tile_begin") = py::none(), py::arg("tile_end") = py::none(),
py::arg("seg_first_tile") = py::none());
m.def("h3_gate_residual_forward", &h3_gate_residual_forward,
"residual + gate[index] * y with the gate row gathered in-kernel (eager rounding order)",
py::arg("residual"), py::arg("y"), py::arg("gate"), py::arg("index"));
m.def("h3_gate_residual_backward", &h3_gate_residual_backward,
"Gated-residual backward: exact dy, deterministic sorted segment sum for dgate",
py::arg("grad"), py::arg("y"), py::arg("gate"), py::arg("index"), py::arg("sorted_pos"),
py::arg("tile_begin"), py::arg("tile_end"), py::arg("seg_first_tile"));
#endif
}
264 changes: 264 additions & 0 deletions csrc/cuda/h3/gate_residual.cu
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2026 RL-Kernel Contributors
//
// MiniMax-H3 gated residual (RFC #420 `adaln_gate_residual`).
//
// out[r, j] = cast(residual[r, j] + cast(gate[i, j] * y[r, j])) i = index[r % S]
//
// The gate row is read from a strided view of the AdaLN table (gate_msa or
// gate_mlp) inside the kernel, and the two roundings sit exactly where the
// eager `residual + gate.index_select(0, i) * y` rounds (__fmul_rn/__fadd_rn
// keep FP32 from contracting them into one FMA), so the result is
// bitwise equal to diffusers. Elementwise: every output depends only on its
// own inputs.
//
// Backward: d_residual = grad; d_y = cast(grad * gate) (the product of two
// 16-bit values is exact in FP32, so this equals the eager VJP bitwise);
// d_gate[i] = sum over positions mapped to i of grad * y, an FP32 segmented
// sum over positions sorted stably by row, in fixed tiles folded in order.

#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAGuard.h>
#include <c10/cuda/CUDAException.h>
#include <cuda_bf16.h>
#include <cuda_fp16.h>

#include <cstdint>
#include <vector>

#include "tile_metadata.cuh"

namespace {

__device__ __forceinline__ float to_f(float v) { return v; }
__device__ __forceinline__ float to_f(__nv_bfloat16 v) { return __bfloat162float(v); }
__device__ __forceinline__ float to_f(__half v) { return __half2float(v); }

template <typename T>
__device__ __forceinline__ T from_f(float v);
template <>
__device__ __forceinline__ float from_f<float>(float v) { return v; }
template <>
__device__ __forceinline__ __nv_bfloat16 from_f<__nv_bfloat16>(float v) { return __float2bfloat16(v); }
template <>
__device__ __forceinline__ __half from_f<__half>(float v) { return __float2half(v); }

template <typename T>
__device__ __forceinline__ float round_t(float v) { return to_f(from_f<T>(v)); }

struct Gate {
const void* rows; // (R, N) view, row stride `stride`
int64_t stride;
const int64_t* index; // (S,)
int64_t seq;
int64_t num_rows;
};

// One block per row (grid-strided); 16-byte vectors when the row allows it.
// `kVecElems` elements are loaded together; each is computed independently.
template <typename T, bool kDy>
__global__ void gate_residual_rows_kernel(const T* __restrict__ a, const T* __restrict__ b,
T* __restrict__ out, int64_t rows, int64_t n,
Gate gate, bool vectorized) {
constexpr int kVecElems = 16 / sizeof(T);
for (int64_t r = blockIdx.x; r < rows; r += gridDim.x) {
const int64_t i = gate.index[r % gate.seq];
CUDA_KERNEL_ASSERT(i >= 0 && i < gate.num_rows);
const T* g = static_cast<const T*>(gate.rows) + i * gate.stride;
const T* ar = a + r * n;
const T* br = kDy ? nullptr : b + r * n;
T* orow = out + r * n;
if (vectorized) {
for (int64_t v = threadIdx.x; v < n / kVecElems; v += blockDim.x) {
const uint4 av = reinterpret_cast<const uint4*>(ar)[v];
const uint4 gv = reinterpret_cast<const uint4*>(g)[v];
uint4 bv = av;
if (!kDy) bv = reinterpret_cast<const uint4*>(br)[v];
const T* ae = reinterpret_cast<const T*>(&av);
const T* ge = reinterpret_cast<const T*>(&gv);
const T* be = reinterpret_cast<const T*>(&bv);
uint4 ov;
T* oe = reinterpret_cast<T*>(&ov);
#pragma unroll
for (int k = 0; k < kVecElems; ++k) {
if (kDy) {
oe[k] = from_f<T>(to_f(ae[k]) * to_f(ge[k])); // grad * gate
} else {
const float p = round_t<T>(__fmul_rn(to_f(ge[k]), to_f(be[k]))); // gate * y
oe[k] = from_f<T>(__fadd_rn(to_f(ae[k]), p)); // residual + p
}
}
reinterpret_cast<uint4*>(orow)[v] = ov;
}
} else {
for (int64_t j = threadIdx.x; j < n; j += blockDim.x) {
if (kDy) {
orow[j] = from_f<T>(to_f(ar[j]) * to_f(g[j]));
} else {
const float p = round_t<T>(__fmul_rn(to_f(g[j]), to_f(br[j])));
orow[j] = from_f<T>(__fadd_rn(to_f(ar[j]), p));
}
}
}
}
}

// partial[tile, j] = sum over sorted positions of the tile (ascending) of grad * y
template <typename T>
__global__ void gate_grad_partial_kernel(const T* __restrict__ grad, const T* __restrict__ y,
const int64_t* __restrict__ sorted_pos,
const int64_t* __restrict__ tile_begin,
const int64_t* __restrict__ tile_end,
float* __restrict__ partial, int64_t n) {
const int64_t tile = blockIdx.x;
const int64_t j = static_cast<int64_t>(blockIdx.y) * blockDim.x + threadIdx.x;
if (j >= n) return;
float acc = 0.0f;
for (int64_t p = tile_begin[tile]; p < tile_end[tile]; ++p) {
const int64_t e = sorted_pos[p] * n + j;
acc = fmaf(to_f(grad[e]), to_f(y[e]), acc);
}
partial[tile * n + j] = acc;
}

template <typename T>
__global__ void gate_grad_fold_kernel(const float* __restrict__ partial,
const int64_t* __restrict__ seg_first_tile,
T* __restrict__ out, int64_t n) {
const int64_t seg = blockIdx.x;
const int64_t j = static_cast<int64_t>(blockIdx.y) * blockDim.x + threadIdx.x;
if (j >= n) return;
float acc = 0.0f;
for (int64_t t = seg_first_tile[seg]; t < seg_first_tile[seg + 1]; ++t) acc += partial[t * n + j];
out[seg * n + j] = from_f<T>(acc);
}

#define H3_DISPATCH(TYPE, NAME, ...) \
[&] { \
switch (TYPE) { \
case at::kFloat: { using T = float; __VA_ARGS__(); break; } \
case at::kHalf: { using T = __half; __VA_ARGS__(); break; } \
case at::kBFloat16: { using T = __nv_bfloat16; __VA_ARGS__(); break; } \
default: TORCH_CHECK(false, NAME, ": unsupported dtype ", TYPE); \
} \
}()

Gate make_gate(const torch::Tensor& gate, const torch::Tensor& index, const torch::Tensor& x) {
TORCH_CHECK(gate.is_cuda() && gate.device() == x.device() && gate.scalar_type() == x.scalar_type(),
"gate must be a CUDA tensor with the activations' dtype");
TORCH_CHECK(gate.dim() == 2 && gate.size(1) == x.size(1) && gate.stride(1) == 1,
"gate must be (R, N) with unit column stride");
// Rows are read as dense N-element spans at `stride(0)`: a view of a wider table is fine, an
// expanded or overlapping one (stride(0) < N) is not a table of rows.
TORCH_CHECK(gate.size(0) <= 1 || gate.stride(0) >= gate.size(1), "gate row stride ",
gate.stride(0), " is smaller than N=", gate.size(1));
TORCH_CHECK(index.is_cuda() && index.device() == x.device() && index.scalar_type() == at::kLong &&
index.dim() == 1 && index.is_contiguous() && index.numel() > 0,
"index must be a non-empty contiguous int64 tensor on ", x.device());
TORCH_CHECK(x.size(0) % index.size(0) == 0, "rows must be a multiple of S");
return Gate{gate.data_ptr(), gate.stride(0), index.data_ptr<int64_t>(), index.size(0), gate.size(0)};
}

void check_act(const torch::Tensor& t, const char* name, const torch::Tensor& like) {
TORCH_CHECK(t.is_cuda() && t.device() == like.device() && t.scalar_type() == like.scalar_type(),
name, " must match the residual's device and dtype");
TORCH_CHECK(t.sizes() == like.sizes() && t.is_contiguous(), name,
" must be contiguous with the residual's shape");
}

unsigned row_blocks(int64_t rows) {
return static_cast<unsigned>(std::min<int64_t>(rows, 1 << 30));
}

bool can_vectorize(const torch::Tensor& gate, int64_t n, std::initializer_list<const torch::Tensor*> acts) {
const int64_t vec = 16 / gate.element_size();
if (n % vec != 0 || (gate.stride(0) * gate.element_size()) % 16 != 0) return false;
if (reinterpret_cast<uintptr_t>(gate.data_ptr()) % 16 != 0) return false;
for (const auto* t : acts) {
if (reinterpret_cast<uintptr_t>(t->data_ptr()) % 16 != 0) return false;
}
return true;
}

} // namespace

// residual, y: (M, N); gate: (R, N) view; index: (S,), M % S == 0.
torch::Tensor h3_gate_residual_forward(torch::Tensor residual, torch::Tensor y, torch::Tensor gate,
torch::Tensor index) {
TORCH_CHECK(residual.is_cuda() && residual.dim() == 2 && residual.is_contiguous() &&
residual.numel() > 0,
"residual must be a non-empty contiguous (M, N) CUDA tensor");
check_act(y, "y", residual);
const Gate g = make_gate(gate, index, residual);
const c10::cuda::CUDAGuard guard(residual.device());
auto out = torch::empty_like(residual);
auto stream = at::cuda::getCurrentCUDAStream();
const bool vec = can_vectorize(gate, residual.size(1), {&residual, &y, &out});
H3_DISPATCH(residual.scalar_type(), "h3_gate_residual_forward", [&] {
gate_residual_rows_kernel<T, false><<<row_blocks(residual.size(0)), 256, 0, stream>>>(
reinterpret_cast<const T*>(residual.data_ptr()), reinterpret_cast<const T*>(y.data_ptr()),
reinterpret_cast<T*>(out.data_ptr()), residual.size(0), residual.size(1), g, vec);
});
C10_CUDA_KERNEL_LAUNCH_CHECK();
return out;
}

// Returns {d_y, d_gate (R, N) in the gate dtype}. d_residual is the incoming grad.
std::vector<torch::Tensor> h3_gate_residual_backward(torch::Tensor grad, torch::Tensor y,
torch::Tensor gate, torch::Tensor index,
torch::Tensor sorted_pos,
torch::Tensor tile_begin,
torch::Tensor tile_end,
torch::Tensor seg_first_tile) {
TORCH_CHECK(grad.is_cuda() && grad.dim() == 2 && grad.is_contiguous(),
"grad must be a contiguous (M, N) CUDA tensor");
check_act(y, "y", grad);
const Gate g = make_gate(gate, index, grad);
for (const auto* t : {&sorted_pos, &tile_begin, &tile_end, &seg_first_tile}) {
TORCH_CHECK(t->is_cuda() && t->device() == grad.device() && t->scalar_type() == at::kLong &&
t->dim() == 1 && t->is_contiguous(),
"tile metadata must be contiguous int64 tensors on ", grad.device());
}
const int64_t rows = grad.size(0);
const int64_t n = grad.size(1);
TORCH_CHECK(seg_first_tile.numel() >= 1, "seg_first_tile must hold R + 1 entries");
const int64_t segments = seg_first_tile.numel() - 1;
TORCH_CHECK(segments == gate.size(0), "segments must equal the gate rows");
TORCH_CHECK(sorted_pos.numel() == rows, "sorted_pos must have M entries");
TORCH_CHECK(tile_begin.numel() == tile_end.numel(), "tile_begin/tile_end length mismatch");
const int64_t tiles = tile_begin.numel();
Comment on lines +223 to +230

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · high
The tile metadata is used without checking that the terminal segment index stays within partial's first dimension. gate_grad_fold_kernel reads seg_first_tile[seg + 1] and loops t < seg_first_tile[seg + 1], indexing partial[t * n + j], but nothing verifies seg_first_tile[segments] == tiles (nor that sorted_pos.numel() == rows, nor tile_begin/tile_end bounds). h3_rmsnorm_backward performs these checks; this entry point does not, so a caller passing mismatched metadata (the tile tensors are a separate public parameter list) reads out of bounds of partial and silently produces a wrong d_gate. Add the missing TORCH_CHECKs here, mirroring the rmsnorm entry point.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not changed in the kernels. The tile metadata is not a public input: it is built by _segment_tiles from the same index tensor (int64 sorted_pos, tile_begin, tile_end, seg_first_tile, non-decreasing by construction, seg_first_tile[R] == tiles) and the native entry points check dtype, rank, contiguity, device, sizes and, since 0f67234, a non-empty seg_first_tile. Validating the values on the host would need a device-to-host read of every tensor on each backward, and the backward tests compare the result with the golden. A caller that builds metadata by hand is outside the contract.

const c10::cuda::CUDAGuard guard(grad.device());
auto stream = at::cuda::getCurrentCUDAStream();
auto dy = torch::empty_like(y);
auto partial = torch::empty({std::max<int64_t>(tiles, 1), n}, grad.options().dtype(at::kFloat));
auto dgate = torch::empty({segments, n}, grad.options());
const int threads = 256;
const int64_t col_blocks64 = (n + threads - 1) / threads;
TORCH_CHECK(col_blocks64 <= 65535, "N=", n, " needs ", col_blocks64,
" column blocks, more than the grid.y limit of 65535");
const unsigned col_blocks = static_cast<unsigned>(col_blocks64);
// The tile list is caller-built metadata: check it before the kernels read through it.
h3_tiles::check_tile_metadata(stream, sorted_pos.data_ptr<int64_t>(), rows,
tile_begin.data_ptr<int64_t>(), tile_end.data_ptr<int64_t>(), tiles,
seg_first_tile.data_ptr<int64_t>(), segments);
H3_DISPATCH(grad.scalar_type(), "h3_gate_residual_backward", [&] {
const T* gp = reinterpret_cast<const T*>(grad.data_ptr());
const T* yp = reinterpret_cast<const T*>(y.data_ptr());
const bool vec = can_vectorize(gate, n, {&grad, &dy});
gate_residual_rows_kernel<T, true><<<row_blocks(rows), threads, 0, stream>>>(
gp, nullptr, reinterpret_cast<T*>(dy.data_ptr()), rows, n, g, vec);
if (tiles > 0) {
gate_grad_partial_kernel<T><<<dim3(static_cast<unsigned>(tiles), col_blocks), threads, 0, stream>>>(
gp, yp, sorted_pos.data_ptr<int64_t>(), tile_begin.data_ptr<int64_t>(),
tile_end.data_ptr<int64_t>(), partial.data_ptr<float>(), n);
}
if (segments > 0) { // a zero-sized grid is not a valid launch
gate_grad_fold_kernel<T><<<dim3(static_cast<unsigned>(segments), col_blocks), threads, 0, stream>>>(
partial.data_ptr<float>(), seg_first_tile.data_ptr<int64_t>(),
reinterpret_cast<T*>(dgate.data_ptr()), n);
}
});
C10_CUDA_KERNEL_LAUNCH_CHECK();
return {dy, dgate};
}
25 changes: 13 additions & 12 deletions csrc/cuda/h3/rmsnorm_modulate.cu
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

#include <cstdint>
#include <limits>
#include <utility>
#include <vector>

#include "tile_metadata.cuh"
Expand Down Expand Up @@ -282,12 +283,6 @@ void check_rows(const torch::Tensor& t, const char* name, const torch::Tensor& l
TORCH_CHECK(t.scalar_type() == like.scalar_type(), name, " must have dtype ", like.scalar_type());
}

void check_index_tensor(const torch::Tensor& t, const char* name, const torch::Tensor& x) {
TORCH_CHECK(t.is_cuda() && t.device() == x.device() && t.scalar_type() == at::kLong &&
t.dim() == 1 && t.is_contiguous(),
name, " must be a contiguous 1-D int64 CUDA tensor on x's device");
}

Modulation make_modulation(const c10::optional<torch::Tensor>& shift,
const c10::optional<torch::Tensor>& scale,
const c10::optional<torch::Tensor>& index, const torch::Tensor& x,
Expand Down Expand Up @@ -315,8 +310,9 @@ Modulation make_modulation(const c10::optional<torch::Tensor>& shift,
// expanded or overlapping one (row_stride < N) is not a table of rows.
TORCH_CHECK(sh.size(0) <= 1 || sh.stride(0) >= n, "shift/scale row stride ", sh.stride(0),
" is smaller than N=", n);
check_index_tensor(ix, "row index", x);
TORCH_CHECK(ix.numel() > 0, "row index must be nonempty");
TORCH_CHECK(ix.is_cuda() && ix.device() == x.device() && ix.scalar_type() == at::kLong &&
ix.dim() == 1 && ix.is_contiguous() && ix.numel() > 0,
"row index must be a non-empty contiguous int64 tensor on ", x.device());
TORCH_CHECK(x.size(0) % ix.size(0) == 0, "x rows (", x.size(0), ") must be a multiple of S (",
ix.size(0), ")");
mod.shift = sh.data_ptr();
Expand Down Expand Up @@ -402,10 +398,15 @@ std::vector<torch::Tensor> h3_rmsnorm_backward(
TORCH_CHECK(sorted_pos.has_value() && tile_begin.has_value() && tile_end.has_value() &&
seg_first_tile.has_value(),
"modulated backward needs the sorted segment tiles");
check_index_tensor(*sorted_pos, "sorted_pos", x);
check_index_tensor(*tile_begin, "tile_begin", x);
check_index_tensor(*tile_end, "tile_end", x);
check_index_tensor(*seg_first_tile, "seg_first_tile", x);
const std::pair<const torch::Tensor*, const char*> tiles[] = {{&*sorted_pos, "sorted_pos"},
{&*tile_begin, "tile_begin"},
{&*tile_end, "tile_end"},
{&*seg_first_tile, "seg_first_tile"}};
for (const auto& [t, name] : tiles) {
TORCH_CHECK(t->is_cuda() && t->device() == x.device() && t->scalar_type() == at::kLong &&
t->dim() == 1 && t->is_contiguous(),
name, ": tile metadata must be contiguous int64 tensors on ", x.device());
}
TORCH_CHECK(sorted_pos->size(0) == rows, "sorted_pos must have M entries");
TORCH_CHECK(seg_first_tile->size(0) == shift->size(0) + 1,
"seg_first_tile must have R + 1 entries");
Expand Down
1 change: 1 addition & 0 deletions docs/.nav.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ nav:
- operators/h3-adaln-projection.md
- operators/h3-adaln-row-gather.md
- operators/h3-rmsnorm.md
- operators/h3-adaln-gate-residual.md
- Developer Guide:
- contributing/README.md
- Contributor Guide: contributing/contributor-guide.md
Expand Down
1 change: 1 addition & 0 deletions docs/operators/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@ Every operator page should include:
- [MiniMax-H3 AdaLN Projection](h3-adaln-projection.md)
- [MiniMax-H3 AdaLN Row Gather](h3-adaln-row-gather.md)
- [MiniMax-H3 RMSNorm and AdaLN Modulation](h3-rmsnorm.md)
- [MiniMax-H3 AdaLN Gated Residual](h3-adaln-gate-residual.md)
- [H3 Flash Attention (optional fast backend)](h3-flash-attention.md)
- [Operator Doc Template](../contributing/operator-doc-template.md)
Loading
Loading