// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2021 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.

#pragma clang fp exceptions(ignore)
#pragma clang fp reassociate(on)
#pragma clang fp contract(fast)

#include <ircd/config.h>
#include <ircd/portable.h>
#include <clc/clc.h>
#include <ircd/simt/simt.h>
#include <ircd/gpt/vector.h>
#include <ircd/gpt/opts.h>
#include <ircd/gpt/ctrl.h>
#include <ircd/gpt/gpu.h>

//
// head
//

__kernel void
__attribute__((visibility("protected")))
ircd_gpt_alloc(__global const void *const restrict model,
               __global void *const restrict master,
               __constant const void *const opts,
               __global void *const restrict ctrl,
               __global void *const restrict frame0,
               __global void *const restrict frame1,
               __global void *const restrict frame2,
               __global void *const restrict frame3,
               __global void *const restrict frame4,
               __global void *const restrict frame5,
               __global void *const restrict frame6,
               __global void *const restrict frame7)
{
}

__kernel void
__attribute__((visibility("protected")))
ircd_gpt_enter(__global const void *const restrict model,
               __global void *const restrict state,
               __global void *const restrict master,
               __constant const struct ircd_gpt_opts *const opts,
               __global struct ircd_gpt_ctrl *const restrict ctrl)
{
	const ushort
	gi = get_global_id(0),
	li = get_local_id(0),
	ln = get_local_size(0),
	cycle = ctrl->clk.cycle;

	if(li == 0)
		ctrl->prof.entered = ircd_simt_cycles();
}

__kernel void
ircd_gpt_lm_embed(__global const struct ircd_gpt_ctrl *const ctrl,
                  __constant const struct ircd_gpt_opts *const opts,
                  __global ircd_gpt_vectorv *const restrict accum,
                  __global const ircd_gpt_vectorv *const restrict pos,
                  __global const ircd_gpt_vectorv *const restrict vocab)
{
	const ushort
	li = get_local_id(0),
	ln = get_local_size(0);

	const uint
	wo = get_global_offset(0);

	assume(ln == 192);
	assume(wo % ln == 0);

	const ushort
	wi = wo / ln + get_group_id(0);

	_ircd_gpt_lm_embed(ctrl, opts, accum, pos, vocab, wi, wi, li);
}

static void
_ircd_gpt_lm_embed(__global const struct ircd_gpt_ctrl *const ctrl,
                   __constant const struct ircd_gpt_opts *const opts,
                   __global ircd_gpt_vectorv *const restrict accum,
                   __global const ircd_gpt_vectorv *const restrict pos,
                   __global const ircd_gpt_vectorv *const restrict vocab,
                   const ushort out_idx,
                   const ushort tok_idx,
                   const ushort elem_idx)
{
	const ushort
	token = ctrl->token[tok_idx];

	const float4
	wpe = pos[tok_idx].elem[elem_idx],
	wte = vocab[token].elem[elem_idx],
	res = wte + wpe;

	accum[out_idx].elem[elem_idx] = res;
}

//
// Frontside
//

void
ircd_gpt_ffnn_fcon_tmul(__constant const struct ircd_gpt_opts *const opts,
                        __local ircd_gpt_ffnn_aperaturev *const restrict out,
                        __local const ircd_gpt_vectorv *const restrict in,
                        __global const ircd_gpt_ffnn_aperaturev *const restrict bias,
                        __global const ircd_gpt_ffnn_aperaturev *const restrict weight,
                        const uint li)
{
	const uint
	lanes = 4,
	segs = ircd_gpt_ffnn_segs,
	height = ircd_gpt_vector_elems / lanes;

	assume(height > 0);
	assume(height % lanes == 0);

	for(uint x = 0; x < segs; ++x)
		out->proj[x][li] = bias->proj[x][li];

	for(uint y = 0; y < height; ++y)
		for(uint k = 0; k < lanes; ++k)
			for(uint x = 0; x < segs; ++x)
			{
				const uint
				row = y * lanes + k;

				out->proj[x][li] += in->elem[y][k] * weight[row].proj[x][li];
			}
}

void
ircd_gpt_ffnn_fcon(__global const struct ircd_gpt_ctrl *const ctrl,
                   __constant const struct ircd_gpt_opts *const opts,
                   __local ircd_gpt_ffnn_aperaturev *const restrict out,
                   __local const ircd_gpt_vectorv *const restrict in,
                   __global const ircd_gpt_ffnn_aperaturev *const restrict bias,
                   __global const ircd_gpt_ffnn_aperaturev *const restrict weight,
                   const uint ln,
                   const uint li)
{
	const uint
	segs = ircd_gpt_ffnn_segs;

	// Fully connected
	ircd_gpt_ffnn_fcon_tmul
	(
		opts,
		out,
		in,
		bias,
		weight,
		li
	);

	for(uint i = 0; i < segs; ++i)
		ircd_gpt_ffnn_gelu(out, out, i * ln + li);
}

void
ircd_gpt_ffnn_proj_tmul(__constant const struct ircd_gpt_opts *const opts,
                        __local ircd_gpt_vectorv *const restrict out,
                        __local const ircd_gpt_ffnn_aperaturev *const restrict in,
                        __global const ircd_gpt_vectorv *const restrict bias,
                        __global const ircd_gpt_vectorv *const restrict weight,
                        const uint li)
{
	const uint
	lanes = 4,
	height = ircd_gpt_ffnn_fcon_elems / lanes;

	assume(height > 0);
	assume(height % lanes == 0);

	out->elem[li] = bias->elem[li];

	for(uint y = 0; y < height; ++y)
		for(uint k = 0; k < lanes; ++k)
		{
			const uint
			row = y * lanes + k;

			out->elem[li] += in->fcon[y][k] * weight[row].elem[li];
		}
}

