Skip to content

Commit 4e7a406

Browse files
committed
ggml : add fused SINKHORN_NORM op (Sinkhorn-Knopp normalization)
1 parent 22b69b6 commit 4e7a406

10 files changed

Lines changed: 407 additions & 30 deletions

File tree

ggml/include/ggml.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ extern "C" {
509509
GGML_OP_RMS_NORM_BACK,
510510
GGML_OP_GROUP_NORM,
511511
GGML_OP_L2_NORM,
512+
GGML_OP_SINKHORN_NORM,
512513

513514
GGML_OP_MUL_MAT,
514515
GGML_OP_MUL_MAT_ID,
@@ -1406,6 +1407,12 @@ extern "C" {
14061407
struct ggml_tensor * a,
14071408
float eps);
14081409

1410+
GGML_API struct ggml_tensor * ggml_sinkhorn_norm(
1411+
struct ggml_context * ctx,
1412+
struct ggml_tensor * a,
1413+
int n_iters,
1414+
float eps);
1415+
14091416
// a - x
14101417
// b - dy
14111418
GGML_API struct ggml_tensor * ggml_rms_norm_back(

ggml/src/ggml-cpu/ggml-cpu.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1833,6 +1833,10 @@ static void ggml_compute_forward(struct ggml_compute_params * params, struct ggm
18331833
{
18341834
ggml_compute_forward_l2_norm(params, tensor);
18351835
} break;
1836+
case GGML_OP_SINKHORN_NORM:
1837+
{
1838+
ggml_compute_forward_sinkhorn_norm(params, tensor);
1839+
} break;
18361840
case GGML_OP_MUL_MAT:
18371841
{
18381842
ggml_compute_forward_mul_mat(params, tensor);
@@ -2306,6 +2310,7 @@ static int ggml_get_n_tasks(struct ggml_tensor * node, int n_threads) {
23062310
case GGML_OP_RMS_NORM:
23072311
case GGML_OP_RMS_NORM_BACK:
23082312
case GGML_OP_L2_NORM:
2313+
case GGML_OP_SINKHORN_NORM:
23092314
case GGML_OP_GROUP_NORM:
23102315
case GGML_OP_CONCAT:
23112316
case GGML_OP_MUL_MAT:

ggml/src/ggml-cpu/ops.cpp

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4237,6 +4237,125 @@ void ggml_compute_forward_l2_norm(
42374237
}
42384238
}
42394239

4240+
// ggml_compute_forward_sinkhorn_norm
4241+
4242+
#define GGML_SINKHORN_NORM_MAX_N 32
4243+
4244+
static void ggml_compute_forward_sinkhorn_norm_f32(
4245+
const ggml_compute_params * params,
4246+
ggml_tensor * dst) {
4247+
4248+
const ggml_tensor * src0 = dst->src[0];
4249+
4250+
GGML_ASSERT(ggml_are_same_shape(src0, dst));
4251+
GGML_ASSERT(ggml_is_contiguous(src0));
4252+
4253+
const int ith = params->ith;
4254+
const int nth = params->nth;
4255+
4256+
GGML_TENSOR_UNARY_OP_LOCALS
4257+
4258+
const int32_t n_iters = ggml_get_op_params_i32(dst, 0);
4259+
const float eps = ggml_get_op_params_f32(dst, 1);
4260+
4261+
const int64_t n = ne00;
4262+
GGML_ASSERT(ne01 == n);
4263+
GGML_ASSERT(n <= GGML_SINKHORN_NORM_MAX_N);
4264+
GGML_ASSERT(n_iters >= 1);
4265+
4266+
const int64_t n_slices = ne02 * ne03;
4267+
4268+
float m[GGML_SINKHORN_NORM_MAX_N * GGML_SINKHORN_NORM_MAX_N];
4269+
4270+
for (int64_t s = ith; s < n_slices; s += nth) {
4271+
const int64_t i02 = s % ne02;
4272+
const int64_t i03 = s / ne02;
4273+
4274+
const float * x = (const float *) ((const char *) src0->data + i02*nb02 + i03*nb03);
4275+
float * y = (float *) (( char *) dst->data + i02*nb2 + i03*nb3);
4276+
4277+
for (int64_t b = 0; b < n; ++b) {
4278+
for (int64_t a = 0; a < n; ++a) {
4279+
m[b*n + a] = x[b*n + a];
4280+
}
4281+
}
4282+
4283+
for (int64_t b = 0; b < n; ++b) {
4284+
float mx = -INFINITY;
4285+
for (int64_t a = 0; a < n; ++a) {
4286+
mx = fmaxf(mx, m[b*n + a]);
4287+
}
4288+
float sum = 0.0f;
4289+
for (int64_t a = 0; a < n; ++a) {
4290+
const float e = expf(m[b*n + a] - mx);
4291+
m[b*n + a] = e;
4292+
sum += e;
4293+
}
4294+
for (int64_t a = 0; a < n; ++a) {
4295+
m[b*n + a] /= sum;
4296+
}
4297+
}
4298+
4299+
for (int64_t i = 0; i < n*n; ++i) {
4300+
m[i] += eps;
4301+
}
4302+
4303+
auto norm_cols = [&]() {
4304+
for (int64_t a = 0; a < n; ++a) {
4305+
float r = 0.0f;
4306+
for (int64_t b = 0; b < n; ++b) {
4307+
r += m[b*n + a];
4308+
}
4309+
r += eps;
4310+
for (int64_t b = 0; b < n; ++b) {
4311+
m[b*n + a] /= r;
4312+
}
4313+
}
4314+
};
4315+
4316+
auto norm_rows = [&]() {
4317+
for (int64_t b = 0; b < n; ++b) {
4318+
float c = 0.0f;
4319+
for (int64_t a = 0; a < n; ++a) {
4320+
c += m[b*n + a];
4321+
}
4322+
c += eps;
4323+
for (int64_t a = 0; a < n; ++a) {
4324+
m[b*n + a] /= c;
4325+
}
4326+
}
4327+
};
4328+
4329+
norm_cols();
4330+
for (int32_t it = 1; it < n_iters; ++it) {
4331+
norm_rows();
4332+
norm_cols();
4333+
}
4334+
4335+
for (int64_t i = 0; i < n*n; ++i) {
4336+
y[i] = m[i];
4337+
}
4338+
}
4339+
}
4340+
4341+
void ggml_compute_forward_sinkhorn_norm(
4342+
const ggml_compute_params * params,
4343+
ggml_tensor * dst) {
4344+
4345+
const ggml_tensor * src0 = dst->src[0];
4346+
4347+
switch (src0->type) {
4348+
case GGML_TYPE_F32:
4349+
{
4350+
ggml_compute_forward_sinkhorn_norm_f32(params, dst);
4351+
} break;
4352+
default:
4353+
{
4354+
GGML_ABORT("fatal error");
4355+
}
4356+
}
4357+
}
4358+
42404359
// ggml_compute_forward_out_prod
42414360

42424361
static void ggml_compute_forward_out_prod_f32(

ggml/src/ggml-cpu/ops.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ void ggml_compute_forward_rms_norm_mul_fused(const struct ggml_compute_params *
4848
void ggml_compute_forward_rms_norm_back(const struct ggml_compute_params * params, struct ggml_tensor * dst);
4949
void ggml_compute_forward_group_norm(const struct ggml_compute_params * params, struct ggml_tensor * dst);
5050
void ggml_compute_forward_l2_norm(const struct ggml_compute_params * params, struct ggml_tensor * dst);
51+
void ggml_compute_forward_sinkhorn_norm(const struct ggml_compute_params * params, struct ggml_tensor * dst);
5152
void ggml_compute_forward_out_prod(const struct ggml_compute_params * params, struct ggml_tensor * dst);
5253
void ggml_compute_forward_scale(const struct ggml_compute_params * params, struct ggml_tensor * dst);
5354
void ggml_compute_forward_set(const struct ggml_compute_params * params, struct ggml_tensor * dst);

ggml/src/ggml-cuda/ggml-cuda.cu

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "ggml-cuda/mmvf.cuh"
3434
#include "ggml-cuda/mmvq.cuh"
3535
#include "ggml-cuda/norm.cuh"
36+
#include "ggml-cuda/sinkhorn-norm.cuh"
3637
#include "ggml-cuda/opt-step-adamw.cuh"
3738
#include "ggml-cuda/opt-step-sgd.cuh"
3839
#include "ggml-cuda/out-prod.cuh"
@@ -2083,6 +2084,9 @@ static bool ggml_cuda_compute_forward(ggml_backend_cuda_context & ctx, struct gg
20832084
case GGML_OP_GROUP_NORM:
20842085
ggml_cuda_op_group_norm(ctx, dst);
20852086
break;
2087+
case GGML_OP_SINKHORN_NORM:
2088+
ggml_cuda_op_sinkhorn_norm(ctx, dst);
2089+
break;
20862090
case GGML_OP_L2_NORM:
20872091
ggml_cuda_op_l2_norm(ctx, dst);
20882092
break;
@@ -4850,6 +4854,11 @@ static bool ggml_backend_cuda_device_supports_op(ggml_backend_dev_t dev, const g
48504854
case GGML_OP_SILU_BACK:
48514855
return ggml_is_contiguous(op->src[0]) && op->src[0]->type == GGML_TYPE_F32;
48524856
break;
4857+
case GGML_OP_SINKHORN_NORM:
4858+
return op->src[0]->type == GGML_TYPE_F32 &&
4859+
ggml_is_contiguous(op->src[0]) &&
4860+
op->src[0]->ne[0] == op->src[0]->ne[1] &&
4861+
op->src[0]->ne[0] <= 8;
48534862
case GGML_OP_NORM:
48544863
case GGML_OP_RMS_NORM:
48554864
case GGML_OP_L2_NORM:
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
#include "sinkhorn-norm.cuh"
2+
3+
#define SINKHORN_NORM_MAX_N 8
4+
5+
template <int MAXN>
6+
static __global__ void sinkhorn_norm_f32(
7+
const float * __restrict__ x,
8+
float * __restrict__ y,
9+
const int n,
10+
const int n_iters,
11+
const float eps,
12+
const int64_t n_slices,
13+
const int64_t slice_stride) {
14+
15+
const int64_t s = (int64_t) blockIdx.x * blockDim.x + threadIdx.x;
16+
if (s >= n_slices) {
17+
return;
18+
}
19+
20+
const float * xs = x + s * slice_stride;
21+
float * ys = y + s * slice_stride;
22+
23+
float m[MAXN * MAXN];
24+
const int nn = n * n;
25+
26+
for (int i = 0; i < nn; ++i) {
27+
m[i] = xs[i];
28+
}
29+
30+
for (int b = 0; b < n; ++b) {
31+
float mx = -INFINITY;
32+
for (int a = 0; a < n; ++a) {
33+
mx = fmaxf(mx, m[b*n + a]);
34+
}
35+
float sum = 0.0f;
36+
for (int a = 0; a < n; ++a) {
37+
const float e = expf(m[b*n + a] - mx);
38+
m[b*n + a] = e;
39+
sum += e;
40+
}
41+
for (int a = 0; a < n; ++a) {
42+
m[b*n + a] /= sum;
43+
}
44+
}
45+
46+
for (int i = 0; i < nn; ++i) {
47+
m[i] += eps;
48+
}
49+
50+
#define NORM_COLS() do { \
51+
for (int a = 0; a < n; ++a) { \
52+
float r = 0.0f; \
53+
for (int b = 0; b < n; ++b) r += m[b*n + a]; \
54+
r += eps; \
55+
for (int b = 0; b < n; ++b) m[b*n + a] /= r; \
56+
} \
57+
} while (0)
58+
59+
#define NORM_ROWS() do { \
60+
for (int b = 0; b < n; ++b) { \
61+
float c = 0.0f; \
62+
for (int a = 0; a < n; ++a) c += m[b*n + a]; \
63+
c += eps; \
64+
for (int a = 0; a < n; ++a) m[b*n + a] /= c; \
65+
} \
66+
} while (0)
67+
68+
NORM_COLS();
69+
for (int it = 1; it < n_iters; ++it) {
70+
NORM_ROWS();
71+
NORM_COLS();
72+
}
73+
74+
#undef NORM_COLS
75+
#undef NORM_ROWS
76+
77+
for (int i = 0; i < nn; ++i) {
78+
ys[i] = m[i];
79+
}
80+
}
81+
82+
template <int N>
83+
static __global__ void sinkhorn_norm_warp_f32(
84+
const float * __restrict__ x,
85+
float * __restrict__ y,
86+
const int n_iters,
87+
const float eps,
88+
const int64_t n_slices) {
89+
90+
constexpr int NN = N * N;
91+
92+
const int64_t g = (int64_t) blockIdx.x * blockDim.x + threadIdx.x;
93+
const int64_t token = g / NN;
94+
if (token >= n_slices) {
95+
return;
96+
}
97+
98+
const int e = threadIdx.x % NN;
99+
100+
float v = x[token * NN + e];
101+
102+
float mx = v;
103+
#pragma unroll
104+
for (int off = 1; off < N; off <<= 1) {
105+
mx = fmaxf(mx, __shfl_xor_sync(0xffffffff, mx, off, NN));
106+
}
107+
float ex = expf(v - mx);
108+
float sum = ex;
109+
#pragma unroll
110+
for (int off = 1; off < N; off <<= 1) {
111+
sum += __shfl_xor_sync(0xffffffff, sum, off, NN);
112+
}
113+
v = ex / sum + eps;
114+
115+
{
116+
float r = v;
117+
#pragma unroll
118+
for (int off = N; off < NN; off <<= 1) {
119+
r += __shfl_xor_sync(0xffffffff, r, off, NN);
120+
}
121+
v /= (r + eps);
122+
}
123+
124+
for (int it = 1; it < n_iters; ++it) {
125+
{
126+
float c = v;
127+
#pragma unroll
128+
for (int off = 1; off < N; off <<= 1) {
129+
c += __shfl_xor_sync(0xffffffff, c, off, NN);
130+
}
131+
v /= (c + eps);
132+
}
133+
{
134+
float r = v;
135+
#pragma unroll
136+
for (int off = N; off < NN; off <<= 1) {
137+
r += __shfl_xor_sync(0xffffffff, r, off, NN);
138+
}
139+
v /= (r + eps);
140+
}
141+
}
142+
143+
y[token * NN + e] = v;
144+
}
145+
146+
static bool sinkhorn_norm_is_pow2(int n) {
147+
return n > 0 && (n & (n - 1)) == 0;
148+
}
149+
150+
void ggml_cuda_op_sinkhorn_norm(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
151+
const ggml_tensor * src0 = dst->src[0];
152+
const float * src0_d = (const float *) src0->data;
153+
float * dst_d = (float *) dst->data;
154+
cudaStream_t stream = ctx.stream();
155+
156+
GGML_ASSERT(src0->type == GGML_TYPE_F32);
157+
GGML_ASSERT( dst->type == GGML_TYPE_F32);
158+
GGML_ASSERT(ggml_is_contiguous(src0));
159+
160+
GGML_TENSOR_UNARY_OP_LOCALS;
161+
162+
const int32_t n_iters = ggml_get_op_params_i32(dst, 0);
163+
const float eps = ggml_get_op_params_f32(dst, 1);
164+
165+
const int n = (int) ne00;
166+
GGML_ASSERT(ne01 == ne00);
167+
GGML_ASSERT(n <= SINKHORN_NORM_MAX_N);
168+
GGML_ASSERT(n_iters >= 1);
169+
170+
const int64_t n_slices = ne02 * ne03;
171+
const int64_t slice_stride = (int64_t) n * n;
172+
173+
const int block_size = 256;
174+
175+
const int warp_size = ggml_cuda_info().devices[ggml_cuda_get_device()].warp_size;
176+
177+
if (sinkhorn_norm_is_pow2(n) && n*n <= warp_size) {
178+
const int64_t total_threads = n_slices * (int64_t) (n*n);
179+
const int64_t num_blocks = (total_threads + block_size - 1) / block_size;
180+
switch (n) {
181+
case 2: sinkhorn_norm_warp_f32<2><<<num_blocks, block_size, 0, stream>>>(src0_d, dst_d, n_iters, eps, n_slices); break;
182+
case 4: sinkhorn_norm_warp_f32<4><<<num_blocks, block_size, 0, stream>>>(src0_d, dst_d, n_iters, eps, n_slices); break;
183+
case 8: sinkhorn_norm_warp_f32<8><<<num_blocks, block_size, 0, stream>>>(src0_d, dst_d, n_iters, eps, n_slices); break;
184+
default: GGML_ABORT("sinkhorn_norm: unsupported warp N=%d", n);
185+
}
186+
return;
187+
}
188+
189+
const int64_t num_blocks = (n_slices + block_size - 1) / block_size;
190+
sinkhorn_norm_f32<SINKHORN_NORM_MAX_N><<<num_blocks, block_size, 0, stream>>>(
191+
src0_d, dst_d, n, n_iters, eps, n_slices, slice_stride);
192+
}

0 commit comments

Comments
 (0)