Gliner/Gpu/GlinerGpuP8B.cs
using System;
using System.Diagnostics;
using System.Threading.Tasks;
// (P8B harness — file renamed from GlinerGpuP8B.cs to break a stale compiler-cache entry)
using Sandbox;

namespace GlinerPoc.Gpu;

/// <summary>
/// Phase 8B parity + benchmark harness (isolated; GPU stays non-default).
///
/// Gates, in order:
///   0  P8A.1 regression: vec canary + real Q projection through the executor
///   1  weight residency: layer-0 subset uploaded once, reused across rounds
///   2  GEMM synthetic bounds parity (tiled16/tiled8 vs CPU)
///   3  GEMM real-shape parity (S=24/40/128/256 × 384→384/384→1536/1536→384,
///      naïve + tiled16 + tiled8 vs CPU oracle)
///   4  resident-weight GEMM benchmarks (CPU vs naïve vs tiled16 vs tiled8;
///      upload excluded; amortized in-job repetition timing, stated limits)
///   5  primitives: add, ReLU, GELU (synthetic + real FFN), LayerNorm
///      (synthetic incl. near-constant + real rel_embeddings), Softmax
///      (synthetic normal/extreme/equal + real attention scores), copy, gather
///   6  ping-pong/scratch buffer reuse across many jobs
///   7  GPU→GPU chain (synthetic, no intermediate readback)
///   8  REAL layer-0 FFN chain: 384→1536 → GELU → 1536→384 vs CPU + fixture
///   9  queued-job cancellation semantics
///  10  async completion propagation while the component continues working
///  11  buffer plan / embedding strategy / perf projection (logged analysis)
///
/// Tolerance: atol 1e-5, rtol 1e-4 (GlinerMath.Compare). Index ops must be
/// exact. Timing: completed-operation latency (submit→readback), amortized
/// over in-job repetitions; enqueue time is never reported as compute time.
/// </summary>
[Title( "GLiNER GPU P8B Backend" )]
[Category( "GLiNER" )]
public sealed class GlinerGpuP8B : Component
{
	[Property]
	public bool RunOnStart { get; set; } = true;

	[Property]
	public GlinerPoc.Packaging.GlinerModelResource ModelResource { get; set; }

	private int _passed;
	private int _failed;
	private GlinerGpuRuntime _rt;
	private GpuBuffer<float> _probe16;

	private static float Lcg( ref uint state )
	{
		state = state * 1664525u + 1013904223u;
		return ((state >> 8) & 0xFFFF) / 65535f - 0.5f;
	}

	private static float[] LcgArray( int count, uint seed )
	{
		uint s = seed;
		var a = new float[count];
		for ( int i = 0; i < count; i++ )
		{
			a[i] = Lcg( ref s );
		}
		return a;
	}

	private static float[] DecodeB64( string b64 )
	{
		byte[] bytes = Convert.FromBase64String( b64 );
		return GlinerPoc.Neural.GlinerMath.DecodeF32( bytes, 0, bytes.Length / 4 );
	}

	protected override void OnStart()
	{
		if ( RunOnStart )
		{
			_ = RunAsync();
		}
	}

	protected override void OnDestroy()
	{
		_rt?.Dispose();
		_rt = null;
	}

	private async Task<GlinerPoc.Neural.GlinerModelWeights> LoadWeightsAsync( string gate )
	{
		// the packaged resource's metadata bytes can still be materializing in
		// the first frames of play; bounded retry (observed once on 26.09.22)
		for ( int attempt = 0; attempt < 20; attempt++ )
		{
			int len = ModelResource?.MetadataData?.Bytes?.Length ?? -1;
			if ( len is > 128 )
			{
				return await Task.RunInThreadAsync( () => GlinerPoc.Neural.GlinerModelWeights.FromResource( ModelResource ) );
			}
			Log.Info( $"[GLI:P8B] {gate}: model metadata not ready (len={len}), waiting..." );
			await Task.Delay( 100 );
		}
		throw new InvalidOperationException( "[GLI:ERROR] packaged model metadata never became available." );
	}

	/// <summary>Submit work with a diagnostic readback and await the result.</summary>
	private async Task<float[]> RunJob( string name, Action work, GpuBuffer<float> buffer, int count )
	{
		bool done = false;
		float[] result = null;
		_rt.Executor.SubmitReadback( name, work, buffer, count, r =>
		{
			result = r;
			done = true;
		} );
		int waits = 0;
		while ( !done && waits < 400 )
		{
			await Task.Delay( 8 );
			waits++;
		}
		if ( !done )
		{
			throw new InvalidOperationException( $"[GLI:ERROR] job '{name}' readback never completed." );
		}
		return result;
	}

	/// <summary>Completed-operation latency for N in-job repetitions of an op (render-frame overhead amortized).</summary>
	private async Task<double> TimedReps( string name, Action opReps, int reps )
	{
		var sw = Stopwatch.StartNew();
		bool done = false;
		_rt.Executor.SubmitReadback( name, opReps, _probe16, 16, _ => done = true );
		int waits = 0;
		while ( !done && waits < 400 )
		{
			await Task.Delay( 8 );
			waits++;
		}
		sw.Stop();
		return done ? sw.Elapsed.TotalMilliseconds / reps : -1;
	}

	private async Task RunAsync()
	{
		var sw = Stopwatch.StartNew();
		Log.Info( "[GLI:P8B] backend harness start" );
		try
		{
			if ( ModelResource is null )
			{
				throw new InvalidOperationException( "[GLI:ERROR] No GlinerModelResource assigned." );
			}
			_rt = new GlinerGpuRuntime();
			_rt.Initialize( Scene );
			_probe16 = _rt.CreateBuffer( "probe16", 16, 1, persistent: true, "timing probe readback" ).Buffer;
			await Task.Delay( 150 ); // let the render hook go live

			await Gate0Canary();
			var weights = await Gate1Residency();

			await GateDebugIdentity();
			await Gate2GemmSynthetic( weights );
			await Gate3GemmRealShapes( weights );
			await Gate4Benchmarks( weights );
			await Gate5Primitives( weights );
			await Gate6BufferReuse();
			await Gate7SyntheticChain();
			await Gate8RealFfnChain( weights );
			Gate9Cancellation();
			await Gate10AsyncPropagation( weights );
			Gate11Analysis( weights );

			_rt.Dispose();
			_rt = null;

			Log.Info( $"[GLI:P8B] harness complete passed={_passed} failed={_failed} total_ms={sw.ElapsedMilliseconds}" );
			Log.Info( _failed == 0
				? "[GLI:P8B] ALL PASS"
				: $"[GLI:P8B] FAILURES PRESENT ({_failed})" );
		}
		catch ( Exception error )
		{
			Log.Error( $"[GLI:P8B] harness failure: {error}" );
		}
	}

	// ---- gate 0: P8A.1 regression through the executor ---------------------------