void
ircd_gpt_ffnn(__global const struct ircd_gpt_ctrl *const ctrl,
              __constant const struct ircd_gpt_opts *const opts,
              __local ircd_gpt_vectorv *const restrict token,
              __local ircd_gpt_ffnn_aperaturev *const restrict buf,
              __local ircd_gpt_vectorv *const restrict tmp,
              __global const ircd_gpt_vectorv *const restrict norm_bias,
              __global const ircd_gpt_vectorv *const restrict norm_weight,
              __global const ircd_gpt_ffnn_aperaturev *const restrict fcon_bias,
              __global const ircd_gpt_ffnn_aperaturev *const restrict fcon_weight,
              __global const ircd_gpt_vectorv *const restrict proj_bias,
              __global const ircd_gpt_vectorv *const restrict proj_weight,
              const uint ln,
              const uint li)
{
	// Layer re-normalization
	ircd_gpt_norm(token, token, tmp, norm_bias, norm_weight, ln, li);

	// ln's writes are still pending but fcon reads results across threads.
	barrier(CLK_LOCAL_MEM_FENCE);

	// Fully connected
	ircd_gpt_ffnn_fcon
	(
		ctrl,
		opts,
		buf,
		token,
		fcon_bias,
		fcon_weight,
		ln,
		li
	);

	// fcon's writes are still pending but proj reads results across threads.
	barrier(CLK_LOCAL_MEM_FENCE);

	// Projection
	ircd_gpt_ffnn_proj_tmul
	(
		opts,
		token,
		buf,
		proj_bias,
		proj_weight,
		li
	);
}

static void
ircd_gpt_attn_self_samax(__global const struct ircd_gpt_ctrl *const ctrl,
                         __constant const struct ircd_gpt_opts *const opts,
                         __local float self[restrict][12],
                         const uint ln,
                         const uint li,
                         const uint wn,
                         const uint wi)
{
	struct ircd_math_samax samax =
	{
		.mu = -10000.0f,
		.sum = 0.0f,
	};

	__attribute__((opencl_unroll_hint))
	for(uint i = 0; i < wn; ++i)
		samax.mu = max(samax.mu, self[i][li]);

	__attribute__((opencl_unroll_hint))
	for(uint i = 0; i < wn; ++i)
		self[i][li] -= samax.mu;

	for(uint i = 0; i < wn; ++i)
		self[i][li] = native_exp(self[i][li]);

	__attribute__((opencl_unroll_hint))
	for(uint i = 0; i < wn; ++i)
		samax.sum += self[i][li];

	samax.sum += FLT_EPSILON;
	samax.lambda = 1.0f / samax.sum;

	__attribute__((opencl_unroll_hint))
	for(uint i = 0; i < wn; ++i)
		self[i][li] *= samax.lambda;
}

static void
ircd_gpt_attn_self_keys(__global const struct ircd_gpt_ctrl *const ctrl,
                        __constant const struct ircd_gpt_opts *const opts,
                        __local float self[restrict][ircd_gpt_attn_rank],
                        __global const ircd_gpt_attn_qkvv *const restrict token,
                        const uint ln,
                        const uint li,
                        const uint wi,
                        const uint kn,
                        const uint i)
{
	assume(i < wi);

	self[i][li] = 0.0f;

	__attribute__((opencl_unroll_hint))
	for(uint k = 0; k < kn; ++k)
	{
		float4
		qry = token[wi].qry.attn[li][k],
		key = token[i].key.attn[li][k],
		res = qry * key;

		self[i][li] += ircd_simt_hadd_f4(res);
	}

	self[i][li] /= 8.0f;
}

static void
ircd_gpt_attn_self_vals(__global const struct ircd_gpt_ctrl *const ctrl,
                        __constant const struct ircd_gpt_opts *const opts,
                        __local ircd_gpt_vectorv *const restrict out,
                        __local const float self[restrict][ircd_gpt_attn_rank],
                        __global const ircd_gpt_attn_qkvv *const restrict token,
                        const uint li,
                        const uint wi,
                        const uint ki,
                        const uint ti)
{
	out->attn[ti][ki] = 0.0f;

	__attribute__((opencl_unroll_hint))
	for(uint i = 0; i < wi; ++i)
	{
		const float4
		val = token[i].val.attn[ti][ki],
		attn = self[i][ti],
		res = attn * val;

		out->attn[ti][ki] += res;
	}
}

static void
ircd_gpt_attn_self(__global struct ircd_gpt_ctrl *const ctrl,
                   __constant const struct ircd_gpt_opts *const opts,
                   __local ircd_gpt_vectorv *const restrict out,
                   __local float self[restrict][ircd_gpt_attn_rank],
                   __global float attns[restrict][ircd_gpt_attn_rank],
                   __global const ircd_gpt_attn_qkvv *const restrict token,
                   const uint ln,
                   const uint li,
                   const uint wi)
{
	//assume(opts->attn_rank == sizeof(self[0]) / sizeof(float));
	assume(opts->attn_rank == ircd_gpt_attn_rank);
	assume(ctrl->count < ircd_gpt_context_tokens);
	assume(ctrl->tokens <= ircd_gpt_context_tokens);
	assume(ctrl->tokens > wi);
	assume(ctrl->tokens > 0);

	const uint
	wn = ctrl->tokens,
	kn = ln / opts->attn_rank,
	ki = li / opts->attn_rank,
	ti = li % opts->attn_rank;

	// Low-rank mask
	if(li < opts->attn_rank)
	{
		// Left attention
		uint i;
		for(i = 0; i < wi; ++i)
			ircd_gpt_attn_self_keys(ctrl, opts, self, token, ln, li, wi, kn, i);

		// Future mask
		__attribute__((opencl_unroll_hint))
		while(i < wn)
			self[i++][li] = -10000.0f;

		// Three-piece softmax
		ircd_gpt_attn_self_samax(ctrl, opts, self, ln, li, wn, wi);
	}

	// Propagate to full width for value dot prod.
	barrier(CLK_LOCAL_MEM_FENCE);
	ircd_gpt_attn_self_vals(ctrl, opts, out, self, token, li, wi, ki, ti);

	// Save softmax results for later analysis/observation.
	if(li < opts->attn_rank)
	{
		__attribute__((opencl_unroll_hint))
		for(uint i = 0; i < wn; ++i)
			attns[i][li] = self[i][li];
	}
}

