@@ -205,6 +205,43 @@ __global__ void k_fused_riccati(const T* gP, const T* gA, const T* gB, const T*
205205 for (uint32_t i = rank; i < NU *NX ; i += size) gK [p*NU *NX + i] = sK [i];
206206}
207207
208+ // ─── UNFUSED device-side GLASS chain ─────────────────────────────────────────
209+ // The same math as the fused kernel, but each primitive launched as its own
210+ // kernel with every intermediate (PB, S, G) round-tripping through GLOBAL
211+ // memory — what a caller writes with general GLASS primitives and NO fused
212+ // riccati_gain. Isolates the fusion/smem benefit from the device-vs-host
213+ // benefit: host chain → device unfused = dispatch+locality of device
214+ // execution; device unfused → fused = composition keeping intermediates
215+ // on-chip. One block per problem, glass::block ops on global pointers.
216+ template <typename T, uint32_t NX , uint32_t NU >
217+ __global__ void k_unf_pb (const T* gP , const T* gB , T* gPB ) {
218+ const size_t p = blockIdx .x ;
219+ glass::gemm<T>(NX , NU , NX , (T)1 , gP + p*NX *NX , gB + p*NX *NU , (T)0 , gPB + p*NX *NU );
220+ }
221+ template <typename T, uint32_t NX , uint32_t NU >
222+ __global__ void k_unf_s (const T* gB , const T* gPB , const T* gR , T* gS ) {
223+ const size_t p = blockIdx .x ;
224+ T* S = gS + p*NU *NU ;
225+ const T* R = gR + p*NU *NU ;
226+ for (uint32_t i = threadIdx .x ; i < NU *NU ; i += blockDim .x ) S[i] = R[i];
227+ __syncthreads ();
228+ // S += Bᵀ·PB (op(A)=Bᵀ is NU×NX)
229+ glass::gemm<T, /* TRANSPOSE_A=*/ true >(NU , NU , NX , (T)1 , gB + p*NX *NU ,
230+ gPB + p*NX *NU , (T)1 , S);
231+ }
232+ template <typename T, uint32_t NX , uint32_t NU >
233+ __global__ void k_unf_g (const T* gPB , const T* gA , T* gG ) {
234+ const size_t p = blockIdx .x ;
235+ // G = (P·B)ᵀ·A = Bᵀ·P·A (P symmetric)
236+ glass::gemm<T, /* TRANSPOSE_A=*/ true >(NU , NX , NX , (T)1 , gPB + p*NX *NU ,
237+ gA + p*NX *NX , (T)0 , gG + p*NU *NX );
238+ }
239+ template <typename T, uint32_t NX , uint32_t NU >
240+ __global__ void k_unf_solve (T* gS , T* gG ) {
241+ const size_t p = blockIdx .x ;
242+ glass::posv<T>(NU , NX , gS + p*NU *NU , gG + p*NU *NX ); // K lands in G
243+ }
244+
208245// ─── per-(dtype, shape) driver ───────────────────────────────────────────────
209246struct Handles { cublasHandle_t cb; cusolverDnHandle_t cs; };
210247
@@ -288,6 +325,12 @@ static void run_shape(Handles H, const char* dt) {
288325 auto fused = [&](int tb) {
289326 k_fused_riccati<T, NX , NU ><<<B, tb, smem>>> (dP, dA, dB_, dR, dK, B);
290327 };
328+ auto unfused = [&](int tb) {
329+ k_unf_pb <T, NX , NU ><<<B, tb>>> (dP, dB_, dPB);
330+ k_unf_s <T, NX , NU ><<<B, tb>>> (dB_, dPB, dR, dS);
331+ k_unf_g <T, NX , NU ><<<B, tb>>> (dPB, dA, dG);
332+ k_unf_solve<T, NX , NU ><<<B, tb>>> (dS, dG);
333+ };
291334 auto chain = [&] {
292335 CK (cudaMemcpyAsync (dS, dR, RR *(size_t )B*sizeof (T), cudaMemcpyDeviceToDevice));
293336 xgemm_sb (H.cb , CUBLAS_OP_N , NX , NU , NX , (T)1 , dP, NX , PP , dB_, NX , BB , (T)0 , dPB, NX , BB , B);
@@ -314,6 +357,12 @@ static void run_shape(Handles H, const char* dt) {
314357 for (size_t i = 0 ; i < KK ; i++) me = fmax (me, fabs ((double )hK[i] - refK[i]));
315358 printf (" CHECK shape=%ux%u dtype=%s impl=chain maxerr=%.3e\n " , NX , NU , dt, me);
316359 if (!(me < tol)) { fprintf (stderr, " FATAL chain mismatch\n " ); exit (4 ); }
360+ unfused (128 ); CK (cudaDeviceSynchronize ());
361+ CK (cudaMemcpy (hK.data (), dG, KK *sizeof (T), cudaMemcpyDeviceToHost));
362+ me = 0 ;
363+ for (size_t i = 0 ; i < KK ; i++) me = fmax (me, fabs ((double )hK[i] - refK[i]));
364+ printf (" CHECK shape=%ux%u dtype=%s impl=glass_unfused maxerr=%.3e\n " , NX , NU , dt, me);
365+ if (!(me < tol)) { fprintf (stderr, " FATAL glass_unfused mismatch\n " ); exit (4 ); }
317366 }
318367
319368 // timing: median over 5 trials of REPS event-bracketed reps
@@ -325,6 +374,9 @@ static void run_shape(Handles H, const char* dt) {
325374 {" fused_tb128" , [&]{ fused (128 ); }},
326375 {" fused_tb256" , [&]{ fused (256 ); }}, // wide-block probe: the (36,12)/(48,16)
327376 {" fused_tb512" , [&]{ fused (512 ); }}, // chain-wins cells may be TB-starved
377+ {" glass_unfused_tb32" , [&]{ unfused (32 ); }},
378+ {" glass_unfused_tb128" , [&]{ unfused (128 ); }},
379+ {" glass_unfused_tb256" , [&]{ unfused (256 ); }},
328380 {" chain" , chain},
329381 };
330382 std::mt19937 order_rng (0x46555345u ^ (NX << 20 ) ^ (NU << 12 ) ^
0 commit comments