	private async Task Gate0Canary()
	{
		const int N = 64;
		var input = new float[N];
		for ( int i = 0; i < N; i++ )
		{
			input[i] = i * 0.25f;
		}
		using var inBuf = new GpuBuffer<float>( N );
		using var outBuf = new GpuBuffer<float>( N );
		inBuf.SetData( input );
		outBuf.Clear( 0xdeadbeefu );

		var result = await RunJob( "canary", () => GlinerGpuOps.VecAddConstant( _rt.VecAdd, inBuf, outBuf, N, 1f ), outBuf, N );
		int mismatches = 0;
		int poison = 0;
		for ( int i = 0; i < N; i++ )
		{
			float e = input[i] + 1f;
			if ( BitConverter.SingleToInt32Bits( result[i] ) != BitConverter.SingleToInt32Bits( e ) )
			{
				mismatches++;
				if ( BitConverter.SingleToInt32Bits( result[i] ) == unchecked((int)0xdeadbeef) )
				{
					poison++;
				}
			}
		}
		Gate( "p8b_canary_vec", mismatches == 0,
			$"executor render-context canary: {N - mismatches}/{N} bit-exact (poisonRemaining={poison})", "" );

		// real Q projection (P8A.1 regression) — naïve kernel
		var weights = await LoadWeightsAsync( "gate0" );
		var p5 = GlinerPoc.Neural.NeuralP5FixturesV2.All[1]; // s40
		float[] xReal = p5.T( "layer_input" );
		float[] wq = weights.GetTensor( "encoder.encoder.layer.0.attention.self.query_proj.weight" );
		float[] bq = weights.GetTensor( "encoder.encoder.layer.0.attention.self.query_proj.bias" );
		float[] cpu = await Task.RunInThreadAsync( () => GlinerPoc.Neural.GlinerMath.Linear( xReal, 40, wq, bq, 384, 384 ) );

		var xb = _rt.CreateBuffer( "q_x", 40, 384, persistent: false, "request input" );
		var wb = _rt.UploadPersistent( "w_l0_q", wq, 384, 384, "weight layer0 Q" ).Desc;
		var bb = _rt.UploadPersistent( "b_l0_q", bq, 1, 384, "bias layer0 Q" ).Desc;
		var yb = _rt.CreateBuffer( "q_y", 40, 384, persistent: false, "activation" );
		xb.Buffer.SetData( xReal );

		var gpu = await RunJob( "q_proj", () => GlinerGpuOps.LinearNaive( _rt.GemmNaive, xb.Buffer, wb.Buffer, bb.Buffer, yb.Buffer, 40, 384, 384, true ), yb.Buffer, 40 * 384 );
		var m = GlinerPoc.Neural.GlinerMath.Compare( gpu, cpu );
		Gate( "p8b_q_projection_real", m.Pass,
			$"P8A.1 regression via executor: maxAbs={m.MaxAbs:0.###e-00} meanAbs={m.MeanAbs:0.###e-00}", "" );
	}

	// ---- gate 1: weight residency subset + reuse -----------------------------------

	private sealed class WeightSet
	{
		public GlinerGpuBufferDesc QW, QB, KW, KB, VW, VB, AttW, AttB, UpW, UpB, DownW, DownB;
		public GlinerGpuBufferDesc EmbLnW, EmbLnB, EncLnW, EncLnB, RelEmb;
		public double UploadMs;
		public long Bytes;
	}