static void
ircd_gpt_attn_proj_tmul(__constant const struct ircd_gpt_opts *const opts,
                        __local ircd_gpt_vectorv *const restrict out,
                        __local const ircd_gpt_vectorv *const restrict in,
                        __global const ircd_gpt_vectorv *const restrict bias,
                        __global const ircd_gpt_vectorv *const restrict weight,
                        const uint li)
{
	const uint
	lanes = 4,
	height = ircd_gpt_vector_elems / 4;

	assume(height > 0);
	assume(height % lanes == 0);

	out->elem[li] = bias->elem[li];

	for(uint y = 0; y < height; ++y)
		for(uint k = 0; k < lanes; ++k)
		{
			const uint
			row = y * lanes + k;

			const float4
			a = in->elem[y][k],
			b = weight[row].elem[li];

			out->elem[li] += a * b;
		}
}

__kernel void
__attribute__((visibility("protected")))
ircd_gpt_coil(__global struct ircd_gpt_ctrl *const ctrl,
              __constant const struct ircd_gpt_opts *const opts,
              __private const uint layer,
              __global ircd_gpt_vectorv *const restrict accum,
              __global float attns[restrict][ircd_gpt_attn_rank],
              __global const ircd_gpt_attn_qkvv *const restrict state,
              __global const ircd_gpt_vectorv *const restrict attn_proj_bias,
              __global const ircd_gpt_vectorv *const restrict attn_proj_weight,
              __global const ircd_gpt_vectorv *const restrict ffnn_norm_bias,
              __global const ircd_gpt_vectorv *const restrict ffnn_norm_weight,
              __global const ircd_gpt_ffnn_aperaturev *const restrict ffnn_fcon_bias,
              __global const ircd_gpt_ffnn_aperaturev *const restrict ffnn_fcon_weight,
              __global const ircd_gpt_vectorv *const restrict ffnn_proj_bias,
              __global const ircd_gpt_vectorv *const restrict ffnn_proj_weight)
{
	const uint
	li = get_local_id(0),
	ln = get_local_size(0),
	wo = get_global_offset(0),
	wi = wo / ln + get_group_id(0);

	assume(ln == 192);
	assume(wo % ln == 0);

	__local union
	{
		float
		attn_self[ircd_gpt_context_tokens][ircd_gpt_attn_rank];

		ircd_gpt_ffnn_aperaturev
		ffnn_fcon[2];

		ircd_gpt_vectorv
		vector[8];
	}
	buf;

	__local ircd_gpt_vectorv
	buf0, buf1,
	*const restrict attn_self = &buf1,
	*const restrict token = &buf0,
	*const restrict tmp = &buf1;

	// Self-attention backend; this computes the self-attention result now
	// that keys and values are globally visible across tokens.
	ircd_gpt_attn_self
	(
		ctrl,
		opts,
		attn_self,
		buf.attn_self,
		attns,
		state,
		ln,
		li,
		wi
	);

	barrier(CLK_LOCAL_MEM_FENCE);

	// Project result of self-attention.
	ircd_gpt_attn_proj_tmul
	(
		opts,
		token,
		attn_self,
		attn_proj_bias,
		attn_proj_weight,
		li
	);

	// Frontend accumulation
	{
		const float4
		attn = token->elem[li],
		resid = accum[wi].elem[li],
		result = resid + attn;

		token->elem[li] = result;
		accum[wi].elem[li] = result;
	}

	barrier(CLK_GLOBAL_MEM_FENCE);

	// Backend mlp
	ircd_gpt_ffnn
	(
		ctrl,
		opts,
		token,
		buf.ffnn_fcon,
		tmp,
		ffnn_norm_bias,
		ffnn_norm_weight,
		ffnn_fcon_bias,
		ffnn_fcon_weight,
		ffnn_proj_bias,
		ffnn_proj_weight,
		ln,
		li
	);

	// Backend accumulation
	{
		const float4
		ffnn = token->elem[li],
		resid = accum[wi].elem[li],
		result = resid + ffnn;

		accum[wi].elem[li] = result;
	}
}

static void
ircd_gpt_attn_fcon_tmul(__constant const struct ircd_gpt_opts *const opts,
                        __local ircd_gpt_attn_aperaturev *const restrict out,
                        __local const ircd_gpt_vectorv *const restrict in,
                        __global const ircd_gpt_attn_aperaturev *const restrict bias,
                        __global const ircd_gpt_attn_aperaturev *const restrict weight,
                        const uint ln,
                        const uint li)
{
	const uint
	lanes = 4,
	segs = ircd_gpt_attn_segs,
	height = ircd_gpt_vector_elems / lanes;

	assume(height > 0);
	assume(height % segs == 0);
	assume(height % lanes == 0);

	for(uint x = 0; x < segs; ++x)
		out->proj[x][li] = bias->proj[x][li];

	for(uint y = 0; y < height; ++y)
		for(uint k = 0; k < lanes; ++k)
			for(uint x = 0; x < segs; ++x)
			{
				const uint
				row = y * lanes + k;

				const float4
				a = in->elem[y][k],
				b = weight[row].proj[x][li];

				out->proj[x][li] += a * b;
			}
}

