1use crate::codegen_sva::fabric_target::{Dataflow, FabricTarget, Precision};
18use crate::tensor_recognize::{
19 ElementwiseSpec, FusedRowSpec, MatmulSpec, ReductionKind, ReductionSpec, RowReduceKind,
20};
21
22use super::knobs::{CudaKnobs, ResolvedPrecision};
23use super::runtime::CUDA_MAKEFILE_TEMPLATE;
24use super::CudaLowerError;
25
26pub fn lower_matmul_to_cuda(spec: &MatmulSpec) -> String {
35 lower_matmul_to_cuda_with(spec, &CudaKnobs::default())
36}
37
38pub fn lower_matmul_to_cuda_with(spec: &MatmulSpec, knobs: &CudaKnobs) -> String {
44 let int_template = |t: &str| {
45 t.replace("{M}", &spec.m.to_string())
46 .replace("{N}", &spec.n.to_string())
47 .replace("{K}", &spec.k.to_string())
48 };
49 match knobs.precision.resolved() {
50 ResolvedPrecision::Int8Dp4a => int_template(CUDA_MATMUL_DP4A_TEMPLATE),
51 ResolvedPrecision::Int64 => int_template(CUDA_MATMUL_TEMPLATE),
52 ResolvedPrecision::Bf16Hmma | ResolvedPrecision::Fp16Hmma => {
56 use super::gemm_tile::{select_hmma_kernel, select_mma, HmmaKernel, MmaKind};
57 let kind = select_mma(
58 knobs.fabric_precision(),
59 spec.m,
60 spec.k,
61 spec.n,
62 knobs.chip.sm_arch,
63 );
64 if !kind.is_tensor_core() || matches!(kind, MmaKind::WmmaI8) {
65 int_template(CUDA_MATMUL_TEMPLATE)
67 } else {
68 match select_hmma_kernel(
71 kind,
72 spec.m,
73 spec.k,
74 spec.n,
75 knobs.chip.sm_arch,
76 &super::gemm_tile::GpuLimits::ampere(),
77 ) {
78 HmmaKernel::CpAsync { tile, stages } => {
79 super::gemm_tile::emit_wmma_cpasync_program(
80 kind, &tile, stages, spec.m, spec.k, spec.n,
81 )
82 }
83 HmmaKernel::Basic => emit_wmma_matmul(kind, spec.m, spec.k, spec.n),
84 }
85 }
86 }
87 }
88}
89
90fn emit_wmma_matmul(kind: super::gemm_tile::MmaKind, m: usize, k: usize, n: usize) -> String {
95 let kernel = super::gemm_tile::emit_wmma(kind);
96 let op = kind.operand_ctype();
97 let acc = kind.acc_ctype();
98 let tag = kind.tag();
99 let conv_a = kind.host_convert("(p % 7) - 3");
100 let conv_b = kind.host_convert("(p % 5) - 2");
101 format!(
102 r#"{kernel}#include <cstdio>
103#include <cstdlib>
104#include <cmath>
105#define M {m}
106#define N {n}
107#define K {k}
108int main(void){{
109 const int MM=M, KK=K, NN=N;
110 {op}* hA=({op}*)malloc((size_t)MM*KK*sizeof({op}));
111 {op}* hB=({op}*)malloc((size_t)KK*NN*sizeof({op}));
112 for(long p=0;p<(long)MM*KK;p++) hA[p]={conv_a};
113 for(long p=0;p<(long)KK*NN;p++) hB[p]={conv_b};
114 {op} *dA,*dB; {acc}* dC;
115 if(cudaMalloc((void**)&dA,(size_t)MM*KK*sizeof({op}))||cudaMalloc((void**)&dB,(size_t)KK*NN*sizeof({op}))||cudaMalloc((void**)&dC,(size_t)MM*NN*sizeof({acc}))){{ fprintf(stderr,"malloc\n"); return 1; }}
116 cudaMemcpy(dA,hA,(size_t)MM*KK*sizeof({op}),cudaMemcpyHostToDevice);
117 cudaMemcpy(dB,hB,(size_t)KK*NN*sizeof({op}),cudaMemcpyHostToDevice);
118 dim3 grid(NN/16, MM/16);
119 gemm_wmma_{tag}<<<grid,32>>>(dA,dB,dC,MM,NN,KK);
120 if(cudaDeviceSynchronize()!=cudaSuccess){{ fprintf(stderr,"sync\n"); return 1; }}
121 {acc}* hC=({acc}*)malloc((size_t)MM*NN*sizeof({acc}));
122 cudaMemcpy(hC,dC,(size_t)MM*NN*sizeof({acc}),cudaMemcpyDeviceToHost);
123 double sum=0; for(long t=0;t<(long)MM*NN;t++) sum+=(double)hC[t];
124 printf("%lld\n",(long long)llround(sum));
125 return 0;
126}}
127"#,
128 kernel = kernel,
129 op = op,
130 acc = acc,
131 tag = tag,
132 m = m,
133 k = k,
134 n = n,
135 conv_a = conv_a,
136 conv_b = conv_b,
137 )
138}
139
140const CUDA_MATMUL_TEMPLATE: &str = r#"// Auto-generated by LOGOS codegen_cuda::matrix — a recognized LOGOS matmul lowered to CUDA.
144// C(M x N) = A(M x K) . B(K x N), row-major; dims are the source program's; checksum == the VM's Show.
145#include <cstdio>
146#include <cstdint>
147#define M {M}
148#define N {N}
149#define K {K}
150// Reproduce the LOGOS program's deterministic int8-range fill (0-based push order).
151__global__ void fill_ab(int64_t* a, int64_t* b){
152 long p = blockIdx.x*(long)blockDim.x + threadIdx.x;
153 if(p < (long)M*K) a[p] = (p % 7) - 3;
154 if(p < (long)K*N) b[p] = (p % 5) - 2;
155}
156__global__ void gemm(const int64_t* a, const int64_t* b, int64_t* c){
157 long j = blockIdx.x*(long)blockDim.x + threadIdx.x;
158 long i = blockIdx.y*(long)blockDim.y + threadIdx.y;
159 if(i >= M || j >= N) return;
160 int64_t acc = 0;
161 for(int k = 0; k < K; ++k) acc += a[i*(long)K + k] * b[(long)k*N + j];
162 c[i*(long)N + j] = acc;
163}
164int main(void){
165 int64_t *a,*b,*c;
166 if(cudaMalloc(&a,(size_t)M*K*8)||cudaMalloc(&b,(size_t)K*N*8)||cudaMalloc(&c,(size_t)M*N*8)){ fprintf(stderr,"malloc\n"); return 1; }
167 long filln = ((long)M*K > (long)K*N) ? (long)M*K : (long)K*N;
168 fill_ab<<<(filln+255)/256,256>>>(a,b);
169 dim3 blk(16,16), grd((N+15)/16,(M+15)/16);
170 gemm<<<grd,blk>>>(a,b,c);
171 if(cudaDeviceSynchronize()!=cudaSuccess){ fprintf(stderr,"sync\n"); return 1; }
172 int64_t* hc = (int64_t*)malloc((size_t)M*N*8);
173 cudaMemcpy(hc, c, (size_t)M*N*8, cudaMemcpyDeviceToHost);
174 int64_t sum = 0;
175 for(long t = 0; t < (long)M*N; ++t) sum += hc[t];
176 printf("%lld\n", (long long)sum);
177 return 0;
178}
179"#;
180
181pub fn lower_elementwise_to_cuda(spec: &ElementwiseSpec) -> String {
187 CUDA_ELEMENTWISE_TEMPLATE
188 .replace("{B_AT}", if spec.row.is_some() { "p % NB" } else { "p" })
189 .replace("{NB}", &spec.row.unwrap_or(spec.n).to_string())
190 .replace("{N}", &spec.n.to_string())
191 .replace("{OP}", spec.op.c_op())
192}
193
194const CUDA_ELEMENTWISE_TEMPLATE: &str = r#"// Auto-generated by LOGOS codegen_cuda::matrix — a recognized LOGOS elementwise op lowered to CUDA.
198// C[p] = A[p] {OP} B[{B_AT}] over N elements (B has NB: N, or a row's width); dims are the source program's; checksum ==
199// the VM's Show.
200#include <cstdio>
201#include <cstdint>
202#define N {N}
203#define NB {NB}
204__global__ void fill_ab(int64_t* a, int64_t* b){
205 long p = blockIdx.x*(long)blockDim.x + threadIdx.x;
206 if(p < N) a[p] = (p % 7) - 3;
207 if(p < NB) b[p] = (p % 5) - 2;
208}
209__global__ void ew(const int64_t* a, const int64_t* b, int64_t* c){
210 long p = blockIdx.x*(long)blockDim.x + threadIdx.x;
211 if(p < N) c[p] = a[p] {OP} b[{B_AT}];
212}
213int main(void){
214 int64_t *a,*b,*c;
215 if(cudaMalloc(&a,(size_t)N*8)||cudaMalloc(&b,(size_t)N*8)||cudaMalloc(&c,(size_t)N*8)){ fprintf(stderr,"malloc\n"); return 1; }
216 fill_ab<<<(N+255)/256,256>>>(a,b);
217 ew<<<(N+255)/256,256>>>(a,b,c);
218 if(cudaDeviceSynchronize()!=cudaSuccess){ fprintf(stderr,"sync\n"); return 1; }
219 int64_t* hc = (int64_t*)malloc((size_t)N*8);
220 cudaMemcpy(hc, c, (size_t)N*8, cudaMemcpyDeviceToHost);
221 int64_t sum = 0;
222 for(long t = 0; t < N; ++t) sum += hc[t];
223 printf("%lld\n", (long long)sum);
224 return 0;
225}
226"#;
227
228pub fn lower_reduction_to_cuda(spec: &ReductionSpec, eps: crate::tensor_recognize::Epsilon) -> String {
233 let frs = FusedRowSpec {
236 rows: 1,
237 cols: spec.n,
238 reduce: match spec.kind {
239 ReductionKind::Rmsnorm => RowReduceKind::RmsNorm,
240 ReductionKind::Softmax => RowReduceKind::Softmax,
241 },
242 x: spec.x,
243 y: spec.y,
244 weight: None,
245 weight_rows: 1,
246 residual: None,
247 bias: None,
248 eps,
249 };
250 super::fused_row::emit_fused_row(&frs, &CudaKnobs::default())
251}
252
253const CUDA_MATMUL_DP4A_TEMPLATE: &str = r#"// Auto-generated by LOGOS codegen_cuda::matrix — recognized LOGOS matmul, __dp4a int8 fast path.
259// C(M x N) = A(M x K) . B(K x N); operands int8, __dp4a accumulation; checksum == the VM's Show (int8-safe).
260#include <cstdio>
261#include <cstdint>
262#define M {M}
263#define N {N}
264#define K {K}
265// Same deterministic fill as the general kernel, stored as signed int8 (values are in [-3,3]/[-2,2]).
266__global__ void fill_ab(signed char* a, signed char* b){
267 long p = blockIdx.x*(long)blockDim.x + threadIdx.x;
268 if(p < (long)M*K) a[p] = (signed char)((p % 7) - 3);
269 if(p < (long)K*N) b[p] = (signed char)((p % 5) - 2);
270}
271__global__ void gemm_dp4a(const signed char* a, const signed char* b, int64_t* c){
272 long j = blockIdx.x*(long)blockDim.x + threadIdx.x;
273 long i = blockIdx.y*(long)blockDim.y + threadIdx.y;
274 if(i >= M || j >= N) return;
275 int acc = 0;
276 for(int kk = 0; kk < K; kk += 4){
277 int a0 = a[i*(long)K + kk];
278 int a1 = (kk+1 < K) ? a[i*(long)K + kk+1] : 0;
279 int a2 = (kk+2 < K) ? a[i*(long)K + kk+2] : 0;
280 int a3 = (kk+3 < K) ? a[i*(long)K + kk+3] : 0;
281 int b0 = b[(long)kk*N + j];
282 int b1 = (kk+1 < K) ? b[(long)(kk+1)*N + j] : 0;
283 int b2 = (kk+2 < K) ? b[(long)(kk+2)*N + j] : 0;
284 int b3 = (kk+3 < K) ? b[(long)(kk+3)*N + j] : 0;
285 int aa = (a0 & 0xff) | ((a1 & 0xff)<<8) | ((a2 & 0xff)<<16) | ((a3 & 0xff)<<24);
286 int bb = (b0 & 0xff) | ((b1 & 0xff)<<8) | ((b2 & 0xff)<<16) | ((b3 & 0xff)<<24);
287 acc = __dp4a(aa, bb, acc);
288 }
289 c[i*(long)N + j] = acc;
290}
291int main(void){
292 signed char *a,*b; int64_t *c;
293 if(cudaMalloc(&a,(size_t)M*K)||cudaMalloc(&b,(size_t)K*N)||cudaMalloc(&c,(size_t)M*N*8)){ fprintf(stderr,"malloc\n"); return 1; }
294 long filln = ((long)M*K > (long)K*N) ? (long)M*K : (long)K*N;
295 fill_ab<<<(filln+255)/256,256>>>(a,b);
296 dim3 blk(16,16), grd((N+15)/16,(M+15)/16);
297 gemm_dp4a<<<grd,blk>>>(a,b,c);
298 if(cudaDeviceSynchronize()!=cudaSuccess){ fprintf(stderr,"sync\n"); return 1; }
299 int64_t* hc = (int64_t*)malloc((size_t)M*N*8);
300 cudaMemcpy(hc, c, (size_t)M*N*8, cudaMemcpyDeviceToHost);
301 int64_t sum = 0;
302 for(long t = 0; t < (long)M*N; ++t) sum += hc[t];
303 printf("%lld\n", (long long)sum);
304 return 0;
305}
306"#;
307
308#[derive(Debug, Clone)]
311pub struct CudaGemmArtifact {
312 pub kernel_cu: String,
315 pub makefile: String,
317 pub m: usize,
318 pub k: usize,
319 pub n: usize,
320}
321
322pub fn sample_gemm_data(m: usize, k: usize, n: usize) -> (Vec<i32>, Vec<i32>) {
328 let weights: Vec<i32> = (0..k * n)
329 .map(|idx| ((idx as i32 * 7 + 3).rem_euclid(255)) - 128)
330 .collect();
331 let acts: Vec<i32> = (0..m * k)
332 .map(|idx| ((idx as i32 * 5 + 1).rem_euclid(255)) - 128)
333 .collect();
334 (weights, acts)
335}
336
337pub fn emit_int8_gemm(
341 m: usize,
342 k: usize,
343 n: usize,
344 weights: &[i32],
345 acts: &[i32],
346) -> Result<CudaGemmArtifact, CudaLowerError> {
347 FabricTarget::rdu_shaped()
349 .require_gemm(Dataflow::WeightStationary, Precision::Int8)
350 .map_err(|_| {
351 CudaLowerError::Unsupported("rdu-shaped target lacks int8 weight-stationary GEMM")
352 })?;
353 assert_eq!(weights.len(), k * n, "weights must be k*n row-major");
354 assert_eq!(acts.len(), m * k, "acts must be m*k row-major");
355
356 let kp = k.div_ceil(4) * 4;
360
361 let mut a_pad = vec![0i32; m * kp];
362 for i in 0..m {
363 for r in 0..k {
364 a_pad[i * kp + r] = acts[i * k + r];
365 }
366 }
367 let mut wt = vec![0i32; n * kp];
368 for r in 0..k {
369 for c in 0..n {
370 wt[c * kp + r] = weights[r * n + c];
371 }
372 }
373
374 let a_lit = i8_array_literal(&a_pad);
375 let wt_lit = i8_array_literal(&wt);
376 let kernel_cu = CUDA_GEMM_TEMPLATE
377 .replace("{M}", &m.to_string())
378 .replace("{N}", &n.to_string())
379 .replace("{KP}", &kp.to_string())
380 .replace("{A_DATA}", &a_lit)
381 .replace("{WT_DATA}", &wt_lit);
382
383 Ok(CudaGemmArtifact {
384 kernel_cu,
385 makefile: CUDA_MAKEFILE_TEMPLATE.replace("{KERNEL_FILE}", super::KERNEL_FILE_NAME),
386 m,
387 k,
388 n,
389 })
390}
391
392pub fn blockscaled_gemm_dequant(
401 m: usize,
402 k: usize,
403 n: usize,
404 wq: &[i32],
405 ws: &[f32],
406 aq: &[i32],
407 as_: &[f32],
408) -> Vec<f64> {
409 assert!(k % 32 == 0);
410 let nb = k / 32;
411 let mut out = vec![0f64; m * n];
412 for i in 0..m {
413 for o in 0..n {
414 let mut acc = 0f64;
415 for t in 0..k {
416 let a = aq[i * k + t] as f64 * as_[i * nb + t / 32] as f64;
417 let w = wq[o * k + t] as f64 * ws[o * nb + t / 32] as f64;
418 acc += a * w;
419 }
420 out[i * n + o] = acc;
421 }
422 }
423 out
424}
425
426pub fn blockscaled_gemm_int8(
430 m: usize,
431 k: usize,
432 n: usize,
433 wq: &[i32],
434 ws: &[f32],
435 aq: &[i32],
436 as_: &[f32],
437) -> Vec<f64> {
438 assert!(k % 32 == 0);
439 let nb = k / 32;
440 let mut out = vec![0f64; m * n];
441 for i in 0..m {
442 for o in 0..n {
443 let mut acc = 0f64;
444 for b in 0..nb {
445 let mut dot: i64 = 0;
446 for j in 0..32 {
447 let t = b * 32 + j;
448 dot += aq[i * k + t] as i64 * wq[o * k + t] as i64;
449 }
450 acc += dot as f64 * as_[i * nb + b] as f64 * ws[o * nb + b] as f64;
451 }
452 out[i * n + o] = acc;
453 }
454 }
455 out
456}
457
458pub fn emit_scaled_int8_gemm(
462 m: usize,
463 k: usize,
464 n: usize,
465 wq: &[i32],
466 ws: &[f32],
467 aq: &[i32],
468 as_: &[f32],
469) -> Result<CudaGemmArtifact, CudaLowerError> {
470 FabricTarget::rdu_shaped()
471 .require_gemm(Dataflow::WeightStationary, Precision::Int8)
472 .map_err(|_| {
473 CudaLowerError::Unsupported("rdu-shaped target lacks int8 weight-stationary GEMM")
474 })?;
475 assert!(k % 32 == 0, "k must be a multiple of the Q8_0 block (32)");
476 let nb = k / 32;
477 assert_eq!(wq.len(), n * k);
478 assert_eq!(ws.len(), n * nb);
479 assert_eq!(aq.len(), m * k);
480 assert_eq!(as_.len(), m * nb);
481
482 let kernel_cu = CUDA_SCALED_GEMM_TEMPLATE
485 .replace("{M}", &m.to_string())
486 .replace("{N}", &n.to_string())
487 .replace("{K}", &k.to_string())
488 .replace("{NB}", &nb.to_string())
489 .replace("{A_DATA}", &i8_array_literal(aq))
490 .replace("{W_DATA}", &i8_array_literal(wq))
491 .replace("{AS_DATA}", &f32_array_literal(as_))
492 .replace("{WS_DATA}", &f32_array_literal(ws));
493
494 Ok(CudaGemmArtifact {
495 kernel_cu,
496 makefile: CUDA_MAKEFILE_TEMPLATE.replace("{KERNEL_FILE}", super::KERNEL_FILE_NAME),
497 m,
498 k,
499 n,
500 })
501}
502
503fn f32_array_literal(xs: &[f32]) -> String {
505 let mut s = String::with_capacity(xs.len() * 14);
506 for (i, x) in xs.iter().enumerate() {
507 if i > 0 {
508 s.push_str(", ");
509 }
510 s.push_str(&format!("{:.9e}f", x));
511 }
512 s
513}
514
515const CUDA_SCALED_GEMM_TEMPLATE: &str = r#"// Auto-generated by LOGOS codegen_cuda::matrix — do not edit.
517// Block-scaled Q8_0 GEMM, O(M x N) = A(M x K) . W(N x K)^T, via Ampere DP4A with per-32-block f32 scales.
518#include <cstdio>
519#include <cstdint>
520
521#define M {M}
522#define N {N}
523#define K {K}
524#define NB {NB} // K / 32 blocks
525
526__device__ static const signed char A[M * K] = { {A_DATA} };
527__device__ static const signed char W[N * K] = { {W_DATA} }; // W[o][t], [out][in]
528__device__ static const float As[M * NB] = { {AS_DATA} };
529__device__ static const float Ws[N * NB] = { {WS_DATA} };
530
531__device__ static inline int pack4(const signed char *p) {
532 return (p[0] & 0xff) | ((p[1] & 0xff) << 8) | ((p[2] & 0xff) << 16) | ((p[3] & 0xff) << 24);
533}
534
535__global__ void gemm_i8s(float *O) {
536 int o = blockIdx.x * blockDim.x + threadIdx.x; // output feature
537 int i = blockIdx.y * blockDim.y + threadIdx.y; // row
538 if (i >= M || o >= N) return;
539 float acc = 0.0f;
540 for (int b = 0; b < NB; ++b) {
541 int d = 0;
542 for (int j = 0; j < 32; j += 4)
543 d = __dp4a(pack4(&A[i * K + b * 32 + j]), pack4(&W[o * K + b * 32 + j]), d);
544 acc += (float)d * As[i * NB + b] * Ws[o * NB + b];
545 }
546 O[i * N + o] = acc;
547}
548
549int main(void) {
550 float *dO = nullptr;
551 if (cudaMalloc(&dO, (size_t)M * N * sizeof(float)) != cudaSuccess) { fprintf(stderr, "cudaMalloc\n"); return 1; }
552 dim3 block(16, 16);
553 dim3 grid((N + block.x - 1) / block.x, (M + block.y - 1) / block.y);
554 gemm_i8s<<<grid, block>>>(dO);
555 cudaError_t err = cudaGetLastError();
556 if (err != cudaSuccess) { fprintf(stderr, "launch: %s\n", cudaGetErrorString(err)); return 1; }
557 if (cudaDeviceSynchronize() != cudaSuccess) { fprintf(stderr, "sync\n"); return 1; }
558 float hO[M * N];
559 if (cudaMemcpy(hO, dO, (size_t)M * N * sizeof(float), cudaMemcpyDeviceToHost) != cudaSuccess) { fprintf(stderr, "memcpy\n"); return 1; }
560 for (int idx = 0; idx < M * N; ++idx) printf("%.9g\n", hO[idx]);
561 cudaFree(dO);
562 return 0;
563}
564"#;
565
566fn i8_array_literal(xs: &[i32]) -> String {
568 let mut s = String::with_capacity(xs.len() * 5);
569 for (i, x) in xs.iter().enumerate() {
570 if i > 0 {
571 s.push_str(", ");
572 }
573 s.push_str(&x.to_string());
574 }
575 s
576}
577
578const CUDA_GEMM_TEMPLATE: &str = r#"// Auto-generated by LOGOS codegen_cuda::matrix — do not edit.
582// Weight-stationary int8 GEMM, O(M x N) = A(M x K) . W(K x N), via Ampere DP4A.
583// Prints O row-major, one int32 per line — byte-comparable to codegen_sva::eval_systolic.
584#include <cstdio>
585#include <cstdint>
586
587#define M {M}
588#define N {N}
589#define KP {KP} // K padded up to a multiple of 4 (pad lanes are 0)
590
591__device__ static const signed char A[M * KP] = { {A_DATA} };
592__device__ static const signed char Wt[N * KP] = { {WT_DATA} }; // Wt[c*KP + r] = W[r][c]
593
594// Pack 4 consecutive signed int8 into an int32 (little-endian lanes), the DP4A operand form.
595__device__ static inline int pack4(const signed char *p) {
596 return (p[0] & 0xff) | ((p[1] & 0xff) << 8) | ((p[2] & 0xff) << 16) | ((p[3] & 0xff) << 24);
597}
598
599__global__ void gemm_i8(int *O) {
600 int c = blockIdx.x * blockDim.x + threadIdx.x; // output column
601 int i = blockIdx.y * blockDim.y + threadIdx.y; // output row
602 if (i >= M || c >= N) return;
603 int acc = 0;
604 for (int r = 0; r < KP; r += 4) {
605 acc = __dp4a(pack4(&A[i * KP + r]), pack4(&Wt[c * KP + r]), acc);
606 }
607 O[i * N + c] = acc;
608}
609
610int main(void) {
611 int *dO = nullptr;
612 if (cudaMalloc(&dO, (size_t)M * N * sizeof(int)) != cudaSuccess) { fprintf(stderr, "cudaMalloc\n"); return 1; }
613 dim3 block(16, 16);
614 dim3 grid((N + block.x - 1) / block.x, (M + block.y - 1) / block.y);
615 gemm_i8<<<grid, block>>>(dO);
616 cudaError_t err = cudaGetLastError();
617 if (err != cudaSuccess) { fprintf(stderr, "launch: %s\n", cudaGetErrorString(err)); return 1; }
618 if (cudaDeviceSynchronize() != cudaSuccess) { fprintf(stderr, "sync\n"); return 1; }
619 int hO[M * N];
620 if (cudaMemcpy(hO, dO, (size_t)M * N * sizeof(int), cudaMemcpyDeviceToHost) != cudaSuccess) { fprintf(stderr, "memcpy\n"); return 1; }
621 for (int idx = 0; idx < M * N; ++idx) printf("%d\n", hO[idx]);
622 cudaFree(dO);
623 return 0;
624}
625"#;
626
627#[cfg(test)]
628#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_cuda/matrix/tests.rs"]
629mod tests;