	private async Task<WeightSet> Gate1Residency()
	{
		var w = new WeightSet();
		GlinerPoc.Neural.GlinerModelWeights weights = await LoadWeightsAsync( "gate1" );
		const string p = "encoder.encoder.layer.0.";
		var sw = Stopwatch.StartNew();

		// NOTE (P8B.38): float[] staging copies come from GetTensor's decode
		// cache; they are NOT retained beyond upload.
		(w.QW, _) = _rt.UploadPersistent( "w_l0q", weights.GetTensor( p + "attention.self.query_proj.weight" ), 384, 384, "weight l0 Q" );
		(w.QB, _) = _rt.UploadPersistent( "b_l0q", weights.GetTensor( p + "attention.self.query_proj.bias" ), 1, 384, "bias l0 Q" );
		(w.KW, _) = _rt.UploadPersistent( "w_l0k", weights.GetTensor( p + "attention.self.key_proj.weight" ), 384, 384, "weight l0 K" );
		(w.KB, _) = _rt.UploadPersistent( "b_l0k", weights.GetTensor( p + "attention.self.key_proj.bias" ), 1, 384, "bias l0 K" );
		(w.VW, _) = _rt.UploadPersistent( "w_l0v", weights.GetTensor( p + "attention.self.value_proj.weight" ), 384, 384, "weight l0 V" );
		(w.VB, _) = _rt.UploadPersistent( "b_l0v", weights.GetTensor( p + "attention.self.value_proj.bias" ), 1, 384, "bias l0 V" );
		(w.AttW, _) = _rt.UploadPersistent( "w_l0ao", weights.GetTensor( p + "attention.output.dense.weight" ), 384, 384, "weight l0 attn-out" );
		(w.AttB, _) = _rt.UploadPersistent( "b_l0ao", weights.GetTensor( p + "attention.output.dense.bias" ), 1, 384, "bias l0 attn-out" );
		(w.UpW, _) = _rt.UploadPersistent( "w_l0up", weights.GetTensor( p + "intermediate.dense.weight" ), 1536, 384, "weight l0 FFN-up" );
		(w.UpB, _) = _rt.UploadPersistent( "b_l0up", weights.GetTensor( p + "intermediate.dense.bias" ), 1, 1536, "bias l0 FFN-up" );
		(w.DownW, _) = _rt.UploadPersistent( "w_l0dn", weights.GetTensor( p + "output.dense.weight" ), 384, 1536, "weight l0 FFN-down" );
		(w.DownB, _) = _rt.UploadPersistent( "b_l0dn", weights.GetTensor( p + "output.dense.bias" ), 1, 384, "bias l0 FFN-down" );
		(w.EmbLnW, _) = _rt.UploadPersistent( "w_emb_ln", weights.GetTensor( "encoder.embeddings.LayerNorm.weight" ), 1, 384, "embeddings LN weight" );
		(w.EmbLnB, _) = _rt.UploadPersistent( "b_emb_ln", weights.GetTensor( "encoder.embeddings.LayerNorm.bias" ), 1, 384, "embeddings LN bias" );
		(w.EncLnW, _) = _rt.UploadPersistent( "w_enc_ln", weights.GetTensor( "encoder.encoder.LayerNorm.weight" ), 1, 384, "rel-emb LN weight" );
		(w.EncLnB, _) = _rt.UploadPersistent( "b_enc_ln", weights.GetTensor( "encoder.encoder.LayerNorm.bias" ), 1, 384, "rel-emb LN bias" );
		(w.RelEmb, _) = _rt.UploadPersistent( "rel_emb", weights.GetTensor( "encoder.encoder.rel_embeddings.weight" ), 512, 384, "rel embeddings raw (8B.23)" );
		sw.Stop();
		w.UploadMs = sw.Elapsed.TotalMilliseconds;
		w.Bytes = _rt.PersistentBytes;

		// reuse proof: 5 rounds of Q GEMM against the SAME resident weight
		// buffers, no re-upload; round 0 checked vs CPU, all rounds bit-stable
		var p5 = GlinerPoc.Neural.NeuralP5FixturesV2.All[1];
		float[] xReal = p5.T( "layer_input" );
		float[] wq = weights.GetTensor( p + "attention.self.query_proj.weight" );
		float[] bq = weights.GetTensor( p + "attention.self.query_proj.bias" );
		float[] cpu = await Task.RunInThreadAsync( () => GlinerPoc.Neural.GlinerMath.Linear( xReal, 40, wq, bq, 384, 384 ) );

		var xb = _rt.GetBuffer( "q_x" );
		xb.Buffer.SetData( xReal );
		var yb = _rt.GetBuffer( "q_y" );
		float[] first = null;
		bool stable = true;
		for ( int round = 0; round < 5; round++ )
		{
			var gpu = await RunJob( $"resid{round}",
				() => GlinerGpuOps.LinearTiled16( _rt.GemmTiled16, xb.Buffer, w.QW.Buffer, w.QB.Buffer, yb.Buffer, 40, 384, 384, true ),
				yb.Buffer, 40 * 384 );
			if ( round == 0 )
			{
				first = gpu;
			}
			else
			{
				for ( int i = 0; i < gpu.Length; i++ )
				{
					if ( BitConverter.SingleToInt32Bits( gpu[i] ) != BitConverter.SingleToInt32Bits( first[i] ) )
					{
						stable = false;
					}
				}
			}
		}
		var m = GlinerPoc.Neural.GlinerMath.Compare( first, cpu );
		Gate( "p8b_weight_residency", m.Pass && stable,
			$"layer-0 subset {w.Bytes / 1024.0:0} KiB uploaded ONCE in {w.UploadMs:0.0} ms; 5 tiled-Q rounds reused resident weights; " +
			$"round0 vs CPU maxAbs={m.MaxAbs:0.###e-00}; rounds bit-stable={stable}", "" );
		return w;
	}

	
	private async Task GateDebugIdentity()
	{
		// identity micro-test: X=I(16), W=I(16), bias=0 -> expect I
		int n = 16;
		var x = new float[n * n];
		var w = new float[n * n];
		for ( int i = 0; i < n; i++ )
		{
			x[i * n + i] = 1f;
			w[i * n + i] = 1f;
		}
		using var xb = new GpuBuffer<float>( n * n );
		using var wb = new GpuBuffer<float>( n * n );
		using var bb = new GpuBuffer<float>( n );
		using var yb = new GpuBuffer<float>( n * n );
		xb.SetData( x );
		wb.SetData( w );
		bb.Clear( 0 );
		var r = await RunJob( "ident", () => GlinerGpuOps.LinearTiled16( _rt.GemmTiled16, xb, wb, bb, yb, n, n, n, false ), yb, n * n );
		var sbd = new System.Text.StringBuilder();
		for ( int i = 0; i < n; i++ )
		{
			for ( int j = 0; j < n; j++ )
			{
				if ( r[i * n + j] != (i == j ? 1f : 0f) )
				{
					_ = sbd.Append( $"[{i},{j}]={r[i * n + j]:0.###} " );
				}
			}
		}
		Log.Info( $"[GLI:P8B] identity16 tiled16: {(sbd.Length == 0 ? "PASS" : "FAIL diffs: " + sbd)}" );
		// asymmetric probe: X row-major distinct values, W=I -> output should equal X
		var x2 = new float[n * n];
		for ( int i = 0; i < n * n; i++ )
		{
			x2[i] = i;
		}
		xb.SetData( x2 );
		var r2 = await RunJob( "ident2", () => GlinerGpuOps.LinearTiled16( _rt.GemmTiled16, xb, wb, bb, yb, n, n, n, false ), yb, n * n );
		bool same = true;
		for ( int i = 0; i < n * n; i++ )
		{
			same &= r2[i] == x2[i];
		}
		Log.Info( $"[GLI:P8B] passthrough16 (X distinct, W=I): {(same ? "PASS" : "FAIL first mismatches: " + FirstMismatch( r2, x2 ))}" );
		// W-probe: X=I, W=distinct -> expect Y[r,c] = W[c,r] (Y = I*W^T)
		var w2 = new float[n * n];
		for ( int i = 0; i < n * n; i++ )
		{
			w2[i] = i;
		}
		xb.Clear( 0 );
		for ( int i = 0; i < n; i++ )
		{
			float[] oneRow = new float[n];
			oneRow[i] = 1f;
			// SetData per row is not offset-capable for 2d; build full matrix
		}
		var xi = new float[n * n];
		for ( int i = 0; i < n; i++ )
		{
			xi[i * n + i] = 1f;
		}
		xb.SetData( xi );
		wb.SetData( w2 );
		var r3 = await RunJob( "ident3", () => GlinerGpuOps.LinearTiled16( _rt.GemmTiled16, xb, wb, bb, yb, n, n, n, false ), yb, n * n );
		bool isWT = true;
		bool isW = true;
		for ( int rr = 0; rr < n; rr++ )
		{
			for ( int cc = 0; cc < n; cc++ )
			{
				isWT &= r3[rr * n + cc] == w2[cc * n + rr];
				isW &= r3[rr * n + cc] == w2[rr * n + cc];
			}
		}
		Log.Info( $"[GLI:P8B] wprobe16 (X=I, W=distinct): isWtranspose={isWT} isW={isW} first={r3[0]:0},{r3[1]:0},{r3[2]:0},{r3[3]:0}" );
	}

	private static string FirstMismatch( float[] a, float[] b )
	{
		var sbd = new System.Text.StringBuilder();
		int shown = 0;
		for ( int i = 0; i < a.Length && shown < 8; i++ )
		{
			if ( a[i] != b[i] )
			{
				_ = sbd.Append( $"[{i}] act={a[i]:0.###} exp={b[i]:0.###} " );
				shown++;
			}
		}
		return sbd.ToString();
	}

	// ---- gate 2: synthetic GEMM bounds (tiled) ---------------------------------------

	private async Task Gate2GemmSynthetic( WeightSet w )
	{
		bool all = true;
		string detail = "";
		foreach ( var (rows, inDim, outDim, bias, seed, name) in new[] {
			(4, 3, 2, true, 7001u, "syn43b"), (4, 3, 2, false, 7002u, "syn43n"), (5, 7, 3, true, 7003u, "syn57") } )
		{
			float[] x = LcgArray( rows * inDim, seed );
			float[] ww = LcgArray( outDim * inDim, seed + 1 );
			float[] b = LcgArray( outDim, seed + 2 );
			float[] cpu = await Task.RunInThreadAsync( () => GlinerPoc.Neural.GlinerMath.Linear( x, rows, ww, bias ? b : null, inDim, outDim ) );

			using var xb = new GpuBuffer<float>( x.Length );
			using var wb = new GpuBuffer<float>( ww.Length );
			using var bb = new GpuBuffer<float>( b.Length );
			using var y16 = new GpuBuffer<float>( rows * outDim );
			using var y8 = new GpuBuffer<float>( rows * outDim );
			xb.SetData( x );
			wb.SetData( ww );
			bb.SetData( b );

			var r16 = await RunJob( "g16", () => GlinerGpuOps.LinearTiled16( _rt.GemmTiled16, xb, wb, bb, y16, rows, inDim, outDim, bias ), y16, rows * outDim );
			var r8 = await RunJob( "g8", () => GlinerGpuOps.LinearTiled8( _rt.GemmTiled8, xb, wb, bb, y8, rows, inDim, outDim, bias ), y8, rows * outDim );
			var m16 = GlinerPoc.Neural.GlinerMath.Compare( r16, cpu );
			var m8 = GlinerPoc.Neural.GlinerMath.Compare( r8, cpu );
			bool ok = m16.Pass && m8.Pass;
			all &= ok;
			detail += $"{name} t16max={m16.MaxAbs:0.###e-00} t8max={m8.MaxAbs:0.###e-00} ";
		}
		Gate( "p8b_gemm_synthetic_tiled", all, $"odd/group-remainder dims incl. ±bias: {detail}", "" );
	}