__kernel void
ircd_gpt_attn_fcon(__global const struct ircd_gpt_ctrl *const ctrl,
                   __constant const struct ircd_gpt_opts *const opts,
                   __private const uint layer,
                   __global ircd_gpt_attn_aperaturev *const restrict state,
                   __global const ircd_gpt_vectorv *const restrict accum,
                   __global const ircd_gpt_vectorv *const restrict norm_bias,
                   __global const ircd_gpt_vectorv *const restrict norm_weight,
                   __global const ircd_gpt_attn_aperaturev *const restrict fcon_bias,
                   __global const ircd_gpt_attn_aperaturev *const restrict fcon_weight)
{
	const uint
	li = get_local_id(0),
	ln = get_local_size(0),
	wo = get_global_offset(0),
	wi = wo / ln + get_group_id(0),
	segs = ircd_gpt_attn_segs;

	assume(ln == 192);
	assume(wo % ln == 0);

	__local ircd_gpt_attn_aperaturev
	attn;

	__local ircd_gpt_vectorv
	token, *const restrict tmp = attn.vector;

	token.elem[li] = accum[wi].elem[li];

	// Layer re-normalization
	ircd_gpt_norm(&token, &token, tmp, norm_bias, norm_weight, ln, li);

	// Ln's writes are still pending; fcon requires results across threads.
	barrier(CLK_LOCAL_MEM_FENCE);

	// Fully connected
	ircd_gpt_attn_fcon_tmul
	(
		opts,
		&attn,
		&token,
		fcon_bias,
		fcon_weight,
		ln,
		li
	);

	// Export queries, keys, and values.
	for(uint x = 0; x < segs; ++x)
		state[wi].proj[x][li] = attn.proj[x][li];
}

__kernel void
ircd_gpt_lm_norm(__global const struct ircd_gpt_ctrl *const ctrl,
                 __constant const struct ircd_gpt_opts *const opts,
                 __global ircd_gpt_vectorv *const restrict accum,
                 __global const ircd_gpt_vectorv *const restrict norm_bias,
                 __global const ircd_gpt_vectorv *const restrict norm_weight)
{
	const uint
	li = get_local_id(0),
	ln = get_local_size(0),
	wo = get_global_offset(0),
	wi = wo / ln + get_group_id(0);

	assume(ln == 192);
	assume(wo % ln == 0);

	__local ircd_gpt_vectorv
	tmp, token;

	token.elem[li] = accum[wi].elem[li];

	// Final re-normalization
	ircd_gpt_norm(&token, &token, &tmp, norm_bias, norm_weight, ln, li);

	accum[wi].elem[li] = token.elem[li];
}

__kernel void
ircd_gpt_lm_logit(__global const struct ircd_gpt_ctrl *const ctrl,
                  __constant const struct ircd_gpt_opts *const opts,
                  __global float *const restrict logit,
                  __global const ircd_gpt_vectorv *const restrict accum,
                  __global const ircd_gpt_vectorv *const restrict pos,
                  __global const ircd_gpt_vectorv *const restrict vocab)
{
	const uint
	gi = get_global_id(0),
	wi = ctrl->count - 1;

	assume(opts->embed_width == 192);
	assume(opts->logits <= 65536);

	if(gi >= opts->logits)
	{
		logit[gi] = -10000.0f;
		return;
	}

	float acc = 0.0f;
	for(uint j = 0; j < opts->embed_width; ++j)
	{
		const float4
		token = vocab[gi].elem[j],
		in = accum[wi].elem[j],
		wpe = pos[wi].elem[j],
		res = in * token + wpe;

		acc += ircd_simt_hadd_f4(res);
	}

	logit[gi] = acc;
}

__kernel void
__attribute__((reqd_work_group_size(256, 1, 1)))
ircd_gpt_lm_logsm(__global struct ircd_gpt_ctrl *const ctrl,
                  __constant const struct ircd_gpt_opts *const opts,
                  __global float logit[restrict 65536])
{
	const uint
	li = get_local_id(0),
	ln = get_local_size(0),
	//wo = get_global_offset(0),
	//wi = wo / ln + get_group_id(0),
	wn = 50432,
	tn = wn / ln,
	start = tn * li,
	stop = min(start + tn, opts->logits);

	__local float
	mu[256], sum[256], lambda[256];

	__local struct ircd_math_samax
	samax;

	assume(ln == 256);

	mu[li] = -10000.0f;
	__attribute__((opencl_unroll_hint))
	for(uint ti = start; ti < stop; ++ti)
		mu[li] = max(mu[li], logit[ti]);

	ircd_simt_reduce_max_flldr(mu, ln, li);

	if(li == 0)
		samax.mu = mu[li];

	ircd_simt_broadcast_flldr(mu, ln, li);

	sum[li] = 0.0f;
	for(uint ti = start; ti < stop; ++ti)
	{
		const float
		sub = logit[ti] - mu[li],
		res = native_exp(sub);

		sum[li] += res;
	}

	ircd_simt_reduce_add_flldr(sum, ln, li);

	if(li == 0)
		sum[li] += FLT_EPSILON,
		samax.sum = sum[li],
		samax.lambda = lambda[li] = 1.0f / sum[li];

	ircd_simt_broadcast_flldr(lambda, ln, li);

	for(uint ti = start; ti < stop; ++ti)
	{
		const float
		sub = logit[ti] - mu[li],
		res = lambda[li] * native_exp(sub);

		logit[ti] = res;
	}
}

