Azeez commited on
Commit ·
4c5264e
1
Parent(s): b43edbb
Add transformers NVFP4Kernel entry points (PackedWeight, pack, gemm, swizzled_sf_shape) for v1
Browse files- build/torch210-cxx11-cu130-aarch64-linux/__init__.py +86 -16
- build/torch210-cxx11-cu130-aarch64-linux/metadata.json +1 -1
- build/torch210-cxx11-cu130-aarch64-linux/nvfp4_dense_gemm/__init__.py +127 -26
- build/torch211-cxx11-cu130-aarch64-linux/__init__.py +86 -16
- build/torch211-cxx11-cu130-aarch64-linux/metadata.json +1 -1
- build/torch211-cxx11-cu130-aarch64-linux/nvfp4_dense_gemm/__init__.py +127 -26
- build/torch212-cxx11-cu130-aarch64-linux/__init__.py +86 -16
- build/torch212-cxx11-cu130-aarch64-linux/metadata.json +1 -1
- build/torch212-cxx11-cu130-aarch64-linux/nvfp4_dense_gemm/__init__.py +127 -26
- build/torch212-cxx11-cu132-aarch64-linux/__init__.py +86 -16
- build/torch212-cxx11-cu132-aarch64-linux/metadata.json +1 -1
- build/torch212-cxx11-cu132-aarch64-linux/nvfp4_dense_gemm/__init__.py +127 -26
build/torch210-cxx11-cu130-aarch64-linux/__init__.py
CHANGED
|
@@ -1,26 +1,83 @@
|
|
| 1 |
# SPDX-License-Identifier: AGPL-3.0-only
|
| 2 |
-
"""Atlas dense NVFP4/FP8 GEMM kernels for Qwen3.6-27B on GB10 (SM121)
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
(intermediate_size=17408, hidden=5120). Two precisions are supported:
|
| 6 |
-
|
| 7 |
-
- W4A16 (NVFP4 weight, BF16 activation): direct path
|
| 8 |
-
- FP8 W/A: via ``predequant_nvfp4_to_fp8`` + ``fp8_gemm_t`` for
|
| 9 |
-
KV-cache scenarios where the weight is loaded as NVFP4 but the
|
| 10 |
-
GEMM runs in FP8 against an FP8-packed activation.
|
| 11 |
"""
|
| 12 |
|
|
|
|
| 13 |
import torch
|
| 14 |
|
| 15 |
from ._ops import ops
|
| 16 |
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
|
| 26 |
def w4a16_gemm(
|
|
@@ -55,3 +112,16 @@ def fp8_gemm_t(A: torch.Tensor, B_fp8: torch.Tensor, C: torch.Tensor) -> None:
|
|
| 55 |
def bf16_to_fp8(src: torch.Tensor, dst: torch.Tensor) -> None:
|
| 56 |
"""Pair-wise BF16 → FP8 E4M3 quantization (no scaling, uses PTX cvt)."""
|
| 57 |
ops.bf16_to_fp8(src, dst)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# SPDX-License-Identifier: AGPL-3.0-only
|
| 2 |
+
"""Atlas dense NVFP4/FP8 GEMM kernels for Qwen3.6-27B on GB10 (SM121),
|
| 3 |
+
with Hugging Face ``transformers`` ``NVFP4Config`` entry points
|
| 4 |
+
(``PackedWeight``, ``pack``, ``gemm``, ``swizzled_sf_shape``).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
"""
|
| 6 |
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
import torch
|
| 9 |
|
| 10 |
from ._ops import ops
|
| 11 |
|
| 12 |
+
FLOAT8_E4M3_MAX = 448.0
|
| 13 |
+
FLOAT4_E2M1_MAX = 6.0
|
| 14 |
+
SF_VEC_SIZE = 16
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def swizzled_sf_shape(rows: int, cols: int) -> tuple[int, int]:
|
| 18 |
+
return (rows + 127) // 128 * 128, (cols // SF_VEC_SIZE + 3) // 4
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@dataclass
|
| 22 |
+
class PackedWeight:
|
| 23 |
+
qweight: torch.Tensor
|
| 24 |
+
sf: torch.Tensor
|
| 25 |
+
global_scale: torch.Tensor
|
| 26 |
+
n: int
|
| 27 |
+
k: int
|
| 28 |
+
swiglu: bool = False
|
| 29 |
+
sf_rowmajor: torch.Tensor | None = None
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _pack_e2m1_torch(wd: torch.Tensor, scale2: float) -> tuple[torch.Tensor, torch.Tensor]:
|
| 33 |
+
n, k = wd.shape
|
| 34 |
+
g = wd.float().reshape(n, k // SF_VEC_SIZE, SF_VEC_SIZE)
|
| 35 |
+
block_amax = g.abs().amax(dim=-1)
|
| 36 |
+
raw_sf = (block_amax / (FLOAT4_E2M1_MAX * max(scale2, 1e-12))).clamp(max=FLOAT8_E4M3_MAX)
|
| 37 |
+
sf_fp8 = raw_sf.to(torch.float8_e4m3fn)
|
| 38 |
+
sf_f32 = sf_fp8.float() * max(scale2, 1e-12)
|
| 39 |
+
scaled = torch.where(sf_f32.unsqueeze(-1) > 0, g / sf_f32.unsqueeze(-1), torch.zeros_like(g))
|
| 40 |
+
ax = scaled.abs()
|
| 41 |
+
nib = torch.zeros_like(ax, dtype=torch.uint8)
|
| 42 |
+
nib = torch.where(ax >= 0.25, torch.tensor(1, device=wd.device, dtype=torch.uint8), nib)
|
| 43 |
+
nib = torch.where(ax >= 0.75, torch.tensor(2, device=wd.device, dtype=torch.uint8), nib)
|
| 44 |
+
nib = torch.where(ax >= 1.25, torch.tensor(3, device=wd.device, dtype=torch.uint8), nib)
|
| 45 |
+
nib = torch.where(ax >= 1.75, torch.tensor(4, device=wd.device, dtype=torch.uint8), nib)
|
| 46 |
+
nib = torch.where(ax >= 2.50, torch.tensor(5, device=wd.device, dtype=torch.uint8), nib)
|
| 47 |
+
nib = torch.where(ax >= 3.50, torch.tensor(6, device=wd.device, dtype=torch.uint8), nib)
|
| 48 |
+
nib = torch.where(ax >= 5.00, torch.tensor(7, device=wd.device, dtype=torch.uint8), nib)
|
| 49 |
+
sign = torch.where(scaled < 0, torch.tensor(8, device=wd.device, dtype=torch.uint8), torch.tensor(0, device=wd.device, dtype=torch.uint8))
|
| 50 |
+
codes = (nib | sign).reshape(n, k // 2, 2)
|
| 51 |
+
qweight = (codes[:, :, 0] | (codes[:, :, 1] << 4)).contiguous()
|
| 52 |
+
return qweight, sf_fp8.view(torch.uint8).contiguous()
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def pack(w: torch.Tensor, device: str | torch.device = "cuda", swiglu: bool = False) -> PackedWeight:
|
| 56 |
+
wd = w.detach().to(device, torch.bfloat16).contiguous()
|
| 57 |
+
n, k = wd.shape
|
| 58 |
+
amax = wd.abs().amax().to(torch.float32).clamp(min=1e-12)
|
| 59 |
+
scale2 = (amax / (FLOAT8_E4M3_MAX * FLOAT4_E2M1_MAX)).item()
|
| 60 |
+
gs = 1.0 / max(scale2, 1e-12)
|
| 61 |
+
qweight, sf_rowmajor = _pack_e2m1_torch(wd, scale2)
|
| 62 |
+
sf_m, sf_n = swizzled_sf_shape(n, k)
|
| 63 |
+
sf = torch.zeros((sf_m, sf_n), device=device, dtype=torch.int32)
|
| 64 |
+
return PackedWeight(
|
| 65 |
+
qweight=qweight,
|
| 66 |
+
sf=sf,
|
| 67 |
+
global_scale=torch.tensor([gs], device=device, dtype=torch.float32),
|
| 68 |
+
n=n,
|
| 69 |
+
k=k,
|
| 70 |
+
swiglu=swiglu,
|
| 71 |
+
sf_rowmajor=sf_rowmajor,
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def gemm(pw: PackedWeight, x: torch.Tensor) -> torch.Tensor:
|
| 76 |
+
x2 = x.reshape(-1, pw.k).to(torch.bfloat16).contiguous()
|
| 77 |
+
out = torch.empty((x2.size(0), pw.n), device=x.device, dtype=torch.bfloat16)
|
| 78 |
+
scale2 = 1.0 / pw.global_scale.item()
|
| 79 |
+
ops.w4a16_gemm(x2, pw.qweight, pw.sf_rowmajor, scale2, out)
|
| 80 |
+
return out.reshape(*x.shape[:-1], pw.n)
|
| 81 |
|
| 82 |
|
| 83 |
def w4a16_gemm(
|
|
|
|
| 112 |
def bf16_to_fp8(src: torch.Tensor, dst: torch.Tensor) -> None:
|
| 113 |
"""Pair-wise BF16 → FP8 E4M3 quantization (no scaling, uses PTX cvt)."""
|
| 114 |
ops.bf16_to_fp8(src, dst)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
__all__ = [
|
| 118 |
+
"PackedWeight",
|
| 119 |
+
"pack",
|
| 120 |
+
"gemm",
|
| 121 |
+
"swizzled_sf_shape",
|
| 122 |
+
"w4a16_gemm",
|
| 123 |
+
"w4a16_gemm_t",
|
| 124 |
+
"predequant_nvfp4_to_fp8",
|
| 125 |
+
"fp8_gemm_t",
|
| 126 |
+
"bf16_to_fp8",
|
| 127 |
+
]
|
build/torch210-cxx11-cu130-aarch64-linux/metadata.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
{
|
| 2 |
"name": "nvfp4-dense-gemm",
|
| 3 |
"id": "_nvfp4_dense_gemm_cuda_bcba8ad",
|
| 4 |
-
"version":
|
| 5 |
"license": "AGPL-3.0-only",
|
| 6 |
"python-depends": [],
|
| 7 |
"backend": {
|
|
|
|
| 1 |
{
|
| 2 |
"name": "nvfp4-dense-gemm",
|
| 3 |
"id": "_nvfp4_dense_gemm_cuda_bcba8ad",
|
| 4 |
+
"version": 1,
|
| 5 |
"license": "AGPL-3.0-only",
|
| 6 |
"python-depends": [],
|
| 7 |
"backend": {
|
build/torch210-cxx11-cu130-aarch64-linux/nvfp4_dense_gemm/__init__.py
CHANGED
|
@@ -1,26 +1,127 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: AGPL-3.0-only
|
| 2 |
+
"""Atlas dense NVFP4/FP8 GEMM kernels for Qwen3.6-27B on GB10 (SM121),
|
| 3 |
+
with Hugging Face ``transformers`` ``NVFP4Config`` entry points
|
| 4 |
+
(``PackedWeight``, ``pack``, ``gemm``, ``swizzled_sf_shape``).
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
from ._ops import ops
|
| 11 |
+
|
| 12 |
+
FLOAT8_E4M3_MAX = 448.0
|
| 13 |
+
FLOAT4_E2M1_MAX = 6.0
|
| 14 |
+
SF_VEC_SIZE = 16
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def swizzled_sf_shape(rows: int, cols: int) -> tuple[int, int]:
|
| 18 |
+
return (rows + 127) // 128 * 128, (cols // SF_VEC_SIZE + 3) // 4
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@dataclass
|
| 22 |
+
class PackedWeight:
|
| 23 |
+
qweight: torch.Tensor
|
| 24 |
+
sf: torch.Tensor
|
| 25 |
+
global_scale: torch.Tensor
|
| 26 |
+
n: int
|
| 27 |
+
k: int
|
| 28 |
+
swiglu: bool = False
|
| 29 |
+
sf_rowmajor: torch.Tensor | None = None
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _pack_e2m1_torch(wd: torch.Tensor, scale2: float) -> tuple[torch.Tensor, torch.Tensor]:
|
| 33 |
+
n, k = wd.shape
|
| 34 |
+
g = wd.float().reshape(n, k // SF_VEC_SIZE, SF_VEC_SIZE)
|
| 35 |
+
block_amax = g.abs().amax(dim=-1)
|
| 36 |
+
raw_sf = (block_amax / (FLOAT4_E2M1_MAX * max(scale2, 1e-12))).clamp(max=FLOAT8_E4M3_MAX)
|
| 37 |
+
sf_fp8 = raw_sf.to(torch.float8_e4m3fn)
|
| 38 |
+
sf_f32 = sf_fp8.float() * max(scale2, 1e-12)
|
| 39 |
+
scaled = torch.where(sf_f32.unsqueeze(-1) > 0, g / sf_f32.unsqueeze(-1), torch.zeros_like(g))
|
| 40 |
+
ax = scaled.abs()
|
| 41 |
+
nib = torch.zeros_like(ax, dtype=torch.uint8)
|
| 42 |
+
nib = torch.where(ax >= 0.25, torch.tensor(1, device=wd.device, dtype=torch.uint8), nib)
|
| 43 |
+
nib = torch.where(ax >= 0.75, torch.tensor(2, device=wd.device, dtype=torch.uint8), nib)
|
| 44 |
+
nib = torch.where(ax >= 1.25, torch.tensor(3, device=wd.device, dtype=torch.uint8), nib)
|
| 45 |
+
nib = torch.where(ax >= 1.75, torch.tensor(4, device=wd.device, dtype=torch.uint8), nib)
|
| 46 |
+
nib = torch.where(ax >= 2.50, torch.tensor(5, device=wd.device, dtype=torch.uint8), nib)
|
| 47 |
+
nib = torch.where(ax >= 3.50, torch.tensor(6, device=wd.device, dtype=torch.uint8), nib)
|
| 48 |
+
nib = torch.where(ax >= 5.00, torch.tensor(7, device=wd.device, dtype=torch.uint8), nib)
|
| 49 |
+
sign = torch.where(scaled < 0, torch.tensor(8, device=wd.device, dtype=torch.uint8), torch.tensor(0, device=wd.device, dtype=torch.uint8))
|
| 50 |
+
codes = (nib | sign).reshape(n, k // 2, 2)
|
| 51 |
+
qweight = (codes[:, :, 0] | (codes[:, :, 1] << 4)).contiguous()
|
| 52 |
+
return qweight, sf_fp8.view(torch.uint8).contiguous()
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def pack(w: torch.Tensor, device: str | torch.device = "cuda", swiglu: bool = False) -> PackedWeight:
|
| 56 |
+
wd = w.detach().to(device, torch.bfloat16).contiguous()
|
| 57 |
+
n, k = wd.shape
|
| 58 |
+
amax = wd.abs().amax().to(torch.float32).clamp(min=1e-12)
|
| 59 |
+
scale2 = (amax / (FLOAT8_E4M3_MAX * FLOAT4_E2M1_MAX)).item()
|
| 60 |
+
gs = 1.0 / max(scale2, 1e-12)
|
| 61 |
+
qweight, sf_rowmajor = _pack_e2m1_torch(wd, scale2)
|
| 62 |
+
sf_m, sf_n = swizzled_sf_shape(n, k)
|
| 63 |
+
sf = torch.zeros((sf_m, sf_n), device=device, dtype=torch.int32)
|
| 64 |
+
return PackedWeight(
|
| 65 |
+
qweight=qweight,
|
| 66 |
+
sf=sf,
|
| 67 |
+
global_scale=torch.tensor([gs], device=device, dtype=torch.float32),
|
| 68 |
+
n=n,
|
| 69 |
+
k=k,
|
| 70 |
+
swiglu=swiglu,
|
| 71 |
+
sf_rowmajor=sf_rowmajor,
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def gemm(pw: PackedWeight, x: torch.Tensor) -> torch.Tensor:
|
| 76 |
+
x2 = x.reshape(-1, pw.k).to(torch.bfloat16).contiguous()
|
| 77 |
+
out = torch.empty((x2.size(0), pw.n), device=x.device, dtype=torch.bfloat16)
|
| 78 |
+
scale2 = 1.0 / pw.global_scale.item()
|
| 79 |
+
ops.w4a16_gemm(x2, pw.qweight, pw.sf_rowmajor, scale2, out)
|
| 80 |
+
return out.reshape(*x.shape[:-1], pw.n)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def w4a16_gemm(
|
| 84 |
+
A: torch.Tensor, B_packed: torch.Tensor, B_scale: torch.Tensor,
|
| 85 |
+
scale2: float, C: torch.Tensor,
|
| 86 |
+
) -> None:
|
| 87 |
+
"""C = A @ B^T where B is NVFP4. Standard layout."""
|
| 88 |
+
ops.w4a16_gemm(A, B_packed, B_scale, scale2, C)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def w4a16_gemm_t(
|
| 92 |
+
A: torch.Tensor, B_packed: torch.Tensor, B_scale: torch.Tensor,
|
| 93 |
+
scale2: float, C: torch.Tensor,
|
| 94 |
+
) -> None:
|
| 95 |
+
"""Transposed-B variant (N-major) — typically faster for [N>K] shapes."""
|
| 96 |
+
ops.w4a16_gemm_t(A, B_packed, B_scale, scale2, C)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def predequant_nvfp4_to_fp8(
|
| 100 |
+
B_packed: torch.Tensor, B_scale: torch.Tensor,
|
| 101 |
+
scale2: float, B_fp8_out: torch.Tensor,
|
| 102 |
+
) -> None:
|
| 103 |
+
"""Materialize an NVFP4-packed weight into a dense FP8 E4M3 tensor."""
|
| 104 |
+
ops.predequant_nvfp4_to_fp8(B_packed, B_scale, scale2, B_fp8_out)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def fp8_gemm_t(A: torch.Tensor, B_fp8: torch.Tensor, C: torch.Tensor) -> None:
|
| 108 |
+
"""C = A @ B^T where both A is BF16 and B is FP8 E4M3."""
|
| 109 |
+
ops.fp8_gemm_t(A, B_fp8, C)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def bf16_to_fp8(src: torch.Tensor, dst: torch.Tensor) -> None:
|
| 113 |
+
"""Pair-wise BF16 → FP8 E4M3 quantization (no scaling, uses PTX cvt)."""
|
| 114 |
+
ops.bf16_to_fp8(src, dst)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
__all__ = [
|
| 118 |
+
"PackedWeight",
|
| 119 |
+
"pack",
|
| 120 |
+
"gemm",
|
| 121 |
+
"swizzled_sf_shape",
|
| 122 |
+
"w4a16_gemm",
|
| 123 |
+
"w4a16_gemm_t",
|
| 124 |
+
"predequant_nvfp4_to_fp8",
|
| 125 |
+
"fp8_gemm_t",
|
| 126 |
+
"bf16_to_fp8",
|
| 127 |
+
]
|
build/torch211-cxx11-cu130-aarch64-linux/__init__.py
CHANGED
|
@@ -1,26 +1,83 @@
|
|
| 1 |
# SPDX-License-Identifier: AGPL-3.0-only
|
| 2 |
-
"""Atlas dense NVFP4/FP8 GEMM kernels for Qwen3.6-27B on GB10 (SM121)
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
(intermediate_size=17408, hidden=5120). Two precisions are supported:
|
| 6 |
-
|
| 7 |
-
- W4A16 (NVFP4 weight, BF16 activation): direct path
|
| 8 |
-
- FP8 W/A: via ``predequant_nvfp4_to_fp8`` + ``fp8_gemm_t`` for
|
| 9 |
-
KV-cache scenarios where the weight is loaded as NVFP4 but the
|
| 10 |
-
GEMM runs in FP8 against an FP8-packed activation.
|
| 11 |
"""
|
| 12 |
|
|
|
|
| 13 |
import torch
|
| 14 |
|
| 15 |
from ._ops import ops
|
| 16 |
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
|
| 26 |
def w4a16_gemm(
|
|
@@ -55,3 +112,16 @@ def fp8_gemm_t(A: torch.Tensor, B_fp8: torch.Tensor, C: torch.Tensor) -> None:
|
|
| 55 |
def bf16_to_fp8(src: torch.Tensor, dst: torch.Tensor) -> None:
|
| 56 |
"""Pair-wise BF16 → FP8 E4M3 quantization (no scaling, uses PTX cvt)."""
|
| 57 |
ops.bf16_to_fp8(src, dst)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# SPDX-License-Identifier: AGPL-3.0-only
|
| 2 |
+
"""Atlas dense NVFP4/FP8 GEMM kernels for Qwen3.6-27B on GB10 (SM121),
|
| 3 |
+
with Hugging Face ``transformers`` ``NVFP4Config`` entry points
|
| 4 |
+
(``PackedWeight``, ``pack``, ``gemm``, ``swizzled_sf_shape``).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
"""
|
| 6 |
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
import torch
|
| 9 |
|
| 10 |
from ._ops import ops
|
| 11 |
|
| 12 |
+
FLOAT8_E4M3_MAX = 448.0
|
| 13 |
+
FLOAT4_E2M1_MAX = 6.0
|
| 14 |
+
SF_VEC_SIZE = 16
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def swizzled_sf_shape(rows: int, cols: int) -> tuple[int, int]:
|
| 18 |
+
return (rows + 127) // 128 * 128, (cols // SF_VEC_SIZE + 3) // 4
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@dataclass
|
| 22 |
+
class PackedWeight:
|
| 23 |
+
qweight: torch.Tensor
|
| 24 |
+
sf: torch.Tensor
|
| 25 |
+
global_scale: torch.Tensor
|
| 26 |
+
n: int
|
| 27 |
+
k: int
|
| 28 |
+
swiglu: bool = False
|
| 29 |
+
sf_rowmajor: torch.Tensor | None = None
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _pack_e2m1_torch(wd: torch.Tensor, scale2: float) -> tuple[torch.Tensor, torch.Tensor]:
|
| 33 |
+
n, k = wd.shape
|
| 34 |
+
g = wd.float().reshape(n, k // SF_VEC_SIZE, SF_VEC_SIZE)
|
| 35 |
+
block_amax = g.abs().amax(dim=-1)
|
| 36 |
+
raw_sf = (block_amax / (FLOAT4_E2M1_MAX * max(scale2, 1e-12))).clamp(max=FLOAT8_E4M3_MAX)
|
| 37 |
+
sf_fp8 = raw_sf.to(torch.float8_e4m3fn)
|
| 38 |
+
sf_f32 = sf_fp8.float() * max(scale2, 1e-12)
|
| 39 |
+
scaled = torch.where(sf_f32.unsqueeze(-1) > 0, g / sf_f32.unsqueeze(-1), torch.zeros_like(g))
|
| 40 |
+
ax = scaled.abs()
|
| 41 |
+
nib = torch.zeros_like(ax, dtype=torch.uint8)
|
| 42 |
+
nib = torch.where(ax >= 0.25, torch.tensor(1, device=wd.device, dtype=torch.uint8), nib)
|
| 43 |
+
nib = torch.where(ax >= 0.75, torch.tensor(2, device=wd.device, dtype=torch.uint8), nib)
|
| 44 |
+
nib = torch.where(ax >= 1.25, torch.tensor(3, device=wd.device, dtype=torch.uint8), nib)
|
| 45 |
+
nib = torch.where(ax >= 1.75, torch.tensor(4, device=wd.device, dtype=torch.uint8), nib)
|
| 46 |
+
nib = torch.where(ax >= 2.50, torch.tensor(5, device=wd.device, dtype=torch.uint8), nib)
|
| 47 |
+
nib = torch.where(ax >= 3.50, torch.tensor(6, device=wd.device, dtype=torch.uint8), nib)
|
| 48 |
+
nib = torch.where(ax >= 5.00, torch.tensor(7, device=wd.device, dtype=torch.uint8), nib)
|
| 49 |
+
sign = torch.where(scaled < 0, torch.tensor(8, device=wd.device, dtype=torch.uint8), torch.tensor(0, device=wd.device, dtype=torch.uint8))
|
| 50 |
+
codes = (nib | sign).reshape(n, k // 2, 2)
|
| 51 |
+
qweight = (codes[:, :, 0] | (codes[:, :, 1] << 4)).contiguous()
|
| 52 |
+
return qweight, sf_fp8.view(torch.uint8).contiguous()
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def pack(w: torch.Tensor, device: str | torch.device = "cuda", swiglu: bool = False) -> PackedWeight:
|
| 56 |
+
wd = w.detach().to(device, torch.bfloat16).contiguous()
|
| 57 |
+
n, k = wd.shape
|
| 58 |
+
amax = wd.abs().amax().to(torch.float32).clamp(min=1e-12)
|
| 59 |
+
scale2 = (amax / (FLOAT8_E4M3_MAX * FLOAT4_E2M1_MAX)).item()
|
| 60 |
+
gs = 1.0 / max(scale2, 1e-12)
|
| 61 |
+
qweight, sf_rowmajor = _pack_e2m1_torch(wd, scale2)
|
| 62 |
+
sf_m, sf_n = swizzled_sf_shape(n, k)
|
| 63 |
+
sf = torch.zeros((sf_m, sf_n), device=device, dtype=torch.int32)
|
| 64 |
+
return PackedWeight(
|
| 65 |
+
qweight=qweight,
|
| 66 |
+
sf=sf,
|
| 67 |
+
global_scale=torch.tensor([gs], device=device, dtype=torch.float32),
|
| 68 |
+
n=n,
|
| 69 |
+
k=k,
|
| 70 |
+
swiglu=swiglu,
|
| 71 |
+
sf_rowmajor=sf_rowmajor,
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def gemm(pw: PackedWeight, x: torch.Tensor) -> torch.Tensor:
|
| 76 |
+
x2 = x.reshape(-1, pw.k).to(torch.bfloat16).contiguous()
|
| 77 |
+
out = torch.empty((x2.size(0), pw.n), device=x.device, dtype=torch.bfloat16)
|
| 78 |
+
scale2 = 1.0 / pw.global_scale.item()
|
| 79 |
+
ops.w4a16_gemm(x2, pw.qweight, pw.sf_rowmajor, scale2, out)
|
| 80 |
+
return out.reshape(*x.shape[:-1], pw.n)
|
| 81 |
|
| 82 |
|
| 83 |
def w4a16_gemm(
|
|
|
|
| 112 |
def bf16_to_fp8(src: torch.Tensor, dst: torch.Tensor) -> None:
|
| 113 |
"""Pair-wise BF16 → FP8 E4M3 quantization (no scaling, uses PTX cvt)."""
|
| 114 |
ops.bf16_to_fp8(src, dst)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
__all__ = [
|
| 118 |
+
"PackedWeight",
|
| 119 |
+
"pack",
|
| 120 |
+
"gemm",
|
| 121 |
+
"swizzled_sf_shape",
|
| 122 |
+
"w4a16_gemm",
|
| 123 |
+
"w4a16_gemm_t",
|
| 124 |
+
"predequant_nvfp4_to_fp8",
|
| 125 |
+
"fp8_gemm_t",
|
| 126 |
+
"bf16_to_fp8",
|
| 127 |
+
]
|
build/torch211-cxx11-cu130-aarch64-linux/metadata.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
{
|
| 2 |
"name": "nvfp4-dense-gemm",
|
| 3 |
"id": "_nvfp4_dense_gemm_cuda_bcba8ad",
|
| 4 |
-
"version":
|
| 5 |
"license": "AGPL-3.0-only",
|
| 6 |
"python-depends": [],
|
| 7 |
"backend": {
|
|
|
|
| 1 |
{
|
| 2 |
"name": "nvfp4-dense-gemm",
|
| 3 |
"id": "_nvfp4_dense_gemm_cuda_bcba8ad",
|
| 4 |
+
"version": 1,
|
| 5 |
"license": "AGPL-3.0-only",
|
| 6 |
"python-depends": [],
|
| 7 |
"backend": {
|
build/torch211-cxx11-cu130-aarch64-linux/nvfp4_dense_gemm/__init__.py
CHANGED
|
@@ -1,26 +1,127 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: AGPL-3.0-only
|
| 2 |
+
"""Atlas dense NVFP4/FP8 GEMM kernels for Qwen3.6-27B on GB10 (SM121),
|
| 3 |
+
with Hugging Face ``transformers`` ``NVFP4Config`` entry points
|
| 4 |
+
(``PackedWeight``, ``pack``, ``gemm``, ``swizzled_sf_shape``).
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
from ._ops import ops
|
| 11 |
+
|
| 12 |
+
FLOAT8_E4M3_MAX = 448.0
|
| 13 |
+
FLOAT4_E2M1_MAX = 6.0
|
| 14 |
+
SF_VEC_SIZE = 16
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def swizzled_sf_shape(rows: int, cols: int) -> tuple[int, int]:
|
| 18 |
+
return (rows + 127) // 128 * 128, (cols // SF_VEC_SIZE + 3) // 4
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@dataclass
|
| 22 |
+
class PackedWeight:
|
| 23 |
+
qweight: torch.Tensor
|
| 24 |
+
sf: torch.Tensor
|
| 25 |
+
global_scale: torch.Tensor
|
| 26 |
+
n: int
|
| 27 |
+
k: int
|
| 28 |
+
swiglu: bool = False
|
| 29 |
+
sf_rowmajor: torch.Tensor | None = None
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _pack_e2m1_torch(wd: torch.Tensor, scale2: float) -> tuple[torch.Tensor, torch.Tensor]:
|
| 33 |
+
n, k = wd.shape
|
| 34 |
+
g = wd.float().reshape(n, k // SF_VEC_SIZE, SF_VEC_SIZE)
|
| 35 |
+
block_amax = g.abs().amax(dim=-1)
|
| 36 |
+
raw_sf = (block_amax / (FLOAT4_E2M1_MAX * max(scale2, 1e-12))).clamp(max=FLOAT8_E4M3_MAX)
|
| 37 |
+
sf_fp8 = raw_sf.to(torch.float8_e4m3fn)
|
| 38 |
+
sf_f32 = sf_fp8.float() * max(scale2, 1e-12)
|
| 39 |
+
scaled = torch.where(sf_f32.unsqueeze(-1) > 0, g / sf_f32.unsqueeze(-1), torch.zeros_like(g))
|
| 40 |
+
ax = scaled.abs()
|
| 41 |
+
nib = torch.zeros_like(ax, dtype=torch.uint8)
|
| 42 |
+
nib = torch.where(ax >= 0.25, torch.tensor(1, device=wd.device, dtype=torch.uint8), nib)
|
| 43 |
+
nib = torch.where(ax >= 0.75, torch.tensor(2, device=wd.device, dtype=torch.uint8), nib)
|
| 44 |
+
nib = torch.where(ax >= 1.25, torch.tensor(3, device=wd.device, dtype=torch.uint8), nib)
|
| 45 |
+
nib = torch.where(ax >= 1.75, torch.tensor(4, device=wd.device, dtype=torch.uint8), nib)
|
| 46 |
+
nib = torch.where(ax >= 2.50, torch.tensor(5, device=wd.device, dtype=torch.uint8), nib)
|
| 47 |
+
nib = torch.where(ax >= 3.50, torch.tensor(6, device=wd.device, dtype=torch.uint8), nib)
|
| 48 |
+
nib = torch.where(ax >= 5.00, torch.tensor(7, device=wd.device, dtype=torch.uint8), nib)
|
| 49 |
+
sign = torch.where(scaled < 0, torch.tensor(8, device=wd.device, dtype=torch.uint8), torch.tensor(0, device=wd.device, dtype=torch.uint8))
|
| 50 |
+
codes = (nib | sign).reshape(n, k // 2, 2)
|
| 51 |
+
qweight = (codes[:, :, 0] | (codes[:, :, 1] << 4)).contiguous()
|
| 52 |
+
return qweight, sf_fp8.view(torch.uint8).contiguous()
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def pack(w: torch.Tensor, device: str | torch.device = "cuda", swiglu: bool = False) -> PackedWeight:
|
| 56 |
+
wd = w.detach().to(device, torch.bfloat16).contiguous()
|
| 57 |
+
n, k = wd.shape
|
| 58 |
+
amax = wd.abs().amax().to(torch.float32).clamp(min=1e-12)
|
| 59 |
+
scale2 = (amax / (FLOAT8_E4M3_MAX * FLOAT4_E2M1_MAX)).item()
|
| 60 |
+
gs = 1.0 / max(scale2, 1e-12)
|
| 61 |
+
qweight, sf_rowmajor = _pack_e2m1_torch(wd, scale2)
|
| 62 |
+
sf_m, sf_n = swizzled_sf_shape(n, k)
|
| 63 |
+
sf = torch.zeros((sf_m, sf_n), device=device, dtype=torch.int32)
|
| 64 |
+
return PackedWeight(
|
| 65 |
+
qweight=qweight,
|
| 66 |
+
sf=sf,
|
| 67 |
+
global_scale=torch.tensor([gs], device=device, dtype=torch.float32),
|
| 68 |
+
n=n,
|
| 69 |
+
k=k,
|
| 70 |
+
swiglu=swiglu,
|
| 71 |
+
sf_rowmajor=sf_rowmajor,
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def gemm(pw: PackedWeight, x: torch.Tensor) -> torch.Tensor:
|
| 76 |
+
x2 = x.reshape(-1, pw.k).to(torch.bfloat16).contiguous()
|
| 77 |
+
out = torch.empty((x2.size(0), pw.n), device=x.device, dtype=torch.bfloat16)
|
| 78 |
+
scale2 = 1.0 / pw.global_scale.item()
|
| 79 |
+
ops.w4a16_gemm(x2, pw.qweight, pw.sf_rowmajor, scale2, out)
|
| 80 |
+
return out.reshape(*x.shape[:-1], pw.n)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def w4a16_gemm(
|
| 84 |
+
A: torch.Tensor, B_packed: torch.Tensor, B_scale: torch.Tensor,
|
| 85 |
+
scale2: float, C: torch.Tensor,
|
| 86 |
+
) -> None:
|
| 87 |
+
"""C = A @ B^T where B is NVFP4. Standard layout."""
|
| 88 |
+
ops.w4a16_gemm(A, B_packed, B_scale, scale2, C)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def w4a16_gemm_t(
|
| 92 |
+
A: torch.Tensor, B_packed: torch.Tensor, B_scale: torch.Tensor,
|
| 93 |
+
scale2: float, C: torch.Tensor,
|
| 94 |
+
) -> None:
|
| 95 |
+
"""Transposed-B variant (N-major) — typically faster for [N>K] shapes."""
|
| 96 |
+
ops.w4a16_gemm_t(A, B_packed, B_scale, scale2, C)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def predequant_nvfp4_to_fp8(
|
| 100 |
+
B_packed: torch.Tensor, B_scale: torch.Tensor,
|
| 101 |
+
scale2: float, B_fp8_out: torch.Tensor,
|
| 102 |
+
) -> None:
|
| 103 |
+
"""Materialize an NVFP4-packed weight into a dense FP8 E4M3 tensor."""
|
| 104 |
+
ops.predequant_nvfp4_to_fp8(B_packed, B_scale, scale2, B_fp8_out)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def fp8_gemm_t(A: torch.Tensor, B_fp8: torch.Tensor, C: torch.Tensor) -> None:
|
| 108 |
+
"""C = A @ B^T where both A is BF16 and B is FP8 E4M3."""
|
| 109 |
+
ops.fp8_gemm_t(A, B_fp8, C)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def bf16_to_fp8(src: torch.Tensor, dst: torch.Tensor) -> None:
|
| 113 |
+
"""Pair-wise BF16 → FP8 E4M3 quantization (no scaling, uses PTX cvt)."""
|
| 114 |
+
ops.bf16_to_fp8(src, dst)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
__all__ = [
|
| 118 |
+
"PackedWeight",
|
| 119 |
+
"pack",
|
| 120 |
+
"gemm",
|
| 121 |
+
"swizzled_sf_shape",
|
| 122 |
+
"w4a16_gemm",
|
| 123 |
+
"w4a16_gemm_t",
|
| 124 |
+
"predequant_nvfp4_to_fp8",
|
| 125 |
+
"fp8_gemm_t",
|
| 126 |
+
"bf16_to_fp8",
|
| 127 |
+
]
|
build/torch212-cxx11-cu130-aarch64-linux/__init__.py
CHANGED
|
@@ -1,26 +1,83 @@
|
|
| 1 |
# SPDX-License-Identifier: AGPL-3.0-only
|
| 2 |
-
"""Atlas dense NVFP4/FP8 GEMM kernels for Qwen3.6-27B on GB10 (SM121)
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
(intermediate_size=17408, hidden=5120). Two precisions are supported:
|
| 6 |
-
|
| 7 |
-
- W4A16 (NVFP4 weight, BF16 activation): direct path
|
| 8 |
-
- FP8 W/A: via ``predequant_nvfp4_to_fp8`` + ``fp8_gemm_t`` for
|
| 9 |
-
KV-cache scenarios where the weight is loaded as NVFP4 but the
|
| 10 |
-
GEMM runs in FP8 against an FP8-packed activation.
|
| 11 |
"""
|
| 12 |
|
|
|
|
| 13 |
import torch
|
| 14 |
|
| 15 |
from ._ops import ops
|
| 16 |
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
|
| 26 |
def w4a16_gemm(
|
|
@@ -55,3 +112,16 @@ def fp8_gemm_t(A: torch.Tensor, B_fp8: torch.Tensor, C: torch.Tensor) -> None:
|
|
| 55 |
def bf16_to_fp8(src: torch.Tensor, dst: torch.Tensor) -> None:
|
| 56 |
"""Pair-wise BF16 → FP8 E4M3 quantization (no scaling, uses PTX cvt)."""
|
| 57 |
ops.bf16_to_fp8(src, dst)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# SPDX-License-Identifier: AGPL-3.0-only
|
| 2 |
+
"""Atlas dense NVFP4/FP8 GEMM kernels for Qwen3.6-27B on GB10 (SM121),
|
| 3 |
+
with Hugging Face ``transformers`` ``NVFP4Config`` entry points
|
| 4 |
+
(``PackedWeight``, ``pack``, ``gemm``, ``swizzled_sf_shape``).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
"""
|
| 6 |
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
import torch
|
| 9 |
|
| 10 |
from ._ops import ops
|
| 11 |
|
| 12 |
+
FLOAT8_E4M3_MAX = 448.0
|
| 13 |
+
FLOAT4_E2M1_MAX = 6.0
|
| 14 |
+
SF_VEC_SIZE = 16
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def swizzled_sf_shape(rows: int, cols: int) -> tuple[int, int]:
|
| 18 |
+
return (rows + 127) // 128 * 128, (cols // SF_VEC_SIZE + 3) // 4
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@dataclass
|
| 22 |
+
class PackedWeight:
|
| 23 |
+
qweight: torch.Tensor
|
| 24 |
+
sf: torch.Tensor
|
| 25 |
+
global_scale: torch.Tensor
|
| 26 |
+
n: int
|
| 27 |
+
k: int
|
| 28 |
+
swiglu: bool = False
|
| 29 |
+
sf_rowmajor: torch.Tensor | None = None
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _pack_e2m1_torch(wd: torch.Tensor, scale2: float) -> tuple[torch.Tensor, torch.Tensor]:
|
| 33 |
+
n, k = wd.shape
|
| 34 |
+
g = wd.float().reshape(n, k // SF_VEC_SIZE, SF_VEC_SIZE)
|
| 35 |
+
block_amax = g.abs().amax(dim=-1)
|
| 36 |
+
raw_sf = (block_amax / (FLOAT4_E2M1_MAX * max(scale2, 1e-12))).clamp(max=FLOAT8_E4M3_MAX)
|
| 37 |
+
sf_fp8 = raw_sf.to(torch.float8_e4m3fn)
|
| 38 |
+
sf_f32 = sf_fp8.float() * max(scale2, 1e-12)
|
| 39 |
+
scaled = torch.where(sf_f32.unsqueeze(-1) > 0, g / sf_f32.unsqueeze(-1), torch.zeros_like(g))
|
| 40 |
+
ax = scaled.abs()
|
| 41 |
+
nib = torch.zeros_like(ax, dtype=torch.uint8)
|
| 42 |
+
nib = torch.where(ax >= 0.25, torch.tensor(1, device=wd.device, dtype=torch.uint8), nib)
|
| 43 |
+
nib = torch.where(ax >= 0.75, torch.tensor(2, device=wd.device, dtype=torch.uint8), nib)
|
| 44 |
+
nib = torch.where(ax >= 1.25, torch.tensor(3, device=wd.device, dtype=torch.uint8), nib)
|
| 45 |
+
nib = torch.where(ax >= 1.75, torch.tensor(4, device=wd.device, dtype=torch.uint8), nib)
|
| 46 |
+
nib = torch.where(ax >= 2.50, torch.tensor(5, device=wd.device, dtype=torch.uint8), nib)
|
| 47 |
+
nib = torch.where(ax >= 3.50, torch.tensor(6, device=wd.device, dtype=torch.uint8), nib)
|
| 48 |
+
nib = torch.where(ax >= 5.00, torch.tensor(7, device=wd.device, dtype=torch.uint8), nib)
|
| 49 |
+
sign = torch.where(scaled < 0, torch.tensor(8, device=wd.device, dtype=torch.uint8), torch.tensor(0, device=wd.device, dtype=torch.uint8))
|
| 50 |
+
codes = (nib | sign).reshape(n, k // 2, 2)
|
| 51 |
+
qweight = (codes[:, :, 0] | (codes[:, :, 1] << 4)).contiguous()
|
| 52 |
+
return qweight, sf_fp8.view(torch.uint8).contiguous()
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def pack(w: torch.Tensor, device: str | torch.device = "cuda", swiglu: bool = False) -> PackedWeight:
|
| 56 |
+
wd = w.detach().to(device, torch.bfloat16).contiguous()
|
| 57 |
+
n, k = wd.shape
|
| 58 |
+
amax = wd.abs().amax().to(torch.float32).clamp(min=1e-12)
|
| 59 |
+
scale2 = (amax / (FLOAT8_E4M3_MAX * FLOAT4_E2M1_MAX)).item()
|
| 60 |
+
gs = 1.0 / max(scale2, 1e-12)
|
| 61 |
+
qweight, sf_rowmajor = _pack_e2m1_torch(wd, scale2)
|
| 62 |
+
sf_m, sf_n = swizzled_sf_shape(n, k)
|
| 63 |
+
sf = torch.zeros((sf_m, sf_n), device=device, dtype=torch.int32)
|
| 64 |
+
return PackedWeight(
|
| 65 |
+
qweight=qweight,
|
| 66 |
+
sf=sf,
|
| 67 |
+
global_scale=torch.tensor([gs], device=device, dtype=torch.float32),
|
| 68 |
+
n=n,
|
| 69 |
+
k=k,
|
| 70 |
+
swiglu=swiglu,
|
| 71 |
+
sf_rowmajor=sf_rowmajor,
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def gemm(pw: PackedWeight, x: torch.Tensor) -> torch.Tensor:
|
| 76 |
+
x2 = x.reshape(-1, pw.k).to(torch.bfloat16).contiguous()
|
| 77 |
+
out = torch.empty((x2.size(0), pw.n), device=x.device, dtype=torch.bfloat16)
|
| 78 |
+
scale2 = 1.0 / pw.global_scale.item()
|
| 79 |
+
ops.w4a16_gemm(x2, pw.qweight, pw.sf_rowmajor, scale2, out)
|
| 80 |
+
return out.reshape(*x.shape[:-1], pw.n)
|
| 81 |
|
| 82 |
|
| 83 |
def w4a16_gemm(
|
|
|
|
| 112 |
def bf16_to_fp8(src: torch.Tensor, dst: torch.Tensor) -> None:
|
| 113 |
"""Pair-wise BF16 → FP8 E4M3 quantization (no scaling, uses PTX cvt)."""
|
| 114 |
ops.bf16_to_fp8(src, dst)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
__all__ = [
|
| 118 |
+
"PackedWeight",
|
| 119 |
+
"pack",
|
| 120 |
+
"gemm",
|
| 121 |
+
"swizzled_sf_shape",
|
| 122 |
+
"w4a16_gemm",
|
| 123 |
+
"w4a16_gemm_t",
|
| 124 |
+
"predequant_nvfp4_to_fp8",
|
| 125 |
+
"fp8_gemm_t",
|
| 126 |
+
"bf16_to_fp8",
|
| 127 |
+
]
|
build/torch212-cxx11-cu130-aarch64-linux/metadata.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
{
|
| 2 |
"name": "nvfp4-dense-gemm",
|
| 3 |
"id": "_nvfp4_dense_gemm_cuda_bcba8ad",
|
| 4 |
-
"version":
|
| 5 |
"license": "AGPL-3.0-only",
|
| 6 |
"python-depends": [],
|
| 7 |
"backend": {
|
|
|
|
| 1 |
{
|
| 2 |
"name": "nvfp4-dense-gemm",
|
| 3 |
"id": "_nvfp4_dense_gemm_cuda_bcba8ad",
|
| 4 |
+
"version": 1,
|
| 5 |
"license": "AGPL-3.0-only",
|
| 6 |
"python-depends": [],
|
| 7 |
"backend": {
|
build/torch212-cxx11-cu130-aarch64-linux/nvfp4_dense_gemm/__init__.py
CHANGED
|
@@ -1,26 +1,127 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: AGPL-3.0-only
|
| 2 |
+
"""Atlas dense NVFP4/FP8 GEMM kernels for Qwen3.6-27B on GB10 (SM121),
|
| 3 |
+
with Hugging Face ``transformers`` ``NVFP4Config`` entry points
|
| 4 |
+
(``PackedWeight``, ``pack``, ``gemm``, ``swizzled_sf_shape``).
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
from ._ops import ops
|
| 11 |
+
|
| 12 |
+
FLOAT8_E4M3_MAX = 448.0
|
| 13 |
+
FLOAT4_E2M1_MAX = 6.0
|
| 14 |
+
SF_VEC_SIZE = 16
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def swizzled_sf_shape(rows: int, cols: int) -> tuple[int, int]:
|
| 18 |
+
return (rows + 127) // 128 * 128, (cols // SF_VEC_SIZE + 3) // 4
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@dataclass
|
| 22 |
+
class PackedWeight:
|
| 23 |
+
qweight: torch.Tensor
|
| 24 |
+
sf: torch.Tensor
|
| 25 |
+
global_scale: torch.Tensor
|
| 26 |
+
n: int
|
| 27 |
+
k: int
|
| 28 |
+
swiglu: bool = False
|
| 29 |
+
sf_rowmajor: torch.Tensor | None = None
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _pack_e2m1_torch(wd: torch.Tensor, scale2: float) -> tuple[torch.Tensor, torch.Tensor]:
|
| 33 |
+
n, k = wd.shape
|
| 34 |
+
g = wd.float().reshape(n, k // SF_VEC_SIZE, SF_VEC_SIZE)
|
| 35 |
+
block_amax = g.abs().amax(dim=-1)
|
| 36 |
+
raw_sf = (block_amax / (FLOAT4_E2M1_MAX * max(scale2, 1e-12))).clamp(max=FLOAT8_E4M3_MAX)
|
| 37 |
+
sf_fp8 = raw_sf.to(torch.float8_e4m3fn)
|
| 38 |
+
sf_f32 = sf_fp8.float() * max(scale2, 1e-12)
|
| 39 |
+
scaled = torch.where(sf_f32.unsqueeze(-1) > 0, g / sf_f32.unsqueeze(-1), torch.zeros_like(g))
|
| 40 |
+
ax = scaled.abs()
|
| 41 |
+
nib = torch.zeros_like(ax, dtype=torch.uint8)
|
| 42 |
+
nib = torch.where(ax >= 0.25, torch.tensor(1, device=wd.device, dtype=torch.uint8), nib)
|
| 43 |
+
nib = torch.where(ax >= 0.75, torch.tensor(2, device=wd.device, dtype=torch.uint8), nib)
|
| 44 |
+
nib = torch.where(ax >= 1.25, torch.tensor(3, device=wd.device, dtype=torch.uint8), nib)
|
| 45 |
+
nib = torch.where(ax >= 1.75, torch.tensor(4, device=wd.device, dtype=torch.uint8), nib)
|
| 46 |
+
nib = torch.where(ax >= 2.50, torch.tensor(5, device=wd.device, dtype=torch.uint8), nib)
|
| 47 |
+
nib = torch.where(ax >= 3.50, torch.tensor(6, device=wd.device, dtype=torch.uint8), nib)
|
| 48 |
+
nib = torch.where(ax >= 5.00, torch.tensor(7, device=wd.device, dtype=torch.uint8), nib)
|
| 49 |
+
sign = torch.where(scaled < 0, torch.tensor(8, device=wd.device, dtype=torch.uint8), torch.tensor(0, device=wd.device, dtype=torch.uint8))
|
| 50 |
+
codes = (nib | sign).reshape(n, k // 2, 2)
|
| 51 |
+
qweight = (codes[:, :, 0] | (codes[:, :, 1] << 4)).contiguous()
|
| 52 |
+
return qweight, sf_fp8.view(torch.uint8).contiguous()
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def pack(w: torch.Tensor, device: str | torch.device = "cuda", swiglu: bool = False) -> PackedWeight:
|
| 56 |
+
wd = w.detach().to(device, torch.bfloat16).contiguous()
|
| 57 |
+
n, k = wd.shape
|
| 58 |
+
amax = wd.abs().amax().to(torch.float32).clamp(min=1e-12)
|
| 59 |
+
scale2 = (amax / (FLOAT8_E4M3_MAX * FLOAT4_E2M1_MAX)).item()
|
| 60 |
+
gs = 1.0 / max(scale2, 1e-12)
|
| 61 |
+
qweight, sf_rowmajor = _pack_e2m1_torch(wd, scale2)
|
| 62 |
+
sf_m, sf_n = swizzled_sf_shape(n, k)
|
| 63 |
+
sf = torch.zeros((sf_m, sf_n), device=device, dtype=torch.int32)
|
| 64 |
+
return PackedWeight(
|
| 65 |
+
qweight=qweight,
|
| 66 |
+
sf=sf,
|
| 67 |
+
global_scale=torch.tensor([gs], device=device, dtype=torch.float32),
|
| 68 |
+
n=n,
|
| 69 |
+
k=k,
|
| 70 |
+
swiglu=swiglu,
|
| 71 |
+
sf_rowmajor=sf_rowmajor,
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def gemm(pw: PackedWeight, x: torch.Tensor) -> torch.Tensor:
|
| 76 |
+
x2 = x.reshape(-1, pw.k).to(torch.bfloat16).contiguous()
|
| 77 |
+
out = torch.empty((x2.size(0), pw.n), device=x.device, dtype=torch.bfloat16)
|
| 78 |
+
scale2 = 1.0 / pw.global_scale.item()
|
| 79 |
+
ops.w4a16_gemm(x2, pw.qweight, pw.sf_rowmajor, scale2, out)
|
| 80 |
+
return out.reshape(*x.shape[:-1], pw.n)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def w4a16_gemm(
|
| 84 |
+
A: torch.Tensor, B_packed: torch.Tensor, B_scale: torch.Tensor,
|
| 85 |
+
scale2: float, C: torch.Tensor,
|
| 86 |
+
) -> None:
|
| 87 |
+
"""C = A @ B^T where B is NVFP4. Standard layout."""
|
| 88 |
+
ops.w4a16_gemm(A, B_packed, B_scale, scale2, C)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def w4a16_gemm_t(
|
| 92 |
+
A: torch.Tensor, B_packed: torch.Tensor, B_scale: torch.Tensor,
|
| 93 |
+
scale2: float, C: torch.Tensor,
|
| 94 |
+
) -> None:
|
| 95 |
+
"""Transposed-B variant (N-major) — typically faster for [N>K] shapes."""
|
| 96 |
+
ops.w4a16_gemm_t(A, B_packed, B_scale, scale2, C)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def predequant_nvfp4_to_fp8(
|
| 100 |
+
B_packed: torch.Tensor, B_scale: torch.Tensor,
|
| 101 |
+
scale2: float, B_fp8_out: torch.Tensor,
|
| 102 |
+
) -> None:
|
| 103 |
+
"""Materialize an NVFP4-packed weight into a dense FP8 E4M3 tensor."""
|
| 104 |
+
ops.predequant_nvfp4_to_fp8(B_packed, B_scale, scale2, B_fp8_out)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def fp8_gemm_t(A: torch.Tensor, B_fp8: torch.Tensor, C: torch.Tensor) -> None:
|
| 108 |
+
"""C = A @ B^T where both A is BF16 and B is FP8 E4M3."""
|
| 109 |
+
ops.fp8_gemm_t(A, B_fp8, C)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def bf16_to_fp8(src: torch.Tensor, dst: torch.Tensor) -> None:
|
| 113 |
+
"""Pair-wise BF16 → FP8 E4M3 quantization (no scaling, uses PTX cvt)."""
|
| 114 |
+
ops.bf16_to_fp8(src, dst)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
__all__ = [
|
| 118 |
+
"PackedWeight",
|
| 119 |
+
"pack",
|
| 120 |
+
"gemm",
|
| 121 |
+
"swizzled_sf_shape",
|
| 122 |
+
"w4a16_gemm",
|
| 123 |
+
"w4a16_gemm_t",
|
| 124 |
+
"predequant_nvfp4_to_fp8",
|
| 125 |
+
"fp8_gemm_t",
|
| 126 |
+
"bf16_to_fp8",
|
| 127 |
+
]
|
build/torch212-cxx11-cu132-aarch64-linux/__init__.py
CHANGED
|
@@ -1,26 +1,83 @@
|
|
| 1 |
# SPDX-License-Identifier: AGPL-3.0-only
|
| 2 |
-
"""Atlas dense NVFP4/FP8 GEMM kernels for Qwen3.6-27B on GB10 (SM121)
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
(intermediate_size=17408, hidden=5120). Two precisions are supported:
|
| 6 |
-
|
| 7 |
-
- W4A16 (NVFP4 weight, BF16 activation): direct path
|
| 8 |
-
- FP8 W/A: via ``predequant_nvfp4_to_fp8`` + ``fp8_gemm_t`` for
|
| 9 |
-
KV-cache scenarios where the weight is loaded as NVFP4 but the
|
| 10 |
-
GEMM runs in FP8 against an FP8-packed activation.
|
| 11 |
"""
|
| 12 |
|
|
|
|
| 13 |
import torch
|
| 14 |
|
| 15 |
from ._ops import ops
|
| 16 |
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
|
| 26 |
def w4a16_gemm(
|
|
@@ -55,3 +112,16 @@ def fp8_gemm_t(A: torch.Tensor, B_fp8: torch.Tensor, C: torch.Tensor) -> None:
|
|
| 55 |
def bf16_to_fp8(src: torch.Tensor, dst: torch.Tensor) -> None:
|
| 56 |
"""Pair-wise BF16 → FP8 E4M3 quantization (no scaling, uses PTX cvt)."""
|
| 57 |
ops.bf16_to_fp8(src, dst)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# SPDX-License-Identifier: AGPL-3.0-only
|
| 2 |
+
"""Atlas dense NVFP4/FP8 GEMM kernels for Qwen3.6-27B on GB10 (SM121),
|
| 3 |
+
with Hugging Face ``transformers`` ``NVFP4Config`` entry points
|
| 4 |
+
(``PackedWeight``, ``pack``, ``gemm``, ``swizzled_sf_shape``).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
"""
|
| 6 |
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
import torch
|
| 9 |
|
| 10 |
from ._ops import ops
|
| 11 |
|
| 12 |
+
FLOAT8_E4M3_MAX = 448.0
|
| 13 |
+
FLOAT4_E2M1_MAX = 6.0
|
| 14 |
+
SF_VEC_SIZE = 16
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def swizzled_sf_shape(rows: int, cols: int) -> tuple[int, int]:
|
| 18 |
+
return (rows + 127) // 128 * 128, (cols // SF_VEC_SIZE + 3) // 4
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@dataclass
|
| 22 |
+
class PackedWeight:
|
| 23 |
+
qweight: torch.Tensor
|
| 24 |
+
sf: torch.Tensor
|
| 25 |
+
global_scale: torch.Tensor
|
| 26 |
+
n: int
|
| 27 |
+
k: int
|
| 28 |
+
swiglu: bool = False
|
| 29 |
+
sf_rowmajor: torch.Tensor | None = None
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _pack_e2m1_torch(wd: torch.Tensor, scale2: float) -> tuple[torch.Tensor, torch.Tensor]:
|
| 33 |
+
n, k = wd.shape
|
| 34 |
+
g = wd.float().reshape(n, k // SF_VEC_SIZE, SF_VEC_SIZE)
|
| 35 |
+
block_amax = g.abs().amax(dim=-1)
|
| 36 |
+
raw_sf = (block_amax / (FLOAT4_E2M1_MAX * max(scale2, 1e-12))).clamp(max=FLOAT8_E4M3_MAX)
|
| 37 |
+
sf_fp8 = raw_sf.to(torch.float8_e4m3fn)
|
| 38 |
+
sf_f32 = sf_fp8.float() * max(scale2, 1e-12)
|
| 39 |
+
scaled = torch.where(sf_f32.unsqueeze(-1) > 0, g / sf_f32.unsqueeze(-1), torch.zeros_like(g))
|
| 40 |
+
ax = scaled.abs()
|
| 41 |
+
nib = torch.zeros_like(ax, dtype=torch.uint8)
|
| 42 |
+
nib = torch.where(ax >= 0.25, torch.tensor(1, device=wd.device, dtype=torch.uint8), nib)
|
| 43 |
+
nib = torch.where(ax >= 0.75, torch.tensor(2, device=wd.device, dtype=torch.uint8), nib)
|
| 44 |
+
nib = torch.where(ax >= 1.25, torch.tensor(3, device=wd.device, dtype=torch.uint8), nib)
|
| 45 |
+
nib = torch.where(ax >= 1.75, torch.tensor(4, device=wd.device, dtype=torch.uint8), nib)
|
| 46 |
+
nib = torch.where(ax >= 2.50, torch.tensor(5, device=wd.device, dtype=torch.uint8), nib)
|
| 47 |
+
nib = torch.where(ax >= 3.50, torch.tensor(6, device=wd.device, dtype=torch.uint8), nib)
|
| 48 |
+
nib = torch.where(ax >= 5.00, torch.tensor(7, device=wd.device, dtype=torch.uint8), nib)
|
| 49 |
+
sign = torch.where(scaled < 0, torch.tensor(8, device=wd.device, dtype=torch.uint8), torch.tensor(0, device=wd.device, dtype=torch.uint8))
|
| 50 |
+
codes = (nib | sign).reshape(n, k // 2, 2)
|
| 51 |
+
qweight = (codes[:, :, 0] | (codes[:, :, 1] << 4)).contiguous()
|
| 52 |
+
return qweight, sf_fp8.view(torch.uint8).contiguous()
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def pack(w: torch.Tensor, device: str | torch.device = "cuda", swiglu: bool = False) -> PackedWeight:
|
| 56 |
+
wd = w.detach().to(device, torch.bfloat16).contiguous()
|
| 57 |
+
n, k = wd.shape
|
| 58 |
+
amax = wd.abs().amax().to(torch.float32).clamp(min=1e-12)
|
| 59 |
+
scale2 = (amax / (FLOAT8_E4M3_MAX * FLOAT4_E2M1_MAX)).item()
|
| 60 |
+
gs = 1.0 / max(scale2, 1e-12)
|
| 61 |
+
qweight, sf_rowmajor = _pack_e2m1_torch(wd, scale2)
|
| 62 |
+
sf_m, sf_n = swizzled_sf_shape(n, k)
|
| 63 |
+
sf = torch.zeros((sf_m, sf_n), device=device, dtype=torch.int32)
|
| 64 |
+
return PackedWeight(
|
| 65 |
+
qweight=qweight,
|
| 66 |
+
sf=sf,
|
| 67 |
+
global_scale=torch.tensor([gs], device=device, dtype=torch.float32),
|
| 68 |
+
n=n,
|
| 69 |
+
k=k,
|
| 70 |
+
swiglu=swiglu,
|
| 71 |
+
sf_rowmajor=sf_rowmajor,
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def gemm(pw: PackedWeight, x: torch.Tensor) -> torch.Tensor:
|
| 76 |
+
x2 = x.reshape(-1, pw.k).to(torch.bfloat16).contiguous()
|
| 77 |
+
out = torch.empty((x2.size(0), pw.n), device=x.device, dtype=torch.bfloat16)
|
| 78 |
+
scale2 = 1.0 / pw.global_scale.item()
|
| 79 |
+
ops.w4a16_gemm(x2, pw.qweight, pw.sf_rowmajor, scale2, out)
|
| 80 |
+
return out.reshape(*x.shape[:-1], pw.n)
|
| 81 |
|
| 82 |
|
| 83 |
def w4a16_gemm(
|
|
|
|
| 112 |
def bf16_to_fp8(src: torch.Tensor, dst: torch.Tensor) -> None:
|
| 113 |
"""Pair-wise BF16 → FP8 E4M3 quantization (no scaling, uses PTX cvt)."""
|
| 114 |
ops.bf16_to_fp8(src, dst)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
__all__ = [
|
| 118 |
+
"PackedWeight",
|
| 119 |
+
"pack",
|
| 120 |
+
"gemm",
|
| 121 |
+
"swizzled_sf_shape",
|
| 122 |
+
"w4a16_gemm",
|
| 123 |
+
"w4a16_gemm_t",
|
| 124 |
+
"predequant_nvfp4_to_fp8",
|
| 125 |
+
"fp8_gemm_t",
|
| 126 |
+
"bf16_to_fp8",
|
| 127 |
+
]
|
build/torch212-cxx11-cu132-aarch64-linux/metadata.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
{
|
| 2 |
"name": "nvfp4-dense-gemm",
|
| 3 |
"id": "_nvfp4_dense_gemm_cuda_bcba8ad",
|
| 4 |
-
"version":
|
| 5 |
"license": "AGPL-3.0-only",
|
| 6 |
"python-depends": [],
|
| 7 |
"backend": {
|
|
|
|
| 1 |
{
|
| 2 |
"name": "nvfp4-dense-gemm",
|
| 3 |
"id": "_nvfp4_dense_gemm_cuda_bcba8ad",
|
| 4 |
+
"version": 1,
|
| 5 |
"license": "AGPL-3.0-only",
|
| 6 |
"python-depends": [],
|
| 7 |
"backend": {
|
build/torch212-cxx11-cu132-aarch64-linux/nvfp4_dense_gemm/__init__.py
CHANGED
|
@@ -1,26 +1,127 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: AGPL-3.0-only
|
| 2 |
+
"""Atlas dense NVFP4/FP8 GEMM kernels for Qwen3.6-27B on GB10 (SM121),
|
| 3 |
+
with Hugging Face ``transformers`` ``NVFP4Config`` entry points
|
| 4 |
+
(``PackedWeight``, ``pack``, ``gemm``, ``swizzled_sf_shape``).
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
from ._ops import ops
|
| 11 |
+
|
| 12 |
+
FLOAT8_E4M3_MAX = 448.0
|
| 13 |
+
FLOAT4_E2M1_MAX = 6.0
|
| 14 |
+
SF_VEC_SIZE = 16
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def swizzled_sf_shape(rows: int, cols: int) -> tuple[int, int]:
|
| 18 |
+
return (rows + 127) // 128 * 128, (cols // SF_VEC_SIZE + 3) // 4
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@dataclass
|
| 22 |
+
class PackedWeight:
|
| 23 |
+
qweight: torch.Tensor
|
| 24 |
+
sf: torch.Tensor
|
| 25 |
+
global_scale: torch.Tensor
|
| 26 |
+
n: int
|
| 27 |
+
k: int
|
| 28 |
+
swiglu: bool = False
|
| 29 |
+
sf_rowmajor: torch.Tensor | None = None
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _pack_e2m1_torch(wd: torch.Tensor, scale2: float) -> tuple[torch.Tensor, torch.Tensor]:
|
| 33 |
+
n, k = wd.shape
|
| 34 |
+
g = wd.float().reshape(n, k // SF_VEC_SIZE, SF_VEC_SIZE)
|
| 35 |
+
block_amax = g.abs().amax(dim=-1)
|
| 36 |
+
raw_sf = (block_amax / (FLOAT4_E2M1_MAX * max(scale2, 1e-12))).clamp(max=FLOAT8_E4M3_MAX)
|
| 37 |
+
sf_fp8 = raw_sf.to(torch.float8_e4m3fn)
|
| 38 |
+
sf_f32 = sf_fp8.float() * max(scale2, 1e-12)
|
| 39 |
+
scaled = torch.where(sf_f32.unsqueeze(-1) > 0, g / sf_f32.unsqueeze(-1), torch.zeros_like(g))
|
| 40 |
+
ax = scaled.abs()
|
| 41 |
+
nib = torch.zeros_like(ax, dtype=torch.uint8)
|
| 42 |
+
nib = torch.where(ax >= 0.25, torch.tensor(1, device=wd.device, dtype=torch.uint8), nib)
|
| 43 |
+
nib = torch.where(ax >= 0.75, torch.tensor(2, device=wd.device, dtype=torch.uint8), nib)
|
| 44 |
+
nib = torch.where(ax >= 1.25, torch.tensor(3, device=wd.device, dtype=torch.uint8), nib)
|
| 45 |
+
nib = torch.where(ax >= 1.75, torch.tensor(4, device=wd.device, dtype=torch.uint8), nib)
|
| 46 |
+
nib = torch.where(ax >= 2.50, torch.tensor(5, device=wd.device, dtype=torch.uint8), nib)
|
| 47 |
+
nib = torch.where(ax >= 3.50, torch.tensor(6, device=wd.device, dtype=torch.uint8), nib)
|
| 48 |
+
nib = torch.where(ax >= 5.00, torch.tensor(7, device=wd.device, dtype=torch.uint8), nib)
|
| 49 |
+
sign = torch.where(scaled < 0, torch.tensor(8, device=wd.device, dtype=torch.uint8), torch.tensor(0, device=wd.device, dtype=torch.uint8))
|
| 50 |
+
codes = (nib | sign).reshape(n, k // 2, 2)
|
| 51 |
+
qweight = (codes[:, :, 0] | (codes[:, :, 1] << 4)).contiguous()
|
| 52 |
+
return qweight, sf_fp8.view(torch.uint8).contiguous()
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def pack(w: torch.Tensor, device: str | torch.device = "cuda", swiglu: bool = False) -> PackedWeight:
|
| 56 |
+
wd = w.detach().to(device, torch.bfloat16).contiguous()
|
| 57 |
+
n, k = wd.shape
|
| 58 |
+
amax = wd.abs().amax().to(torch.float32).clamp(min=1e-12)
|
| 59 |
+
scale2 = (amax / (FLOAT8_E4M3_MAX * FLOAT4_E2M1_MAX)).item()
|
| 60 |
+
gs = 1.0 / max(scale2, 1e-12)
|
| 61 |
+
qweight, sf_rowmajor = _pack_e2m1_torch(wd, scale2)
|
| 62 |
+
sf_m, sf_n = swizzled_sf_shape(n, k)
|
| 63 |
+
sf = torch.zeros((sf_m, sf_n), device=device, dtype=torch.int32)
|
| 64 |
+
return PackedWeight(
|
| 65 |
+
qweight=qweight,
|
| 66 |
+
sf=sf,
|
| 67 |
+
global_scale=torch.tensor([gs], device=device, dtype=torch.float32),
|
| 68 |
+
n=n,
|
| 69 |
+
k=k,
|
| 70 |
+
swiglu=swiglu,
|
| 71 |
+
sf_rowmajor=sf_rowmajor,
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def gemm(pw: PackedWeight, x: torch.Tensor) -> torch.Tensor:
|
| 76 |
+
x2 = x.reshape(-1, pw.k).to(torch.bfloat16).contiguous()
|
| 77 |
+
out = torch.empty((x2.size(0), pw.n), device=x.device, dtype=torch.bfloat16)
|
| 78 |
+
scale2 = 1.0 / pw.global_scale.item()
|
| 79 |
+
ops.w4a16_gemm(x2, pw.qweight, pw.sf_rowmajor, scale2, out)
|
| 80 |
+
return out.reshape(*x.shape[:-1], pw.n)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def w4a16_gemm(
|
| 84 |
+
A: torch.Tensor, B_packed: torch.Tensor, B_scale: torch.Tensor,
|
| 85 |
+
scale2: float, C: torch.Tensor,
|
| 86 |
+
) -> None:
|
| 87 |
+
"""C = A @ B^T where B is NVFP4. Standard layout."""
|
| 88 |
+
ops.w4a16_gemm(A, B_packed, B_scale, scale2, C)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def w4a16_gemm_t(
|
| 92 |
+
A: torch.Tensor, B_packed: torch.Tensor, B_scale: torch.Tensor,
|
| 93 |
+
scale2: float, C: torch.Tensor,
|
| 94 |
+
) -> None:
|
| 95 |
+
"""Transposed-B variant (N-major) — typically faster for [N>K] shapes."""
|
| 96 |
+
ops.w4a16_gemm_t(A, B_packed, B_scale, scale2, C)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def predequant_nvfp4_to_fp8(
|
| 100 |
+
B_packed: torch.Tensor, B_scale: torch.Tensor,
|
| 101 |
+
scale2: float, B_fp8_out: torch.Tensor,
|
| 102 |
+
) -> None:
|
| 103 |
+
"""Materialize an NVFP4-packed weight into a dense FP8 E4M3 tensor."""
|
| 104 |
+
ops.predequant_nvfp4_to_fp8(B_packed, B_scale, scale2, B_fp8_out)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def fp8_gemm_t(A: torch.Tensor, B_fp8: torch.Tensor, C: torch.Tensor) -> None:
|
| 108 |
+
"""C = A @ B^T where both A is BF16 and B is FP8 E4M3."""
|
| 109 |
+
ops.fp8_gemm_t(A, B_fp8, C)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def bf16_to_fp8(src: torch.Tensor, dst: torch.Tensor) -> None:
|
| 113 |
+
"""Pair-wise BF16 → FP8 E4M3 quantization (no scaling, uses PTX cvt)."""
|
| 114 |
+
ops.bf16_to_fp8(src, dst)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
__all__ = [
|
| 118 |
+
"PackedWeight",
|
| 119 |
+
"pack",
|
| 120 |
+
"gemm",
|
| 121 |
+
"swizzled_sf_shape",
|
| 122 |
+
"w4a16_gemm",
|
| 123 |
+
"w4a16_gemm_t",
|
| 124 |
+
"predequant_nvfp4_to_fp8",
|
| 125 |
+
"fp8_gemm_t",
|
| 126 |
+
"bf16_to_fp8",
|
| 127 |
+
]
|