	// ---- gate 3: real-shape GEMM parity ----------------------------------------------

	private async Task Gate3GemmRealShapes( WeightSet w )
	{
		var p5s24 = GlinerPoc.Neural.NeuralP5FixturesV2.All[0];
		var p5s40 = GlinerPoc.Neural.NeuralP5FixturesV2.All[1];

		bool allPass = true;
		foreach ( int s in new[] { 24, 40, 128, 256 } )
		{
			float[] x384 = s == 24 ? p5s24.T( "layer_input" )
				: s == 40 ? p5s40.T( "layer_input" )
				: LcgArray( s * 384, (uint)(8000 + s) );
			float[] x1536 = LcgArray( s * 1536, (uint)(8100 + s) );

			// CPU oracle (uses the CPU-side staging copies of the same weights)
			float[] xC = x384, xC2 = x1536;
			var cpuRefs = await Task.RunInThreadAsync( () => new[] {
				GlinerPoc.Neural.GlinerMath.Linear( xC, s, w.QW.CpuStaging, w.QB.CpuStaging, 384, 384 ),
				GlinerPoc.Neural.GlinerMath.Linear( xC, s, w.UpW.CpuStaging, w.UpB.CpuStaging, 384, 1536 ),
				GlinerPoc.Neural.GlinerMath.Linear( xC2, s, w.DownW.CpuStaging, w.DownB.CpuStaging, 1536, 384 ) } );

			var xb = _rt.CreateBuffer( "g_x", s, 1536, false, "gemm input" );
			var yb = _rt.CreateBuffer( "g_y", s, 1536, false, "gemm output" );

			string row = $"S{s}: ";
			bool shapePass = true;
			for ( int c = 0; c < 3; c++ )
			{
				int inD = c == 2 ? 1536 : 384;
				int outD = c == 1 ? 1536 : 384;
				float[] xin = c == 2 ? x1536 : x384;
				var wDesc = c == 0 ? w.QW : c == 1 ? w.UpW : w.DownW;
				var bDesc = c == 0 ? w.QB : c == 1 ? w.UpB : w.DownB;
				int count = s * outD;
				xb.Buffer.SetData( xin );

				var rn = await RunJob( "gn", () => GlinerGpuOps.LinearNaive( _rt.GemmNaive, xb.Buffer, wDesc.Buffer, bDesc.Buffer, yb.Buffer, s, inD, outD, true ), yb.Buffer, count );
				var r16 = await RunJob( "g16", () => GlinerGpuOps.LinearTiled16( _rt.GemmTiled16, xb.Buffer, wDesc.Buffer, bDesc.Buffer, yb.Buffer, s, inD, outD, true ), yb.Buffer, count );
				var r8 = await RunJob( "g8", () => GlinerGpuOps.LinearTiled8( _rt.GemmTiled8, xb.Buffer, wDesc.Buffer, bDesc.Buffer, yb.Buffer, s, inD, outD, true ), yb.Buffer, count );

				var mn = GlinerPoc.Neural.GlinerMath.Compare( rn, cpuRefs[c] );
				var m16 = GlinerPoc.Neural.GlinerMath.Compare( r16, cpuRefs[c] );
				var m8 = GlinerPoc.Neural.GlinerMath.Compare( r8, cpuRefs[c] );
				bool ok = mn.Pass && m16.Pass && m8.Pass;
				shapePass &= ok;
				row += $"{inD}to{outD}[n={mn.MaxAbs:0.###e-00} t16={m16.MaxAbs:0.###e-00} t8={m8.MaxAbs:0.###e-00}] ";
			}
			allPass &= shapePass;
			Gate( $"p8b_gemm_S{s}", shapePass, row, "" );
		}
		Gate( "p8b_gemm_real_shapes", allPass, "all real shapes × 3 kernels vs CPU oracle (atol 1e-5 / rtol 1e-4)", "" );
	}

	// ---- gate 4: resident-weight benchmarks -------------------------------------------

	private async Task Gate4Benchmarks( WeightSet w )
	{
		// overhead calibration: empty job + tiny readback
		double overhead = await TimedReps( "calib", () => { }, 20 );
		Log.Info( $"[GLI:P8B] TIMING calibration: empty-job+tiny-readback = {overhead:0.00} ms/op (render-frame latency dominates single jobs; amortized below)" );

		var p5s40 = GlinerPoc.Neural.NeuralP5FixturesV2.All[1];
		float[] x40 = p5s40.T( "layer_input" );

		var xb = _rt.GetBuffer( "g_x" );
		var yb = _rt.GetBuffer( "g_y" );

		Log.Info( "[GLI:P8B] TIMING header: shape | cpuMs | naiveMs | tiled16Ms | tiled8Ms  (GPU = amortized completed-op latency, K=20 in-job reps, weights+input resident, readback excluded from kernel time)" );
		foreach ( int s in new[] { 40, 128, 256 } )
		{
			float[] x384 = s == 40 ? x40 : LcgArray( s * 384, (uint)(9000 + s) );
			float[] x1536 = LcgArray( s * 1536, (uint)(9100 + s) );
			for ( int c = 0; c < 3; c++ )
			{
				int inD = c == 2 ? 1536 : 384;
				int outD = c == 1 ? 1536 : 384;
				float[] xin = c == 2 ? x1536 : x384;
				var wDesc = c == 0 ? w.QW : c == 1 ? w.UpW : w.DownW;
				var bDesc = c == 0 ? w.QB : c == 1 ? w.UpB : w.DownB;
				int count = s * outD;
				xb.Buffer.SetData( xin );

				float[] xw = wDesc.CpuStaging;
				float[] bw = bDesc.CpuStaging;
				float[] xc = xin;
				double cpuMs = await Task.RunInThreadAsync( () =>
				{
					var sw = Stopwatch.StartNew();
					_ = GlinerPoc.Neural.GlinerMath.Linear( xc, s, xw, bw, inD, outD );
					return sw.Elapsed.TotalMilliseconds;
				} );

				const int K = 20;
				double naive = await TimedReps( "bn", () => { for ( int i = 0; i < K; i++ ) GlinerGpuOps.LinearNaive( _rt.GemmNaive, xb.Buffer, wDesc.Buffer, bDesc.Buffer, yb.Buffer, s, inD, outD, true ); }, K );
				double t16 = await TimedReps( "b16", () => { for ( int i = 0; i < K; i++ ) GlinerGpuOps.LinearTiled16( _rt.GemmTiled16, xb.Buffer, wDesc.Buffer, bDesc.Buffer, yb.Buffer, s, inD, outD, true ); }, K );
				double t8 = await TimedReps( "b8", () => { for ( int i = 0; i < K; i++ ) GlinerGpuOps.LinearTiled8( _rt.GemmTiled8, xb.Buffer, wDesc.Buffer, bDesc.Buffer, yb.Buffer, s, inD, outD, true ); }, K );
				Log.Info( $"[GLI:P8B] TIMING S{s} {inD}to{outD} | cpu={cpuMs:0.00} | naive={naive:0.00} | t16={t16:0.00} | t8={t8:0.00}" );
			}
		}
		Gate( "p8b_benchmarks", true, "see TIMING lines (calibrated completed-op latency; enqueue time never reported as compute)", "" );
	}


// poke2 (watcher trigger)
	// ---- gate 5: primitives --------------------------------------------------------------