void
ircd_gpt_lm_result_top(__local struct ircd_gpt_ctrl *const ctrl,
                       __constant const struct ircd_gpt_opts *const opts,
                       __local const ushort *const restrict idx,
                       __global const float *const restrict logsm,
                       const uint i)
{
	const ushort
	token = idx[i];

	const float
	samax = logsm[token];

	ctrl->top[i].token = token;
	ctrl->top[i].samax = samax;
}

void
ircd_gpt_lm_result_label_mean(__local struct ircd_gpt_ctrl *const ctrl,
                              __constant const struct ircd_gpt_opts *const opts,
                              __local struct ircd_math_mean *const mean,
                              const float last)
{
	const uint
	div = mean->div + 1,
	sum_sel = mean->div % 4;

	const float
	sum = mean->sum[0] + mean->sum[1] + mean->sum[2] + mean->sum[3] + last,
	res = sum / div;

	mean->sum[sum_sel] += last;
	mean->div = div;
	mean->last = last;
	mean->mean = res;
}

void
ircd_gpt_lm_result_label(__local struct ircd_gpt_ctrl *const ctrl,
                         __constant const struct ircd_gpt_opts *const opts,
                         __local struct ircd_gpt_ctrl_label *const label,
                         __global const float *const restrict logsm)
{
	const ushort
	token = label->logit.token;

	const float
	samax = logsm[token],
	loss = 0.0f - native_log(samax),
	ppl = (1.0f - samax) * native_log2(opts->logits);

	label->logit.samax = samax;
	ircd_gpt_lm_result_label_mean(ctrl, opts, &label->loss, loss);
	ircd_gpt_lm_result_label_mean(ctrl, opts, &label->ppl, ppl);
}

ushort
ircd_gpt_lm_result_select(__local struct ircd_gpt_ctrl *const ctrl,
                          __constant const struct ircd_gpt_opts *const opts,
                          __local const ushort *const restrict idx,
                          __global const float *const restrict logsm)
{
	const ulong
	ent_k = max(opts->top_k, 1U) - 1,
	rnd = ircd_simt_rand_xoshiro256pl(ctrl->rand);

	const float
	ent_p = min(max(opts->top_p, 0.0f), 1.0f),
	thresh = ent_p;

	float acc = 1.0f;
	ushort select = 0;
	for(; select < ent_k; ++select)
		if((acc -= logsm[idx[select]]) < thresh)
			break;

	const ushort
	token = idx[select];

	return token;
}

static ushort
ircd_gpt_lm_result(__local struct ircd_gpt_ctrl *const ctrl,
                   __constant const struct ircd_gpt_opts *const opts,
                   __local const ushort *const restrict idx,
                   __global const float *const restrict logsm)
{
	const ushort
	token = ircd_gpt_lm_result_select(ctrl, opts, idx, logsm);

	// Update the dynamic result label.
	ctrl->select.logit.token = token;
	ircd_gpt_lm_result_label(ctrl, opts, &ctrl->select, logsm);

	// Update the dynamic target label.
	ctrl->target.logit.token = ctrl->count < ctrl->tokens?
		ctrl->token[ctrl->count]:
		ctrl->select.logit.token;

	ircd_gpt_lm_result_label(ctrl, opts, &ctrl->target, logsm);

	const bool
	hit = ctrl->select.logit.token == ctrl->target.logit.token;

	// Update the token context.
	if(ctrl->count == ctrl->tokens)
		ctrl->token[ctrl->tokens++] = ctrl->select.logit.token;
	else
		ctrl->accept = -2;

	ctrl->miss += !hit;
	ctrl->hit += hit;
	ctrl->count++;
	return token;
}

static void
ircd_gpt_lm_result_attns(__local struct ircd_gpt_ctrl *const ctrl,
                         __constant const struct ircd_gpt_opts *const opts,
                         __global const float *const restrict attns,
                         const uint ln,
                         const uint li)
{
	const uint
	layer = li / opts->layers,
	head = li % opts->attn_rank,
	base = layer * opts->attn_self_elems;

	uint best = 0;
	float bestv = 10000.0f;
	for(uint i = 0; i < ctrl->count; ++i)
	{
		const uint
		bx = (((i + 1) * i) / 2) * opts->attn_rank,
		idx = base + bx + i * 12 + head;

		if(attns[idx] < bestv)
			bestv = attns[idx],
			best = i;
	}

	ctrl->attn[layer][head] = best;
}

__kernel void
__attribute__((visibility("protected")))
__attribute__((reqd_work_group_size(256, 1, 1)))
ircd_gpt_lm_select(__global struct ircd_gpt_ctrl *const restrict ctrl_,
                   __constant const struct ircd_gpt_opts *const opts,
                   __global const float logsm[restrict 65536],
                   __global const float *const restrict attns)
{
	const uint
	li = get_local_id(0),
	ln = get_local_size(0),
	logits_pad = ln - (opts->logits % ln),
	tn = (opts->logits + logits_pad) / ln,
	start = tn * li,
	stop = min(start + tn, opts->logits);

	__local ushort idx[256];
	__local struct ircd_gpt_ctrl ctrl;
	__private event_t event[1];

	assume(ln == 256);
	assume(start < stop);

	event[0] = async_work_group_copy
	(
		(__local char16 *)&ctrl,
		(__global const char16 *)ctrl_,
		sizeof(struct ircd_gpt_ctrl) / sizeof(char16),
		0
	);

	idx[li] = start;
	for(uint j = start + 1; j < stop; ++j)
		if(logsm[j] > logsm[idx[li]])
			idx[li] = j;

	ircd_simt_sort_idx16_flldr(idx, logsm, ln, li);
	wait_group_events(1, event);

	if(ctrl.count >= opts->buffer_tokens)
		return;

	if(li < opts->top_n)
		ircd_gpt_lm_result_top(&ctrl, opts, idx, logsm, li);

	if(li < opts->labels)
		ircd_gpt_lm_result_label(&ctrl, opts, ctrl.label + li, logsm);

	if(li < opts->layers * opts->attn_rank)
		ircd_gpt_lm_result_attns(&ctrl, opts, attns, ln, li);

	barrier(CLK_LOCAL_MEM_FENCE);

	if(li == 0)
		ircd_gpt_lm_result(&ctrl, opts, idx, logsm);

	barrier(CLK_LOCAL_MEM_FENCE);

	event[0] = async_work_group_copy
	(
		(__global char16 *)ctrl_,
		(__local const char16 *)&ctrl,
		sizeof(struct ircd_gpt_ctrl) / sizeof(char16),
		0
	);

	wait_group_events(1, event);
}

