Dentro de la API Graph cuDNN de NVIDIA: fusión, ajuste automático y reutilización de planes con la interfaz cuDNN
@section(“5. SDPA (Flash Attention) con enmascaramiento causal”) def sdpa_demo(): si no HAS_SDPA: aumente RuntimeError(f”SDPA fusionado necesita SM80+ (Amperios), esta GPU es sm_{SM}”) b, h, s, d = 4, 16, 1024, 64 escala = 1.0 / math.sqrt(d) SDPA_FLOPS = 4 * b * h * s * s * d * 0.5 q = antorcha.randn(b, h, s, d, dispositivo=DEV, dtipo=DTYPE) k = antorcha.randn(b, h, s, d, dispositivo=DEV, dtipo=DTYPE) v = antorcha.randn(b, h, s, d, dispositivo=DEV, dtipo=DTYPE) o = antorcha.empty(b, h, s, d, dispositivo=DEV, dtype=DTYPE) g = cudnn.pygraph( handle=HANDLE, nombre=”sdpa”, io_data_type=TORCH2CUDNN[DTYPE]middle_data_type=cudnn.data_type.FLOAT, Compute_data_type=cudnn.data_type.FLOAT, ) Q, Kt, V = tensor_of(g, q, “Q”), tensor_of(g, k, “K”), tensor_of(g, v, “V”) causal = Verdadero intento: O, _stats = g.sdpa(name=”sdpa”, q=Q, k=Kt, v=V, is_inference=True, attn_scale=scale, use_causal_mask=True) excepto TypeError: intente: O, _stats = g.sdpa(name=”sdpa”, q=Q, k=Kt, v=V, is_inference=True, attn_scale=scale, diagonal_alignment=cudnn.diagonal_alignment.TOP_LEFT, right_bound=0) excepto Excepción: causal = False O, _stats = g.sdpa(name=”sdpa”, q=Q, k=Kt, v=V, is_inference=True, attn_scale=scale) print(f” enmascaramiento causal: {causal}”) O.set_output(True).set_data_type(TORCH2CUDNN[DTYPE]) O.set_dim(list(o.size())).set_stride(list(o.stride())) build(g) ws = workspace_for(g) pack = {Q: q, Kt: k, V: v, O: o} g.execute(pack, ws) torch.cuda.synchronize() ref = torch.nn.functional.scaled_dot_product_attention(q, k, v, is_causal=causal, escala=escala) rel = ((o.float() – ref.float()).abs().max() / ref.float().abs().max()).item() print(f” forma: b{b} h{h} s{s} d{d} espacio de trabajo {ws.numel()/1024:.1f} KiB”) print(f” rel err : {rel:.2e}”) ms = bench(lambda: g.execute(pack, ws)) ms_t = bench(lambda: torch.nn.functional.scaled_dot_product_attention( q, k, v, is_causal=causal, scale=scale)) print() report(“cuDNN FE SDPA”, ms, SDPA_FLOPS) report(“torch SDPA (elección del backend)”, ms_t, SDPA_FLOPS) print(” Nota: es posible que la antorcha ya esté despachando a cuDNN o FlashAttention,”) print(” entonces la paridad aquí es el resultado esperado y saludable.”) return f”{ms:.3f} ms, {tflops(SDPA_FLOPS, ms):.1f} TFLOP/s” sdpa_demo() @section(“6. Serializar un gráfico creado, recargarlo, ejecutarlo por UID”) def serialization(): Bsz, M, Kd, Nd = 8, 256, 512, 256 a = antorcha.randn(Bsz, M, Kd, dispositivo=DEV, dtipo=DTYPE) bm = antorcha.randn(Bsz, Kd, Nd, dispositivo=DEV, dtipo=DTYPE) out = antorcha.empty(Bsz, M, Nd, dispositivo=DEV, dtipo=DTYPE) UID_A, UID_B, UID_C = 1, 2, 3 g = cudnn.pygraph( mango=MANGO, nombre=”serializable_mm”, io_data_type=TORCH2CUDNN[DTYPE]middle_data_type=cudnn.data_type.FLOAT, Compute_data_type=cudnn.data_type.FLOAT, ) A = tensor_of(g, a, “A”).set_uid(UID_A) Bt = tensor_of(g, bm, “B”).set_uid(UID_B) C = g.matmul(A=A, B=Bt, Compute_data_type=cudnn.data_type.FLOAT) C.set_output(True).set_data_type(TORCH2CUDNN[DTYPE]).set_uid(UID_C) t0 = time.perf_counter() build(g) cold_ms = (time.perf_counter() – t0) * 1e3 blob = g.serialize() print(f” compilación en frío: {cold_ms:.1f} ms”) print(f” plan serializado: {len(blob)} bytes (guarde esto en caché en el disco/envíelo)”) t0 = time.perf_counter() g2 = cudnn.pygraph() intente: g2.deserialize(HANDLE, blob) excepto TypeError: g2.deserialize(blob) warm_ms = (time.perf_counter() – t0) * 1e3 print(f” deserialize: {warm_ms:.1f} ms -> {cold_ms/max(warm_ms,1e-6):.1f}inicio x más rápido”) ws = torch.empty(max(g2.get_workspace_size(), 1), dispositivo=DEV, dtype=torch.uint8) g2.execute({UID_A: a, UID_B: bm, UID_C: out}, ws, handle=HANDLE) torch.cuda.synchronize() ref = torch.bmm(a.float(), bm.float()) rel = ((out.float() – ref).abs().max() / ref.abs().max()).item() print(f” rel err after reload: {rel:.2e}”) return f”{len(blob)} B blob, recarga {cold_ms/max(warm_ms,1e-6):.1f}x más rápido que reconstruir” serialización()