	private async Task Gate5Primitives( WeightSet w )
	{
		// add
		{
			float[] a = LcgArray( 8192, 5001 );
			float[] b = LcgArray( 8192, 5002 );
			float[] cpu = GlinerPoc.Neural.GlinerMath.Add( a, b );
			using var ab = new GpuBuffer<float>( 8192 );
			using var bb = new GpuBuffer<float>( 8192 );
			using var cb = new GpuBuffer<float>( 8192 );
			ab.SetData( a );
			bb.SetData( b );
			var r = await RunJob( "add", () => GlinerGpuOps.Add( _rt.Add, ab, bb, cb, 8192 ), cb, 8192 );
			int exact = 0;
			for ( int i = 0; i < 8192; i++ )
			{
				if ( BitConverter.SingleToInt32Bits( r[i] ) == BitConverter.SingleToInt32Bits( cpu[i] ) )
				{
					exact++;
				}
			}
			Gate( "p8b_add", exact == 8192, $"residual add 8192 vals bit-exact {exact}/8192", "" );
		}

		// relu
		{
			float[] x = LcgArray( 15367, 5101 );
			float[] cpu = GlinerPoc.Neural.GlinerMath.Relu( x );
			using var xb = new GpuBuffer<float>( 15367 );
			using var yb = new GpuBuffer<float>( 15367 );
			xb.SetData( x );
			var r = await RunJob( "relu", () => GlinerGpuOps.Relu( _rt.Relu, xb, yb, 15367 ), yb, 15367 );
			int exact = 0;
			for ( int i = 0; i < 15367; i++ )
			{
				if ( BitConverter.SingleToInt32Bits( r[i] ) == BitConverter.SingleToInt32Bits( cpu[i] ) )
				{
					exact++;
				}
			}
			Gate( "p8b_relu", exact == 15367, $"ReLU 15367 vals bit-exact {exact}/15367", "" );
		}

		// gelu: synthetic + real FFN pre-activation
		{
			var geluCase = Array.Find( GlinerPoc.Neural.NeuralP4FixturesV2.KernelCases, k => k.Name == "gelu_erf" );
			float[] pre = GlinerPoc.Neural.NeuralP5FixturesV2.All[1].T( "ffn_pre" );
			float[] cpuSyn = GlinerPoc.Neural.GlinerMath.GeluErf( geluCase.Input );
			float[] cpuReal = await Task.RunInThreadAsync( () => GlinerPoc.Neural.GlinerMath.GeluErf( pre ) );
			float[] pyReal = GlinerPoc.Neural.NeuralP5FixturesV2.All[1].T( "gelu" );

			using var xb = new GpuBuffer<float>( pre.Length );
			using var yb = new GpuBuffer<float>( pre.Length );
			using var xb2 = new GpuBuffer<float>( geluCase.Input.Length );
			using var yb2 = new GpuBuffer<float>( geluCase.Input.Length );
			xb.SetData( pre );
			xb2.SetData( geluCase.Input );
			var rReal = await RunJob( "gelu_r", () => GlinerGpuOps.Gelu( _rt.Gelu, xb, yb, pre.Length ), yb, pre.Length );
			var rSyn = await RunJob( "gelu_s", () => GlinerGpuOps.Gelu( _rt.Gelu, xb2, yb2, geluCase.Input.Length ), yb2, geluCase.Input.Length );
			var mSyn = GlinerPoc.Neural.GlinerMath.Compare( rSyn, cpuSyn );
			var mReal = GlinerPoc.Neural.GlinerMath.Compare( rReal, cpuReal );
			var mPy = GlinerPoc.Neural.GlinerMath.Compare( rReal, pyReal );
			Gate( "p8b_gelu", mSyn.Pass && mReal.Pass && mPy.Pass,
				$"syn n={geluCase.Input.Length} maxAbs={mSyn.MaxAbs:0.###e-00} | real ffn_pre [{pre.Length / 1536 * 1536 / 1536}x1536] GPUvsCPU={mReal.MaxAbs:0.###e-00} GPUvsPython={mPy.MaxAbs:0.###e-00}", "" );
		}

		// layernorm: synthetic cases + real rel_embeddings 512x384
		{
			float[] lnW = DecodeB64( GlinerPoc.Neural.NeuralP4FixturesV2.LayerNormWeightB64 );
			float[] lnB = DecodeB64( GlinerPoc.Neural.NeuralP4FixturesV2.LayerNormBiasB64 );
			bool all = true;
			string det = "";
			using var xb = new GpuBuffer<float>( 512 * 384 );
			using var yb = new GpuBuffer<float>( 512 * 384 );
			using var wb = new GpuBuffer<float>( 384 );
			using var bb2 = new GpuBuffer<float>( 384 );
			wb.SetData( lnW );
			bb2.SetData( lnB );
			foreach ( var kc in GlinerPoc.Neural.NeuralP4FixturesV2.KernelCases )
			{
				if ( !kc.Name.StartsWith( "layernorm" ) )
				{
					continue;
				}
				int rows = kc.Shape[0];
				int dim = kc.Shape[1];
				float[] cpu = GlinerPoc.Neural.GlinerMath.LayerNorm( kc.Input, rows, dim, lnW, lnB, 1e-7f );
				xb.SetData( kc.Input );
				var r = await RunJob( "ln", () => GlinerGpuOps.LayerNorm( _rt.LayerNorm, xb, wb, bb2, yb, rows, dim, 1e-7f ), yb, rows * dim );
				var m = GlinerPoc.Neural.GlinerMath.Compare( r, cpu );
				all &= m.Pass;
				det += $"{kc.Name.Replace( "layernorm_", "" )}={m.MaxAbs:0.###e-00} ";
			}
			// real: raw rel_embeddings through encoder LN vs P4 fixture
			float[] relRaw = ( await LoadWeightsAsync( "gate5" ) ).GetTensor( "encoder.encoder.rel_embeddings.weight" );
			float[] relCpu = GlinerPoc.Neural.GlinerMath.LayerNorm( relRaw, 512, 384, w.EncLnW.CpuStaging, w.EncLnB.CpuStaging, 1e-7f );
			float[] relPy = DecodeB64( GlinerPoc.Neural.NeuralP4FixturesV2.NormalizedRelEmbeddings );
			xb.SetData( relRaw );
			wb.SetData( w.EncLnW.CpuStaging );
			bb2.SetData( w.EncLnB.CpuStaging );
			var rr = await RunJob( "ln_rel", () => GlinerGpuOps.LayerNorm( _rt.LayerNorm, xb, wb, bb2, yb, 512, 384, 1e-7f ), yb, 512 * 384 );
			var mCpu = GlinerPoc.Neural.GlinerMath.Compare( rr, relCpu );
			var mPy = GlinerPoc.Neural.GlinerMath.Compare( rr, relPy );
			Gate( "p8b_layernorm", all && mCpu.Pass && mPy.Pass,
				$"synthetic[{det}] | real rel 512x384 GPUvsCPU={mCpu.MaxAbs:0.###e-00} GPUvsPython={mPy.MaxAbs:0.###e-00} (sequential-order kernel)", "" );
		}

		// softmax: synthetic normal/extreme/equal + real scores_premask
		{
			bool all = true;
			string det = "";
			using var xb = new GpuBuffer<float>( 6 * 40 * 40 );
			using var yb = new GpuBuffer<float>( 6 * 40 * 40 );
			foreach ( var (name, data, rows, dim) in new[] {
				( "normal", SoftmaxSynthetic( 64, 40, 7101 ), 64, 40 ),
				( "extreme", SoftmaxExtreme( 32, 40 ), 32, 40 ),
				( "equal", SoftmaxEqual( 8, 256 ), 8, 256 ) } )
			{
				float[] cpu = GlinerPoc.Neural.GlinerMath.SoftmaxRows( data, rows, dim );
				xb.SetData( data );
				var r = await RunJob( "sm", () => GlinerGpuOps.SoftmaxRows( _rt.Softmax, xb, yb, rows, dim ), yb, rows * dim );
				var m = GlinerPoc.Neural.GlinerMath.Compare( r, cpu );
				bool finite = true;
				foreach ( float v in r )
				{
					finite &= float.IsFinite( v );
				}
				all &= m.Pass && finite;
				det += $"{name}={m.MaxAbs:0.###e-00} finite={finite} ";
			}
			// real: s40 scores_premask [6,40,40] as 240 rows of 40; vs CPU + probs fixture
			float[] scores = GlinerPoc.Neural.NeuralP5FixturesV2.All[1].T( "scores_premask" );
			float[] probsPy = GlinerPoc.Neural.NeuralP5FixturesV2.All[1].T( "probs" );
			float[] cpuReal = await Task.RunInThreadAsync( () => GlinerPoc.Neural.GlinerMath.SoftmaxRows( scores, 240, 40 ) );
			xb.SetData( scores );
			var rp = await RunJob( "sm_real", () => GlinerGpuOps.SoftmaxRows( _rt.Softmax, xb, yb, 240, 40 ), yb, 240 * 40 );
			var mCpu = GlinerPoc.Neural.GlinerMath.Compare( rp, cpuReal );
			var mPy = GlinerPoc.Neural.GlinerMath.Compare( rp, probsPy );
			double maxRowSumErr = 0;
			for ( int rI = 0; rI < 240; rI++ )
			{
				float sum = 0;
				for ( int j = 0; j < 40; j++ )
				{
					sum += rp[rI * 40 + j];
				}
				maxRowSumErr = Math.Max( maxRowSumErr, Math.Abs( sum - 1f ) );
			}
			Gate( "p8b_softmax", all && mCpu.Pass && mPy.Pass && maxRowSumErr < 1e-5,
				$"synthetic[{det}] | real scores_premask 240x40 GPUvsCPU={mCpu.MaxAbs:0.###e-00} GPUvsPython={mPy.MaxAbs:0.###e-00} maxRowSumErr={maxRowSumErr:0.###e-00}", "" );
		}

		// copy + gather (exact index parity)
		{
			float[] src = LcgArray( 4096, 7301 );
			using var sb = new GpuBuffer<float>( 4096 );
			using var db = new GpuBuffer<float>( 4096 );
			sb.SetData( src );
			var r = await RunJob( "copy", () => GlinerGpuOps.Copy( _rt.Copy, sb, db, 2048, 1024 ), db, 2048 );
			int exact = 0;
			for ( int i = 0; i < 2048; i++ )
			{
				if ( BitConverter.SingleToInt32Bits( r[i] ) == BitConverter.SingleToInt32Bits( src[1024 + i] ) )
				{
					exact++;
				}
			}
			Gate( "p8b_copy", exact == 2048, $"device copy+offset bit-exact {exact}/2048", "" );

			// embedding-style row gather: table [512,384], indices from the P4 fixture ids
			int[] ids = GlinerPoc.Neural.NeuralP4FixturesV2.EmbeddingRowIds;
			int rows = ids.Length;
			int maxId = 0;
			for ( int i = 0; i < ids.Length; i++ )
			{
				maxId = Math.Max( maxId, ids[i] );
			}
			// fixture ids are real word-embedding rows; table sized to cover them
			float[] table = LcgArray( (maxId + 1) * 384, 7401 );
			var expect = new float[rows * 384];
			for ( int rI = 0; rI < rows; rI++ )
			{
				for ( int j = 0; j < 384; j++ )
				{
					expect[rI * 384 + j] = table[ids[rI] * 384 + j];
				}
			}
			using var tb = new GpuBuffer<float>( (maxId + 1) * 384 );
			using var ib = new GpuBuffer<int>( rows );
			using var gb = new GpuBuffer<float>( rows * 384 );
			tb.SetData( table );
			ib.SetData( ids );
			var rg = await RunJob( "gather", () => GlinerGpuOps.GatherRows( _rt.Gather, tb, ib, gb, rows, 384 ), gb, rows * 384 );
			int gexact = 0;
			for ( int i = 0; i < rows * 384; i++ )
			{
				if ( BitConverter.SingleToInt32Bits( rg[i] ) == BitConverter.SingleToInt32Bits( expect[i] ) )
				{
					gexact++;
				}
			}
			Gate( "p8b_gather", gexact == rows * 384, $"row gather {rows}x384 via fixture ids bit-exact {gexact}/{rows * 384}", "" );
		}
	}

