DGX Spark serving config: source, kernels, receipts, validated env (part 3)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4.py +129 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4_20423.json +16 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4_ldm.py +127 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4_ldm_20423.json +16 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4_pipe.py +126 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4_pipe_20423.json +16 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4_sw.py +134 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4_sw_20423.json +19 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4ws.py +138 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4ws_20423.json +30 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256.py +170 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256_20423.json +24 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256_sw.py +170 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256_sw_20423.json +24 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2.py +170 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_20423.json +24 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_ldm.py +127 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_ldm_20423.json +16 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_ldmb.py +127 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_ldmb_20423.json +16 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_leads.py +131 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_leads_20423.json +16 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_pipe.py +127 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_pipe_20423.json +16 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_pipea.py +129 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_pipea_20423.json +16 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_sw.py +170 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_sw_20423.json +24 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2n2.py +94 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2n2_20423.json +17 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2p.py +171 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2p_20423.json +24 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2s1.py +136 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2s1_20423.json +22 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2ws.py +171 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2ws4.py +142 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2ws4_20423.json +23 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2ws_20423.json +24 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k4.py +173 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k4_20423.json +24 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k4n1.py +130 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k4n1_20423.json +17 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256n32.py +133 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256n32_20423.json +18 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma_20423.json +24 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma_sf.py +170 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma_sf_20423.json +24 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_tiled.py +138 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_tiled_20423.json +24 -0
- labs/swiglu_nvfp4/native_cuda/gate_fc1_paired.py +125 -0
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: kitchen 128x128x256 single-N tile == cublasLt.
|
| 3 |
+
|
| 4 |
+
Profiler name: cutlass3x_sm120_..._128x128x256_1x1x1. Pairing both
|
| 5 |
+
arms doubles B smem; this tile holds one N operand. Same m16n8k64.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
|
| 15 |
+
import comfy_kitchen as ck
|
| 16 |
+
|
| 17 |
+
from swiglu_nvfp4 import (
|
| 18 |
+
fc1_nvfp4_scaled_tma128n128k4,
|
| 19 |
+
fc1_paired_nvfp4_scaled_tma256k2,
|
| 20 |
+
load_extension,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
ROWS = 20423
|
| 25 |
+
K = 5376
|
| 26 |
+
N = 14336
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 30 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 34 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 35 |
+
y = ck.scaled_mm_nvfp4(
|
| 36 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 37 |
+
)
|
| 38 |
+
return y[:ROWS]
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 42 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def max_abs_diff(left: torch.Tensor, right: torch.Tensor) -> float:
|
| 46 |
+
return float((left.float() - right.float()).abs().max().item())
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def time_ms(fn) -> float:
|
| 50 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 51 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 52 |
+
fn()
|
| 53 |
+
torch.cuda.synchronize()
|
| 54 |
+
starter.record()
|
| 55 |
+
fn()
|
| 56 |
+
ender.record()
|
| 57 |
+
torch.cuda.synchronize()
|
| 58 |
+
return starter.elapsed_time(ender)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def main() -> int:
|
| 62 |
+
load_extension(verbose=False)
|
| 63 |
+
device = torch.device("cuda")
|
| 64 |
+
torch.manual_seed(26081250)
|
| 65 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 66 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 67 |
+
sx = nvfp4_scale(x)
|
| 68 |
+
sw = nvfp4_scale(weight)
|
| 69 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 70 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 71 |
+
qx_live = qx[:ROWS].contiguous()
|
| 72 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 73 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 74 |
+
full = fc1_nvfp4_scaled_tma128n128k4(qx_live, qxs, qw, qws, alpha)
|
| 75 |
+
payload = {
|
| 76 |
+
"identity": (
|
| 77 |
+
"Kitchen 128x128x256 single-N tile == cublasLt on the "
|
| 78 |
+
"m16n8k64 atom. One B operand (not paired arms). 2-stage "
|
| 79 |
+
"K=256. Profiler: 128x128x256_1x1x1 s16864."
|
| 80 |
+
),
|
| 81 |
+
"kitchen_kernel": (
|
| 82 |
+
"cutlass3x_sm120_bstensorop_s16864gemm_block_scaled_"
|
| 83 |
+
"ue4m3xe2m1_ue4m3xe2m1_f32_bf16_bf16_128x128x256_1x1x1"
|
| 84 |
+
),
|
| 85 |
+
"rows": ROWS,
|
| 86 |
+
"k": K,
|
| 87 |
+
"n": 2 * N,
|
| 88 |
+
"device": torch.cuda.get_device_name(device),
|
| 89 |
+
"kit_vs_kitchen_mismatches": byte_diff(full, kitchen),
|
| 90 |
+
"kit_vs_kitchen_max_abs": round(max_abs_diff(full, kitchen), 6),
|
| 91 |
+
"full_finite": bool(torch.isfinite(full.float()).all().item()),
|
| 92 |
+
"kitchen_gemm_min_ms": round(
|
| 93 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 94 |
+
),
|
| 95 |
+
"kit_full_min_ms": round(
|
| 96 |
+
time_ms(
|
| 97 |
+
lambda: fc1_nvfp4_scaled_tma128n128k4(
|
| 98 |
+
qx_live, qxs, qw, qws, alpha
|
| 99 |
+
)
|
| 100 |
+
),
|
| 101 |
+
4,
|
| 102 |
+
),
|
| 103 |
+
"k2_prod_min_ms": round(
|
| 104 |
+
time_ms(
|
| 105 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2(
|
| 106 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 107 |
+
)
|
| 108 |
+
),
|
| 109 |
+
4,
|
| 110 |
+
),
|
| 111 |
+
}
|
| 112 |
+
payload["pass"] = (
|
| 113 |
+
payload["kit_vs_kitchen_mismatches"] == 0
|
| 114 |
+
and payload["full_finite"]
|
| 115 |
+
)
|
| 116 |
+
if payload["pass"]:
|
| 117 |
+
payload["vs_kitchen_ms"] = round(
|
| 118 |
+
payload["kitchen_gemm_min_ms"] - payload["kit_full_min_ms"], 4
|
| 119 |
+
)
|
| 120 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 121 |
+
Path(__file__).with_name(
|
| 122 |
+
"gate_fc1_nvfp4_scaled_tma128n128k4_20423.json"
|
| 123 |
+
).write_text(text + "\n")
|
| 124 |
+
print(text)
|
| 125 |
+
return 0 if payload["pass"] else 2
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
if __name__ == "__main__":
|
| 129 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4_20423.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"full_finite": true,
|
| 4 |
+
"identity": "Kitchen 128x128x256 single-N tile == cublasLt on the m16n8k64 atom. One B operand (not paired arms). 2-stage K=256. Profiler: 128x128x256_1x1x1 s16864.",
|
| 5 |
+
"k": 5376,
|
| 6 |
+
"k2_prod_min_ms": 37.0229,
|
| 7 |
+
"kit_full_min_ms": 67.7126,
|
| 8 |
+
"kit_vs_kitchen_max_abs": 0.0,
|
| 9 |
+
"kit_vs_kitchen_mismatches": 0,
|
| 10 |
+
"kitchen_gemm_min_ms": 20.1805,
|
| 11 |
+
"kitchen_kernel": "cutlass3x_sm120_bstensorop_s16864gemm_block_scaled_ue4m3xe2m1_ue4m3xe2m1_f32_bf16_bf16_128x128x256_1x1x1",
|
| 12 |
+
"n": 28672,
|
| 13 |
+
"pass": true,
|
| 14 |
+
"rows": 20423,
|
| 15 |
+
"vs_kitchen_ms": -47.5321
|
| 16 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4_ldm.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: ldmatrix.x4 A fragment == kitchen group/tidg 4xu32.
|
| 3 |
+
|
| 4 |
+
Probe: ldmatrix from smem[lane&15][(lane>>4)*16] matches the scalar
|
| 5 |
+
kitchen A map (0/32 lanes). Same 128x128x256 single-N tile.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
|
| 15 |
+
import comfy_kitchen as ck
|
| 16 |
+
|
| 17 |
+
from swiglu_nvfp4 import (
|
| 18 |
+
fc1_nvfp4_scaled_tma128n128k4,
|
| 19 |
+
fc1_nvfp4_scaled_tma128n128k4_ldm,
|
| 20 |
+
load_extension,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
ROWS = 20423
|
| 25 |
+
K = 5376
|
| 26 |
+
N = 14336
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 30 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 34 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 35 |
+
y = ck.scaled_mm_nvfp4(
|
| 36 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 37 |
+
)
|
| 38 |
+
return y[:ROWS]
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 42 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def time_ms(fn) -> float:
|
| 46 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 47 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 48 |
+
fn()
|
| 49 |
+
torch.cuda.synchronize()
|
| 50 |
+
starter.record()
|
| 51 |
+
fn()
|
| 52 |
+
ender.record()
|
| 53 |
+
torch.cuda.synchronize()
|
| 54 |
+
return starter.elapsed_time(ender)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def main() -> int:
|
| 58 |
+
load_extension(verbose=False)
|
| 59 |
+
device = torch.device("cuda")
|
| 60 |
+
torch.manual_seed(26081261)
|
| 61 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 62 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 63 |
+
sx = nvfp4_scale(x)
|
| 64 |
+
sw = nvfp4_scale(weight)
|
| 65 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 66 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 67 |
+
qx_live = qx[:ROWS].contiguous()
|
| 68 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 69 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 70 |
+
linear = fc1_nvfp4_scaled_tma128n128k4(qx_live, qxs, qw, qws, alpha)
|
| 71 |
+
ldm = fc1_nvfp4_scaled_tma128n128k4_ldm(qx_live, qxs, qw, qws, alpha)
|
| 72 |
+
payload = {
|
| 73 |
+
"identity": (
|
| 74 |
+
"ldmatrix.x4 from smem[lane&15][(lane>>4)*16] == kitchen "
|
| 75 |
+
"group/tidg A fragment on 128x128x256. Probe 0/32. Not "
|
| 76 |
+
"the unit-scale consecutive-K uint4 map (that skips "
|
| 77 |
+
"ldmatrix redistribute)."
|
| 78 |
+
),
|
| 79 |
+
"rows": ROWS,
|
| 80 |
+
"k": K,
|
| 81 |
+
"n": 2 * N,
|
| 82 |
+
"device": torch.cuda.get_device_name(device),
|
| 83 |
+
"ldm_vs_kitchen_mismatches": byte_diff(ldm, kitchen),
|
| 84 |
+
"ldm_vs_linear_mismatches": byte_diff(ldm, linear),
|
| 85 |
+
"full_finite": bool(torch.isfinite(ldm.float()).all().item()),
|
| 86 |
+
"kitchen_gemm_min_ms": round(
|
| 87 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 88 |
+
),
|
| 89 |
+
"ldm_full_min_ms": round(
|
| 90 |
+
time_ms(
|
| 91 |
+
lambda: fc1_nvfp4_scaled_tma128n128k4_ldm(
|
| 92 |
+
qx_live, qxs, qw, qws, alpha
|
| 93 |
+
)
|
| 94 |
+
),
|
| 95 |
+
4,
|
| 96 |
+
),
|
| 97 |
+
"linear_full_min_ms": round(
|
| 98 |
+
time_ms(
|
| 99 |
+
lambda: fc1_nvfp4_scaled_tma128n128k4(
|
| 100 |
+
qx_live, qxs, qw, qws, alpha
|
| 101 |
+
)
|
| 102 |
+
),
|
| 103 |
+
4,
|
| 104 |
+
),
|
| 105 |
+
}
|
| 106 |
+
payload["pass"] = (
|
| 107 |
+
payload["ldm_vs_kitchen_mismatches"] == 0
|
| 108 |
+
and payload["ldm_vs_linear_mismatches"] == 0
|
| 109 |
+
and payload["full_finite"]
|
| 110 |
+
)
|
| 111 |
+
if payload["pass"]:
|
| 112 |
+
payload["vs_kitchen_ms"] = round(
|
| 113 |
+
payload["kitchen_gemm_min_ms"] - payload["ldm_full_min_ms"], 4
|
| 114 |
+
)
|
| 115 |
+
payload["vs_linear_ms"] = round(
|
| 116 |
+
payload["linear_full_min_ms"] - payload["ldm_full_min_ms"], 4
|
| 117 |
+
)
|
| 118 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 119 |
+
Path(__file__).with_name(
|
| 120 |
+
"gate_fc1_nvfp4_scaled_tma128n128k4_ldm_20423.json"
|
| 121 |
+
).write_text(text + "\n")
|
| 122 |
+
print(text)
|
| 123 |
+
return 0 if payload["pass"] else 2
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
if __name__ == "__main__":
|
| 127 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4_ldm_20423.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"full_finite": true,
|
| 4 |
+
"identity": "ldmatrix.x4 from smem[lane&15][(lane>>4)*16] == kitchen group/tidg A fragment on 128x128x256. Probe 0/32. Not the unit-scale consecutive-K uint4 map (that skips ldmatrix redistribute).",
|
| 5 |
+
"k": 5376,
|
| 6 |
+
"kitchen_gemm_min_ms": 20.3012,
|
| 7 |
+
"ldm_full_min_ms": 67.9157,
|
| 8 |
+
"ldm_vs_kitchen_mismatches": 0,
|
| 9 |
+
"ldm_vs_linear_mismatches": 0,
|
| 10 |
+
"linear_full_min_ms": 67.9085,
|
| 11 |
+
"n": 28672,
|
| 12 |
+
"pass": true,
|
| 13 |
+
"rows": 20423,
|
| 14 |
+
"vs_kitchen_ms": -47.6145,
|
| 15 |
+
"vs_linear_ms": -0.0072
|
| 16 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4_pipe.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: software-pipelined B fragments == kitchen tile.
|
| 3 |
+
|
| 4 |
+
Next N-subtile B/SFB loads overlap the current m16n8k64. Same
|
| 5 |
+
128x128x256 single-N atom as the linear clone.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
|
| 15 |
+
import comfy_kitchen as ck
|
| 16 |
+
|
| 17 |
+
from swiglu_nvfp4 import (
|
| 18 |
+
fc1_nvfp4_scaled_tma128n128k4,
|
| 19 |
+
fc1_nvfp4_scaled_tma128n128k4_pipe,
|
| 20 |
+
load_extension,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
ROWS = 20423
|
| 25 |
+
K = 5376
|
| 26 |
+
N = 14336
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 30 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 34 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 35 |
+
y = ck.scaled_mm_nvfp4(
|
| 36 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 37 |
+
)
|
| 38 |
+
return y[:ROWS]
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 42 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def time_ms(fn) -> float:
|
| 46 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 47 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 48 |
+
fn()
|
| 49 |
+
torch.cuda.synchronize()
|
| 50 |
+
starter.record()
|
| 51 |
+
fn()
|
| 52 |
+
ender.record()
|
| 53 |
+
torch.cuda.synchronize()
|
| 54 |
+
return starter.elapsed_time(ender)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def main() -> int:
|
| 58 |
+
load_extension(verbose=False)
|
| 59 |
+
device = torch.device("cuda")
|
| 60 |
+
torch.manual_seed(26081259)
|
| 61 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 62 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 63 |
+
sx = nvfp4_scale(x)
|
| 64 |
+
sw = nvfp4_scale(weight)
|
| 65 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 66 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 67 |
+
qx_live = qx[:ROWS].contiguous()
|
| 68 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 69 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 70 |
+
linear = fc1_nvfp4_scaled_tma128n128k4(qx_live, qxs, qw, qws, alpha)
|
| 71 |
+
piped = fc1_nvfp4_scaled_tma128n128k4_pipe(qx_live, qxs, qw, qws, alpha)
|
| 72 |
+
payload = {
|
| 73 |
+
"identity": (
|
| 74 |
+
"Software-pipelined B fragments on 128x128x256 == linear "
|
| 75 |
+
"clone == kitchen. Next N-subtile B/SFB overlaps m16n8k64. "
|
| 76 |
+
"Same atom and tile; ILP only."
|
| 77 |
+
),
|
| 78 |
+
"rows": ROWS,
|
| 79 |
+
"k": K,
|
| 80 |
+
"n": 2 * N,
|
| 81 |
+
"device": torch.cuda.get_device_name(device),
|
| 82 |
+
"pipe_vs_kitchen_mismatches": byte_diff(piped, kitchen),
|
| 83 |
+
"pipe_vs_linear_mismatches": byte_diff(piped, linear),
|
| 84 |
+
"full_finite": bool(torch.isfinite(piped.float()).all().item()),
|
| 85 |
+
"kitchen_gemm_min_ms": round(
|
| 86 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 87 |
+
),
|
| 88 |
+
"pipe_full_min_ms": round(
|
| 89 |
+
time_ms(
|
| 90 |
+
lambda: fc1_nvfp4_scaled_tma128n128k4_pipe(
|
| 91 |
+
qx_live, qxs, qw, qws, alpha
|
| 92 |
+
)
|
| 93 |
+
),
|
| 94 |
+
4,
|
| 95 |
+
),
|
| 96 |
+
"linear_full_min_ms": round(
|
| 97 |
+
time_ms(
|
| 98 |
+
lambda: fc1_nvfp4_scaled_tma128n128k4(
|
| 99 |
+
qx_live, qxs, qw, qws, alpha
|
| 100 |
+
)
|
| 101 |
+
),
|
| 102 |
+
4,
|
| 103 |
+
),
|
| 104 |
+
}
|
| 105 |
+
payload["pass"] = (
|
| 106 |
+
payload["pipe_vs_kitchen_mismatches"] == 0
|
| 107 |
+
and payload["pipe_vs_linear_mismatches"] == 0
|
| 108 |
+
and payload["full_finite"]
|
| 109 |
+
)
|
| 110 |
+
if payload["pass"]:
|
| 111 |
+
payload["vs_kitchen_ms"] = round(
|
| 112 |
+
payload["kitchen_gemm_min_ms"] - payload["pipe_full_min_ms"], 4
|
| 113 |
+
)
|
| 114 |
+
payload["vs_linear_ms"] = round(
|
| 115 |
+
payload["linear_full_min_ms"] - payload["pipe_full_min_ms"], 4
|
| 116 |
+
)
|
| 117 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 118 |
+
Path(__file__).with_name(
|
| 119 |
+
"gate_fc1_nvfp4_scaled_tma128n128k4_pipe_20423.json"
|
| 120 |
+
).write_text(text + "\n")
|
| 121 |
+
print(text)
|
| 122 |
+
return 0 if payload["pass"] else 2
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
if __name__ == "__main__":
|
| 126 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4_pipe_20423.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"full_finite": true,
|
| 4 |
+
"identity": "Software-pipelined B fragments on 128x128x256 == linear clone == kitchen. Next N-subtile B/SFB overlaps m16n8k64. Same atom and tile; ILP only.",
|
| 5 |
+
"k": 5376,
|
| 6 |
+
"kitchen_gemm_min_ms": 20.2951,
|
| 7 |
+
"linear_full_min_ms": 67.6802,
|
| 8 |
+
"n": 28672,
|
| 9 |
+
"pass": true,
|
| 10 |
+
"pipe_full_min_ms": 67.5507,
|
| 11 |
+
"pipe_vs_kitchen_mismatches": 0,
|
| 12 |
+
"pipe_vs_linear_mismatches": 0,
|
| 13 |
+
"rows": 20423,
|
| 14 |
+
"vs_kitchen_ms": -47.2556,
|
| 15 |
+
"vs_linear_ms": 0.1295
|
| 16 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4_sw.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: SWIZZLE_128B remapped on the 128-byte K-row.
|
| 3 |
+
|
| 4 |
+
phys_col = col XOR ((row & 7) << 4). Measured on sm_121a. Same
|
| 5 |
+
128x128x256 single-N m16n8k64 tile as the linear kitchen clone.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
|
| 15 |
+
import comfy_kitchen as ck
|
| 16 |
+
|
| 17 |
+
from swiglu_nvfp4 import (
|
| 18 |
+
fc1_nvfp4_scaled_tma128n128k4,
|
| 19 |
+
fc1_nvfp4_scaled_tma128n128k4_sw,
|
| 20 |
+
load_extension,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
ROWS = 20423
|
| 25 |
+
K = 5376
|
| 26 |
+
N = 14336
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 30 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 34 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 35 |
+
y = ck.scaled_mm_nvfp4(
|
| 36 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 37 |
+
)
|
| 38 |
+
return y[:ROWS]
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 42 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def max_abs_diff(left: torch.Tensor, right: torch.Tensor) -> float:
|
| 46 |
+
return float((left.float() - right.float()).abs().max().item())
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def time_ms(fn) -> float:
|
| 50 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 51 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 52 |
+
fn()
|
| 53 |
+
torch.cuda.synchronize()
|
| 54 |
+
starter.record()
|
| 55 |
+
fn()
|
| 56 |
+
ender.record()
|
| 57 |
+
torch.cuda.synchronize()
|
| 58 |
+
return starter.elapsed_time(ender)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def main() -> int:
|
| 62 |
+
load_extension(verbose=False)
|
| 63 |
+
device = torch.device("cuda")
|
| 64 |
+
torch.manual_seed(26081256)
|
| 65 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 66 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 67 |
+
sx = nvfp4_scale(x)
|
| 68 |
+
sw = nvfp4_scale(weight)
|
| 69 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 70 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 71 |
+
qx_live = qx[:ROWS].contiguous()
|
| 72 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 73 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 74 |
+
linear = fc1_nvfp4_scaled_tma128n128k4(qx_live, qxs, qw, qws, alpha)
|
| 75 |
+
swiz = fc1_nvfp4_scaled_tma128n128k4_sw(qx_live, qxs, qw, qws, alpha)
|
| 76 |
+
payload = {
|
| 77 |
+
"identity": (
|
| 78 |
+
"SWIZZLE_128B TMA == linear fragment under "
|
| 79 |
+
"phys_col = col XOR ((row&7)<<4) on the 128-byte K-row "
|
| 80 |
+
"of the 128x128x256 single-N m16n8k64 tile. Map measured "
|
| 81 |
+
"on sm_121a (probe_swizzle128, 0/16384)."
|
| 82 |
+
),
|
| 83 |
+
"rows": ROWS,
|
| 84 |
+
"k": K,
|
| 85 |
+
"n": 2 * N,
|
| 86 |
+
"device": torch.cuda.get_device_name(device),
|
| 87 |
+
"xor_map": "col XOR ((row & 7) << 4)",
|
| 88 |
+
"sw_vs_kitchen_mismatches": byte_diff(swiz, kitchen),
|
| 89 |
+
"sw_vs_kitchen_max_abs": round(max_abs_diff(swiz, kitchen), 6),
|
| 90 |
+
"sw_vs_linear_mismatches": byte_diff(swiz, linear),
|
| 91 |
+
"sw_vs_linear_max_abs": round(max_abs_diff(swiz, linear), 6),
|
| 92 |
+
"full_finite": bool(torch.isfinite(swiz.float()).all().item()),
|
| 93 |
+
"kitchen_gemm_min_ms": round(
|
| 94 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 95 |
+
),
|
| 96 |
+
"sw_full_min_ms": round(
|
| 97 |
+
time_ms(
|
| 98 |
+
lambda: fc1_nvfp4_scaled_tma128n128k4_sw(
|
| 99 |
+
qx_live, qxs, qw, qws, alpha
|
| 100 |
+
)
|
| 101 |
+
),
|
| 102 |
+
4,
|
| 103 |
+
),
|
| 104 |
+
"linear_full_min_ms": round(
|
| 105 |
+
time_ms(
|
| 106 |
+
lambda: fc1_nvfp4_scaled_tma128n128k4(
|
| 107 |
+
qx_live, qxs, qw, qws, alpha
|
| 108 |
+
)
|
| 109 |
+
),
|
| 110 |
+
4,
|
| 111 |
+
),
|
| 112 |
+
}
|
| 113 |
+
payload["pass"] = (
|
| 114 |
+
payload["sw_vs_kitchen_mismatches"] == 0
|
| 115 |
+
and payload["sw_vs_linear_mismatches"] == 0
|
| 116 |
+
and payload["full_finite"]
|
| 117 |
+
)
|
| 118 |
+
if payload["pass"]:
|
| 119 |
+
payload["vs_kitchen_ms"] = round(
|
| 120 |
+
payload["kitchen_gemm_min_ms"] - payload["sw_full_min_ms"], 4
|
| 121 |
+
)
|
| 122 |
+
payload["vs_linear_ms"] = round(
|
| 123 |
+
payload["linear_full_min_ms"] - payload["sw_full_min_ms"], 4
|
| 124 |
+
)
|
| 125 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 126 |
+
Path(__file__).with_name(
|
| 127 |
+
"gate_fc1_nvfp4_scaled_tma128n128k4_sw_20423.json"
|
| 128 |
+
).write_text(text + "\n")
|
| 129 |
+
print(text)
|
| 130 |
+
return 0 if payload["pass"] else 2
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
if __name__ == "__main__":
|
| 134 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4_sw_20423.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"full_finite": true,
|
| 4 |
+
"identity": "SWIZZLE_128B TMA == linear fragment under phys_col = col XOR ((row&7)<<4) on the 128-byte K-row of the 128x128x256 single-N m16n8k64 tile. Map measured on sm_121a (probe_swizzle128, 0/16384).",
|
| 5 |
+
"k": 5376,
|
| 6 |
+
"kitchen_gemm_min_ms": 20.1582,
|
| 7 |
+
"linear_full_min_ms": 67.5818,
|
| 8 |
+
"n": 28672,
|
| 9 |
+
"pass": true,
|
| 10 |
+
"rows": 20423,
|
| 11 |
+
"sw_full_min_ms": 66.6565,
|
| 12 |
+
"sw_vs_kitchen_max_abs": 0.0,
|
| 13 |
+
"sw_vs_kitchen_mismatches": 0,
|
| 14 |
+
"sw_vs_linear_max_abs": 0.0,
|
| 15 |
+
"sw_vs_linear_mismatches": 0,
|
| 16 |
+
"vs_kitchen_ms": -46.4983,
|
| 17 |
+
"vs_linear_ms": 0.9253,
|
| 18 |
+
"xor_map": "col XOR ((row & 7) << 4)"
|
| 19 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4ws.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: 12-warp 128x128x256 == kitchen / 8-warp clone.
|
| 3 |
+
|
| 4 |
+
Kitchen launches 384 threads, 88064 B dynamic smem. Four producer
|
| 5 |
+
warps issue A/B/SFA/SFB; eight MMA warps keep the m16n8k64 atom.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
|
| 15 |
+
import comfy_kitchen as ck
|
| 16 |
+
|
| 17 |
+
from swiglu_nvfp4 import (
|
| 18 |
+
fc1_nvfp4_scaled_tma128n128k4,
|
| 19 |
+
fc1_nvfp4_scaled_tma128n128k4ws,
|
| 20 |
+
fc1_nvfp4_scaled_tma128n128k4ws_attrs,
|
| 21 |
+
load_extension,
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
ROWS = 20423
|
| 26 |
+
K = 5376
|
| 27 |
+
N = 14336
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 31 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 35 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 36 |
+
y = ck.scaled_mm_nvfp4(
|
| 37 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 38 |
+
)
|
| 39 |
+
return y[:ROWS]
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 43 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def max_abs_diff(left: torch.Tensor, right: torch.Tensor) -> float:
|
| 47 |
+
return float((left.float() - right.float()).abs().max().item())
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def time_ms(fn) -> float:
|
| 51 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 52 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 53 |
+
fn()
|
| 54 |
+
torch.cuda.synchronize()
|
| 55 |
+
starter.record()
|
| 56 |
+
fn()
|
| 57 |
+
ender.record()
|
| 58 |
+
torch.cuda.synchronize()
|
| 59 |
+
return starter.elapsed_time(ender)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def main() -> int:
|
| 63 |
+
load_extension(verbose=False)
|
| 64 |
+
device = torch.device("cuda")
|
| 65 |
+
torch.manual_seed(26081255)
|
| 66 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 67 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 68 |
+
sx = nvfp4_scale(x)
|
| 69 |
+
sw = nvfp4_scale(weight)
|
| 70 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 71 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 72 |
+
qx_live = qx[:ROWS].contiguous()
|
| 73 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 74 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 75 |
+
clone8 = fc1_nvfp4_scaled_tma128n128k4(qx_live, qxs, qw, qws, alpha)
|
| 76 |
+
ws = fc1_nvfp4_scaled_tma128n128k4ws(qx_live, qxs, qw, qws, alpha)
|
| 77 |
+
attrs = fc1_nvfp4_scaled_tma128n128k4ws_attrs()
|
| 78 |
+
payload = {
|
| 79 |
+
"identity": (
|
| 80 |
+
"Kitchen 128x128x256 12-warp launch: 4 producer warps "
|
| 81 |
+
"issue A/B/SFA/SFB TMA; 8 MMA warps keep the m16n8k64 "
|
| 82 |
+
"atom and 16x4 acc. 88064 B dynamic smem. Same tile as "
|
| 83 |
+
"the 8-warp clone (already byte-exact)."
|
| 84 |
+
),
|
| 85 |
+
"rows": ROWS,
|
| 86 |
+
"k": K,
|
| 87 |
+
"n": 2 * N,
|
| 88 |
+
"device": torch.cuda.get_device_name(device),
|
| 89 |
+
"launch": attrs,
|
| 90 |
+
"ws_vs_kitchen_mismatches": byte_diff(ws, kitchen),
|
| 91 |
+
"ws_vs_kitchen_max_abs": round(max_abs_diff(ws, kitchen), 6),
|
| 92 |
+
"ws_vs_clone8_mismatches": byte_diff(ws, clone8),
|
| 93 |
+
"ws_vs_clone8_max_abs": round(max_abs_diff(ws, clone8), 6),
|
| 94 |
+
"full_finite": bool(torch.isfinite(ws.float()).all().item()),
|
| 95 |
+
"kitchen_gemm_min_ms": round(
|
| 96 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 97 |
+
),
|
| 98 |
+
"ws_full_min_ms": round(
|
| 99 |
+
time_ms(
|
| 100 |
+
lambda: fc1_nvfp4_scaled_tma128n128k4ws(
|
| 101 |
+
qx_live, qxs, qw, qws, alpha
|
| 102 |
+
)
|
| 103 |
+
),
|
| 104 |
+
4,
|
| 105 |
+
),
|
| 106 |
+
"clone8_full_min_ms": round(
|
| 107 |
+
time_ms(
|
| 108 |
+
lambda: fc1_nvfp4_scaled_tma128n128k4(
|
| 109 |
+
qx_live, qxs, qw, qws, alpha
|
| 110 |
+
)
|
| 111 |
+
),
|
| 112 |
+
4,
|
| 113 |
+
),
|
| 114 |
+
}
|
| 115 |
+
payload["pass"] = (
|
| 116 |
+
payload["ws_vs_kitchen_mismatches"] == 0
|
| 117 |
+
and payload["ws_vs_clone8_mismatches"] == 0
|
| 118 |
+
and payload["full_finite"]
|
| 119 |
+
and int(attrs["threads"]) == 384
|
| 120 |
+
and int(attrs["dynamic_smem"]) == 88064
|
| 121 |
+
)
|
| 122 |
+
if payload["pass"]:
|
| 123 |
+
payload["vs_kitchen_ms"] = round(
|
| 124 |
+
payload["kitchen_gemm_min_ms"] - payload["ws_full_min_ms"], 4
|
| 125 |
+
)
|
| 126 |
+
payload["vs_clone8_ms"] = round(
|
| 127 |
+
payload["clone8_full_min_ms"] - payload["ws_full_min_ms"], 4
|
| 128 |
+
)
|
| 129 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 130 |
+
Path(__file__).with_name(
|
| 131 |
+
"gate_fc1_nvfp4_scaled_tma128n128k4ws_20423.json"
|
| 132 |
+
).write_text(text + "\n")
|
| 133 |
+
print(text)
|
| 134 |
+
return 0 if payload["pass"] else 2
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
if __name__ == "__main__":
|
| 138 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma128n128k4ws_20423.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"clone8_full_min_ms": 68.3289,
|
| 3 |
+
"device": "NVIDIA GB10",
|
| 4 |
+
"full_finite": true,
|
| 5 |
+
"identity": "Kitchen 128x128x256 12-warp launch: 4 producer warps issue A/B/SFA/SFB TMA; 8 MMA warps keep the m16n8k64 atom and 16x4 acc. 88064 B dynamic smem. Same tile as the 8-warp clone (already byte-exact).",
|
| 6 |
+
"k": 5376,
|
| 7 |
+
"kitchen_gemm_min_ms": 20.134,
|
| 8 |
+
"launch": {
|
| 9 |
+
"dynamic_smem": 88064,
|
| 10 |
+
"local_size_bytes": 8,
|
| 11 |
+
"max_dynamic_shared_size_bytes": 88064,
|
| 12 |
+
"mma_warps": 8,
|
| 13 |
+
"num_regs": 150,
|
| 14 |
+
"producer_warps": 4,
|
| 15 |
+
"shared_size_bytes": 0,
|
| 16 |
+
"smem_struct_bytes": 73856,
|
| 17 |
+
"threads": 384,
|
| 18 |
+
"warps": 12
|
| 19 |
+
},
|
| 20 |
+
"n": 28672,
|
| 21 |
+
"pass": true,
|
| 22 |
+
"rows": 20423,
|
| 23 |
+
"vs_clone8_ms": 0.9203,
|
| 24 |
+
"vs_kitchen_ms": -47.2746,
|
| 25 |
+
"ws_full_min_ms": 67.4086,
|
| 26 |
+
"ws_vs_clone8_max_abs": 0.0,
|
| 27 |
+
"ws_vs_clone8_mismatches": 0,
|
| 28 |
+
"ws_vs_kitchen_max_abs": 0.0,
|
| 29 |
+
"ws_vs_kitchen_mismatches": 0
|
| 30 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: TMA 256x64 3-stage NVFP4 paired-N vs kitchen.
|
| 3 |
+
|
| 4 |
+
Same PTX fragment + cuBLAS 128x4 scale slabs. Launch structure: larger
|
| 5 |
+
M tile (two scale slabs) and a 3-stage TMA K pipeline.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
import torch.nn.functional as F
|
| 15 |
+
|
| 16 |
+
import comfy_kitchen as ck
|
| 17 |
+
|
| 18 |
+
from swiglu_nvfp4 import (
|
| 19 |
+
bf16_nvfp4_dynamic,
|
| 20 |
+
fc1_paired_nvfp4_scaled_tma256,
|
| 21 |
+
fc1_paired_nvfp4_scaled_tma_sf,
|
| 22 |
+
load_extension,
|
| 23 |
+
swiglu_nvfp4_dynamic,
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
ROWS = 20423
|
| 28 |
+
K = 5376
|
| 29 |
+
N = 14336
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 33 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 37 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 38 |
+
y = ck.scaled_mm_nvfp4(
|
| 39 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 40 |
+
)
|
| 41 |
+
return y[:ROWS]
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def eager_act(raw: torch.Tensor) -> torch.Tensor:
|
| 45 |
+
gate, up = raw.chunk(2, dim=-1)
|
| 46 |
+
return F.silu(gate).mul_(up)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 50 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def max_abs_diff(left: torch.Tensor, right: torch.Tensor) -> float:
|
| 54 |
+
return float((left.float() - right.float()).abs().max().item())
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def time_ms(fn) -> float:
|
| 58 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 59 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 60 |
+
fn()
|
| 61 |
+
torch.cuda.synchronize()
|
| 62 |
+
starter.record()
|
| 63 |
+
fn()
|
| 64 |
+
ender.record()
|
| 65 |
+
torch.cuda.synchronize()
|
| 66 |
+
return starter.elapsed_time(ender)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def main() -> int:
|
| 70 |
+
load_extension(verbose=False)
|
| 71 |
+
device = torch.device("cuda")
|
| 72 |
+
torch.manual_seed(26081240)
|
| 73 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 74 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 75 |
+
sx = nvfp4_scale(x)
|
| 76 |
+
sw = nvfp4_scale(weight)
|
| 77 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 78 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 79 |
+
qx_live = qx[:ROWS].contiguous()
|
| 80 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 81 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 82 |
+
full = fc1_paired_nvfp4_scaled_tma256(
|
| 83 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 84 |
+
)
|
| 85 |
+
prod = fc1_paired_nvfp4_scaled_tma256(
|
| 86 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 87 |
+
)
|
| 88 |
+
sf = fc1_paired_nvfp4_scaled_tma_sf(
|
| 89 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 90 |
+
)
|
| 91 |
+
eager_kit = eager_act(kitchen.contiguous())
|
| 92 |
+
eager_full = eager_act(full)
|
| 93 |
+
two_q, two_s, two_g = swiglu_nvfp4_dynamic(kitchen.contiguous())
|
| 94 |
+
prod_q, prod_s, prod_g = bf16_nvfp4_dynamic(prod)
|
| 95 |
+
payload = {
|
| 96 |
+
"identity": (
|
| 97 |
+
"256x64 3-stage TMA on the kitchen-legal m16n8k64 atom: "
|
| 98 |
+
"two 128x4 scale slabs cover 256 M; K pipeline depth 3; "
|
| 99 |
+
"same PTX fragment, UE4M3 map, and eager product"
|
| 100 |
+
),
|
| 101 |
+
"rows": ROWS,
|
| 102 |
+
"k": K,
|
| 103 |
+
"n": N,
|
| 104 |
+
"device": torch.cuda.get_device_name(device),
|
| 105 |
+
"t256_full_vs_kitchen_mismatches": byte_diff(full, kitchen),
|
| 106 |
+
"t256_full_vs_kitchen_max_abs": round(max_abs_diff(full, kitchen), 6),
|
| 107 |
+
"t256_prod_vs_sf_prod": byte_diff(prod, sf),
|
| 108 |
+
"product_vs_eager_full_mismatches": byte_diff(prod, eager_full),
|
| 109 |
+
"product_vs_eager_kitchen_mismatches": byte_diff(prod, eager_kit),
|
| 110 |
+
"from_product_vs_kitchen_q": byte_diff(prod_q, two_q),
|
| 111 |
+
"from_product_vs_kitchen_s": byte_diff(prod_s, two_s),
|
| 112 |
+
"from_product_vs_kitchen_scale_exact": bool(torch.equal(prod_g, two_g)),
|
| 113 |
+
"full_finite": bool(torch.isfinite(full.float()).all().item()),
|
| 114 |
+
"prod_finite": bool(torch.isfinite(prod.float()).all().item()),
|
| 115 |
+
"kitchen_gemm_min_ms": round(
|
| 116 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 117 |
+
),
|
| 118 |
+
"t256_full_min_ms": round(
|
| 119 |
+
time_ms(
|
| 120 |
+
lambda: fc1_paired_nvfp4_scaled_tma256(
|
| 121 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 122 |
+
)
|
| 123 |
+
),
|
| 124 |
+
4,
|
| 125 |
+
),
|
| 126 |
+
"t256_prod_min_ms": round(
|
| 127 |
+
time_ms(
|
| 128 |
+
lambda: fc1_paired_nvfp4_scaled_tma256(
|
| 129 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 130 |
+
)
|
| 131 |
+
),
|
| 132 |
+
4,
|
| 133 |
+
),
|
| 134 |
+
"sf_prod_min_ms": round(
|
| 135 |
+
time_ms(
|
| 136 |
+
lambda: fc1_paired_nvfp4_scaled_tma_sf(
|
| 137 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 138 |
+
)
|
| 139 |
+
),
|
| 140 |
+
4,
|
| 141 |
+
),
|
| 142 |
+
"from_product_min_ms": round(
|
| 143 |
+
time_ms(lambda: bf16_nvfp4_dynamic(prod)), 4
|
| 144 |
+
),
|
| 145 |
+
}
|
| 146 |
+
payload["pass"] = (
|
| 147 |
+
payload["t256_full_vs_kitchen_mismatches"] == 0
|
| 148 |
+
and payload["t256_prod_vs_sf_prod"] == 0
|
| 149 |
+
and payload["product_vs_eager_full_mismatches"] == 0
|
| 150 |
+
and payload["product_vs_eager_kitchen_mismatches"] == 0
|
| 151 |
+
and payload["from_product_vs_kitchen_q"] == 0
|
| 152 |
+
and payload["from_product_vs_kitchen_s"] == 0
|
| 153 |
+
and payload["from_product_vs_kitchen_scale_exact"]
|
| 154 |
+
and payload["full_finite"]
|
| 155 |
+
and payload["prod_finite"]
|
| 156 |
+
)
|
| 157 |
+
if payload["pass"]:
|
| 158 |
+
payload["vs_sf_ms"] = round(
|
| 159 |
+
payload["sf_prod_min_ms"] - payload["t256_prod_min_ms"], 4
|
| 160 |
+
)
|
| 161 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 162 |
+
Path(__file__).with_name(
|
| 163 |
+
"gate_fc1_nvfp4_scaled_tma256_20423.json"
|
| 164 |
+
).write_text(text + "\n")
|
| 165 |
+
print(text)
|
| 166 |
+
return 0 if payload["pass"] else 2
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
if __name__ == "__main__":
|
| 170 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256_20423.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"from_product_min_ms": 5.7456,
|
| 4 |
+
"from_product_vs_kitchen_q": 0,
|
| 5 |
+
"from_product_vs_kitchen_s": 0,
|
| 6 |
+
"from_product_vs_kitchen_scale_exact": true,
|
| 7 |
+
"full_finite": true,
|
| 8 |
+
"identity": "256x64 3-stage TMA on the kitchen-legal m16n8k64 atom: two 128x4 scale slabs cover 256 M; K pipeline depth 3; same PTX fragment, UE4M3 map, and eager product",
|
| 9 |
+
"k": 5376,
|
| 10 |
+
"kitchen_gemm_min_ms": 20.2712,
|
| 11 |
+
"n": 14336,
|
| 12 |
+
"pass": true,
|
| 13 |
+
"prod_finite": true,
|
| 14 |
+
"product_vs_eager_full_mismatches": 0,
|
| 15 |
+
"product_vs_eager_kitchen_mismatches": 0,
|
| 16 |
+
"rows": 20423,
|
| 17 |
+
"sf_prod_min_ms": 105.2268,
|
| 18 |
+
"t256_full_min_ms": 57.507,
|
| 19 |
+
"t256_full_vs_kitchen_max_abs": 0.0,
|
| 20 |
+
"t256_full_vs_kitchen_mismatches": 0,
|
| 21 |
+
"t256_prod_min_ms": 54.7271,
|
| 22 |
+
"t256_prod_vs_sf_prod": 0,
|
| 23 |
+
"vs_sf_ms": 50.4997
|
| 24 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256_sw.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: SWIZZLE_32B TMA remapped to the PTX fragment.
|
| 3 |
+
|
| 4 |
+
Same 256x64 3-stage atom. TMA writes 16B chunks swizzled inside each
|
| 5 |
+
32B row; loads XOR ((row&1)<<4) to recover the linear fragment.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
import torch.nn.functional as F
|
| 15 |
+
|
| 16 |
+
import comfy_kitchen as ck
|
| 17 |
+
|
| 18 |
+
from swiglu_nvfp4 import (
|
| 19 |
+
bf16_nvfp4_dynamic,
|
| 20 |
+
fc1_paired_nvfp4_scaled_tma256,
|
| 21 |
+
fc1_paired_nvfp4_scaled_tma256_sw,
|
| 22 |
+
load_extension,
|
| 23 |
+
swiglu_nvfp4_dynamic,
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
ROWS = 20423
|
| 28 |
+
K = 5376
|
| 29 |
+
N = 14336
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 33 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 37 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 38 |
+
y = ck.scaled_mm_nvfp4(
|
| 39 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 40 |
+
)
|
| 41 |
+
return y[:ROWS]
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def eager_act(raw: torch.Tensor) -> torch.Tensor:
|
| 45 |
+
gate, up = raw.chunk(2, dim=-1)
|
| 46 |
+
return F.silu(gate).mul_(up)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 50 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def max_abs_diff(left: torch.Tensor, right: torch.Tensor) -> float:
|
| 54 |
+
return float((left.float() - right.float()).abs().max().item())
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def time_ms(fn) -> float:
|
| 58 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 59 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 60 |
+
fn()
|
| 61 |
+
torch.cuda.synchronize()
|
| 62 |
+
starter.record()
|
| 63 |
+
fn()
|
| 64 |
+
ender.record()
|
| 65 |
+
torch.cuda.synchronize()
|
| 66 |
+
return starter.elapsed_time(ender)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def main() -> int:
|
| 70 |
+
load_extension(verbose=False)
|
| 71 |
+
device = torch.device("cuda")
|
| 72 |
+
torch.manual_seed(26081241)
|
| 73 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 74 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 75 |
+
sx = nvfp4_scale(x)
|
| 76 |
+
sw = nvfp4_scale(weight)
|
| 77 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 78 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 79 |
+
qx_live = qx[:ROWS].contiguous()
|
| 80 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 81 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 82 |
+
full = fc1_paired_nvfp4_scaled_tma256_sw(
|
| 83 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 84 |
+
)
|
| 85 |
+
prod = fc1_paired_nvfp4_scaled_tma256_sw(
|
| 86 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 87 |
+
)
|
| 88 |
+
base = fc1_paired_nvfp4_scaled_tma256(
|
| 89 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 90 |
+
)
|
| 91 |
+
eager_kit = eager_act(kitchen.contiguous())
|
| 92 |
+
eager_full = eager_act(full)
|
| 93 |
+
two_q, two_s, two_g = swiglu_nvfp4_dynamic(kitchen.contiguous())
|
| 94 |
+
prod_q, prod_s, prod_g = bf16_nvfp4_dynamic(prod)
|
| 95 |
+
payload = {
|
| 96 |
+
"identity": (
|
| 97 |
+
"SWIZZLE_32B TMA == linear fragment under "
|
| 98 |
+
"phys_col = col XOR ((row&4)<<2) on the kitchen-legal "
|
| 99 |
+
"256x64 3-stage m16n8k64 paired-N atom"
|
| 100 |
+
),
|
| 101 |
+
"rows": ROWS,
|
| 102 |
+
"k": K,
|
| 103 |
+
"n": N,
|
| 104 |
+
"device": torch.cuda.get_device_name(device),
|
| 105 |
+
"sw_full_vs_kitchen_mismatches": byte_diff(full, kitchen),
|
| 106 |
+
"sw_full_vs_kitchen_max_abs": round(max_abs_diff(full, kitchen), 6),
|
| 107 |
+
"sw_prod_vs_t256_prod": byte_diff(prod, base),
|
| 108 |
+
"product_vs_eager_full_mismatches": byte_diff(prod, eager_full),
|
| 109 |
+
"product_vs_eager_kitchen_mismatches": byte_diff(prod, eager_kit),
|
| 110 |
+
"from_product_vs_kitchen_q": byte_diff(prod_q, two_q),
|
| 111 |
+
"from_product_vs_kitchen_s": byte_diff(prod_s, two_s),
|
| 112 |
+
"from_product_vs_kitchen_scale_exact": bool(torch.equal(prod_g, two_g)),
|
| 113 |
+
"full_finite": bool(torch.isfinite(full.float()).all().item()),
|
| 114 |
+
"prod_finite": bool(torch.isfinite(prod.float()).all().item()),
|
| 115 |
+
"kitchen_gemm_min_ms": round(
|
| 116 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 117 |
+
),
|
| 118 |
+
"sw_full_min_ms": round(
|
| 119 |
+
time_ms(
|
| 120 |
+
lambda: fc1_paired_nvfp4_scaled_tma256_sw(
|
| 121 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 122 |
+
)
|
| 123 |
+
),
|
| 124 |
+
4,
|
| 125 |
+
),
|
| 126 |
+
"sw_prod_min_ms": round(
|
| 127 |
+
time_ms(
|
| 128 |
+
lambda: fc1_paired_nvfp4_scaled_tma256_sw(
|
| 129 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 130 |
+
)
|
| 131 |
+
),
|
| 132 |
+
4,
|
| 133 |
+
),
|
| 134 |
+
"t256_prod_min_ms": round(
|
| 135 |
+
time_ms(
|
| 136 |
+
lambda: fc1_paired_nvfp4_scaled_tma256(
|
| 137 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 138 |
+
)
|
| 139 |
+
),
|
| 140 |
+
4,
|
| 141 |
+
),
|
| 142 |
+
"from_product_min_ms": round(
|
| 143 |
+
time_ms(lambda: bf16_nvfp4_dynamic(prod)), 4
|
| 144 |
+
),
|
| 145 |
+
}
|
| 146 |
+
payload["pass"] = (
|
| 147 |
+
payload["sw_full_vs_kitchen_mismatches"] == 0
|
| 148 |
+
and payload["sw_prod_vs_t256_prod"] == 0
|
| 149 |
+
and payload["product_vs_eager_full_mismatches"] == 0
|
| 150 |
+
and payload["product_vs_eager_kitchen_mismatches"] == 0
|
| 151 |
+
and payload["from_product_vs_kitchen_q"] == 0
|
| 152 |
+
and payload["from_product_vs_kitchen_s"] == 0
|
| 153 |
+
and payload["from_product_vs_kitchen_scale_exact"]
|
| 154 |
+
and payload["full_finite"]
|
| 155 |
+
and payload["prod_finite"]
|
| 156 |
+
)
|
| 157 |
+
if payload["pass"]:
|
| 158 |
+
payload["vs_t256_ms"] = round(
|
| 159 |
+
payload["t256_prod_min_ms"] - payload["sw_prod_min_ms"], 4
|
| 160 |
+
)
|
| 161 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 162 |
+
Path(__file__).with_name(
|
| 163 |
+
"gate_fc1_nvfp4_scaled_tma256_sw_20423.json"
|
| 164 |
+
).write_text(text + "\n")
|
| 165 |
+
print(text)
|
| 166 |
+
return 0 if payload["pass"] else 2
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
if __name__ == "__main__":
|
| 170 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256_sw_20423.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"from_product_min_ms": 5.7545,
|
| 4 |
+
"from_product_vs_kitchen_q": 0,
|
| 5 |
+
"from_product_vs_kitchen_s": 0,
|
| 6 |
+
"from_product_vs_kitchen_scale_exact": true,
|
| 7 |
+
"full_finite": true,
|
| 8 |
+
"identity": "SWIZZLE_32B TMA == linear fragment under phys_col = col XOR ((row&4)<<2) on the kitchen-legal 256x64 3-stage m16n8k64 paired-N atom",
|
| 9 |
+
"k": 5376,
|
| 10 |
+
"kitchen_gemm_min_ms": 20.2633,
|
| 11 |
+
"n": 14336,
|
| 12 |
+
"pass": true,
|
| 13 |
+
"prod_finite": true,
|
| 14 |
+
"product_vs_eager_full_mismatches": 0,
|
| 15 |
+
"product_vs_eager_kitchen_mismatches": 0,
|
| 16 |
+
"rows": 20423,
|
| 17 |
+
"sw_full_min_ms": 57.6358,
|
| 18 |
+
"sw_full_vs_kitchen_max_abs": 0.0,
|
| 19 |
+
"sw_full_vs_kitchen_mismatches": 0,
|
| 20 |
+
"sw_prod_min_ms": 55.2272,
|
| 21 |
+
"sw_prod_vs_t256_prod": 0,
|
| 22 |
+
"t256_prod_min_ms": 54.41,
|
| 23 |
+
"vs_t256_ms": -0.8172
|
| 24 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: K=128 TMA box == two K=64 MMA steps.
|
| 3 |
+
|
| 4 |
+
One TMA of 64 packed bytes feeds two m16n8k64 atoms. Same 256x64
|
| 5 |
+
3-stage tile, PTX fragment, and cuBLAS 128x4 scale slabs (two groups).
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
import torch.nn.functional as F
|
| 15 |
+
|
| 16 |
+
import comfy_kitchen as ck
|
| 17 |
+
|
| 18 |
+
from swiglu_nvfp4 import (
|
| 19 |
+
bf16_nvfp4_dynamic,
|
| 20 |
+
fc1_paired_nvfp4_scaled_tma256,
|
| 21 |
+
fc1_paired_nvfp4_scaled_tma256k2,
|
| 22 |
+
load_extension,
|
| 23 |
+
swiglu_nvfp4_dynamic,
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
ROWS = 20423
|
| 28 |
+
K = 5376
|
| 29 |
+
N = 14336
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 33 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 37 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 38 |
+
y = ck.scaled_mm_nvfp4(
|
| 39 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 40 |
+
)
|
| 41 |
+
return y[:ROWS]
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def eager_act(raw: torch.Tensor) -> torch.Tensor:
|
| 45 |
+
gate, up = raw.chunk(2, dim=-1)
|
| 46 |
+
return F.silu(gate).mul_(up)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 50 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def max_abs_diff(left: torch.Tensor, right: torch.Tensor) -> float:
|
| 54 |
+
return float((left.float() - right.float()).abs().max().item())
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def time_ms(fn) -> float:
|
| 58 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 59 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 60 |
+
fn()
|
| 61 |
+
torch.cuda.synchronize()
|
| 62 |
+
starter.record()
|
| 63 |
+
fn()
|
| 64 |
+
ender.record()
|
| 65 |
+
torch.cuda.synchronize()
|
| 66 |
+
return starter.elapsed_time(ender)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def main() -> int:
|
| 70 |
+
load_extension(verbose=False)
|
| 71 |
+
device = torch.device("cuda")
|
| 72 |
+
torch.manual_seed(26081242)
|
| 73 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 74 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 75 |
+
sx = nvfp4_scale(x)
|
| 76 |
+
sw = nvfp4_scale(weight)
|
| 77 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 78 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 79 |
+
qx_live = qx[:ROWS].contiguous()
|
| 80 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 81 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 82 |
+
full = fc1_paired_nvfp4_scaled_tma256k2(
|
| 83 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 84 |
+
)
|
| 85 |
+
prod = fc1_paired_nvfp4_scaled_tma256k2(
|
| 86 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 87 |
+
)
|
| 88 |
+
base = fc1_paired_nvfp4_scaled_tma256(
|
| 89 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 90 |
+
)
|
| 91 |
+
eager_kit = eager_act(kitchen.contiguous())
|
| 92 |
+
eager_full = eager_act(full)
|
| 93 |
+
two_q, two_s, two_g = swiglu_nvfp4_dynamic(kitchen.contiguous())
|
| 94 |
+
prod_q, prod_s, prod_g = bf16_nvfp4_dynamic(prod)
|
| 95 |
+
payload = {
|
| 96 |
+
"identity": (
|
| 97 |
+
"K=128 TMA box == two sequential K=64 MMA steps: one 64-byte "
|
| 98 |
+
"packed row feeds two m16n8k64 atoms; two 128x4 scale slabs "
|
| 99 |
+
"cover the 8 K-scale columns. Same 256x64 3-stage tile."
|
| 100 |
+
),
|
| 101 |
+
"rows": ROWS,
|
| 102 |
+
"k": K,
|
| 103 |
+
"n": N,
|
| 104 |
+
"device": torch.cuda.get_device_name(device),
|
| 105 |
+
"k2_full_vs_kitchen_mismatches": byte_diff(full, kitchen),
|
| 106 |
+
"k2_full_vs_kitchen_max_abs": round(max_abs_diff(full, kitchen), 6),
|
| 107 |
+
"k2_prod_vs_t256_prod": byte_diff(prod, base),
|
| 108 |
+
"product_vs_eager_full_mismatches": byte_diff(prod, eager_full),
|
| 109 |
+
"product_vs_eager_kitchen_mismatches": byte_diff(prod, eager_kit),
|
| 110 |
+
"from_product_vs_kitchen_q": byte_diff(prod_q, two_q),
|
| 111 |
+
"from_product_vs_kitchen_s": byte_diff(prod_s, two_s),
|
| 112 |
+
"from_product_vs_kitchen_scale_exact": bool(torch.equal(prod_g, two_g)),
|
| 113 |
+
"full_finite": bool(torch.isfinite(full.float()).all().item()),
|
| 114 |
+
"prod_finite": bool(torch.isfinite(prod.float()).all().item()),
|
| 115 |
+
"kitchen_gemm_min_ms": round(
|
| 116 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 117 |
+
),
|
| 118 |
+
"k2_full_min_ms": round(
|
| 119 |
+
time_ms(
|
| 120 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2(
|
| 121 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 122 |
+
)
|
| 123 |
+
),
|
| 124 |
+
4,
|
| 125 |
+
),
|
| 126 |
+
"k2_prod_min_ms": round(
|
| 127 |
+
time_ms(
|
| 128 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2(
|
| 129 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 130 |
+
)
|
| 131 |
+
),
|
| 132 |
+
4,
|
| 133 |
+
),
|
| 134 |
+
"t256_prod_min_ms": round(
|
| 135 |
+
time_ms(
|
| 136 |
+
lambda: fc1_paired_nvfp4_scaled_tma256(
|
| 137 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 138 |
+
)
|
| 139 |
+
),
|
| 140 |
+
4,
|
| 141 |
+
),
|
| 142 |
+
"from_product_min_ms": round(
|
| 143 |
+
time_ms(lambda: bf16_nvfp4_dynamic(prod)), 4
|
| 144 |
+
),
|
| 145 |
+
}
|
| 146 |
+
payload["pass"] = (
|
| 147 |
+
payload["k2_full_vs_kitchen_mismatches"] == 0
|
| 148 |
+
and payload["k2_prod_vs_t256_prod"] == 0
|
| 149 |
+
and payload["product_vs_eager_full_mismatches"] == 0
|
| 150 |
+
and payload["product_vs_eager_kitchen_mismatches"] == 0
|
| 151 |
+
and payload["from_product_vs_kitchen_q"] == 0
|
| 152 |
+
and payload["from_product_vs_kitchen_s"] == 0
|
| 153 |
+
and payload["from_product_vs_kitchen_scale_exact"]
|
| 154 |
+
and payload["full_finite"]
|
| 155 |
+
and payload["prod_finite"]
|
| 156 |
+
)
|
| 157 |
+
if payload["pass"]:
|
| 158 |
+
payload["vs_t256_ms"] = round(
|
| 159 |
+
payload["t256_prod_min_ms"] - payload["k2_prod_min_ms"], 4
|
| 160 |
+
)
|
| 161 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 162 |
+
Path(__file__).with_name(
|
| 163 |
+
"gate_fc1_nvfp4_scaled_tma256k2_20423.json"
|
| 164 |
+
).write_text(text + "\n")
|
| 165 |
+
print(text)
|
| 166 |
+
return 0 if payload["pass"] else 2
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
if __name__ == "__main__":
|
| 170 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_20423.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"from_product_min_ms": 5.7447,
|
| 4 |
+
"from_product_vs_kitchen_q": 0,
|
| 5 |
+
"from_product_vs_kitchen_s": 0,
|
| 6 |
+
"from_product_vs_kitchen_scale_exact": true,
|
| 7 |
+
"full_finite": true,
|
| 8 |
+
"identity": "K=128 TMA box == two sequential K=64 MMA steps: one 64-byte packed row feeds two m16n8k64 atoms; two 128x4 scale slabs cover the 8 K-scale columns. Same 256x64 3-stage tile.",
|
| 9 |
+
"k": 5376,
|
| 10 |
+
"k2_full_min_ms": 40.1835,
|
| 11 |
+
"k2_full_vs_kitchen_max_abs": 0.0,
|
| 12 |
+
"k2_full_vs_kitchen_mismatches": 0,
|
| 13 |
+
"k2_prod_min_ms": 36.8901,
|
| 14 |
+
"k2_prod_vs_t256_prod": 0,
|
| 15 |
+
"kitchen_gemm_min_ms": 20.1649,
|
| 16 |
+
"n": 14336,
|
| 17 |
+
"pass": true,
|
| 18 |
+
"prod_finite": true,
|
| 19 |
+
"product_vs_eager_full_mismatches": 0,
|
| 20 |
+
"product_vs_eager_kitchen_mismatches": 0,
|
| 21 |
+
"rows": 20423,
|
| 22 |
+
"t256_prod_min_ms": 56.1152,
|
| 23 |
+
"vs_t256_ms": 19.2251
|
| 24 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_ldm.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: ldmatrix.x4 A on k2 == kitchen / scalar k2.
|
| 3 |
+
|
| 4 |
+
Best lab mainloop (256x64 K=128 3-stage) with the kitchen A fragment
|
| 5 |
+
via ldmatrix. Same paired-N store.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
|
| 15 |
+
import comfy_kitchen as ck
|
| 16 |
+
|
| 17 |
+
from swiglu_nvfp4 import (
|
| 18 |
+
fc1_paired_nvfp4_scaled_tma256k2,
|
| 19 |
+
fc1_paired_nvfp4_scaled_tma256k2_ldm,
|
| 20 |
+
load_extension,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
ROWS = 20423
|
| 25 |
+
K = 5376
|
| 26 |
+
N = 14336
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 30 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 34 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 35 |
+
y = ck.scaled_mm_nvfp4(
|
| 36 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 37 |
+
)
|
| 38 |
+
return y[:ROWS]
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 42 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def time_ms(fn) -> float:
|
| 46 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 47 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 48 |
+
fn()
|
| 49 |
+
torch.cuda.synchronize()
|
| 50 |
+
starter.record()
|
| 51 |
+
fn()
|
| 52 |
+
ender.record()
|
| 53 |
+
torch.cuda.synchronize()
|
| 54 |
+
return starter.elapsed_time(ender)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def main() -> int:
|
| 58 |
+
load_extension(verbose=False)
|
| 59 |
+
device = torch.device("cuda")
|
| 60 |
+
torch.manual_seed(26081263)
|
| 61 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 62 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 63 |
+
sx = nvfp4_scale(x)
|
| 64 |
+
sw = nvfp4_scale(weight)
|
| 65 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 66 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 67 |
+
qx_live = qx[:ROWS].contiguous()
|
| 68 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 69 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 70 |
+
k2 = fc1_paired_nvfp4_scaled_tma256k2(
|
| 71 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 72 |
+
)
|
| 73 |
+
ldm = fc1_paired_nvfp4_scaled_tma256k2_ldm(qx_live, qxs, qw, qws, alpha)
|
| 74 |
+
payload = {
|
| 75 |
+
"identity": (
|
| 76 |
+
"ldmatrix.x4 A on k2 (256x64 K=128) == kitchen group/tidg "
|
| 77 |
+
"fragment == scalar k2. Best lab mainloop + kitchen A load."
|
| 78 |
+
),
|
| 79 |
+
"rows": ROWS,
|
| 80 |
+
"k": K,
|
| 81 |
+
"n": 2 * N,
|
| 82 |
+
"device": torch.cuda.get_device_name(device),
|
| 83 |
+
"ldm_vs_kitchen_mismatches": byte_diff(ldm, kitchen),
|
| 84 |
+
"ldm_vs_k2_mismatches": byte_diff(ldm, k2),
|
| 85 |
+
"full_finite": bool(torch.isfinite(ldm.float()).all().item()),
|
| 86 |
+
"kitchen_gemm_min_ms": round(
|
| 87 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 88 |
+
),
|
| 89 |
+
"ldm_full_min_ms": round(
|
| 90 |
+
time_ms(
|
| 91 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2_ldm(
|
| 92 |
+
qx_live, qxs, qw, qws, alpha
|
| 93 |
+
)
|
| 94 |
+
),
|
| 95 |
+
4,
|
| 96 |
+
),
|
| 97 |
+
"k2_full_min_ms": round(
|
| 98 |
+
time_ms(
|
| 99 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2(
|
| 100 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 101 |
+
)
|
| 102 |
+
),
|
| 103 |
+
4,
|
| 104 |
+
),
|
| 105 |
+
}
|
| 106 |
+
payload["pass"] = (
|
| 107 |
+
payload["ldm_vs_kitchen_mismatches"] == 0
|
| 108 |
+
and payload["ldm_vs_k2_mismatches"] == 0
|
| 109 |
+
and payload["full_finite"]
|
| 110 |
+
)
|
| 111 |
+
if payload["pass"]:
|
| 112 |
+
payload["vs_kitchen_ms"] = round(
|
| 113 |
+
payload["kitchen_gemm_min_ms"] - payload["ldm_full_min_ms"], 4
|
| 114 |
+
)
|
| 115 |
+
payload["vs_k2_ms"] = round(
|
| 116 |
+
payload["k2_full_min_ms"] - payload["ldm_full_min_ms"], 4
|
| 117 |
+
)
|
| 118 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 119 |
+
Path(__file__).with_name(
|
| 120 |
+
"gate_fc1_nvfp4_scaled_tma256k2_ldm_20423.json"
|
| 121 |
+
).write_text(text + "\n")
|
| 122 |
+
print(text)
|
| 123 |
+
return 0 if payload["pass"] else 2
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
if __name__ == "__main__":
|
| 127 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_ldm_20423.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"full_finite": true,
|
| 4 |
+
"identity": "ldmatrix.x4 A on k2 (256x64 K=128) == kitchen group/tidg fragment == scalar k2. Best lab mainloop + kitchen A load.",
|
| 5 |
+
"k": 5376,
|
| 6 |
+
"k2_full_min_ms": 38.9594,
|
| 7 |
+
"kitchen_gemm_min_ms": 20.2181,
|
| 8 |
+
"ldm_full_min_ms": 38.9408,
|
| 9 |
+
"ldm_vs_k2_mismatches": 0,
|
| 10 |
+
"ldm_vs_kitchen_mismatches": 0,
|
| 11 |
+
"n": 28672,
|
| 12 |
+
"pass": true,
|
| 13 |
+
"rows": 20423,
|
| 14 |
+
"vs_k2_ms": 0.0186,
|
| 15 |
+
"vs_kitchen_ms": -18.7227
|
| 16 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_ldmb.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: ldmatrix.x4 B on k2 == kitchen / scalar k2.
|
| 3 |
+
|
| 4 |
+
One x4 covers two 8-N subtiles via the proven A-fragment map
|
| 5 |
+
smem[lane&15][(lane>>4)*16]. Same 256x64 K=128 paired-N store.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
|
| 15 |
+
import comfy_kitchen as ck
|
| 16 |
+
|
| 17 |
+
from swiglu_nvfp4 import (
|
| 18 |
+
fc1_paired_nvfp4_scaled_tma256k2,
|
| 19 |
+
fc1_paired_nvfp4_scaled_tma256k2_ldmb,
|
| 20 |
+
load_extension,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
ROWS = 20423
|
| 25 |
+
K = 5376
|
| 26 |
+
N = 14336
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 30 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 34 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 35 |
+
y = ck.scaled_mm_nvfp4(
|
| 36 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 37 |
+
)
|
| 38 |
+
return y[:ROWS]
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 42 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def time_ms(fn) -> float:
|
| 46 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 47 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 48 |
+
fn()
|
| 49 |
+
torch.cuda.synchronize()
|
| 50 |
+
starter.record()
|
| 51 |
+
fn()
|
| 52 |
+
ender.record()
|
| 53 |
+
torch.cuda.synchronize()
|
| 54 |
+
return starter.elapsed_time(ender)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def main() -> int:
|
| 58 |
+
load_extension(verbose=False)
|
| 59 |
+
device = torch.device("cuda")
|
| 60 |
+
torch.manual_seed(26081264)
|
| 61 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 62 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 63 |
+
sx = nvfp4_scale(x)
|
| 64 |
+
sw = nvfp4_scale(weight)
|
| 65 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 66 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 67 |
+
qx_live = qx[:ROWS].contiguous()
|
| 68 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 69 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 70 |
+
k2 = fc1_paired_nvfp4_scaled_tma256k2(
|
| 71 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 72 |
+
)
|
| 73 |
+
ldmb = fc1_paired_nvfp4_scaled_tma256k2_ldmb(qx_live, qxs, qw, qws, alpha)
|
| 74 |
+
payload = {
|
| 75 |
+
"identity": (
|
| 76 |
+
"ldmatrix.x4 B on k2 (256x64 K=128) pairs two 8-N subtiles "
|
| 77 |
+
"via the A-fragment map == kitchen == scalar k2."
|
| 78 |
+
),
|
| 79 |
+
"rows": ROWS,
|
| 80 |
+
"k": K,
|
| 81 |
+
"n": 2 * N,
|
| 82 |
+
"device": torch.cuda.get_device_name(device),
|
| 83 |
+
"ldmb_vs_kitchen_mismatches": byte_diff(ldmb, kitchen),
|
| 84 |
+
"ldmb_vs_k2_mismatches": byte_diff(ldmb, k2),
|
| 85 |
+
"full_finite": bool(torch.isfinite(ldmb.float()).all().item()),
|
| 86 |
+
"kitchen_gemm_min_ms": round(
|
| 87 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 88 |
+
),
|
| 89 |
+
"ldmb_full_min_ms": round(
|
| 90 |
+
time_ms(
|
| 91 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2_ldmb(
|
| 92 |
+
qx_live, qxs, qw, qws, alpha
|
| 93 |
+
)
|
| 94 |
+
),
|
| 95 |
+
4,
|
| 96 |
+
),
|
| 97 |
+
"k2_full_min_ms": round(
|
| 98 |
+
time_ms(
|
| 99 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2(
|
| 100 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 101 |
+
)
|
| 102 |
+
),
|
| 103 |
+
4,
|
| 104 |
+
),
|
| 105 |
+
}
|
| 106 |
+
payload["pass"] = (
|
| 107 |
+
payload["ldmb_vs_kitchen_mismatches"] == 0
|
| 108 |
+
and payload["ldmb_vs_k2_mismatches"] == 0
|
| 109 |
+
and payload["full_finite"]
|
| 110 |
+
)
|
| 111 |
+
if payload["pass"]:
|
| 112 |
+
payload["vs_kitchen_ms"] = round(
|
| 113 |
+
payload["kitchen_gemm_min_ms"] - payload["ldmb_full_min_ms"], 4
|
| 114 |
+
)
|
| 115 |
+
payload["vs_k2_ms"] = round(
|
| 116 |
+
payload["k2_full_min_ms"] - payload["ldmb_full_min_ms"], 4
|
| 117 |
+
)
|
| 118 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 119 |
+
Path(__file__).with_name(
|
| 120 |
+
"gate_fc1_nvfp4_scaled_tma256k2_ldmb_20423.json"
|
| 121 |
+
).write_text(text + "\n")
|
| 122 |
+
print(text)
|
| 123 |
+
return 0 if payload["pass"] else 2
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
if __name__ == "__main__":
|
| 127 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_ldmb_20423.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"full_finite": true,
|
| 4 |
+
"identity": "ldmatrix.x4 B on k2 (256x64 K=128) pairs two 8-N subtiles via the A-fragment map == kitchen == scalar k2.",
|
| 5 |
+
"k": 5376,
|
| 6 |
+
"k2_full_min_ms": 40.1911,
|
| 7 |
+
"kitchen_gemm_min_ms": 20.2815,
|
| 8 |
+
"ldmb_full_min_ms": 39.4436,
|
| 9 |
+
"ldmb_vs_k2_mismatches": 0,
|
| 10 |
+
"ldmb_vs_kitchen_mismatches": 0,
|
| 11 |
+
"n": 28672,
|
| 12 |
+
"pass": true,
|
| 13 |
+
"rows": 20423,
|
| 14 |
+
"vs_k2_ms": 0.7475,
|
| 15 |
+
"vs_kitchen_ms": -19.1621
|
| 16 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_leads.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: leader-only SFA/SFB on k2 == kitchen / scalar k2.
|
| 3 |
+
|
| 4 |
+
scale_vec::4X with selectors {0,0}: SFA from 16 lanes
|
| 5 |
+
((lane>>1)&1)==0; SFB from 8 lanes (lane&3)==0. Other lanes
|
| 6 |
+
pass 0. Same 256x64 K=128 paired-N store.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import json
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
|
| 16 |
+
import comfy_kitchen as ck
|
| 17 |
+
|
| 18 |
+
from swiglu_nvfp4 import (
|
| 19 |
+
fc1_paired_nvfp4_scaled_tma256k2,
|
| 20 |
+
fc1_paired_nvfp4_scaled_tma256k2_leads,
|
| 21 |
+
load_extension,
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
ROWS = 20423
|
| 26 |
+
K = 5376
|
| 27 |
+
N = 14336
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 31 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 35 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 36 |
+
y = ck.scaled_mm_nvfp4(
|
| 37 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 38 |
+
)
|
| 39 |
+
return y[:ROWS]
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 43 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def time_ms(fn) -> float:
|
| 47 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 48 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 49 |
+
fn()
|
| 50 |
+
torch.cuda.synchronize()
|
| 51 |
+
starter.record()
|
| 52 |
+
fn()
|
| 53 |
+
ender.record()
|
| 54 |
+
torch.cuda.synchronize()
|
| 55 |
+
return starter.elapsed_time(ender)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def main() -> int:
|
| 59 |
+
load_extension(verbose=False)
|
| 60 |
+
device = torch.device("cuda")
|
| 61 |
+
torch.manual_seed(26081267)
|
| 62 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 63 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 64 |
+
sx = nvfp4_scale(x)
|
| 65 |
+
sw = nvfp4_scale(weight)
|
| 66 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 67 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 68 |
+
qx_live = qx[:ROWS].contiguous()
|
| 69 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 70 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 71 |
+
k2 = fc1_paired_nvfp4_scaled_tma256k2(
|
| 72 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 73 |
+
)
|
| 74 |
+
leads = fc1_paired_nvfp4_scaled_tma256k2_leads(
|
| 75 |
+
qx_live, qxs, qw, qws, alpha
|
| 76 |
+
)
|
| 77 |
+
payload = {
|
| 78 |
+
"identity": (
|
| 79 |
+
"leader-only SFA/SFB on k2 (256x64 K=128): scale_vec::4X "
|
| 80 |
+
"selectors {0,0} read 16 SFA + 8 SFB lanes; others 0 "
|
| 81 |
+
"== kitchen == scalar k2."
|
| 82 |
+
),
|
| 83 |
+
"rows": ROWS,
|
| 84 |
+
"k": K,
|
| 85 |
+
"n": 2 * N,
|
| 86 |
+
"device": torch.cuda.get_device_name(device),
|
| 87 |
+
"leads_vs_kitchen_mismatches": byte_diff(leads, kitchen),
|
| 88 |
+
"leads_vs_k2_mismatches": byte_diff(leads, k2),
|
| 89 |
+
"full_finite": bool(torch.isfinite(leads.float()).all().item()),
|
| 90 |
+
"kitchen_gemm_min_ms": round(
|
| 91 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 92 |
+
),
|
| 93 |
+
"leads_full_min_ms": round(
|
| 94 |
+
time_ms(
|
| 95 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2_leads(
|
| 96 |
+
qx_live, qxs, qw, qws, alpha
|
| 97 |
+
)
|
| 98 |
+
),
|
| 99 |
+
4,
|
| 100 |
+
),
|
| 101 |
+
"k2_full_min_ms": round(
|
| 102 |
+
time_ms(
|
| 103 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2(
|
| 104 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 105 |
+
)
|
| 106 |
+
),
|
| 107 |
+
4,
|
| 108 |
+
),
|
| 109 |
+
}
|
| 110 |
+
payload["pass"] = (
|
| 111 |
+
payload["leads_vs_kitchen_mismatches"] == 0
|
| 112 |
+
and payload["leads_vs_k2_mismatches"] == 0
|
| 113 |
+
and payload["full_finite"]
|
| 114 |
+
)
|
| 115 |
+
if payload["pass"]:
|
| 116 |
+
payload["vs_kitchen_ms"] = round(
|
| 117 |
+
payload["kitchen_gemm_min_ms"] - payload["leads_full_min_ms"], 4
|
| 118 |
+
)
|
| 119 |
+
payload["vs_k2_ms"] = round(
|
| 120 |
+
payload["k2_full_min_ms"] - payload["leads_full_min_ms"], 4
|
| 121 |
+
)
|
| 122 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 123 |
+
Path(__file__).with_name(
|
| 124 |
+
"gate_fc1_nvfp4_scaled_tma256k2_leads_20423.json"
|
| 125 |
+
).write_text(text + "\n")
|
| 126 |
+
print(text)
|
| 127 |
+
return 0 if payload["pass"] else 2
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
if __name__ == "__main__":
|
| 131 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_leads_20423.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"full_finite": true,
|
| 4 |
+
"identity": "leader-only SFA/SFB on k2 (256x64 K=128): scale_vec::4X selectors {0,0} read 16 SFA + 8 SFB lanes; others 0 == kitchen == scalar k2.",
|
| 5 |
+
"k": 5376,
|
| 6 |
+
"k2_full_min_ms": 39.226,
|
| 7 |
+
"kitchen_gemm_min_ms": 19.4048,
|
| 8 |
+
"leads_full_min_ms": 39.8251,
|
| 9 |
+
"leads_vs_k2_mismatches": 0,
|
| 10 |
+
"leads_vs_kitchen_mismatches": 0,
|
| 11 |
+
"n": 28672,
|
| 12 |
+
"pass": true,
|
| 13 |
+
"rows": 20423,
|
| 14 |
+
"vs_k2_ms": -0.5991,
|
| 15 |
+
"vs_kitchen_ms": -20.4203
|
| 16 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_pipe.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: software-pipelined B on k2 == kitchen / scalar k2.
|
| 3 |
+
|
| 4 |
+
Next N-subtile B/SFB (both arms) overlaps m16n8k64. Same 256x64
|
| 5 |
+
K=128 paired-N store. Not the 128x128x256 pipe.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
|
| 15 |
+
import comfy_kitchen as ck
|
| 16 |
+
|
| 17 |
+
from swiglu_nvfp4 import (
|
| 18 |
+
fc1_paired_nvfp4_scaled_tma256k2,
|
| 19 |
+
fc1_paired_nvfp4_scaled_tma256k2_pipe,
|
| 20 |
+
load_extension,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
ROWS = 20423
|
| 25 |
+
K = 5376
|
| 26 |
+
N = 14336
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 30 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 34 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 35 |
+
y = ck.scaled_mm_nvfp4(
|
| 36 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 37 |
+
)
|
| 38 |
+
return y[:ROWS]
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 42 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def time_ms(fn) -> float:
|
| 46 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 47 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 48 |
+
fn()
|
| 49 |
+
torch.cuda.synchronize()
|
| 50 |
+
starter.record()
|
| 51 |
+
fn()
|
| 52 |
+
ender.record()
|
| 53 |
+
torch.cuda.synchronize()
|
| 54 |
+
return starter.elapsed_time(ender)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def main() -> int:
|
| 58 |
+
load_extension(verbose=False)
|
| 59 |
+
device = torch.device("cuda")
|
| 60 |
+
torch.manual_seed(26081266)
|
| 61 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 62 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 63 |
+
sx = nvfp4_scale(x)
|
| 64 |
+
sw = nvfp4_scale(weight)
|
| 65 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 66 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 67 |
+
qx_live = qx[:ROWS].contiguous()
|
| 68 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 69 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 70 |
+
k2 = fc1_paired_nvfp4_scaled_tma256k2(
|
| 71 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 72 |
+
)
|
| 73 |
+
pipe = fc1_paired_nvfp4_scaled_tma256k2_pipe(qx_live, qxs, qw, qws, alpha)
|
| 74 |
+
payload = {
|
| 75 |
+
"identity": (
|
| 76 |
+
"software-pipelined B on k2 (256x64 K=128): next N-subtile "
|
| 77 |
+
"B/SFB both arms overlap m16n8k64 == kitchen == scalar k2."
|
| 78 |
+
),
|
| 79 |
+
"rows": ROWS,
|
| 80 |
+
"k": K,
|
| 81 |
+
"n": 2 * N,
|
| 82 |
+
"device": torch.cuda.get_device_name(device),
|
| 83 |
+
"pipe_vs_kitchen_mismatches": byte_diff(pipe, kitchen),
|
| 84 |
+
"pipe_vs_k2_mismatches": byte_diff(pipe, k2),
|
| 85 |
+
"full_finite": bool(torch.isfinite(pipe.float()).all().item()),
|
| 86 |
+
"kitchen_gemm_min_ms": round(
|
| 87 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 88 |
+
),
|
| 89 |
+
"pipe_full_min_ms": round(
|
| 90 |
+
time_ms(
|
| 91 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2_pipe(
|
| 92 |
+
qx_live, qxs, qw, qws, alpha
|
| 93 |
+
)
|
| 94 |
+
),
|
| 95 |
+
4,
|
| 96 |
+
),
|
| 97 |
+
"k2_full_min_ms": round(
|
| 98 |
+
time_ms(
|
| 99 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2(
|
| 100 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 101 |
+
)
|
| 102 |
+
),
|
| 103 |
+
4,
|
| 104 |
+
),
|
| 105 |
+
}
|
| 106 |
+
payload["pass"] = (
|
| 107 |
+
payload["pipe_vs_kitchen_mismatches"] == 0
|
| 108 |
+
and payload["pipe_vs_k2_mismatches"] == 0
|
| 109 |
+
and payload["full_finite"]
|
| 110 |
+
)
|
| 111 |
+
if payload["pass"]:
|
| 112 |
+
payload["vs_kitchen_ms"] = round(
|
| 113 |
+
payload["kitchen_gemm_min_ms"] - payload["pipe_full_min_ms"], 4
|
| 114 |
+
)
|
| 115 |
+
payload["vs_k2_ms"] = round(
|
| 116 |
+
payload["k2_full_min_ms"] - payload["pipe_full_min_ms"], 4
|
| 117 |
+
)
|
| 118 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 119 |
+
Path(__file__).with_name(
|
| 120 |
+
"gate_fc1_nvfp4_scaled_tma256k2_pipe_20423.json"
|
| 121 |
+
).write_text(text + "\n")
|
| 122 |
+
print(text)
|
| 123 |
+
return 0 if payload["pass"] else 2
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
if __name__ == "__main__":
|
| 127 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_pipe_20423.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"full_finite": true,
|
| 4 |
+
"identity": "software-pipelined B on k2 (256x64 K=128): next N-subtile B/SFB both arms overlap m16n8k64 == kitchen == scalar k2.",
|
| 5 |
+
"k": 5376,
|
| 6 |
+
"k2_full_min_ms": 39.9199,
|
| 7 |
+
"kitchen_gemm_min_ms": 20.1408,
|
| 8 |
+
"n": 28672,
|
| 9 |
+
"pass": true,
|
| 10 |
+
"pipe_full_min_ms": 40.6522,
|
| 11 |
+
"pipe_vs_k2_mismatches": 0,
|
| 12 |
+
"pipe_vs_kitchen_mismatches": 0,
|
| 13 |
+
"rows": 20423,
|
| 14 |
+
"vs_k2_ms": -0.7323,
|
| 15 |
+
"vs_kitchen_ms": -20.5114
|
| 16 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_pipea.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: software-pipelined A on k2 == kitchen / scalar k2.
|
| 3 |
+
|
| 4 |
+
Next K=64 A/SFA overlaps m16n8k64. Same 256x64 K=128 paired-N store.
|
| 5 |
+
Not the B-pipe identity.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
|
| 15 |
+
import comfy_kitchen as ck
|
| 16 |
+
|
| 17 |
+
from swiglu_nvfp4 import (
|
| 18 |
+
fc1_paired_nvfp4_scaled_tma256k2,
|
| 19 |
+
fc1_paired_nvfp4_scaled_tma256k2_pipea,
|
| 20 |
+
load_extension,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
ROWS = 20423
|
| 25 |
+
K = 5376
|
| 26 |
+
N = 14336
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 30 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 34 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 35 |
+
y = ck.scaled_mm_nvfp4(
|
| 36 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 37 |
+
)
|
| 38 |
+
return y[:ROWS]
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 42 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def time_ms(fn) -> float:
|
| 46 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 47 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 48 |
+
fn()
|
| 49 |
+
torch.cuda.synchronize()
|
| 50 |
+
starter.record()
|
| 51 |
+
fn()
|
| 52 |
+
ender.record()
|
| 53 |
+
torch.cuda.synchronize()
|
| 54 |
+
return starter.elapsed_time(ender)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def main() -> int:
|
| 58 |
+
load_extension(verbose=False)
|
| 59 |
+
device = torch.device("cuda")
|
| 60 |
+
torch.manual_seed(26081269)
|
| 61 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 62 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 63 |
+
sx = nvfp4_scale(x)
|
| 64 |
+
sw = nvfp4_scale(weight)
|
| 65 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 66 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 67 |
+
qx_live = qx[:ROWS].contiguous()
|
| 68 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 69 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 70 |
+
k2 = fc1_paired_nvfp4_scaled_tma256k2(
|
| 71 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 72 |
+
)
|
| 73 |
+
pipea = fc1_paired_nvfp4_scaled_tma256k2_pipea(
|
| 74 |
+
qx_live, qxs, qw, qws, alpha
|
| 75 |
+
)
|
| 76 |
+
payload = {
|
| 77 |
+
"identity": (
|
| 78 |
+
"software-pipelined A on k2 (256x64 K=128): next K=64 "
|
| 79 |
+
"A/SFA overlaps m16n8k64 == kitchen == scalar k2."
|
| 80 |
+
),
|
| 81 |
+
"rows": ROWS,
|
| 82 |
+
"k": K,
|
| 83 |
+
"n": 2 * N,
|
| 84 |
+
"device": torch.cuda.get_device_name(device),
|
| 85 |
+
"pipea_vs_kitchen_mismatches": byte_diff(pipea, kitchen),
|
| 86 |
+
"pipea_vs_k2_mismatches": byte_diff(pipea, k2),
|
| 87 |
+
"full_finite": bool(torch.isfinite(pipea.float()).all().item()),
|
| 88 |
+
"kitchen_gemm_min_ms": round(
|
| 89 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 90 |
+
),
|
| 91 |
+
"pipea_full_min_ms": round(
|
| 92 |
+
time_ms(
|
| 93 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2_pipea(
|
| 94 |
+
qx_live, qxs, qw, qws, alpha
|
| 95 |
+
)
|
| 96 |
+
),
|
| 97 |
+
4,
|
| 98 |
+
),
|
| 99 |
+
"k2_full_min_ms": round(
|
| 100 |
+
time_ms(
|
| 101 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2(
|
| 102 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 103 |
+
)
|
| 104 |
+
),
|
| 105 |
+
4,
|
| 106 |
+
),
|
| 107 |
+
}
|
| 108 |
+
payload["pass"] = (
|
| 109 |
+
payload["pipea_vs_kitchen_mismatches"] == 0
|
| 110 |
+
and payload["pipea_vs_k2_mismatches"] == 0
|
| 111 |
+
and payload["full_finite"]
|
| 112 |
+
)
|
| 113 |
+
if payload["pass"]:
|
| 114 |
+
payload["vs_kitchen_ms"] = round(
|
| 115 |
+
payload["kitchen_gemm_min_ms"] - payload["pipea_full_min_ms"], 4
|
| 116 |
+
)
|
| 117 |
+
payload["vs_k2_ms"] = round(
|
| 118 |
+
payload["k2_full_min_ms"] - payload["pipea_full_min_ms"], 4
|
| 119 |
+
)
|
| 120 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 121 |
+
Path(__file__).with_name(
|
| 122 |
+
"gate_fc1_nvfp4_scaled_tma256k2_pipea_20423.json"
|
| 123 |
+
).write_text(text + "\n")
|
| 124 |
+
print(text)
|
| 125 |
+
return 0 if payload["pass"] else 2
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
if __name__ == "__main__":
|
| 129 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_pipea_20423.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"full_finite": true,
|
| 4 |
+
"identity": "software-pipelined A on k2 (256x64 K=128): next K=64 A/SFA overlaps m16n8k64 == kitchen == scalar k2.",
|
| 5 |
+
"k": 5376,
|
| 6 |
+
"k2_full_min_ms": 39.3463,
|
| 7 |
+
"kitchen_gemm_min_ms": 20.2426,
|
| 8 |
+
"n": 28672,
|
| 9 |
+
"pass": true,
|
| 10 |
+
"pipea_full_min_ms": 39.3357,
|
| 11 |
+
"pipea_vs_k2_mismatches": 0,
|
| 12 |
+
"pipea_vs_kitchen_mismatches": 0,
|
| 13 |
+
"rows": 20423,
|
| 14 |
+
"vs_k2_ms": 0.0106,
|
| 15 |
+
"vs_kitchen_ms": -19.0931
|
| 16 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_sw.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: SWIZZLE_64B remapped on the K=128 TMA box.
|
| 3 |
+
|
| 4 |
+
phys_col = col XOR (((row >> 1) & 3) << 4). Same two m16n8k64 atoms,
|
| 5 |
+
256x64 3-stage tile, and cuBLAS 128x4 scale slabs.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
import torch.nn.functional as F
|
| 15 |
+
|
| 16 |
+
import comfy_kitchen as ck
|
| 17 |
+
|
| 18 |
+
from swiglu_nvfp4 import (
|
| 19 |
+
bf16_nvfp4_dynamic,
|
| 20 |
+
fc1_paired_nvfp4_scaled_tma256k2,
|
| 21 |
+
fc1_paired_nvfp4_scaled_tma256k2_sw,
|
| 22 |
+
load_extension,
|
| 23 |
+
swiglu_nvfp4_dynamic,
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
ROWS = 20423
|
| 28 |
+
K = 5376
|
| 29 |
+
N = 14336
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 33 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 37 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 38 |
+
y = ck.scaled_mm_nvfp4(
|
| 39 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 40 |
+
)
|
| 41 |
+
return y[:ROWS]
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def eager_act(raw: torch.Tensor) -> torch.Tensor:
|
| 45 |
+
gate, up = raw.chunk(2, dim=-1)
|
| 46 |
+
return F.silu(gate).mul_(up)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 50 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def max_abs_diff(left: torch.Tensor, right: torch.Tensor) -> float:
|
| 54 |
+
return float((left.float() - right.float()).abs().max().item())
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def time_ms(fn) -> float:
|
| 58 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 59 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 60 |
+
fn()
|
| 61 |
+
torch.cuda.synchronize()
|
| 62 |
+
starter.record()
|
| 63 |
+
fn()
|
| 64 |
+
ender.record()
|
| 65 |
+
torch.cuda.synchronize()
|
| 66 |
+
return starter.elapsed_time(ender)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def main() -> int:
|
| 70 |
+
load_extension(verbose=False)
|
| 71 |
+
device = torch.device("cuda")
|
| 72 |
+
torch.manual_seed(26081243)
|
| 73 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 74 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 75 |
+
sx = nvfp4_scale(x)
|
| 76 |
+
sw = nvfp4_scale(weight)
|
| 77 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 78 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 79 |
+
qx_live = qx[:ROWS].contiguous()
|
| 80 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 81 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 82 |
+
full = fc1_paired_nvfp4_scaled_tma256k2_sw(
|
| 83 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 84 |
+
)
|
| 85 |
+
prod = fc1_paired_nvfp4_scaled_tma256k2_sw(
|
| 86 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 87 |
+
)
|
| 88 |
+
base = fc1_paired_nvfp4_scaled_tma256k2(
|
| 89 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 90 |
+
)
|
| 91 |
+
eager_kit = eager_act(kitchen.contiguous())
|
| 92 |
+
eager_full = eager_act(full)
|
| 93 |
+
two_q, two_s, two_g = swiglu_nvfp4_dynamic(kitchen.contiguous())
|
| 94 |
+
prod_q, prod_s, prod_g = bf16_nvfp4_dynamic(prod)
|
| 95 |
+
payload = {
|
| 96 |
+
"identity": (
|
| 97 |
+
"SWIZZLE_64B TMA == linear fragment under "
|
| 98 |
+
"phys_col = col XOR (((row>>1)&3)<<4) on the kitchen-legal "
|
| 99 |
+
"K=128 / 256x64 3-stage m16n8k64 paired-N atom"
|
| 100 |
+
),
|
| 101 |
+
"rows": ROWS,
|
| 102 |
+
"k": K,
|
| 103 |
+
"n": N,
|
| 104 |
+
"device": torch.cuda.get_device_name(device),
|
| 105 |
+
"sw_full_vs_kitchen_mismatches": byte_diff(full, kitchen),
|
| 106 |
+
"sw_full_vs_kitchen_max_abs": round(max_abs_diff(full, kitchen), 6),
|
| 107 |
+
"sw_prod_vs_k2_prod": byte_diff(prod, base),
|
| 108 |
+
"product_vs_eager_full_mismatches": byte_diff(prod, eager_full),
|
| 109 |
+
"product_vs_eager_kitchen_mismatches": byte_diff(prod, eager_kit),
|
| 110 |
+
"from_product_vs_kitchen_q": byte_diff(prod_q, two_q),
|
| 111 |
+
"from_product_vs_kitchen_s": byte_diff(prod_s, two_s),
|
| 112 |
+
"from_product_vs_kitchen_scale_exact": bool(torch.equal(prod_g, two_g)),
|
| 113 |
+
"full_finite": bool(torch.isfinite(full.float()).all().item()),
|
| 114 |
+
"prod_finite": bool(torch.isfinite(prod.float()).all().item()),
|
| 115 |
+
"kitchen_gemm_min_ms": round(
|
| 116 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 117 |
+
),
|
| 118 |
+
"sw_full_min_ms": round(
|
| 119 |
+
time_ms(
|
| 120 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2_sw(
|
| 121 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 122 |
+
)
|
| 123 |
+
),
|
| 124 |
+
4,
|
| 125 |
+
),
|
| 126 |
+
"sw_prod_min_ms": round(
|
| 127 |
+
time_ms(
|
| 128 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2_sw(
|
| 129 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 130 |
+
)
|
| 131 |
+
),
|
| 132 |
+
4,
|
| 133 |
+
),
|
| 134 |
+
"k2_prod_min_ms": round(
|
| 135 |
+
time_ms(
|
| 136 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2(
|
| 137 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 138 |
+
)
|
| 139 |
+
),
|
| 140 |
+
4,
|
| 141 |
+
),
|
| 142 |
+
"from_product_min_ms": round(
|
| 143 |
+
time_ms(lambda: bf16_nvfp4_dynamic(prod)), 4
|
| 144 |
+
),
|
| 145 |
+
}
|
| 146 |
+
payload["pass"] = (
|
| 147 |
+
payload["sw_full_vs_kitchen_mismatches"] == 0
|
| 148 |
+
and payload["sw_prod_vs_k2_prod"] == 0
|
| 149 |
+
and payload["product_vs_eager_full_mismatches"] == 0
|
| 150 |
+
and payload["product_vs_eager_kitchen_mismatches"] == 0
|
| 151 |
+
and payload["from_product_vs_kitchen_q"] == 0
|
| 152 |
+
and payload["from_product_vs_kitchen_s"] == 0
|
| 153 |
+
and payload["from_product_vs_kitchen_scale_exact"]
|
| 154 |
+
and payload["full_finite"]
|
| 155 |
+
and payload["prod_finite"]
|
| 156 |
+
)
|
| 157 |
+
if payload["pass"]:
|
| 158 |
+
payload["vs_k2_ms"] = round(
|
| 159 |
+
payload["k2_prod_min_ms"] - payload["sw_prod_min_ms"], 4
|
| 160 |
+
)
|
| 161 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 162 |
+
Path(__file__).with_name(
|
| 163 |
+
"gate_fc1_nvfp4_scaled_tma256k2_sw_20423.json"
|
| 164 |
+
).write_text(text + "\n")
|
| 165 |
+
print(text)
|
| 166 |
+
return 0 if payload["pass"] else 2
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
if __name__ == "__main__":
|
| 170 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2_sw_20423.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"from_product_min_ms": 6.1193,
|
| 4 |
+
"from_product_vs_kitchen_q": 0,
|
| 5 |
+
"from_product_vs_kitchen_s": 0,
|
| 6 |
+
"from_product_vs_kitchen_scale_exact": true,
|
| 7 |
+
"full_finite": true,
|
| 8 |
+
"identity": "SWIZZLE_64B TMA == linear fragment under phys_col = col XOR (((row>>1)&3)<<4) on the kitchen-legal K=128 / 256x64 3-stage m16n8k64 paired-N atom",
|
| 9 |
+
"k": 5376,
|
| 10 |
+
"k2_prod_min_ms": 36.5662,
|
| 11 |
+
"kitchen_gemm_min_ms": 20.2433,
|
| 12 |
+
"n": 14336,
|
| 13 |
+
"pass": true,
|
| 14 |
+
"prod_finite": true,
|
| 15 |
+
"product_vs_eager_full_mismatches": 0,
|
| 16 |
+
"product_vs_eager_kitchen_mismatches": 0,
|
| 17 |
+
"rows": 20423,
|
| 18 |
+
"sw_full_min_ms": 39.6867,
|
| 19 |
+
"sw_full_vs_kitchen_max_abs": 0.0,
|
| 20 |
+
"sw_full_vs_kitchen_mismatches": 0,
|
| 21 |
+
"sw_prod_min_ms": 37.5803,
|
| 22 |
+
"sw_prod_vs_k2_prod": 0,
|
| 23 |
+
"vs_k2_ms": -1.0141
|
| 24 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2n2.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: sequential N-halves stream A once on 256x64 acc.
|
| 3 |
+
|
| 4 |
+
One A K=128 TMA feeds two 64-wide N-halves. Acc0 stays in the k2
|
| 5 |
+
2x8x4 register budget; acc1 swaps through opt-in smem. Same PTX
|
| 6 |
+
fragment and cuBLAS 128x4 scale slabs.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import json
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
|
| 16 |
+
import comfy_kitchen as ck
|
| 17 |
+
|
| 18 |
+
from swiglu_nvfp4 import (
|
| 19 |
+
fc1_paired_nvfp4_scaled_tma256k2n2,
|
| 20 |
+
load_extension,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
ROWS = 20423
|
| 25 |
+
K = 5376
|
| 26 |
+
N = 14336
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 30 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def main() -> int:
|
| 34 |
+
load_extension(verbose=False)
|
| 35 |
+
device = torch.device("cuda")
|
| 36 |
+
props = torch.cuda.get_device_properties(device)
|
| 37 |
+
optin = int(getattr(props, "shared_memory_per_block_optin", 0))
|
| 38 |
+
acc_bytes = 256 * 128 * 4
|
| 39 |
+
tile_smem = 209024
|
| 40 |
+
torch.manual_seed(26081247)
|
| 41 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 42 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 43 |
+
sx = nvfp4_scale(x)
|
| 44 |
+
sw = nvfp4_scale(weight)
|
| 45 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 46 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 47 |
+
qx_live = qx[:ROWS].contiguous()
|
| 48 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 49 |
+
launch_error = ""
|
| 50 |
+
launched = False
|
| 51 |
+
try:
|
| 52 |
+
_ = fc1_paired_nvfp4_scaled_tma256k2n2(
|
| 53 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 54 |
+
)
|
| 55 |
+
launched = True
|
| 56 |
+
except RuntimeError as exc:
|
| 57 |
+
launch_error = str(exc)
|
| 58 |
+
payload = {
|
| 59 |
+
"identity": (
|
| 60 |
+
"Sequential N-halves that stream A once need a second "
|
| 61 |
+
"256x64 FP32 acc (128 KiB) plus TMA. GB10 block opt-in "
|
| 62 |
+
"is 99 KiB, so acc1 cannot live in smem. Fail-closed."
|
| 63 |
+
),
|
| 64 |
+
"rows": ROWS,
|
| 65 |
+
"k": K,
|
| 66 |
+
"n": N,
|
| 67 |
+
"device": torch.cuda.get_device_name(device),
|
| 68 |
+
"smem_per_block": int(props.shared_memory_per_block),
|
| 69 |
+
"smem_per_sm": int(props.shared_memory_per_multiprocessor),
|
| 70 |
+
"smem_optin": optin,
|
| 71 |
+
"acc1_bytes": acc_bytes,
|
| 72 |
+
"tile_smem_bytes": tile_smem,
|
| 73 |
+
"acc1_exceeds_optin": acc_bytes > optin,
|
| 74 |
+
"tile_exceeds_optin": tile_smem > optin,
|
| 75 |
+
"launched": launched,
|
| 76 |
+
"launch_error": launch_error,
|
| 77 |
+
}
|
| 78 |
+
payload["pass"] = (
|
| 79 |
+
not launched
|
| 80 |
+
and acc_bytes > optin
|
| 81 |
+
and tile_smem > optin
|
| 82 |
+
and optin <= 101376
|
| 83 |
+
and "opt-in" in launch_error
|
| 84 |
+
)
|
| 85 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 86 |
+
Path(__file__).with_name(
|
| 87 |
+
"gate_fc1_nvfp4_scaled_tma256k2n2_20423.json"
|
| 88 |
+
).write_text(text + "\n")
|
| 89 |
+
print(text)
|
| 90 |
+
return 0 if payload["pass"] else 2
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
if __name__ == "__main__":
|
| 94 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2n2_20423.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"acc1_bytes": 131072,
|
| 3 |
+
"acc1_exceeds_optin": true,
|
| 4 |
+
"device": "NVIDIA GB10",
|
| 5 |
+
"identity": "Sequential N-halves that stream A once need a second 256x64 FP32 acc (128 KiB) plus TMA. GB10 block opt-in is 99 KiB, so acc1 cannot live in smem. Fail-closed.",
|
| 6 |
+
"k": 5376,
|
| 7 |
+
"launch_error": "sequential N-halves need 209024 B smem (131072 B FP32 acc1 + TMA); device opt-in is 101376 B",
|
| 8 |
+
"launched": false,
|
| 9 |
+
"n": 14336,
|
| 10 |
+
"pass": true,
|
| 11 |
+
"rows": 20423,
|
| 12 |
+
"smem_optin": 101376,
|
| 13 |
+
"smem_per_block": 49152,
|
| 14 |
+
"smem_per_sm": 102400,
|
| 15 |
+
"tile_exceeds_optin": true,
|
| 16 |
+
"tile_smem_bytes": 209024
|
| 17 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2p.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: persistent k2 N-walk == grid k2.
|
| 3 |
+
|
| 4 |
+
One 8-warp CTA owns 256 M and walks every 64-wide N tile. Same
|
| 5 |
+
m16n8k64 atom, 2x8x4 acc, and 3-stage K=128 TMA. A-stationary
|
| 6 |
+
so L2 can reuse the 256-row panel. Smem matches k2 (under 99 KiB).
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import json
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
import torch.nn.functional as F
|
| 16 |
+
|
| 17 |
+
import comfy_kitchen as ck
|
| 18 |
+
|
| 19 |
+
from swiglu_nvfp4 import (
|
| 20 |
+
bf16_nvfp4_dynamic,
|
| 21 |
+
fc1_paired_nvfp4_scaled_tma256k2,
|
| 22 |
+
fc1_paired_nvfp4_scaled_tma256k2p,
|
| 23 |
+
load_extension,
|
| 24 |
+
swiglu_nvfp4_dynamic,
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
ROWS = 20423
|
| 29 |
+
K = 5376
|
| 30 |
+
N = 14336
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 34 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 38 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 39 |
+
y = ck.scaled_mm_nvfp4(
|
| 40 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 41 |
+
)
|
| 42 |
+
return y[:ROWS]
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def eager_act(raw: torch.Tensor) -> torch.Tensor:
|
| 46 |
+
gate, up = raw.chunk(2, dim=-1)
|
| 47 |
+
return F.silu(gate).mul_(up)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 51 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def max_abs_diff(left: torch.Tensor, right: torch.Tensor) -> float:
|
| 55 |
+
return float((left.float() - right.float()).abs().max().item())
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def time_ms(fn) -> float:
|
| 59 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 60 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 61 |
+
fn()
|
| 62 |
+
torch.cuda.synchronize()
|
| 63 |
+
starter.record()
|
| 64 |
+
fn()
|
| 65 |
+
ender.record()
|
| 66 |
+
torch.cuda.synchronize()
|
| 67 |
+
return starter.elapsed_time(ender)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def main() -> int:
|
| 71 |
+
load_extension(verbose=False)
|
| 72 |
+
device = torch.device("cuda")
|
| 73 |
+
torch.manual_seed(26081249)
|
| 74 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 75 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 76 |
+
sx = nvfp4_scale(x)
|
| 77 |
+
sw = nvfp4_scale(weight)
|
| 78 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 79 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 80 |
+
qx_live = qx[:ROWS].contiguous()
|
| 81 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 82 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 83 |
+
full = fc1_paired_nvfp4_scaled_tma256k2p(
|
| 84 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 85 |
+
)
|
| 86 |
+
prod = fc1_paired_nvfp4_scaled_tma256k2p(
|
| 87 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 88 |
+
)
|
| 89 |
+
k2 = fc1_paired_nvfp4_scaled_tma256k2(
|
| 90 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 91 |
+
)
|
| 92 |
+
eager_kit = eager_act(kitchen.contiguous())
|
| 93 |
+
eager_full = eager_act(full)
|
| 94 |
+
two_q, two_s, two_g = swiglu_nvfp4_dynamic(kitchen.contiguous())
|
| 95 |
+
prod_q, prod_s, prod_g = bf16_nvfp4_dynamic(prod)
|
| 96 |
+
payload = {
|
| 97 |
+
"identity": (
|
| 98 |
+
"Persistent 8-warp k2 N-walk == grid k2: one CTA owns "
|
| 99 |
+
"256 M and walks every 64-wide N tile. Same m16n8k64 "
|
| 100 |
+
"atom, 2x8x4 acc, 3-stage K=128 TMA. A-stationary."
|
| 101 |
+
),
|
| 102 |
+
"rows": ROWS,
|
| 103 |
+
"k": K,
|
| 104 |
+
"n": N,
|
| 105 |
+
"device": torch.cuda.get_device_name(device),
|
| 106 |
+
"p_full_vs_kitchen_mismatches": byte_diff(full, kitchen),
|
| 107 |
+
"p_full_vs_kitchen_max_abs": round(max_abs_diff(full, kitchen), 6),
|
| 108 |
+
"p_prod_vs_k2_prod": byte_diff(prod, k2),
|
| 109 |
+
"product_vs_eager_full_mismatches": byte_diff(prod, eager_full),
|
| 110 |
+
"product_vs_eager_kitchen_mismatches": byte_diff(prod, eager_kit),
|
| 111 |
+
"from_product_vs_kitchen_q": byte_diff(prod_q, two_q),
|
| 112 |
+
"from_product_vs_kitchen_s": byte_diff(prod_s, two_s),
|
| 113 |
+
"from_product_vs_kitchen_scale_exact": bool(torch.equal(prod_g, two_g)),
|
| 114 |
+
"full_finite": bool(torch.isfinite(full.float()).all().item()),
|
| 115 |
+
"prod_finite": bool(torch.isfinite(prod.float()).all().item()),
|
| 116 |
+
"kitchen_gemm_min_ms": round(
|
| 117 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 118 |
+
),
|
| 119 |
+
"p_full_min_ms": round(
|
| 120 |
+
time_ms(
|
| 121 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2p(
|
| 122 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 123 |
+
)
|
| 124 |
+
),
|
| 125 |
+
4,
|
| 126 |
+
),
|
| 127 |
+
"p_prod_min_ms": round(
|
| 128 |
+
time_ms(
|
| 129 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2p(
|
| 130 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 131 |
+
)
|
| 132 |
+
),
|
| 133 |
+
4,
|
| 134 |
+
),
|
| 135 |
+
"k2_prod_min_ms": round(
|
| 136 |
+
time_ms(
|
| 137 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2(
|
| 138 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 139 |
+
)
|
| 140 |
+
),
|
| 141 |
+
4,
|
| 142 |
+
),
|
| 143 |
+
"from_product_min_ms": round(
|
| 144 |
+
time_ms(lambda: bf16_nvfp4_dynamic(prod)), 4
|
| 145 |
+
),
|
| 146 |
+
}
|
| 147 |
+
payload["pass"] = (
|
| 148 |
+
payload["p_full_vs_kitchen_mismatches"] == 0
|
| 149 |
+
and payload["p_prod_vs_k2_prod"] == 0
|
| 150 |
+
and payload["product_vs_eager_full_mismatches"] == 0
|
| 151 |
+
and payload["product_vs_eager_kitchen_mismatches"] == 0
|
| 152 |
+
and payload["from_product_vs_kitchen_q"] == 0
|
| 153 |
+
and payload["from_product_vs_kitchen_s"] == 0
|
| 154 |
+
and payload["from_product_vs_kitchen_scale_exact"]
|
| 155 |
+
and payload["full_finite"]
|
| 156 |
+
and payload["prod_finite"]
|
| 157 |
+
)
|
| 158 |
+
if payload["pass"]:
|
| 159 |
+
payload["vs_k2_ms"] = round(
|
| 160 |
+
payload["k2_prod_min_ms"] - payload["p_prod_min_ms"], 4
|
| 161 |
+
)
|
| 162 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 163 |
+
Path(__file__).with_name(
|
| 164 |
+
"gate_fc1_nvfp4_scaled_tma256k2p_20423.json"
|
| 165 |
+
).write_text(text + "\n")
|
| 166 |
+
print(text)
|
| 167 |
+
return 0 if payload["pass"] else 2
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
if __name__ == "__main__":
|
| 171 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2p_20423.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"from_product_min_ms": 6.057,
|
| 4 |
+
"from_product_vs_kitchen_q": 0,
|
| 5 |
+
"from_product_vs_kitchen_s": 0,
|
| 6 |
+
"from_product_vs_kitchen_scale_exact": true,
|
| 7 |
+
"full_finite": true,
|
| 8 |
+
"identity": "Persistent 8-warp k2 N-walk == grid k2: one CTA owns 256 M and walks every 64-wide N tile. Same m16n8k64 atom, 2x8x4 acc, 3-stage K=128 TMA. A-stationary.",
|
| 9 |
+
"k": 5376,
|
| 10 |
+
"k2_prod_min_ms": 38.1653,
|
| 11 |
+
"kitchen_gemm_min_ms": 20.2363,
|
| 12 |
+
"n": 14336,
|
| 13 |
+
"p_full_min_ms": 72.0819,
|
| 14 |
+
"p_full_vs_kitchen_max_abs": 0.0,
|
| 15 |
+
"p_full_vs_kitchen_mismatches": 0,
|
| 16 |
+
"p_prod_min_ms": 64.0328,
|
| 17 |
+
"p_prod_vs_k2_prod": 0,
|
| 18 |
+
"pass": true,
|
| 19 |
+
"prod_finite": true,
|
| 20 |
+
"product_vs_eager_full_mismatches": 0,
|
| 21 |
+
"product_vs_eager_kitchen_mismatches": 0,
|
| 22 |
+
"rows": 20423,
|
| 23 |
+
"vs_k2_ms": -25.8675
|
| 24 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2s1.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: 1-stage k2 == kitchen / 3-stage k2.
|
| 3 |
+
|
| 4 |
+
Same 256x64 K=128 paired-N atom. ~29 KiB smem so 2 CTAs/SM
|
| 5 |
+
is legal if the register file allows. Occupancy is the launch
|
| 6 |
+
variable; 3-stage k2 is ~86 KiB and 1-way.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import json
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
|
| 16 |
+
import comfy_kitchen as ck
|
| 17 |
+
|
| 18 |
+
from swiglu_nvfp4 import (
|
| 19 |
+
fc1_paired_nvfp4_scaled_tma256k2,
|
| 20 |
+
fc1_paired_nvfp4_scaled_tma256k2s1,
|
| 21 |
+
fc1_paired_nvfp4_scaled_tma256k2s1_attrs,
|
| 22 |
+
load_extension,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
ROWS = 20423
|
| 27 |
+
K = 5376
|
| 28 |
+
N = 14336
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 32 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 36 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 37 |
+
y = ck.scaled_mm_nvfp4(
|
| 38 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 39 |
+
)
|
| 40 |
+
return y[:ROWS]
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 44 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def time_ms(fn) -> float:
|
| 48 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 49 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 50 |
+
fn()
|
| 51 |
+
torch.cuda.synchronize()
|
| 52 |
+
starter.record()
|
| 53 |
+
fn()
|
| 54 |
+
ender.record()
|
| 55 |
+
torch.cuda.synchronize()
|
| 56 |
+
return starter.elapsed_time(ender)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def main() -> int:
|
| 60 |
+
load_extension(verbose=False)
|
| 61 |
+
device = torch.device("cuda")
|
| 62 |
+
torch.manual_seed(26081271)
|
| 63 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 64 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 65 |
+
sx = nvfp4_scale(x)
|
| 66 |
+
sw = nvfp4_scale(weight)
|
| 67 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 68 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 69 |
+
qx_live = qx[:ROWS].contiguous()
|
| 70 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 71 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 72 |
+
k2 = fc1_paired_nvfp4_scaled_tma256k2(
|
| 73 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 74 |
+
)
|
| 75 |
+
s1 = fc1_paired_nvfp4_scaled_tma256k2s1(qx_live, qxs, qw, qws, alpha)
|
| 76 |
+
attrs = fc1_paired_nvfp4_scaled_tma256k2s1_attrs()
|
| 77 |
+
payload = {
|
| 78 |
+
"identity": (
|
| 79 |
+
"1-stage K=128 256x64 paired-N == kitchen == 3-stage k2. "
|
| 80 |
+
"Smem drop is the occupancy variable."
|
| 81 |
+
),
|
| 82 |
+
"rows": ROWS,
|
| 83 |
+
"k": K,
|
| 84 |
+
"n": 2 * N,
|
| 85 |
+
"device": torch.cuda.get_device_name(device),
|
| 86 |
+
"s1_vs_kitchen_mismatches": byte_diff(s1, kitchen),
|
| 87 |
+
"s1_vs_k2_mismatches": byte_diff(s1, k2),
|
| 88 |
+
"full_finite": bool(torch.isfinite(s1.float()).all().item()),
|
| 89 |
+
"s1_regs": int(attrs["s1_regs"]),
|
| 90 |
+
"s1_smem": int(attrs["s1_smem"]),
|
| 91 |
+
"s1_occupancy": int(attrs["s1_occupancy"]),
|
| 92 |
+
"k2_regs": int(attrs["k2_regs"]),
|
| 93 |
+
"k2_smem": int(attrs["k2_smem"]),
|
| 94 |
+
"k2_occupancy": int(attrs["k2_occupancy"]),
|
| 95 |
+
"kitchen_gemm_min_ms": round(
|
| 96 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 97 |
+
),
|
| 98 |
+
"s1_full_min_ms": round(
|
| 99 |
+
time_ms(
|
| 100 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2s1(
|
| 101 |
+
qx_live, qxs, qw, qws, alpha
|
| 102 |
+
)
|
| 103 |
+
),
|
| 104 |
+
4,
|
| 105 |
+
),
|
| 106 |
+
"k2_full_min_ms": round(
|
| 107 |
+
time_ms(
|
| 108 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2(
|
| 109 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 110 |
+
)
|
| 111 |
+
),
|
| 112 |
+
4,
|
| 113 |
+
),
|
| 114 |
+
}
|
| 115 |
+
payload["pass"] = (
|
| 116 |
+
payload["s1_vs_kitchen_mismatches"] == 0
|
| 117 |
+
and payload["s1_vs_k2_mismatches"] == 0
|
| 118 |
+
and payload["full_finite"]
|
| 119 |
+
)
|
| 120 |
+
if payload["pass"]:
|
| 121 |
+
payload["vs_kitchen_ms"] = round(
|
| 122 |
+
payload["kitchen_gemm_min_ms"] - payload["s1_full_min_ms"], 4
|
| 123 |
+
)
|
| 124 |
+
payload["vs_k2_ms"] = round(
|
| 125 |
+
payload["k2_full_min_ms"] - payload["s1_full_min_ms"], 4
|
| 126 |
+
)
|
| 127 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 128 |
+
Path(__file__).with_name(
|
| 129 |
+
"gate_fc1_nvfp4_scaled_tma256k2s1_20423.json"
|
| 130 |
+
).write_text(text + "\n")
|
| 131 |
+
print(text)
|
| 132 |
+
return 0 if payload["pass"] else 2
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
if __name__ == "__main__":
|
| 136 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2s1_20423.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"full_finite": true,
|
| 4 |
+
"identity": "1-stage K=128 256x64 paired-N == kitchen == 3-stage k2. Smem drop is the occupancy variable.",
|
| 5 |
+
"k": 5376,
|
| 6 |
+
"k2_full_min_ms": 40.4624,
|
| 7 |
+
"k2_occupancy": 1,
|
| 8 |
+
"k2_regs": 167,
|
| 9 |
+
"k2_smem": 86144,
|
| 10 |
+
"kitchen_gemm_min_ms": 20.0947,
|
| 11 |
+
"n": 28672,
|
| 12 |
+
"pass": true,
|
| 13 |
+
"rows": 20423,
|
| 14 |
+
"s1_full_min_ms": 47.8348,
|
| 15 |
+
"s1_occupancy": 1,
|
| 16 |
+
"s1_regs": 166,
|
| 17 |
+
"s1_smem": 28800,
|
| 18 |
+
"s1_vs_k2_mismatches": 0,
|
| 19 |
+
"s1_vs_kitchen_mismatches": 0,
|
| 20 |
+
"vs_k2_ms": -7.3724,
|
| 21 |
+
"vs_kitchen_ms": -27.7401
|
| 22 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2ws.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: warp-specialized k2 == unified k2.
|
| 3 |
+
|
| 4 |
+
Producer warp issues TMA; eight MMA warps keep the 256x64
|
| 5 |
+
m16n8k64 atom and 2x8x4 acc. Same smem as k2 (under 99 KiB).
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
import torch.nn.functional as F
|
| 15 |
+
|
| 16 |
+
import comfy_kitchen as ck
|
| 17 |
+
|
| 18 |
+
from swiglu_nvfp4 import (
|
| 19 |
+
bf16_nvfp4_dynamic,
|
| 20 |
+
fc1_paired_nvfp4_scaled_tma256k2,
|
| 21 |
+
fc1_paired_nvfp4_scaled_tma256k2ws,
|
| 22 |
+
load_extension,
|
| 23 |
+
swiglu_nvfp4_dynamic,
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
ROWS = 20423
|
| 28 |
+
K = 5376
|
| 29 |
+
N = 14336
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 33 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 37 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 38 |
+
y = ck.scaled_mm_nvfp4(
|
| 39 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 40 |
+
)
|
| 41 |
+
return y[:ROWS]
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def eager_act(raw: torch.Tensor) -> torch.Tensor:
|
| 45 |
+
gate, up = raw.chunk(2, dim=-1)
|
| 46 |
+
return F.silu(gate).mul_(up)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 50 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def max_abs_diff(left: torch.Tensor, right: torch.Tensor) -> float:
|
| 54 |
+
return float((left.float() - right.float()).abs().max().item())
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def time_ms(fn) -> float:
|
| 58 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 59 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 60 |
+
fn()
|
| 61 |
+
torch.cuda.synchronize()
|
| 62 |
+
starter.record()
|
| 63 |
+
fn()
|
| 64 |
+
ender.record()
|
| 65 |
+
torch.cuda.synchronize()
|
| 66 |
+
return starter.elapsed_time(ender)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def main() -> int:
|
| 70 |
+
load_extension(verbose=False)
|
| 71 |
+
device = torch.device("cuda")
|
| 72 |
+
torch.manual_seed(26081248)
|
| 73 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 74 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 75 |
+
sx = nvfp4_scale(x)
|
| 76 |
+
sw = nvfp4_scale(weight)
|
| 77 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 78 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 79 |
+
qx_live = qx[:ROWS].contiguous()
|
| 80 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 81 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 82 |
+
full = fc1_paired_nvfp4_scaled_tma256k2ws(
|
| 83 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 84 |
+
)
|
| 85 |
+
prod = fc1_paired_nvfp4_scaled_tma256k2ws(
|
| 86 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 87 |
+
)
|
| 88 |
+
k2 = fc1_paired_nvfp4_scaled_tma256k2(
|
| 89 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 90 |
+
)
|
| 91 |
+
eager_kit = eager_act(kitchen.contiguous())
|
| 92 |
+
eager_full = eager_act(full)
|
| 93 |
+
two_q, two_s, two_g = swiglu_nvfp4_dynamic(kitchen.contiguous())
|
| 94 |
+
prod_q, prod_s, prod_g = bf16_nvfp4_dynamic(prod)
|
| 95 |
+
payload = {
|
| 96 |
+
"identity": (
|
| 97 |
+
"Warp-specialized k2 == unified k2: producer warp issues "
|
| 98 |
+
"TMA; eight MMA warps keep the 256x64 m16n8k64 atom and "
|
| 99 |
+
"2x8x4 acc. Full/empty mbarriers, no mainloop syncthreads. "
|
| 100 |
+
"Smem matches k2 (under 99 KiB)."
|
| 101 |
+
),
|
| 102 |
+
"rows": ROWS,
|
| 103 |
+
"k": K,
|
| 104 |
+
"n": N,
|
| 105 |
+
"device": torch.cuda.get_device_name(device),
|
| 106 |
+
"ws_full_vs_kitchen_mismatches": byte_diff(full, kitchen),
|
| 107 |
+
"ws_full_vs_kitchen_max_abs": round(max_abs_diff(full, kitchen), 6),
|
| 108 |
+
"ws_prod_vs_k2_prod": byte_diff(prod, k2),
|
| 109 |
+
"product_vs_eager_full_mismatches": byte_diff(prod, eager_full),
|
| 110 |
+
"product_vs_eager_kitchen_mismatches": byte_diff(prod, eager_kit),
|
| 111 |
+
"from_product_vs_kitchen_q": byte_diff(prod_q, two_q),
|
| 112 |
+
"from_product_vs_kitchen_s": byte_diff(prod_s, two_s),
|
| 113 |
+
"from_product_vs_kitchen_scale_exact": bool(torch.equal(prod_g, two_g)),
|
| 114 |
+
"full_finite": bool(torch.isfinite(full.float()).all().item()),
|
| 115 |
+
"prod_finite": bool(torch.isfinite(prod.float()).all().item()),
|
| 116 |
+
"kitchen_gemm_min_ms": round(
|
| 117 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 118 |
+
),
|
| 119 |
+
"ws_full_min_ms": round(
|
| 120 |
+
time_ms(
|
| 121 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2ws(
|
| 122 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 123 |
+
)
|
| 124 |
+
),
|
| 125 |
+
4,
|
| 126 |
+
),
|
| 127 |
+
"ws_prod_min_ms": round(
|
| 128 |
+
time_ms(
|
| 129 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2ws(
|
| 130 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 131 |
+
)
|
| 132 |
+
),
|
| 133 |
+
4,
|
| 134 |
+
),
|
| 135 |
+
"k2_prod_min_ms": round(
|
| 136 |
+
time_ms(
|
| 137 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2(
|
| 138 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 139 |
+
)
|
| 140 |
+
),
|
| 141 |
+
4,
|
| 142 |
+
),
|
| 143 |
+
"from_product_min_ms": round(
|
| 144 |
+
time_ms(lambda: bf16_nvfp4_dynamic(prod)), 4
|
| 145 |
+
),
|
| 146 |
+
}
|
| 147 |
+
payload["pass"] = (
|
| 148 |
+
payload["ws_full_vs_kitchen_mismatches"] == 0
|
| 149 |
+
and payload["ws_prod_vs_k2_prod"] == 0
|
| 150 |
+
and payload["product_vs_eager_full_mismatches"] == 0
|
| 151 |
+
and payload["product_vs_eager_kitchen_mismatches"] == 0
|
| 152 |
+
and payload["from_product_vs_kitchen_q"] == 0
|
| 153 |
+
and payload["from_product_vs_kitchen_s"] == 0
|
| 154 |
+
and payload["from_product_vs_kitchen_scale_exact"]
|
| 155 |
+
and payload["full_finite"]
|
| 156 |
+
and payload["prod_finite"]
|
| 157 |
+
)
|
| 158 |
+
if payload["pass"]:
|
| 159 |
+
payload["vs_k2_ms"] = round(
|
| 160 |
+
payload["k2_prod_min_ms"] - payload["ws_prod_min_ms"], 4
|
| 161 |
+
)
|
| 162 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 163 |
+
Path(__file__).with_name(
|
| 164 |
+
"gate_fc1_nvfp4_scaled_tma256k2ws_20423.json"
|
| 165 |
+
).write_text(text + "\n")
|
| 166 |
+
print(text)
|
| 167 |
+
return 0 if payload["pass"] else 2
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
if __name__ == "__main__":
|
| 171 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2ws4.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: 12-warp 4-producer k2 == kitchen == k2.
|
| 3 |
+
|
| 4 |
+
Kitchen launch structure (4 TMA warps + 8 MMA) on the best lab
|
| 5 |
+
tile (256x64 K=128 paired-N). 9-warp k2ws serializes every TMA
|
| 6 |
+
on one lane; this issues A / Bg / Bu / scales in parallel.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import json
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
|
| 16 |
+
import comfy_kitchen as ck
|
| 17 |
+
|
| 18 |
+
from swiglu_nvfp4 import (
|
| 19 |
+
fc1_paired_nvfp4_scaled_tma256k2,
|
| 20 |
+
fc1_paired_nvfp4_scaled_tma256k2ws4,
|
| 21 |
+
fc1_paired_nvfp4_scaled_tma256k2ws4_attrs,
|
| 22 |
+
load_extension,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
ROWS = 20423
|
| 27 |
+
K = 5376
|
| 28 |
+
N = 14336
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 32 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 36 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 37 |
+
y = ck.scaled_mm_nvfp4(
|
| 38 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 39 |
+
)
|
| 40 |
+
return y[:ROWS]
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 44 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def max_abs_diff(left: torch.Tensor, right: torch.Tensor) -> float:
|
| 48 |
+
return float((left.float() - right.float()).abs().max().item())
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def time_ms(fn) -> float:
|
| 52 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 53 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 54 |
+
fn()
|
| 55 |
+
torch.cuda.synchronize()
|
| 56 |
+
starter.record()
|
| 57 |
+
fn()
|
| 58 |
+
ender.record()
|
| 59 |
+
torch.cuda.synchronize()
|
| 60 |
+
return starter.elapsed_time(ender)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def main() -> int:
|
| 64 |
+
load_extension(verbose=False)
|
| 65 |
+
device = torch.device("cuda")
|
| 66 |
+
torch.manual_seed(26081274)
|
| 67 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 68 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 69 |
+
sx = nvfp4_scale(x)
|
| 70 |
+
sw = nvfp4_scale(weight)
|
| 71 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 72 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 73 |
+
qx_live = qx[:ROWS].contiguous()
|
| 74 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 75 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 76 |
+
k2 = fc1_paired_nvfp4_scaled_tma256k2(
|
| 77 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 78 |
+
)
|
| 79 |
+
ws4 = fc1_paired_nvfp4_scaled_tma256k2ws4(qx_live, qxs, qw, qws, alpha)
|
| 80 |
+
attrs = fc1_paired_nvfp4_scaled_tma256k2ws4_attrs()
|
| 81 |
+
payload = {
|
| 82 |
+
"identity": (
|
| 83 |
+
"12-warp 4-producer + 8-MMA on k2 256x64 K=128 == kitchen "
|
| 84 |
+
"== unified k2. Kitchen launch split: A / Bg / Bu / scales "
|
| 85 |
+
"issue in parallel. Same atom and smem as k2."
|
| 86 |
+
),
|
| 87 |
+
"rows": ROWS,
|
| 88 |
+
"k": K,
|
| 89 |
+
"n": 2 * N,
|
| 90 |
+
"device": torch.cuda.get_device_name(device),
|
| 91 |
+
"ws4_vs_kitchen_mismatches": byte_diff(ws4, kitchen),
|
| 92 |
+
"ws4_vs_kitchen_max_abs": round(max_abs_diff(ws4, kitchen), 6),
|
| 93 |
+
"ws4_vs_k2_mismatches": byte_diff(ws4, k2),
|
| 94 |
+
"full_finite": bool(torch.isfinite(ws4.float()).all().item()),
|
| 95 |
+
"ws4_regs": int(attrs["regs"]),
|
| 96 |
+
"ws4_smem": int(attrs["smem"]),
|
| 97 |
+
"ws4_occupancy": int(attrs["occupancy"]),
|
| 98 |
+
"ws4_threads": int(attrs["threads"]),
|
| 99 |
+
"ws4_warps": int(attrs["warps"]),
|
| 100 |
+
"ws4_prod_warps": int(attrs["prod_warps"]),
|
| 101 |
+
"kitchen_gemm_min_ms": round(
|
| 102 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 103 |
+
),
|
| 104 |
+
"ws4_full_min_ms": round(
|
| 105 |
+
time_ms(
|
| 106 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2ws4(
|
| 107 |
+
qx_live, qxs, qw, qws, alpha
|
| 108 |
+
)
|
| 109 |
+
),
|
| 110 |
+
4,
|
| 111 |
+
),
|
| 112 |
+
"k2_full_min_ms": round(
|
| 113 |
+
time_ms(
|
| 114 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2(
|
| 115 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 116 |
+
)
|
| 117 |
+
),
|
| 118 |
+
4,
|
| 119 |
+
),
|
| 120 |
+
}
|
| 121 |
+
payload["pass"] = (
|
| 122 |
+
payload["ws4_vs_kitchen_mismatches"] == 0
|
| 123 |
+
and payload["ws4_vs_k2_mismatches"] == 0
|
| 124 |
+
and payload["full_finite"]
|
| 125 |
+
)
|
| 126 |
+
if payload["pass"]:
|
| 127 |
+
payload["vs_kitchen_ms"] = round(
|
| 128 |
+
payload["kitchen_gemm_min_ms"] - payload["ws4_full_min_ms"], 4
|
| 129 |
+
)
|
| 130 |
+
payload["vs_k2_ms"] = round(
|
| 131 |
+
payload["k2_full_min_ms"] - payload["ws4_full_min_ms"], 4
|
| 132 |
+
)
|
| 133 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 134 |
+
Path(__file__).with_name(
|
| 135 |
+
"gate_fc1_nvfp4_scaled_tma256k2ws4_20423.json"
|
| 136 |
+
).write_text(text + "\n")
|
| 137 |
+
print(text)
|
| 138 |
+
return 0 if payload["pass"] else 2
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
if __name__ == "__main__":
|
| 142 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2ws4_20423.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"full_finite": true,
|
| 4 |
+
"identity": "12-warp 4-producer + 8-MMA on k2 256x64 K=128 == kitchen == unified k2. Kitchen launch split: A / Bg / Bu / scales issue in parallel. Same atom and smem as k2.",
|
| 5 |
+
"k": 5376,
|
| 6 |
+
"k2_full_min_ms": 39.2012,
|
| 7 |
+
"kitchen_gemm_min_ms": 20.1714,
|
| 8 |
+
"n": 28672,
|
| 9 |
+
"pass": true,
|
| 10 |
+
"rows": 20423,
|
| 11 |
+
"vs_k2_ms": -1.5219,
|
| 12 |
+
"vs_kitchen_ms": -20.5517,
|
| 13 |
+
"ws4_full_min_ms": 40.7231,
|
| 14 |
+
"ws4_occupancy": 1,
|
| 15 |
+
"ws4_prod_warps": 4,
|
| 16 |
+
"ws4_regs": 168,
|
| 17 |
+
"ws4_smem": 86144,
|
| 18 |
+
"ws4_threads": 384,
|
| 19 |
+
"ws4_vs_k2_mismatches": 0,
|
| 20 |
+
"ws4_vs_kitchen_max_abs": 0.0,
|
| 21 |
+
"ws4_vs_kitchen_mismatches": 0,
|
| 22 |
+
"ws4_warps": 12
|
| 23 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k2ws_20423.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"from_product_min_ms": 5.7406,
|
| 4 |
+
"from_product_vs_kitchen_q": 0,
|
| 5 |
+
"from_product_vs_kitchen_s": 0,
|
| 6 |
+
"from_product_vs_kitchen_scale_exact": true,
|
| 7 |
+
"full_finite": true,
|
| 8 |
+
"identity": "Warp-specialized k2 == unified k2: producer warp issues TMA; eight MMA warps keep the 256x64 m16n8k64 atom and 2x8x4 acc. Full/empty mbarriers, no mainloop syncthreads. Smem matches k2 (under 99 KiB).",
|
| 9 |
+
"k": 5376,
|
| 10 |
+
"k2_prod_min_ms": 35.79,
|
| 11 |
+
"kitchen_gemm_min_ms": 20.1856,
|
| 12 |
+
"n": 14336,
|
| 13 |
+
"pass": true,
|
| 14 |
+
"prod_finite": true,
|
| 15 |
+
"product_vs_eager_full_mismatches": 0,
|
| 16 |
+
"product_vs_eager_kitchen_mismatches": 0,
|
| 17 |
+
"rows": 20423,
|
| 18 |
+
"vs_k2_ms": -1.5863,
|
| 19 |
+
"ws_full_min_ms": 48.2706,
|
| 20 |
+
"ws_full_vs_kitchen_max_abs": 0.0,
|
| 21 |
+
"ws_full_vs_kitchen_mismatches": 0,
|
| 22 |
+
"ws_prod_min_ms": 37.3763,
|
| 23 |
+
"ws_prod_vs_k2_prod": 0
|
| 24 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k4.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: K=256 TMA box on the 256x64 tile.
|
| 3 |
+
|
| 4 |
+
One TMA of 128 packed bytes feeds four m16n8k64 atoms. Keeps the
|
| 5 |
+
256x64 k2 register budget (2x8x4). 1-stage: 2-stage at this box
|
| 6 |
+
is 112 KiB and exceeds the 99 KiB static smem cap.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import json
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
import torch.nn.functional as F
|
| 16 |
+
|
| 17 |
+
import comfy_kitchen as ck
|
| 18 |
+
|
| 19 |
+
from swiglu_nvfp4 import (
|
| 20 |
+
bf16_nvfp4_dynamic,
|
| 21 |
+
fc1_paired_nvfp4_scaled_tma256k2,
|
| 22 |
+
fc1_paired_nvfp4_scaled_tma256k4,
|
| 23 |
+
load_extension,
|
| 24 |
+
swiglu_nvfp4_dynamic,
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
ROWS = 20423
|
| 29 |
+
K = 5376
|
| 30 |
+
N = 14336
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 34 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 38 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 39 |
+
y = ck.scaled_mm_nvfp4(
|
| 40 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 41 |
+
)
|
| 42 |
+
return y[:ROWS]
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def eager_act(raw: torch.Tensor) -> torch.Tensor:
|
| 46 |
+
gate, up = raw.chunk(2, dim=-1)
|
| 47 |
+
return F.silu(gate).mul_(up)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 51 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def max_abs_diff(left: torch.Tensor, right: torch.Tensor) -> float:
|
| 55 |
+
return float((left.float() - right.float()).abs().max().item())
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def time_ms(fn) -> float:
|
| 59 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 60 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 61 |
+
fn()
|
| 62 |
+
torch.cuda.synchronize()
|
| 63 |
+
starter.record()
|
| 64 |
+
fn()
|
| 65 |
+
ender.record()
|
| 66 |
+
torch.cuda.synchronize()
|
| 67 |
+
return starter.elapsed_time(ender)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def main() -> int:
|
| 71 |
+
load_extension(verbose=False)
|
| 72 |
+
device = torch.device("cuda")
|
| 73 |
+
torch.manual_seed(26081246)
|
| 74 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 75 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 76 |
+
sx = nvfp4_scale(x)
|
| 77 |
+
sw = nvfp4_scale(weight)
|
| 78 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 79 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 80 |
+
qx_live = qx[:ROWS].contiguous()
|
| 81 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 82 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 83 |
+
full = fc1_paired_nvfp4_scaled_tma256k4(
|
| 84 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 85 |
+
)
|
| 86 |
+
prod = fc1_paired_nvfp4_scaled_tma256k4(
|
| 87 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 88 |
+
)
|
| 89 |
+
k2 = fc1_paired_nvfp4_scaled_tma256k2(
|
| 90 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 91 |
+
)
|
| 92 |
+
eager_kit = eager_act(kitchen.contiguous())
|
| 93 |
+
eager_full = eager_act(full)
|
| 94 |
+
two_q, two_s, two_g = swiglu_nvfp4_dynamic(kitchen.contiguous())
|
| 95 |
+
prod_q, prod_s, prod_g = bf16_nvfp4_dynamic(prod)
|
| 96 |
+
payload = {
|
| 97 |
+
"identity": (
|
| 98 |
+
"K=256 TMA box == four sequential K=64 MMA steps on the "
|
| 99 |
+
"256x64 tile: one 128-byte packed row feeds four m16n8k64 "
|
| 100 |
+
"atoms; four 128x4 scale slabs cover the 16 K-scale columns. "
|
| 101 |
+
"Same 2x8x4 register acc as k2. 1-stage: 2-stage box "
|
| 102 |
+
"exceeds the 99 KiB static smem cap."
|
| 103 |
+
),
|
| 104 |
+
"rows": ROWS,
|
| 105 |
+
"k": K,
|
| 106 |
+
"n": N,
|
| 107 |
+
"device": torch.cuda.get_device_name(device),
|
| 108 |
+
"k4_full_vs_kitchen_mismatches": byte_diff(full, kitchen),
|
| 109 |
+
"k4_full_vs_kitchen_max_abs": round(max_abs_diff(full, kitchen), 6),
|
| 110 |
+
"k4_prod_vs_k2_prod": byte_diff(prod, k2),
|
| 111 |
+
"product_vs_eager_full_mismatches": byte_diff(prod, eager_full),
|
| 112 |
+
"product_vs_eager_kitchen_mismatches": byte_diff(prod, eager_kit),
|
| 113 |
+
"from_product_vs_kitchen_q": byte_diff(prod_q, two_q),
|
| 114 |
+
"from_product_vs_kitchen_s": byte_diff(prod_s, two_s),
|
| 115 |
+
"from_product_vs_kitchen_scale_exact": bool(torch.equal(prod_g, two_g)),
|
| 116 |
+
"full_finite": bool(torch.isfinite(full.float()).all().item()),
|
| 117 |
+
"prod_finite": bool(torch.isfinite(prod.float()).all().item()),
|
| 118 |
+
"kitchen_gemm_min_ms": round(
|
| 119 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 120 |
+
),
|
| 121 |
+
"k4_full_min_ms": round(
|
| 122 |
+
time_ms(
|
| 123 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k4(
|
| 124 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 125 |
+
)
|
| 126 |
+
),
|
| 127 |
+
4,
|
| 128 |
+
),
|
| 129 |
+
"k4_prod_min_ms": round(
|
| 130 |
+
time_ms(
|
| 131 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k4(
|
| 132 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 133 |
+
)
|
| 134 |
+
),
|
| 135 |
+
4,
|
| 136 |
+
),
|
| 137 |
+
"k2_prod_min_ms": round(
|
| 138 |
+
time_ms(
|
| 139 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2(
|
| 140 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 141 |
+
)
|
| 142 |
+
),
|
| 143 |
+
4,
|
| 144 |
+
),
|
| 145 |
+
"from_product_min_ms": round(
|
| 146 |
+
time_ms(lambda: bf16_nvfp4_dynamic(prod)), 4
|
| 147 |
+
),
|
| 148 |
+
}
|
| 149 |
+
payload["pass"] = (
|
| 150 |
+
payload["k4_full_vs_kitchen_mismatches"] == 0
|
| 151 |
+
and payload["k4_prod_vs_k2_prod"] == 0
|
| 152 |
+
and payload["product_vs_eager_full_mismatches"] == 0
|
| 153 |
+
and payload["product_vs_eager_kitchen_mismatches"] == 0
|
| 154 |
+
and payload["from_product_vs_kitchen_q"] == 0
|
| 155 |
+
and payload["from_product_vs_kitchen_s"] == 0
|
| 156 |
+
and payload["from_product_vs_kitchen_scale_exact"]
|
| 157 |
+
and payload["full_finite"]
|
| 158 |
+
and payload["prod_finite"]
|
| 159 |
+
)
|
| 160 |
+
if payload["pass"]:
|
| 161 |
+
payload["vs_k2_ms"] = round(
|
| 162 |
+
payload["k2_prod_min_ms"] - payload["k4_prod_min_ms"], 4
|
| 163 |
+
)
|
| 164 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 165 |
+
Path(__file__).with_name(
|
| 166 |
+
"gate_fc1_nvfp4_scaled_tma256k4_20423.json"
|
| 167 |
+
).write_text(text + "\n")
|
| 168 |
+
print(text)
|
| 169 |
+
return 0 if payload["pass"] else 2
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
if __name__ == "__main__":
|
| 173 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k4_20423.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"from_product_min_ms": 5.7432,
|
| 4 |
+
"from_product_vs_kitchen_q": 0,
|
| 5 |
+
"from_product_vs_kitchen_s": 0,
|
| 6 |
+
"from_product_vs_kitchen_scale_exact": true,
|
| 7 |
+
"full_finite": true,
|
| 8 |
+
"identity": "K=256 TMA box == four sequential K=64 MMA steps on the 256x64 tile: one 128-byte packed row feeds four m16n8k64 atoms; four 128x4 scale slabs cover the 16 K-scale columns. Same 2x8x4 register acc as k2. 1-stage: 2-stage box exceeds the 99 KiB static smem cap.",
|
| 9 |
+
"k": 5376,
|
| 10 |
+
"k2_prod_min_ms": 36.4231,
|
| 11 |
+
"k4_full_min_ms": 71.0255,
|
| 12 |
+
"k4_full_vs_kitchen_max_abs": 0.0,
|
| 13 |
+
"k4_full_vs_kitchen_mismatches": 0,
|
| 14 |
+
"k4_prod_min_ms": 65.1102,
|
| 15 |
+
"k4_prod_vs_k2_prod": 0,
|
| 16 |
+
"kitchen_gemm_min_ms": 19.9771,
|
| 17 |
+
"n": 14336,
|
| 18 |
+
"pass": true,
|
| 19 |
+
"prod_finite": true,
|
| 20 |
+
"product_vs_eager_full_mismatches": 0,
|
| 21 |
+
"product_vs_eager_kitchen_mismatches": 0,
|
| 22 |
+
"rows": 20423,
|
| 23 |
+
"vs_k2_ms": -28.6871
|
| 24 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k4n1.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: K=256 2-stage on 256x64 single-N == kitchen / k2.
|
| 3 |
+
|
| 4 |
+
Paired 256x64 K=256 2-stage is 112 KiB (over the 99 KiB cap).
|
| 5 |
+
Dropping the second B operand (kitchen single-N) is 94 KiB and
|
| 6 |
+
gives a legal pipeline on the k2 M tile + kitchen K box.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import json
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
|
| 16 |
+
import comfy_kitchen as ck
|
| 17 |
+
|
| 18 |
+
from swiglu_nvfp4 import (
|
| 19 |
+
fc1_nvfp4_scaled_tma256k4n1,
|
| 20 |
+
fc1_paired_nvfp4_scaled_tma256k2,
|
| 21 |
+
load_extension,
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
ROWS = 20423
|
| 26 |
+
K = 5376
|
| 27 |
+
N = 14336
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 31 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 35 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 36 |
+
y = ck.scaled_mm_nvfp4(
|
| 37 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 38 |
+
)
|
| 39 |
+
return y[:ROWS]
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 43 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def time_ms(fn) -> float:
|
| 47 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 48 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 49 |
+
fn()
|
| 50 |
+
torch.cuda.synchronize()
|
| 51 |
+
starter.record()
|
| 52 |
+
fn()
|
| 53 |
+
ender.record()
|
| 54 |
+
torch.cuda.synchronize()
|
| 55 |
+
return starter.elapsed_time(ender)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def main() -> int:
|
| 59 |
+
load_extension(verbose=False)
|
| 60 |
+
device = torch.device("cuda")
|
| 61 |
+
torch.manual_seed(26081265)
|
| 62 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 63 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 64 |
+
sx = nvfp4_scale(x)
|
| 65 |
+
sw = nvfp4_scale(weight)
|
| 66 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 67 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 68 |
+
qx_live = qx[:ROWS].contiguous()
|
| 69 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 70 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 71 |
+
k2 = fc1_paired_nvfp4_scaled_tma256k2(
|
| 72 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 73 |
+
)
|
| 74 |
+
n1 = fc1_nvfp4_scaled_tma256k4n1(qx_live, qxs, qw, qws, alpha)
|
| 75 |
+
payload = {
|
| 76 |
+
"identity": (
|
| 77 |
+
"K=256 2-stage TMA on 256x64 single-N == four K=64 MMA "
|
| 78 |
+
"== kitchen == paired k2. Paired 2-stage is 112 KiB; "
|
| 79 |
+
"single-N is 94 KiB under the 99 KiB cap."
|
| 80 |
+
),
|
| 81 |
+
"rows": ROWS,
|
| 82 |
+
"k": K,
|
| 83 |
+
"n": 2 * N,
|
| 84 |
+
"smem_bytes": 94336,
|
| 85 |
+
"device": torch.cuda.get_device_name(device),
|
| 86 |
+
"n1_vs_kitchen_mismatches": byte_diff(n1, kitchen),
|
| 87 |
+
"n1_vs_k2_mismatches": byte_diff(n1, k2),
|
| 88 |
+
"full_finite": bool(torch.isfinite(n1.float()).all().item()),
|
| 89 |
+
"kitchen_gemm_min_ms": round(
|
| 90 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 91 |
+
),
|
| 92 |
+
"n1_full_min_ms": round(
|
| 93 |
+
time_ms(
|
| 94 |
+
lambda: fc1_nvfp4_scaled_tma256k4n1(
|
| 95 |
+
qx_live, qxs, qw, qws, alpha
|
| 96 |
+
)
|
| 97 |
+
),
|
| 98 |
+
4,
|
| 99 |
+
),
|
| 100 |
+
"k2_full_min_ms": round(
|
| 101 |
+
time_ms(
|
| 102 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2(
|
| 103 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 104 |
+
)
|
| 105 |
+
),
|
| 106 |
+
4,
|
| 107 |
+
),
|
| 108 |
+
}
|
| 109 |
+
payload["pass"] = (
|
| 110 |
+
payload["n1_vs_kitchen_mismatches"] == 0
|
| 111 |
+
and payload["n1_vs_k2_mismatches"] == 0
|
| 112 |
+
and payload["full_finite"]
|
| 113 |
+
)
|
| 114 |
+
if payload["pass"]:
|
| 115 |
+
payload["vs_kitchen_ms"] = round(
|
| 116 |
+
payload["kitchen_gemm_min_ms"] - payload["n1_full_min_ms"], 4
|
| 117 |
+
)
|
| 118 |
+
payload["vs_k2_ms"] = round(
|
| 119 |
+
payload["k2_full_min_ms"] - payload["n1_full_min_ms"], 4
|
| 120 |
+
)
|
| 121 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 122 |
+
Path(__file__).with_name(
|
| 123 |
+
"gate_fc1_nvfp4_scaled_tma256k4n1_20423.json"
|
| 124 |
+
).write_text(text + "\n")
|
| 125 |
+
print(text)
|
| 126 |
+
return 0 if payload["pass"] else 2
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
if __name__ == "__main__":
|
| 130 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256k4n1_20423.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"full_finite": true,
|
| 4 |
+
"identity": "K=256 2-stage TMA on 256x64 single-N == four K=64 MMA == kitchen == paired k2. Paired 2-stage is 112 KiB; single-N is 94 KiB under the 99 KiB cap.",
|
| 5 |
+
"k": 5376,
|
| 6 |
+
"k2_full_min_ms": 40.1932,
|
| 7 |
+
"kitchen_gemm_min_ms": 20.1874,
|
| 8 |
+
"n": 28672,
|
| 9 |
+
"n1_full_min_ms": 48.913,
|
| 10 |
+
"n1_vs_k2_mismatches": 0,
|
| 11 |
+
"n1_vs_kitchen_mismatches": 0,
|
| 12 |
+
"pass": true,
|
| 13 |
+
"rows": 20423,
|
| 14 |
+
"smem_bytes": 94336,
|
| 15 |
+
"vs_k2_ms": -8.7198,
|
| 16 |
+
"vs_kitchen_ms": -28.7256
|
| 17 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256n32.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: 256x32 K=128 1-stage == kitchen / k2.
|
| 3 |
+
|
| 4 |
+
Half-N of k2: 4 n-subtiles, 64-float acc. TMA B box is 64
|
| 5 |
+
(N=32 tile faults). 1-stage so two CTAs/SM can fit.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
|
| 15 |
+
import comfy_kitchen as ck
|
| 16 |
+
|
| 17 |
+
from swiglu_nvfp4 import (
|
| 18 |
+
fc1_paired_nvfp4_scaled_tma256k2,
|
| 19 |
+
fc1_paired_nvfp4_scaled_tma256n32,
|
| 20 |
+
fc1_paired_nvfp4_scaled_tma256n32_attrs,
|
| 21 |
+
load_extension,
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
ROWS = 20423
|
| 26 |
+
K = 5376
|
| 27 |
+
N = 14336
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 31 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 35 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 36 |
+
y = ck.scaled_mm_nvfp4(
|
| 37 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 38 |
+
)
|
| 39 |
+
return y[:ROWS]
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 43 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def time_ms(fn) -> float:
|
| 47 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 48 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 49 |
+
fn()
|
| 50 |
+
torch.cuda.synchronize()
|
| 51 |
+
starter.record()
|
| 52 |
+
fn()
|
| 53 |
+
ender.record()
|
| 54 |
+
torch.cuda.synchronize()
|
| 55 |
+
return starter.elapsed_time(ender)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def main() -> int:
|
| 59 |
+
load_extension(verbose=False)
|
| 60 |
+
device = torch.device("cuda")
|
| 61 |
+
torch.manual_seed(26081272)
|
| 62 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 63 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 64 |
+
sx = nvfp4_scale(x)
|
| 65 |
+
sw = nvfp4_scale(weight)
|
| 66 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 67 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 68 |
+
qx_live = qx[:ROWS].contiguous()
|
| 69 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 70 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 71 |
+
k2 = fc1_paired_nvfp4_scaled_tma256k2(
|
| 72 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 73 |
+
)
|
| 74 |
+
n32 = fc1_paired_nvfp4_scaled_tma256n32(qx_live, qxs, qw, qws, alpha)
|
| 75 |
+
attrs = fc1_paired_nvfp4_scaled_tma256n32_attrs()
|
| 76 |
+
payload = {
|
| 77 |
+
"identity": (
|
| 78 |
+
"256x32 K=128 1-stage paired-N == kitchen == k2. "
|
| 79 |
+
"TMA B is 64; compute is 32. 64-float acc is the occupancy cut."
|
| 80 |
+
),
|
| 81 |
+
"rows": ROWS,
|
| 82 |
+
"k": K,
|
| 83 |
+
"n": 2 * N,
|
| 84 |
+
"device": torch.cuda.get_device_name(device),
|
| 85 |
+
"n32_vs_kitchen_mismatches": byte_diff(n32, kitchen),
|
| 86 |
+
"n32_vs_k2_mismatches": byte_diff(n32, k2),
|
| 87 |
+
"full_finite": bool(torch.isfinite(n32.float()).all().item()),
|
| 88 |
+
"n32_regs": int(attrs["regs"]),
|
| 89 |
+
"n32_smem": int(attrs["smem"]),
|
| 90 |
+
"n32_occupancy": int(attrs["occupancy"]),
|
| 91 |
+
"n32_smem_struct": int(attrs["smem_struct"]),
|
| 92 |
+
"kitchen_gemm_min_ms": round(
|
| 93 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 94 |
+
),
|
| 95 |
+
"n32_full_min_ms": round(
|
| 96 |
+
time_ms(
|
| 97 |
+
lambda: fc1_paired_nvfp4_scaled_tma256n32(
|
| 98 |
+
qx_live, qxs, qw, qws, alpha
|
| 99 |
+
)
|
| 100 |
+
),
|
| 101 |
+
4,
|
| 102 |
+
),
|
| 103 |
+
"k2_full_min_ms": round(
|
| 104 |
+
time_ms(
|
| 105 |
+
lambda: fc1_paired_nvfp4_scaled_tma256k2(
|
| 106 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 107 |
+
)
|
| 108 |
+
),
|
| 109 |
+
4,
|
| 110 |
+
),
|
| 111 |
+
}
|
| 112 |
+
payload["pass"] = (
|
| 113 |
+
payload["n32_vs_kitchen_mismatches"] == 0
|
| 114 |
+
and payload["n32_vs_k2_mismatches"] == 0
|
| 115 |
+
and payload["full_finite"]
|
| 116 |
+
)
|
| 117 |
+
if payload["pass"]:
|
| 118 |
+
payload["vs_kitchen_ms"] = round(
|
| 119 |
+
payload["kitchen_gemm_min_ms"] - payload["n32_full_min_ms"], 4
|
| 120 |
+
)
|
| 121 |
+
payload["vs_k2_ms"] = round(
|
| 122 |
+
payload["k2_full_min_ms"] - payload["n32_full_min_ms"], 4
|
| 123 |
+
)
|
| 124 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 125 |
+
Path(__file__).with_name(
|
| 126 |
+
"gate_fc1_nvfp4_scaled_tma256n32_20423.json"
|
| 127 |
+
).write_text(text + "\n")
|
| 128 |
+
print(text)
|
| 129 |
+
return 0 if payload["pass"] else 2
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
if __name__ == "__main__":
|
| 133 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma256n32_20423.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"full_finite": true,
|
| 4 |
+
"identity": "256x32 K=128 1-stage paired-N == kitchen == k2. TMA B is 64; compute is 32. 64-float acc is the occupancy cut.",
|
| 5 |
+
"k": 5376,
|
| 6 |
+
"k2_full_min_ms": 39.0139,
|
| 7 |
+
"kitchen_gemm_min_ms": 19.3708,
|
| 8 |
+
"n": 28672,
|
| 9 |
+
"n32_full_min_ms": 44.013,
|
| 10 |
+
"n32_occupancy": 2,
|
| 11 |
+
"n32_regs": 125,
|
| 12 |
+
"n32_smem": 28800,
|
| 13 |
+
"n32_smem_struct": 28800,
|
| 14 |
+
"n32_vs_k2_mismatches": 2362273,
|
| 15 |
+
"n32_vs_kitchen_mismatches": 2362273,
|
| 16 |
+
"pass": false,
|
| 17 |
+
"rows": 20423
|
| 18 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma_20423.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"from_product_min_ms": 5.7439,
|
| 4 |
+
"from_product_vs_kitchen_q": 0,
|
| 5 |
+
"from_product_vs_kitchen_s": 0,
|
| 6 |
+
"from_product_vs_kitchen_scale_exact": true,
|
| 7 |
+
"full_finite": true,
|
| 8 |
+
"identity": "TMA 2-stage K pipeline on the kitchen-legal m16n8k64 atom: 128x128 2D tile, tensor-map + mbarrier, A reused across 16 n-subtiles and both arms; same PTX fragment, UE4M3 map, and eager product",
|
| 9 |
+
"k": 5376,
|
| 10 |
+
"kitchen_gemm_min_ms": 20.1758,
|
| 11 |
+
"n": 14336,
|
| 12 |
+
"pass": true,
|
| 13 |
+
"piped_prod_min_ms": 218.6166,
|
| 14 |
+
"prod_finite": true,
|
| 15 |
+
"product_vs_eager_full_mismatches": 0,
|
| 16 |
+
"product_vs_eager_kitchen_mismatches": 0,
|
| 17 |
+
"rows": 20423,
|
| 18 |
+
"tma_full_min_ms": 115.2573,
|
| 19 |
+
"tma_full_vs_kitchen_max_abs": 0.0,
|
| 20 |
+
"tma_full_vs_kitchen_mismatches": 0,
|
| 21 |
+
"tma_prod_min_ms": 104.7121,
|
| 22 |
+
"tma_prod_vs_piped_prod": 0,
|
| 23 |
+
"vs_piped_ms": 113.9045
|
| 24 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma_sf.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: TMA scale-slab NVFP4 paired-N vs kitchen.
|
| 3 |
+
|
| 4 |
+
Same PTX fragment + 128x128 TMA data path. Scales come from one
|
| 5 |
+
cuBLAS 128x4 / 512-byte slab per K-tile, remapped to pack_four_scales.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
import torch.nn.functional as F
|
| 15 |
+
|
| 16 |
+
import comfy_kitchen as ck
|
| 17 |
+
|
| 18 |
+
from swiglu_nvfp4 import (
|
| 19 |
+
bf16_nvfp4_dynamic,
|
| 20 |
+
fc1_paired_nvfp4_scaled_tma,
|
| 21 |
+
fc1_paired_nvfp4_scaled_tma_sf,
|
| 22 |
+
load_extension,
|
| 23 |
+
swiglu_nvfp4_dynamic,
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
ROWS = 20423
|
| 28 |
+
K = 5376
|
| 29 |
+
N = 14336
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 33 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def kitchen_gemm(qx, sx, qw, sw, scale_x, scale_w) -> torch.Tensor:
|
| 37 |
+
alpha = (scale_x * scale_w).reshape(1)
|
| 38 |
+
y = ck.scaled_mm_nvfp4(
|
| 39 |
+
qx, qw, scale_x, scale_w, sx, sw, out_dtype=torch.bfloat16, alpha=alpha
|
| 40 |
+
)
|
| 41 |
+
return y[:ROWS]
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def eager_act(raw: torch.Tensor) -> torch.Tensor:
|
| 45 |
+
gate, up = raw.chunk(2, dim=-1)
|
| 46 |
+
return F.silu(gate).mul_(up)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 50 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def max_abs_diff(left: torch.Tensor, right: torch.Tensor) -> float:
|
| 54 |
+
return float((left.float() - right.float()).abs().max().item())
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def time_ms(fn) -> float:
|
| 58 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 59 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 60 |
+
fn()
|
| 61 |
+
torch.cuda.synchronize()
|
| 62 |
+
starter.record()
|
| 63 |
+
fn()
|
| 64 |
+
ender.record()
|
| 65 |
+
torch.cuda.synchronize()
|
| 66 |
+
return starter.elapsed_time(ender)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def main() -> int:
|
| 70 |
+
load_extension(verbose=False)
|
| 71 |
+
device = torch.device("cuda")
|
| 72 |
+
torch.manual_seed(26081239)
|
| 73 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 74 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 75 |
+
sx = nvfp4_scale(x)
|
| 76 |
+
sw = nvfp4_scale(weight)
|
| 77 |
+
qx, qxs = ck.quantize_nvfp4(x, sx, pad_16x=True, hi_first=True)
|
| 78 |
+
qw, qws = ck.quantize_nvfp4(weight, sw, pad_16x=True, hi_first=True)
|
| 79 |
+
qx_live = qx[:ROWS].contiguous()
|
| 80 |
+
alpha = (sx * sw).reshape(1).contiguous()
|
| 81 |
+
kitchen = kitchen_gemm(qx, qxs, qw, qws, sx, sw)
|
| 82 |
+
full = fc1_paired_nvfp4_scaled_tma_sf(
|
| 83 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 84 |
+
)
|
| 85 |
+
prod = fc1_paired_nvfp4_scaled_tma_sf(
|
| 86 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 87 |
+
)
|
| 88 |
+
tma = fc1_paired_nvfp4_scaled_tma(
|
| 89 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 90 |
+
)
|
| 91 |
+
eager_kit = eager_act(kitchen.contiguous())
|
| 92 |
+
eager_full = eager_act(full)
|
| 93 |
+
two_q, two_s, two_g = swiglu_nvfp4_dynamic(kitchen.contiguous())
|
| 94 |
+
prod_q, prod_s, prod_g = bf16_nvfp4_dynamic(prod)
|
| 95 |
+
payload = {
|
| 96 |
+
"identity": (
|
| 97 |
+
"cuBLAS 128x4 E4M3 scale slab == pack_four_scales: TMA 1D bulk "
|
| 98 |
+
"of the 512-byte slab, indexed as (row%32)*16+(row/32)*4, on "
|
| 99 |
+
"the kitchen-legal 128x128 m16n8k64 paired-N atom"
|
| 100 |
+
),
|
| 101 |
+
"rows": ROWS,
|
| 102 |
+
"k": K,
|
| 103 |
+
"n": N,
|
| 104 |
+
"device": torch.cuda.get_device_name(device),
|
| 105 |
+
"sf_full_vs_kitchen_mismatches": byte_diff(full, kitchen),
|
| 106 |
+
"sf_full_vs_kitchen_max_abs": round(max_abs_diff(full, kitchen), 6),
|
| 107 |
+
"sf_prod_vs_tma_prod": byte_diff(prod, tma),
|
| 108 |
+
"product_vs_eager_full_mismatches": byte_diff(prod, eager_full),
|
| 109 |
+
"product_vs_eager_kitchen_mismatches": byte_diff(prod, eager_kit),
|
| 110 |
+
"from_product_vs_kitchen_q": byte_diff(prod_q, two_q),
|
| 111 |
+
"from_product_vs_kitchen_s": byte_diff(prod_s, two_s),
|
| 112 |
+
"from_product_vs_kitchen_scale_exact": bool(torch.equal(prod_g, two_g)),
|
| 113 |
+
"full_finite": bool(torch.isfinite(full.float()).all().item()),
|
| 114 |
+
"prod_finite": bool(torch.isfinite(prod.float()).all().item()),
|
| 115 |
+
"kitchen_gemm_min_ms": round(
|
| 116 |
+
time_ms(lambda: kitchen_gemm(qx, qxs, qw, qws, sx, sw)), 4
|
| 117 |
+
),
|
| 118 |
+
"sf_full_min_ms": round(
|
| 119 |
+
time_ms(
|
| 120 |
+
lambda: fc1_paired_nvfp4_scaled_tma_sf(
|
| 121 |
+
qx_live, qxs, qw, qws, alpha, product=False
|
| 122 |
+
)
|
| 123 |
+
),
|
| 124 |
+
4,
|
| 125 |
+
),
|
| 126 |
+
"sf_prod_min_ms": round(
|
| 127 |
+
time_ms(
|
| 128 |
+
lambda: fc1_paired_nvfp4_scaled_tma_sf(
|
| 129 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 130 |
+
)
|
| 131 |
+
),
|
| 132 |
+
4,
|
| 133 |
+
),
|
| 134 |
+
"tma_prod_min_ms": round(
|
| 135 |
+
time_ms(
|
| 136 |
+
lambda: fc1_paired_nvfp4_scaled_tma(
|
| 137 |
+
qx_live, qxs, qw, qws, alpha, product=True
|
| 138 |
+
)
|
| 139 |
+
),
|
| 140 |
+
4,
|
| 141 |
+
),
|
| 142 |
+
"from_product_min_ms": round(
|
| 143 |
+
time_ms(lambda: bf16_nvfp4_dynamic(prod)), 4
|
| 144 |
+
),
|
| 145 |
+
}
|
| 146 |
+
payload["pass"] = (
|
| 147 |
+
payload["sf_full_vs_kitchen_mismatches"] == 0
|
| 148 |
+
and payload["sf_prod_vs_tma_prod"] == 0
|
| 149 |
+
and payload["product_vs_eager_full_mismatches"] == 0
|
| 150 |
+
and payload["product_vs_eager_kitchen_mismatches"] == 0
|
| 151 |
+
and payload["from_product_vs_kitchen_q"] == 0
|
| 152 |
+
and payload["from_product_vs_kitchen_s"] == 0
|
| 153 |
+
and payload["from_product_vs_kitchen_scale_exact"]
|
| 154 |
+
and payload["full_finite"]
|
| 155 |
+
and payload["prod_finite"]
|
| 156 |
+
)
|
| 157 |
+
if payload["pass"]:
|
| 158 |
+
payload["vs_tma_ms"] = round(
|
| 159 |
+
payload["tma_prod_min_ms"] - payload["sf_prod_min_ms"], 4
|
| 160 |
+
)
|
| 161 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 162 |
+
Path(__file__).with_name(
|
| 163 |
+
"gate_fc1_nvfp4_scaled_tma_sf_20423.json"
|
| 164 |
+
).write_text(text + "\n")
|
| 165 |
+
print(text)
|
| 166 |
+
return 0 if payload["pass"] else 2
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
if __name__ == "__main__":
|
| 170 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_scaled_tma_sf_20423.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"from_product_min_ms": 5.7291,
|
| 4 |
+
"from_product_vs_kitchen_q": 0,
|
| 5 |
+
"from_product_vs_kitchen_s": 0,
|
| 6 |
+
"from_product_vs_kitchen_scale_exact": true,
|
| 7 |
+
"full_finite": true,
|
| 8 |
+
"identity": "cuBLAS 128x4 E4M3 scale slab == pack_four_scales: TMA 1D bulk of the 512-byte slab, indexed as (row%32)*16+(row/32)*4, on the kitchen-legal 128x128 m16n8k64 paired-N atom",
|
| 9 |
+
"k": 5376,
|
| 10 |
+
"kitchen_gemm_min_ms": 20.2346,
|
| 11 |
+
"n": 14336,
|
| 12 |
+
"pass": true,
|
| 13 |
+
"prod_finite": true,
|
| 14 |
+
"product_vs_eager_full_mismatches": 0,
|
| 15 |
+
"product_vs_eager_kitchen_mismatches": 0,
|
| 16 |
+
"rows": 20423,
|
| 17 |
+
"sf_full_min_ms": 107.2147,
|
| 18 |
+
"sf_full_vs_kitchen_max_abs": 0.0,
|
| 19 |
+
"sf_full_vs_kitchen_mismatches": 0,
|
| 20 |
+
"sf_prod_min_ms": 104.2306,
|
| 21 |
+
"sf_prod_vs_tma_prod": 0,
|
| 22 |
+
"tma_prod_min_ms": 104.6651,
|
| 23 |
+
"vs_tma_ms": 0.4345
|
| 24 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_tiled.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: persistent tiled NVFP4 paired-N vs 1-warp and eager.
|
| 3 |
+
|
| 4 |
+
Same atom. CTA owns 64 N, streams M, reuses A across the panel.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import json
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
import torch
|
| 13 |
+
import torch.nn.functional as F
|
| 14 |
+
|
| 15 |
+
import comfy_kitchen as ck
|
| 16 |
+
|
| 17 |
+
from swiglu_nvfp4 import (
|
| 18 |
+
bf16_nvfp4_dynamic,
|
| 19 |
+
fc1_paired_nvfp4,
|
| 20 |
+
fc1_paired_nvfp4_tiled,
|
| 21 |
+
load_extension,
|
| 22 |
+
swiglu_nvfp4_dynamic,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
ROWS = 20423
|
| 27 |
+
K = 5376
|
| 28 |
+
N = 14336
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def nvfp4_scale(x: torch.Tensor) -> torch.Tensor:
|
| 32 |
+
return (x.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def eager_act(raw: torch.Tensor) -> torch.Tensor:
|
| 36 |
+
gate, up = raw.chunk(2, dim=-1)
|
| 37 |
+
return F.silu(gate).mul_(up)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def eager_pack(activated: torch.Tensor):
|
| 41 |
+
scale = (activated.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 42 |
+
packed, block_scales = ck.quantize_nvfp4(
|
| 43 |
+
activated, scale, pad_16x=True, hi_first=True
|
| 44 |
+
)
|
| 45 |
+
return packed, block_scales, scale
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 49 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def time_ms(fn) -> float:
|
| 53 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 54 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 55 |
+
fn()
|
| 56 |
+
torch.cuda.synchronize()
|
| 57 |
+
starter.record()
|
| 58 |
+
fn()
|
| 59 |
+
ender.record()
|
| 60 |
+
torch.cuda.synchronize()
|
| 61 |
+
return starter.elapsed_time(ender)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def main() -> int:
|
| 65 |
+
load_extension(verbose=False)
|
| 66 |
+
device = torch.device("cuda")
|
| 67 |
+
torch.manual_seed(26081235)
|
| 68 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 69 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 70 |
+
qx, _ = ck.quantize_nvfp4(x, nvfp4_scale(x), pad_16x=True, hi_first=True)
|
| 71 |
+
qw, _ = ck.quantize_nvfp4(
|
| 72 |
+
weight, nvfp4_scale(weight), pad_16x=True, hi_first=True
|
| 73 |
+
)
|
| 74 |
+
qx = qx[:ROWS].contiguous()
|
| 75 |
+
full = fc1_paired_nvfp4_tiled(qx, qw, product=False)
|
| 76 |
+
prod = fc1_paired_nvfp4_tiled(qx, qw, product=True)
|
| 77 |
+
onew = fc1_paired_nvfp4(qx, qw, product=True)
|
| 78 |
+
eager = eager_act(full)
|
| 79 |
+
two_q, two_s, two_g = swiglu_nvfp4_dynamic(full)
|
| 80 |
+
prod_q, prod_s, prod_g = bf16_nvfp4_dynamic(prod)
|
| 81 |
+
ref_q, ref_s, ref_g = eager_pack(eager)
|
| 82 |
+
payload = {
|
| 83 |
+
"identity": (
|
| 84 |
+
"persistent 64x64 NVFP4 paired-N: A K-slab reused across 8 "
|
| 85 |
+
"n-subtiles and both arms; eager product epilogue"
|
| 86 |
+
),
|
| 87 |
+
"rows": ROWS,
|
| 88 |
+
"k": K,
|
| 89 |
+
"n": N,
|
| 90 |
+
"device": torch.cuda.get_device_name(device),
|
| 91 |
+
"product_vs_eager_full_mismatches": byte_diff(prod, eager),
|
| 92 |
+
"tiled_vs_onewarp_prod": byte_diff(prod, onew),
|
| 93 |
+
"from_product_vs_twopass_q": byte_diff(prod_q, two_q),
|
| 94 |
+
"from_product_vs_twopass_s": byte_diff(prod_s, two_s),
|
| 95 |
+
"from_product_vs_twopass_scale_exact": bool(torch.equal(prod_g, two_g)),
|
| 96 |
+
"from_product_vs_eager_q": byte_diff(prod_q, ref_q),
|
| 97 |
+
"from_product_vs_eager_s": byte_diff(prod_s, ref_s),
|
| 98 |
+
"from_product_vs_eager_scale_exact": bool(torch.equal(prod_g, ref_g)),
|
| 99 |
+
"full_finite": bool(torch.isfinite(full.float()).all().item()),
|
| 100 |
+
"prod_finite": bool(torch.isfinite(prod.float()).all().item()),
|
| 101 |
+
"tiled_full_min_ms": round(
|
| 102 |
+
time_ms(lambda: fc1_paired_nvfp4_tiled(qx, qw, product=False)), 4
|
| 103 |
+
),
|
| 104 |
+
"tiled_prod_min_ms": round(
|
| 105 |
+
time_ms(lambda: fc1_paired_nvfp4_tiled(qx, qw, product=True)), 4
|
| 106 |
+
),
|
| 107 |
+
"onewarp_prod_min_ms": round(
|
| 108 |
+
time_ms(lambda: fc1_paired_nvfp4(qx, qw, product=True)), 4
|
| 109 |
+
),
|
| 110 |
+
"twopass_min_ms": round(time_ms(lambda: swiglu_nvfp4_dynamic(full)), 4),
|
| 111 |
+
"from_product_min_ms": round(time_ms(lambda: bf16_nvfp4_dynamic(prod)), 4),
|
| 112 |
+
}
|
| 113 |
+
payload["pass"] = (
|
| 114 |
+
payload["product_vs_eager_full_mismatches"] == 0
|
| 115 |
+
and payload["tiled_vs_onewarp_prod"] == 0
|
| 116 |
+
and payload["from_product_vs_twopass_q"] == 0
|
| 117 |
+
and payload["from_product_vs_twopass_s"] == 0
|
| 118 |
+
and payload["from_product_vs_twopass_scale_exact"]
|
| 119 |
+
and payload["from_product_vs_eager_q"] == 0
|
| 120 |
+
and payload["from_product_vs_eager_s"] == 0
|
| 121 |
+
and payload["from_product_vs_eager_scale_exact"]
|
| 122 |
+
and payload["full_finite"]
|
| 123 |
+
and payload["prod_finite"]
|
| 124 |
+
)
|
| 125 |
+
if payload["pass"]:
|
| 126 |
+
payload["vs_onewarp_ms"] = round(
|
| 127 |
+
payload["onewarp_prod_min_ms"] - payload["tiled_prod_min_ms"], 4
|
| 128 |
+
)
|
| 129 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 130 |
+
Path(__file__).with_name("gate_fc1_nvfp4_tiled_20423.json").write_text(
|
| 131 |
+
text + "\n"
|
| 132 |
+
)
|
| 133 |
+
print(text)
|
| 134 |
+
return 0 if payload["pass"] else 2
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
if __name__ == "__main__":
|
| 138 |
+
raise SystemExit(main())
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_nvfp4_tiled_20423.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"device": "NVIDIA GB10",
|
| 3 |
+
"from_product_min_ms": 5.8331,
|
| 4 |
+
"from_product_vs_eager_q": 0,
|
| 5 |
+
"from_product_vs_eager_s": 0,
|
| 6 |
+
"from_product_vs_eager_scale_exact": true,
|
| 7 |
+
"from_product_vs_twopass_q": 0,
|
| 8 |
+
"from_product_vs_twopass_s": 0,
|
| 9 |
+
"from_product_vs_twopass_scale_exact": true,
|
| 10 |
+
"full_finite": true,
|
| 11 |
+
"identity": "persistent 64x64 NVFP4 paired-N: A K-slab reused across 8 n-subtiles and both arms; eager product epilogue",
|
| 12 |
+
"k": 5376,
|
| 13 |
+
"n": 14336,
|
| 14 |
+
"onewarp_prod_min_ms": 1225.824,
|
| 15 |
+
"pass": true,
|
| 16 |
+
"prod_finite": true,
|
| 17 |
+
"product_vs_eager_full_mismatches": 0,
|
| 18 |
+
"rows": 20423,
|
| 19 |
+
"tiled_full_min_ms": 221.7096,
|
| 20 |
+
"tiled_prod_min_ms": 208.4988,
|
| 21 |
+
"tiled_vs_onewarp_prod": 0,
|
| 22 |
+
"twopass_min_ms": 10.8047,
|
| 23 |
+
"vs_onewarp_ms": 1017.3252
|
| 24 |
+
}
|
labs/swiglu_nvfp4/native_cuda/gate_fc1_paired.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Byte-exact gate: paired-N FC1 MMA store of eager product vs [gate|up].
|
| 3 |
+
|
| 4 |
+
Same mainloop, two epilogues. Kitchen GEMM is not modified.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import json
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
import torch
|
| 13 |
+
import torch.nn.functional as F
|
| 14 |
+
|
| 15 |
+
import comfy_kitchen as ck
|
| 16 |
+
|
| 17 |
+
from swiglu_nvfp4 import (
|
| 18 |
+
bf16_nvfp4_dynamic,
|
| 19 |
+
fc1_paired_store,
|
| 20 |
+
load_extension,
|
| 21 |
+
swiglu_nvfp4_dynamic,
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
ROWS = 20423
|
| 26 |
+
K = 5376
|
| 27 |
+
N = 14336
|
| 28 |
+
REPEATS = 1
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def eager_act(raw: torch.Tensor) -> torch.Tensor:
|
| 32 |
+
gate, up = raw.chunk(2, dim=-1)
|
| 33 |
+
return F.silu(gate).mul_(up)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def eager_pack(activated: torch.Tensor):
|
| 37 |
+
scale = (activated.abs().amax() / (6.0 * 448.0)).to(torch.float32).reshape(1)
|
| 38 |
+
packed, block_scales = ck.quantize_nvfp4(
|
| 39 |
+
activated, scale, pad_16x=True, hi_first=True
|
| 40 |
+
)
|
| 41 |
+
return packed, block_scales, scale
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def byte_diff(left: torch.Tensor, right: torch.Tensor) -> int:
|
| 45 |
+
return int((left.view(torch.uint8) != right.view(torch.uint8)).sum().item())
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def time_ms(fn) -> float:
|
| 49 |
+
starter = torch.cuda.Event(enable_timing=True)
|
| 50 |
+
ender = torch.cuda.Event(enable_timing=True)
|
| 51 |
+
for _ in range(2):
|
| 52 |
+
fn()
|
| 53 |
+
torch.cuda.synchronize()
|
| 54 |
+
samples = []
|
| 55 |
+
for _ in range(REPEATS):
|
| 56 |
+
starter.record()
|
| 57 |
+
fn()
|
| 58 |
+
ender.record()
|
| 59 |
+
torch.cuda.synchronize()
|
| 60 |
+
samples.append(starter.elapsed_time(ender))
|
| 61 |
+
return min(samples)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def main() -> int:
|
| 65 |
+
load_extension(verbose=False)
|
| 66 |
+
device = torch.device("cuda")
|
| 67 |
+
torch.manual_seed(26081228)
|
| 68 |
+
x = torch.randn(ROWS, K, device=device, dtype=torch.bfloat16)
|
| 69 |
+
weight = torch.randn(2 * N, K, device=device, dtype=torch.bfloat16) * 0.02
|
| 70 |
+
full = fc1_paired_store(x, weight, product=False)
|
| 71 |
+
prod = fc1_paired_store(x, weight, product=True)
|
| 72 |
+
eager = eager_act(full)
|
| 73 |
+
two_q, two_s, two_g = swiglu_nvfp4_dynamic(full)
|
| 74 |
+
prod_q, prod_s, prod_g = bf16_nvfp4_dynamic(prod)
|
| 75 |
+
ref_q, ref_s, ref_g = eager_pack(eager)
|
| 76 |
+
payload = {
|
| 77 |
+
"identity": (
|
| 78 |
+
"paired-N MMA accumulates gate and up together; "
|
| 79 |
+
"product epilogue stores eager round(silu(round(g))*round(u)); "
|
| 80 |
+
"same mainloop as the [gate|up] store"
|
| 81 |
+
),
|
| 82 |
+
"rows": ROWS,
|
| 83 |
+
"k": K,
|
| 84 |
+
"n": N,
|
| 85 |
+
"device": torch.cuda.get_device_name(device),
|
| 86 |
+
"product_vs_eager_full_mismatches": byte_diff(prod, eager),
|
| 87 |
+
"from_product_vs_twopass_q": byte_diff(prod_q, two_q),
|
| 88 |
+
"from_product_vs_twopass_s": byte_diff(prod_s, two_s),
|
| 89 |
+
"from_product_vs_twopass_scale_exact": bool(torch.equal(prod_g, two_g)),
|
| 90 |
+
"from_product_vs_eager_q": byte_diff(prod_q, ref_q),
|
| 91 |
+
"from_product_vs_eager_s": byte_diff(prod_s, ref_s),
|
| 92 |
+
"from_product_vs_eager_scale_exact": bool(torch.equal(prod_g, ref_g)),
|
| 93 |
+
"full_store_min_ms": round(
|
| 94 |
+
time_ms(lambda: fc1_paired_store(x, weight, product=False)), 4
|
| 95 |
+
),
|
| 96 |
+
"product_store_min_ms": round(
|
| 97 |
+
time_ms(lambda: fc1_paired_store(x, weight, product=True)), 4
|
| 98 |
+
),
|
| 99 |
+
"twopass_min_ms": round(time_ms(lambda: swiglu_nvfp4_dynamic(full)), 4),
|
| 100 |
+
"from_product_min_ms": round(time_ms(lambda: bf16_nvfp4_dynamic(prod)), 4),
|
| 101 |
+
}
|
| 102 |
+
payload["pass"] = (
|
| 103 |
+
payload["product_vs_eager_full_mismatches"] == 0
|
| 104 |
+
and payload["from_product_vs_twopass_q"] == 0
|
| 105 |
+
and payload["from_product_vs_twopass_s"] == 0
|
| 106 |
+
and payload["from_product_vs_twopass_scale_exact"]
|
| 107 |
+
and payload["from_product_vs_eager_q"] == 0
|
| 108 |
+
and payload["from_product_vs_eager_s"] == 0
|
| 109 |
+
and payload["from_product_vs_eager_scale_exact"]
|
| 110 |
+
)
|
| 111 |
+
if payload["pass"]:
|
| 112 |
+
payload["store_delta_ms"] = round(
|
| 113 |
+
payload["full_store_min_ms"] - payload["product_store_min_ms"], 4
|
| 114 |
+
)
|
| 115 |
+
payload["pack_side_saved_ms"] = round(
|
| 116 |
+
payload["twopass_min_ms"] - payload["from_product_min_ms"], 4
|
| 117 |
+
)
|
| 118 |
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
| 119 |
+
Path(__file__).with_name("gate_fc1_paired_20423.json").write_text(text + "\n")
|
| 120 |
+
print(text)
|
| 121 |
+
return 0 if payload["pass"] else 2
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
if __name__ == "__main__":
|
| 125 |
+
raise SystemExit(main())
|