__kernel void
__attribute__((visibility("protected")))
__attribute__((reqd_work_group_size(256, 1, 1)))
ircd_gpt_leave(__global const void *const restrict model,
               __global void *const restrict state,
               __global void *const restrict master,
               __constant const struct ircd_gpt_opts *const opts,
               __global struct ircd_gpt_ctrl *const ctrl_,
               __global struct ircd_gpt_ctrl *const frame)
{
	const ushort
	li = get_local_id(0),
	ln = get_local_size(0);

	assume(ln == 256);

	__local struct ircd_gpt_ctrl _ctrl;
	__local struct ircd_gpt_ctrl *const ctrl = &_ctrl;

	if(li == 0)
		*ctrl = *ctrl_;

	barrier(CLK_LOCAL_MEM_FENCE);

	if(li == 0 && ctrl->accept < 0)
		ircd_gpt_accept(ctrl, opts);

	barrier(CLK_LOCAL_MEM_FENCE);

	const uint
	batch_size = opts->batch_size,
	samps = opts->training_steps + opts->validation_steps + opts->testing_steps,
	steps = samps / batch_size;

	const bool
	accepting = ctrl->accept >= 0,
	cycling = !accepting,
	sampling = accepting,
	stepping = sampling && (ctrl->clk.samp + 1) >= batch_size,
	epoching = stepping && (ctrl->clk.step + 1) >= steps;

	if(li == 0)
		ctrl->prof.finished = ircd_simt_cycles();

	if(li == 0)
	{
		// BARTS won't update the scalar after the copy. In this case we'll
		// be setting magic for all remaining frames :/
		#if defined(__R600__)
		ctrl->magic = 0xC7012C70UL;
		#endif

		*frame = *ctrl;
		frame->magic = 0xC7012C70UL;
	}

	if(li == 0 && !accepting)
	{
		ctrl->clk.cycle += cycling;
		ctrl->clk.samp += sampling;
		ctrl->clk.step += stepping;
		ctrl->clk.epoch += epoching;
	}

	if(li == 0 && accepting)
		ctrl->magic = 0xC7012C70UL;

	if(li == 0)
		*ctrl_ = *ctrl;
}

void
ircd_gpt_accept(__local struct ircd_gpt_ctrl *const ctrl,
                __constant const struct ircd_gpt_opts *const opts)
{
	const bool
	unlimited = opts->limit < 0,
	unprocessed = ctrl->accept == -2,
	acceptable = ctrl->accept < 0 && !unprocessed;

	const uint
	batch_size = opts->batch_size,
	samps = opts->training_steps + opts->validation_steps + opts->testing_steps,
	steps = samps / batch_size,
	unproc = ctrl->tokens - ctrl->count,
	limit = opts->limit;

	const int
	cycle_remain = opts->context_tokens - (ctrl->clk.cycle + 1), // cycle not yet incr
	token_remain = opts->context_tokens - ctrl->count, // but count already incr
	remain = min(cycle_remain, token_remain);

	int
	accept = ircd_gpt_accept_check(ctrl, opts),
	dispatch = accept < 0? min(abs(accept), (uint)remain): 0;

	if(opts->limit > 0 && ctrl->clk.cycle >= limit - 1)
	{
		accept = accept >= 0? accept: -1;
		dispatch = 0;
	}

	if(opts->limit == 0 && unprocessed)
	{
		accept = accept >= 0? accept: -1;
		dispatch = min((uint)remain, unproc);
	}

	if(opts->limit != 0 && !acceptable)
	{
		accept = accept >= 0? accept: -1;
		dispatch = max(dispatch, 1);
	}

	ctrl->accept = accept;
	ctrl->dispatch = min((uint)dispatch, opts->frames);
}

int
ircd_gpt_accept_check(__local struct ircd_gpt_ctrl *const ctrl,
                      __constant const struct ircd_gpt_opts *const opts)
{
	int best = opts->frames;
	for(uint i = 0; i < 4; ++i)
	{
		const int
		remain = ircd_gpt_accept_match(ctrl, opts, i);

		if(remain == 0)
			return i;

		if(remain < best)
			best = remain;
	}

	return -best;
}

uint
ircd_gpt_accept_match(__local struct ircd_gpt_ctrl *const ctrl,
                      __constant const struct ircd_gpt_opts *const opts,
                      const uint i)
{
	const uint
	len = ircd_gpt_accept_len(ctrl, opts, i),
	n = min(ctrl->count, len),
	maxlen = opts->frames;

	uint ret = len?: maxlen;
	for(uint j = 1; j <= n; ++j)
	{
		uint match = 0;
		for(; match < j; ++match)
		{
			const uint
			accept = opts->accept[i][match],
			token = ctrl->token[ctrl->count - j + match];

			if(token != accept)
				break;
		}

		if(match >= j)
			if(!(ret = len - match))
				break;
	}

	ret = max(ret, ctrl->tokens - ctrl->count);
	ret = min(ret, maxlen);
	return ret;
}