	private static float[] SoftmaxSynthetic( int rows, int dim, uint seed ) => LcgArray( rows * dim, seed );

	private static float[] SoftmaxExtreme( int rows, int dim )
	{
		var a = new float[rows * dim];
		uint s = 7201;
		for ( int i = 0; i < a.Length; i++ )
		{
			a[i] = Lcg( ref s ) * 200f; // large magnitude, mixed signs
		}
		return a;
	}

	private static float[] SoftmaxEqual( int rows, int dim )
	{
		var a = new float[rows * dim];
		for ( int i = 0; i < a.Length; i++ )
		{
			a[i] = 3.25f;
		}
		return a;
	}

	// ---- gate 6: ping-pong / scratch reuse -----------------------------------------------

	private async Task Gate6BufferReuse()
	{
		var hiddenA = _rt.CreateBuffer( "hiddenA", 256, 384, persistent: true, "activation ping-pong A" );
		var hiddenB = _rt.CreateBuffer( "hiddenB", 256, 384, persistent: true, "activation ping-pong B" );
		var ffn = _rt.CreateBuffer( "ffnScratch", 256, 1536, persistent: true, "FFN scratch" );

		float[] a0 = LcgArray( 256 * 384, 7501 );
		float[] b0 = LcgArray( 256 * 384, 7502 );
		hiddenA.Buffer.SetData( a0 );

		// 30 alternating ops across separate jobs: A->B copy, B+=const (vec), into ffn (vec on first 256*1536? use copy of A into ffn region), A=f(A+B)
		for ( int i = 0; i < 10; i++ )
		{
			_ = await RunJobNoReadback( "pp1", () => GlinerGpuOps.Copy( _rt.Copy, hiddenA.Buffer, hiddenB.Buffer, 256 * 384 ) );
			_ = await RunJobNoReadback( "pp2", () => GlinerGpuOps.Add( _rt.Add, hiddenA.Buffer, hiddenB.Buffer, hiddenB.Buffer, 256 * 384 ) );
			_ = await RunJobNoReadback( "pp3", () => GlinerGpuOps.VecAddConstant( _rt.VecAdd, hiddenB.Buffer, hiddenA.Buffer, 256 * 384, 0.125f ) );
		}
		// expected CPU chain: A1 = a0; per iter: B = A; B = A+B; A = B+0.125
		float[] aC = new float[a0.Length];
		for ( int i = 0; i < a0.Length; i++ )
		{
			aC[i] = a0[i];
		}
		float[] bC = new float[256 * 384];
		for ( int i = 0; i < 10; i++ )
		{
			for ( int j = 0; j < aC.Length; j++ )
			{
				bC[j] = aC[j];
			}
			for ( int j = 0; j < aC.Length; j++ )
			{
				bC[j] = aC[j] + bC[j];
			}
			for ( int j = 0; j < aC.Length; j++ )
			{
				aC[j] = bC[j] + 0.125f;
			}
		}
		var r = await RunJob( "pp_read", () => { }, hiddenA.Buffer, 256 * 384 );
		int exact = 0;
		for ( int i = 0; i < r.Length; i++ )
		{
			if ( BitConverter.SingleToInt32Bits( r[i] ) == BitConverter.SingleToInt32Bits( aC[i] ) )
			{
				exact++;
			}
		}
		int bufferCount = 0;
		foreach ( var _ in _rt.Buffers )
		{
			bufferCount++;
		}
		Gate( "p8b_buffer_reuse", exact == r.Length,
			$"30 jobs across ping-pong hiddenA/B + FFN scratch ({_rt.Buffers.Count} runtime buffers, no per-job allocation); final bit-exact {exact}/{r.Length}", "" );
		_ = bufferCount;
	}