uint
ircd_gpt_accept_len(__local struct ircd_gpt_ctrl *const ctrl,
                    __constant const struct ircd_gpt_opts *const opts,
                    const uint i)
{
	uint len = 0;
	for(; len < 8; ++len)
		if(opts->accept[i][len] == (ushort)-1U)
			break;

	return len;
}

//
// backside
//

void
__attribute__((always_inline))
ircd_gpt_prop_elem(__global const struct ircd_gpt_ctrl *const ctrl,
                   __constant const struct ircd_gpt_opts *const opts,
                   __global float4 *const restrict param_,
                   __global float4 *const restrict exp_avg_,
                   __global float4 *const restrict exp_avg_sqr_)
{
	const uint
	li = get_local_id(0),
	ts = ctrl->clk.step;

	const float4
	param = param_[li],
	grad = ctrl->target.loss.mean,
	alpha[2] = { 1.0f - opts->beta[0], 1.0f - opts->beta[1], },
	exp_avg = ts? exp_avg_[li]: 0.0f,
	exp_avg_sqr = ts? exp_avg_sqr_[li]: 0.0f,
	exp_avg_mul = exp_avg * opts->beta[0],
	exp_avg_dot = exp_avg_mul + alpha[0] * grad,
	exp_avg_sqr_mul = exp_avg_sqr * opts->beta[1],
	exp_avg_sqr_dot = exp_avg_sqr_mul + alpha[1] * grad * grad,
	denom = native_sqrt(exp_avg_sqr_dot) + FLT_EPSILON,
	delta = opts->alpha * (exp_avg_dot / denom),
	update = param - delta;

	param_[li] = update;
	exp_avg_[li] = exp_avg_dot;
	exp_avg_sqr_[li] = exp_avg_sqr_dot;
}

//
// backpropagations
//

__kernel void
__attribute__((always_inline))
ircd_gpt_norm_prop(__global const struct ircd_gpt_ctrl *const ctrl,
                   __constant const struct ircd_gpt_opts *const opts,
                   __global ircd_gpt_vectorv *const restrict bias,
                   __global ircd_gpt_vectorv *const restrict bias_m0,
                   __global ircd_gpt_vectorv *const restrict bias_m1,
                   __global ircd_gpt_vectorv *const restrict weight,
                   __global ircd_gpt_vectorv *const restrict weight_m0,
                   __global ircd_gpt_vectorv *const restrict weight_m1)
{
	ircd_gpt_prop_elem
	(
		ctrl, opts,
		bias->elem,
		bias_m0->elem,
		bias_m1->elem
	);

	ircd_gpt_prop_elem
	(
		ctrl, opts,
		weight->elem,
		weight_m0->elem,
		weight_m1->elem
	);
}

__kernel void
ircd_gpt_coil_prop_attn(__global const struct ircd_gpt_ctrl *const ctrl,
                        __constant const struct ircd_gpt_opts *const opts,
                        __global ircd_gpt_vectorv *const restrict norm_bias,
                        __global ircd_gpt_vectorv *const restrict norm_bias_m0,
                        __global ircd_gpt_vectorv *const restrict norm_bias_m1,
                        __global ircd_gpt_vectorv *const restrict norm_weight,
                        __global ircd_gpt_vectorv *const restrict norm_weight_m0,
                        __global ircd_gpt_vectorv *const restrict norm_weight_m1,
                        __global ircd_gpt_attn_aperaturev *const restrict fcon_bias,
                        __global ircd_gpt_attn_aperaturev *const restrict fcon_bias_m0,
                        __global ircd_gpt_attn_aperaturev *const restrict fcon_bias_m1,
                        __global ircd_gpt_attn_aperaturev *const restrict fcon_weight,
                        __global ircd_gpt_attn_aperaturev *const restrict fcon_weight_m0,
                        __global ircd_gpt_attn_aperaturev *const restrict fcon_weight_m1,
                        __global ircd_gpt_vectorv *const restrict proj_bias,
                        __global ircd_gpt_vectorv *const restrict proj_bias_m0,
                        __global ircd_gpt_vectorv *const restrict proj_bias_m1,
                        __global ircd_gpt_vectorv *const restrict proj_weight,
                        __global ircd_gpt_vectorv *const restrict proj_weight_m0,
                        __global ircd_gpt_vectorv *const restrict proj_weight_m1)
{
	const uint
	fcon_height = opts->embed_elems,
	proj_height = opts->embed_elems,
	segs = 3;

	ircd_gpt_norm_prop
	(
		ctrl, opts,
		norm_bias,
		norm_bias_m0,
		norm_bias_m1,
		norm_weight,
		norm_weight_m0,
		norm_weight_m1
	);

	for(uint j = 0; j < segs; ++j)
		ircd_gpt_prop_elem
		(
			ctrl, opts,
			fcon_bias->proj[j],
			fcon_bias_m0->proj[j],
			fcon_bias_m1->proj[j]
		);

	for(uint i = 0; i < fcon_height; ++i)
		for(uint j = 0; j < segs; ++j)
			ircd_gpt_prop_elem
			(
				ctrl, opts,
				fcon_weight[i].proj[j],
				fcon_weight_m0[i].proj[j],
				fcon_weight_m1[i].proj[j]
			);

	ircd_gpt_prop_elem
	(
		ctrl, opts,
		proj_bias->elem,
		proj_bias_m0->elem,
		proj_bias_m1->elem
	);

	for(uint i = 0; i < proj_height; ++i)
		ircd_gpt_prop_elem
		(
			ctrl, opts,
			proj_weight[i].elem,
			proj_weight_m0[i].elem,
			proj_weight_m1[i].elem
		);
}

__kernel void
ircd_gpt_coil_prop_ffnn(__global const struct ircd_gpt_ctrl *const ctrl,
                        __constant const struct ircd_gpt_opts *const opts,
                        __global ircd_gpt_vectorv *const restrict norm_bias,
                        __global ircd_gpt_vectorv *const restrict norm_bias_m0,
                        __global ircd_gpt_vectorv *const restrict norm_bias_m1,
                        __global ircd_gpt_vectorv *const restrict norm_weight,
                        __global ircd_gpt_vectorv *const restrict norm_weight_m0,
                        __global ircd_gpt_vectorv *const restrict norm_weight_m1,
                        __global ircd_gpt_ffnn_aperaturev *const restrict fcon_bias,
                        __global ircd_gpt_ffnn_aperaturev *const restrict fcon_bias_m0,
                        __global ircd_gpt_ffnn_aperaturev *const restrict fcon_bias_m1,
                        __global ircd_gpt_ffnn_aperaturev *const restrict fcon_weight,
                        __global ircd_gpt_ffnn_aperaturev *const restrict fcon_weight_m0,
                        __global ircd_gpt_ffnn_aperaturev *const restrict fcon_weight_m1,
                        __global ircd_gpt_vectorv *const restrict proj_bias,
                        __global ircd_gpt_vectorv *const restrict proj_bias_m0,
                        __global ircd_gpt_vectorv *const restrict proj_bias_m1,
                        __global ircd_gpt_vectorv *const restrict proj_weight,
                        __global ircd_gpt_vectorv *const restrict proj_weight_m0,
                        __global ircd_gpt_vectorv *const restrict proj_weight_m1)
{
	const uint
	fcon_height = opts->embed_elems,
	proj_height = opts->ffnn_elems,
	segs = 4;

	ircd_gpt_norm_prop
	(
		ctrl, opts,
		norm_bias,
		norm_bias_m0,
		norm_bias_m1,
		norm_weight,
		norm_weight_m0,
		norm_weight_m1
	);

	for(uint j = 0; j < segs; ++j)
		ircd_gpt_prop_elem
		(
			ctrl, opts,
			fcon_bias->proj[j],
			fcon_bias_m0->proj[j],
			fcon_bias_m1->proj[j]
		);

	for(uint i = 0; i < fcon_height; ++i)
		for(uint j = 0; j < segs; ++j)
			ircd_gpt_prop_elem
			(
				ctrl, opts,
				fcon_weight[i].proj[j],
				fcon_weight_m0[i].proj[j],
				fcon_weight_m1[i].proj[j]
			);

	ircd_gpt_prop_elem
	(
		ctrl, opts,
		proj_bias->elem,
		proj_bias_m0->elem,
		proj_bias_m1->elem
	);

	for(uint i = 0; i < proj_height; ++i)
		ircd_gpt_prop_elem
		(
			ctrl, opts,
			proj_weight[i].elem,
			proj_weight_m0[i].elem,
			proj_weight_m1[i].elem
		);
}

__kernel void
ircd_gpt_lm_embed_prop(__global const struct ircd_gpt_ctrl *const ctrl,
                       __constant const struct ircd_gpt_opts *const opts,
                       __global ircd_gpt_vectorv *const restrict pos,
                       __global ircd_gpt_vectorv *const restrict pos_m0,
                       __global ircd_gpt_vectorv *const restrict pos_m1,
                       __global ircd_gpt_vectorv *const restrict token,
                       __global ircd_gpt_vectorv *const restrict token_m0,
                       __global ircd_gpt_vectorv *const restrict token_m1)
{
	const uint
	ln = get_local_size(0),
	wi = get_global_offset(0) / ln + get_group_id(0),
	wn = ctrl->count,
	cn = opts->context_tokens / wn,
	ci = cn * wi,
	tn = opts->logits / wn,
	ti = tn * wi;

	for(uint i = ci; i < ci + cn; ++i)
		ircd_gpt_prop_elem
		(
			ctrl, opts,
			pos[i].elem,
			pos_m0[i].elem,
			pos_m1[i].elem
		);

	for(uint i = ti; i < ti + tn; ++i)
		ircd_gpt_prop_elem
		(
			ctrl, opts,
			token[i].elem,
			token_m0[i].elem,
			token_m1[i].elem
		);
}

/// Gaussian Error Linear Unit
void
ircd_gpt_ffnn_gelu(__local ircd_gpt_ffnn_aperaturev *const out,
                   __local const ircd_gpt_ffnn_aperaturev *const in_,
                   const uint i)
{
	const float4
	in = in_->fcon[i];

	float4 a;
	a = 0.044715f;
	a *= in;
	a *= in;
	a += 1.0f;
	a *= 0.7978845608f;
	a *= in;

	a = tanh(a);
	a += 1.0f;
	a *= in;
	a *= 0.5f;

	out->fcon[i] = a;
}

void
ircd_gpt_norm(__local ircd_gpt_vectorv *const out,
              __local const ircd_gpt_vectorv *const in,
              __local ircd_gpt_vectorv *const restrict tmp,
              __global const ircd_gpt_vectorv *const restrict bias,
              __global const ircd_gpt_vectorv *const restrict weight,
              const uint ln,
              const uint li)
{
	// Layer re-normalization
	ircd_simt_math_norm_f4lldr(out->elem, in->elem, tmp->elem, ln, li);
	ircd_gpt_norm_fmad(out, out, bias, weight, li);
}

void
ircd_gpt_norm_fmad(__local ircd_gpt_vectorv *const out,
                   __local const ircd_gpt_vectorv *const in,
                   __global const ircd_gpt_vectorv *const restrict bias,
                   __global const ircd_gpt_vectorv *const restrict weight,
                   const uint i)
{
	out->elem[i] = in->elem[i] * weight->elem[i] + bias->elem[i];
}