	private async Task<bool> RunJobNoReadback( string name, Action work )
	{
		_rt.Executor.Submit( name, work );
		await Task.Delay( 24 ); // >= one frame
		return true;
	}

	// ---- gate 7: synthetic GPU→GPU chain ----------------------------------------------------

	private async Task Gate7SyntheticChain()
	{
		int rows = 4, inD = 3, mid = 2, outD = 3;
		float[] x = LcgArray( rows * inD, 7601 );
		float[] w1 = LcgArray( mid * inD, 7602 );
		float[] b1 = LcgArray( mid, 7603 );
		float[] w2 = LcgArray( outD * mid, 7604 );
		float[] b2 = LcgArray( outD, 7605 );
		float[] cpu = await Task.RunInThreadAsync( () =>
		{
			float[] h = GlinerPoc.Neural.GlinerMath.Linear( x, rows, w1, b1, inD, mid );
			h = GlinerPoc.Neural.GlinerMath.Relu( h );
			return GlinerPoc.Neural.GlinerMath.Linear( h, rows, w2, b2, mid, outD );
		} );

		using var xb = new GpuBuffer<float>( x.Length );
		using var w1b = new GpuBuffer<float>( w1.Length );
		using var b1b = new GpuBuffer<float>( b1.Length );
		using var w2b = new GpuBuffer<float>( w2.Length );
		using var b2b = new GpuBuffer<float>( b2.Length );
		using var hb = new GpuBuffer<float>( rows * mid );
		using var hb2 = new GpuBuffer<float>( rows * mid );
		using var yb = new GpuBuffer<float>( rows * outD );
		xb.SetData( x );
		w1b.SetData( w1 );
		b1b.SetData( b1 );
		w2b.SetData( w2 );
		b2b.SetData( b2 );

		// ONE job: GEMM -> ReLU -> GEMM, intermediate stays on GPU, single readback.
		// Graphics.UavBarrier between dependent dispatches: without it the second
		// GEMM read stale intermediate data (measured: chains failed at maxAbs
		// 0.16-18 until barriers were inserted after each producer kernel).
		var r = await RunJob( "chain", () =>
		{
			GlinerGpuOps.LinearTiled16( _rt.GemmTiled16, xb, w1b, b1b, hb, rows, inD, mid, true );
			Graphics.UavBarrier( hb );
			GlinerGpuOps.Relu( _rt.Relu, hb, hb2, rows * mid );
			Graphics.UavBarrier( hb2 );
			GlinerGpuOps.LinearTiled16( _rt.GemmTiled16, hb2, w2b, b2b, yb, rows, mid, outD, true );
		}, yb, rows * outD );
		var m = GlinerPoc.Neural.GlinerMath.Compare( r, cpu );
		Gate( "p8b_chain_synthetic", m.Pass,
			$"3 dispatches in ONE render job (GEMM→ReLU→GEMM), zero intermediate CPU readback; maxAbs={m.MaxAbs:0.###e-00}", "" );
	}

	// ---- gate 8: REAL layer-0 FFN chain ----------------------------------------------------

	private async Task Gate8RealFfnChain( WeightSet w )
	{
		var p5 = GlinerPoc.Neural.NeuralP5FixturesV2.All[1]; // s40
		float[] x = p5.T( "attn_ln" );       // real FFN input (post-attention LN)
		float[] pyOut = p5.T( "ffn_out" );   // Python fixture for the down-projection output
		double cpuChainMs = 0;
		float[] cpu = await Task.RunInThreadAsync( () =>
		{
			var sw = Stopwatch.StartNew();
			float[] h = GlinerPoc.Neural.GlinerMath.Linear( x, 40, ReadAllCpu( w.UpW ), ReadAllCpu( w.UpB ), 384, 1536 );
			h = GlinerPoc.Neural.GlinerMath.GeluErf( h );
			float[] o = GlinerPoc.Neural.GlinerMath.Linear( h, 40, ReadAllCpu( w.DownW ), ReadAllCpu( w.DownB ), 1536, 384 );
			cpuChainMs = sw.Elapsed.TotalMilliseconds;
			return o;
		} );

		var xb = _rt.CreateBuffer( "ffn_x", 40, 384, false, "request input" );
		var mid = _rt.CreateBuffer( "ffn_mid", 40, 1536, false, "activation" );
		var mid2 = _rt.CreateBuffer( "ffn_mid2", 40, 1536, false, "activation" );
		var yb = _rt.CreateBuffer( "ffn_y", 40, 384, false, "activation" );
		xb.Buffer.SetData( x );

		var swGpu = Stopwatch.StartNew();
		var r = await RunJob( "ffn_chain", () =>
		{
			GlinerGpuOps.LinearTiled16( _rt.GemmTiled16, xb.Buffer, w.UpW.Buffer, w.UpB.Buffer, mid.Buffer, 40, 384, 1536, true );
			Graphics.UavBarrier( mid.Buffer );
			GlinerGpuOps.Gelu( _rt.Gelu, mid.Buffer, mid2.Buffer, 40 * 1536 );
			Graphics.UavBarrier( mid2.Buffer );
			GlinerGpuOps.LinearTiled16( _rt.GemmTiled16, mid2.Buffer, w.DownW.Buffer, w.DownB.Buffer, yb.Buffer, 40, 1536, 384, true );
		}, yb.Buffer, 40 * 384 );
		swGpu.Stop();

		var mCpu = GlinerPoc.Neural.GlinerMath.Compare( r, cpu );
		var mPy = GlinerPoc.Neural.GlinerMath.Compare( r, pyOut );
		Gate( "p8b_ffn_chain_real", mCpu.Pass && mPy.Pass,
			$"REAL layer-0 FFN 384→1536→GELU→1536→384 (3 dispatches, 1 job, resident weights, single readback) | " +
			$"GPUvsCPU maxAbs={mCpu.MaxAbs:0.###e-00} meanAbs={mCpu.MeanAbs:0.###e-00} | GPUvsPython maxAbs={mPy.MaxAbs:0.###e-00} | " +
			$"cpuChainMs={cpuChainMs:0.00} gpuSubmitToResultMs={swGpu.Elapsed.TotalMilliseconds:0.0}", "" );
	}

	private static float[] ReadAllCpu( GlinerGpuBufferDesc d ) => d.CpuStaging;


	private void Gate9Cancellation()
	{
		int ran = 0;
		for ( int i = 0; i < 4; i++ )
		{
			_rt.Executor.Submit( $"cancelme{i}", () => ran++ );
		}
		_rt.Executor.CancelQueued(); // same frame, before the render hook drains
		_rt.Executor.Submit( "keepme", () => ran += 10 );
		// let frames pass; verification is async
		_ = VerifyCancellationAsync( ran );
	}

	private async Task VerifyCancellationAsync( int ranBefore )
	{
		await Task.Delay( 200 );
		int executed = _rt.Executor.JobsExecuted;
		int cancelled = _rt.Executor.JobsCancelled;
		// ranBefore is captured pre-drain; verify via counters instead
		_ = ranBefore;
		Gate( "p8b_cancellation", cancelled >= 4 && executed >= 1,
			$"4 queued jobs cancelled before drain (JobsCancelled={cancelled}), follow-up job executed (JobsExecuted total={executed}); dispatched jobs always run to completion — stale results are the caller's to discard", "" );
	}

	// ---- gate 10: async completion propagation --------------------------------------------------

	private async Task Gate10AsyncPropagation( WeightSet w )
	{
		var p5 = GlinerPoc.Neural.NeuralP5FixturesV2.All[1];
		float[] x = p5.T( "attn_ln" );
		var xb = _rt.GetBuffer( "ffn_x" );
		var mid = _rt.GetBuffer( "ffn_mid" );
		var mid2 = _rt.GetBuffer( "ffn_mid2" );
		var yb = _rt.GetBuffer( "ffn_y" );
		xb.Buffer.SetData( x );

		bool done = false;
		double[] submitToResult = { 0 };
		var sw = Stopwatch.StartNew();
		_rt.Executor.SubmitReadback( "async_ffn", () =>
		{
			GlinerGpuOps.LinearTiled16( _rt.GemmTiled16, xb.Buffer, w.UpW.Buffer, w.UpB.Buffer, mid.Buffer, 40, 384, 1536, true );
			Graphics.UavBarrier( mid.Buffer );
			GlinerGpuOps.Gelu( _rt.Gelu, mid.Buffer, mid2.Buffer, 40 * 1536 );
			Graphics.UavBarrier( mid2.Buffer );
			GlinerGpuOps.LinearTiled16( _rt.GemmTiled16, mid2.Buffer, w.DownW.Buffer, w.DownB.Buffer, yb.Buffer, 40, 1536, 384, true );
		}, yb.Buffer, 40 * 384, _ =>
		{
			submitToResult[0] = sw.Elapsed.TotalMilliseconds;
			done = true;
		} );

		// component keeps doing CPU work while the GPU chain is in flight
		double cpuSpin = 0;
		await Task.RunInThreadAsync( () =>
		{
			var s = Stopwatch.StartNew();
			float[] z = NeuralMathFallback( x );
			_ = z;
			cpuSpin = s.Elapsed.TotalMilliseconds;
		} );
		int waits = 0;
		while ( !done && waits < 400 )
		{
			await Task.Delay( 8 );
			waits++;
		}
		Gate( "p8b_async_completion", done,
			$"GPU chain submitted, CPU worked {cpuSpin:0} ms concurrently, async readback completed after {submitToResult[0]:0.0} ms (waits={waits}); render thread never blocked by waiting", "" );
	}

	private static float[] NeuralMathFallback( float[] x ) => GlinerPoc.Neural.GlinerMath.Relu( x );

	// ---- gate 11: plans + analysis ---------------------------------------------------------------

	private void Gate11Analysis( WeightSet w )
	{
		// ---- buffer plan (S<=256) ----
		long hidden = 2L * 256 * 384 * 4;          // ping-pong A/B
		long qkv = 3L * 256 * 384 * 4;             // Q,K,V
		long pos = 2L * 512 * 384 * 4;             // posQ/posK head rows (rel table)
		long scores = 3L * 6 * 256 * 256 * 4;      // c2c / c2p / p2c contributions
		long probs = 6L * 256 * 256 * 4;           // probabilities
		long context = 256L * 384 * 4;             // merged context (may alias a QKV slot)
		long ffn = 256L * 1536 * 4;                // FFN intermediate
		long readback = 256L * 384 * 4;            // final staging
		long totalAct = hidden + qkv + pos + scores + probs + context + ffn + readback;
		Log.Info( $"[GLI:P8B] PLAN S<=256 activations: pingpong={hidden / 1024}KiB qkv={qkv / 1024}KiB pos={pos / 1024}KiB scores={scores / 1024}KiB probs={probs / 1024}KiB context={context / 1024}KiB ffn={ffn / 1024}KiB staging={readback / 1024}KiB TOTAL={totalAct / 1024}KiB" );
		Log.Info( "[GLI:P8B] PLAN alias notes: probs may alias scores after combine; context may alias a QKV slot; scores family shares one buffer across c2c/c2p/p2c ONLY sequentially (P8C lifetime graph)" );
		Log.Info( $"[GLI:P8B] PLAN weights: full classification set = 283,777,540 B (270.6 MiB); P8B resident subset now = {w.Bytes} B ({w.Bytes / 1024.0:0} KiB); total model+activations ≈ {(283_777_540 + totalAct) / (1024.0 * 1024):0.0} MiB = {100.0 * (283_777_540 + totalAct) / (11_229L * 1024 * 1024):0.0}% of the 11,229 MiB budget" );

		// ---- embedding strategy (P8B.22) ----
		long fullTable = 128_011L * 384 * 4;
		long perRequestRows = 256L * 384 * 4;
		Log.Info( $"[GLI:P8B] EMBED strategy: A full-table GPU residency = {fullTable / (1024 * 1024):0.0} MiB one-time upload; B CPU-gather rows + per-request upload ≤ {perRequestRows / 1024} KiB (~0.05 ms measured class of upload)" );
		Log.Info( "[GLI:P8B] EMBED recommendation: B for P8D — ≤256-token requests make per-request row upload negligible, GPU init stays ~87 MiB (layers+rel+classifier) instead of +196.6 MiB, and the CPU gather (GetRows) is already exact/validated. Revisit only for batched multi-request GPU inference." );

		Gate( "p8b_analysis", true, "buffer plan + embedding strategy + residency budget logged (see PLAN/EMBED lines)", "" );
	}

	private void Gate( string name, bool pass, string detail, string extra )
	{
		if ( pass )
		{
			_passed++;
			Log.Info( $"[GLI:P8B] PASS {name} {detail} {extra}" );
		}
		else
		{
			_failed++;
			Log.Error( $"[GLI:P8B] FAIL {name} {detail} {extra}" );
		}
	}
}
// assembled
// final
// watcher trigger final
// r2
// r3
// r4
// r5
// r6
// r7