Promote latest kernel artifacts to main
Browse files- README.md +113 -6
- benchmarks/benchmark.py +93 -6
- build/torch211-cxx11-cu128-x86_64-linux/__init__.py +122 -0
- build/torch211-cxx11-cu128-x86_64-linux/{_fp4_gemm_cuda_dc57d15.abi3.so → _fp4_gemm_cuda_6971f7a.abi3.so} +2 -2
- build/torch211-cxx11-cu128-x86_64-linux/_ops.py +3 -3
- build/torch211-cxx11-cu128-x86_64-linux/metadata.json +5 -5
- build/torch211-cxx11-cu130-x86_64-linux/__init__.py +122 -0
- build/torch211-cxx11-cu130-x86_64-linux/{_fp4_gemm_cuda_dc57d15.abi3.so → _fp4_gemm_cuda_6971f7a.abi3.so} +2 -2
- build/torch211-cxx11-cu130-x86_64-linux/_ops.py +3 -3
- build/torch211-cxx11-cu130-x86_64-linux/metadata.json +5 -5
- build/torch212-cxx11-cu130-x86_64-linux/__init__.py +122 -0
- build/torch212-cxx11-cu130-x86_64-linux/{_fp4_gemm_cuda_dc57d15.abi3.so → _fp4_gemm_cuda_6971f7a.abi3.so} +2 -2
- build/torch212-cxx11-cu130-x86_64-linux/_ops.py +3 -3
- build/torch212-cxx11-cu130-x86_64-linux/metadata.json +5 -5
- build/torch212-cxx11-cu132-x86_64-linux/__init__.py +122 -0
- build/torch212-cxx11-cu132-x86_64-linux/{_fp4_gemm_cuda_dc57d15.abi3.so → _fp4_gemm_cuda_6971f7a.abi3.so} +2 -2
- build/torch212-cxx11-cu132-x86_64-linux/_ops.py +3 -3
- build/torch212-cxx11-cu132-x86_64-linux/metadata.json +5 -5
- build/torch213-cxx11-cu130-x86_64-linux/__init__.py +122 -0
- build/torch213-cxx11-cu130-x86_64-linux/_fp4_gemm_cuda_6971f7a.abi3.so +3 -0
- build/torch213-cxx11-cu130-x86_64-linux/_fp4_gemm_cuda_dc57d15.abi3.so +0 -3
- build/torch213-cxx11-cu130-x86_64-linux/_ops.py +3 -3
- build/torch213-cxx11-cu130-x86_64-linux/metadata.json +5 -5
- build/torch213-cxx11-cu132-x86_64-linux/__init__.py +122 -0
- build/torch213-cxx11-cu132-x86_64-linux/_fp4_gemm_cuda_6971f7a.abi3.so +3 -0
- build/torch213-cxx11-cu132-x86_64-linux/_fp4_gemm_cuda_dc57d15.abi3.so +0 -3
- build/torch213-cxx11-cu132-x86_64-linux/_ops.py +3 -3
- build/torch213-cxx11-cu132-x86_64-linux/metadata.json +5 -5
README.md
CHANGED
|
@@ -1,9 +1,116 @@
|
|
| 1 |
-
#
|
| 2 |
|
| 3 |
-
|
| 4 |
-
that resolve repositories through the default Hugging Face model repo API.
|
| 5 |
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
-
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# fp4-gemm
|
| 2 |
|
| 3 |
+
FlashRT native Blackwell NVFP4 A4W4 GEMM kernels.
|
|
|
|
| 4 |
|
| 5 |
+
This package consumes packed FP4 E2M1 tensors plus CUTLASS Sm1xx SFA/SFB scale
|
| 6 |
+
buffers and produces BF16 output. It is designed to pair with
|
| 7 |
+
`flashrt/fp4-fused-ops` and other static low-bit transformer/diffuser runtime
|
| 8 |
+
paths.
|
| 9 |
|
| 10 |
+
## Available Functions
|
| 11 |
+
|
| 12 |
+
- `sfa_size_bytes(rows, dim)`
|
| 13 |
+
- `quantize_fp4_sfa_fp16(x, packed=None, sfa=None, is_sfb=False)`
|
| 14 |
+
- `quantize_fp4_sfa_bf16(x, packed=None, sfa=None, is_sfb=False)`
|
| 15 |
+
- `dequantize_fp4_sfa_fp16(packed, sfa, out=None, is_sfb=False)`
|
| 16 |
+
- `nvfp4_gemm_bf16(a_packed, b_packed, sfa, sfb, alpha=1.0, out=None, variant=-1)`
|
| 17 |
+
- `nvfp4_gemm_bias_bf16(a_packed, b_packed, sfa, sfb, bias, out=None)`
|
| 18 |
+
- `nvfp4_gemm_bias_residual_bf16(a_packed, b_packed, sfa, sfb, bias, residual, out=None)`
|
| 19 |
+
- `nvfp4_gemm_residual_bf16(a_packed, b_packed, sfa, sfb, residual, alpha=1.0, out=None)`
|
| 20 |
+
- `nvfp4_gemm_bias_gelu_bf16(a_packed, b_packed, sfa, sfb, bias, alpha=1.0, out=None)`
|
| 21 |
+
- `nvfp4_gemm_bias_gelu_nvfp4(a_packed, b_packed, sfa, sfb, bias, alpha=1.0, out_packed=None, out_sfa=None)`
|
| 22 |
+
- `nvfp4_gemm_streamk_bf16(a_packed, b_packed, sfa, sfb, alpha=1.0, out=None)`
|
| 23 |
+
- `nvfp4_gemm_streamk_bias_bf16(a_packed, b_packed, sfa, sfb, bias, alpha=1.0, out=None)`
|
| 24 |
+
- `fp4_w4a16_linear_bf16(...)` is retained as a compatibility alias
|
| 25 |
+
|
| 26 |
+
## Tensor Contract
|
| 27 |
+
|
| 28 |
+
- `a_packed`: `torch.uint8`, shape `(M, K / 2)`.
|
| 29 |
+
- `b_packed`: `torch.uint8`, shape `(N, K / 2)`.
|
| 30 |
+
- `sfa`: `torch.uint8`, CUTLASS SFA layout for `(M, K)`.
|
| 31 |
+
- `sfb`: `torch.uint8`, CUTLASS SFB layout for `(N, K)`.
|
| 32 |
+
- output: `torch.bfloat16`, shape `(M, N)`.
|
| 33 |
+
- `K` must be divisible by 16.
|
| 34 |
+
- Targets: Blackwell `sm_110a` (Jetson AGX Thor, CUDA 13+) and `sm_120a`
|
| 35 |
+
(RTX Blackwell, CUDA 12.8+).
|
| 36 |
+
|
| 37 |
+
`variant` selects the CUTLASS schedule:
|
| 38 |
+
|
| 39 |
+
- `-1`: architecture-aware auto-dispatch (public default).
|
| 40 |
+
- `0`: default `<128,128,256>` cooperative schedule.
|
| 41 |
+
- `1`: widen `<128,256,128>` schedule, intended for very large `N`.
|
| 42 |
+
- `2`: pingpong schedule for A/B testing shape-specific wins.
|
| 43 |
+
|
| 44 |
+
The canonical linear API and FP4/SFA quantize/dequantize helpers are available
|
| 45 |
+
on both SM110 and SM120. SM110 additionally provides the GROOT N1.7 production
|
| 46 |
+
epilogues `nvfp4_gemm_bias_bf16`, `nvfp4_gemm_bias_residual_bf16`, and
|
| 47 |
+
`nvfp4_gemm_bias_gelu_nvfp4`. The latter emits packed FP4 plus CUTLASS SFA so
|
| 48 |
+
the following projection can consume it without a BF16 materialization and a
|
| 49 |
+
standalone quantization launch. Stream-K and the older BF16 GELU epilogue keep
|
| 50 |
+
their existing SM120 dispatch and reject unsupported architectures explicitly.
|
| 51 |
+
|
| 52 |
+
The SM110 release gate includes the production `(M,N,K)` shapes
|
| 53 |
+
`(41,4608,1536)`, `(41,6144,1536)`, and `(41,1536,6144)`, plus the legacy
|
| 54 |
+
`M=51` compatibility row. The kernels are the native sources used by FlashRT's
|
| 55 |
+
GROOT N1.7 Thor NVFP4 pipeline.
|
| 56 |
+
|
| 57 |
+
## Minimal Usage
|
| 58 |
+
|
| 59 |
+
```python
|
| 60 |
+
from kernels import get_kernel
|
| 61 |
+
import torch
|
| 62 |
+
|
| 63 |
+
ops = get_kernel("flashrt/fp4-gemm", version=1, trust_remote_code=True)
|
| 64 |
+
|
| 65 |
+
x = torch.randn((32, 256), device="cuda", dtype=torch.float16)
|
| 66 |
+
w = torch.randn((512, 256), device="cuda", dtype=torch.float16)
|
| 67 |
+
|
| 68 |
+
a_packed, sfa = ops.quantize_fp4_sfa_fp16(x, is_sfb=False)
|
| 69 |
+
b_packed, sfb = ops.quantize_fp4_sfa_fp16(w, is_sfb=True)
|
| 70 |
+
|
| 71 |
+
y = ops.nvfp4_gemm_bf16(a_packed, b_packed, sfa, sfb, alpha=1.0)
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
For BF16 model activations, use the direct producer so the hot path does not
|
| 75 |
+
materialize an intermediate FP16 tensor:
|
| 76 |
+
|
| 77 |
+
```python
|
| 78 |
+
x_bf16 = torch.randn((1, 5120), device="cuda", dtype=torch.bfloat16)
|
| 79 |
+
a_packed, sfa = ops.quantize_fp4_sfa_bf16(x_bf16)
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
The BF16 entry writes the same E2M1 bytes and CUTLASS SFA/SFB layout as
|
| 83 |
+
`quantize_fp4_sfa_fp16(x_bf16.to(torch.float16))` for finite FP16-range
|
| 84 |
+
inputs. It is an additive API; the existing FP16 producer remains unchanged.
|
| 85 |
+
|
| 86 |
+
The quantize/dequantize helpers are included for examples and validation. A
|
| 87 |
+
production runtime should keep weights prepacked and should avoid quantizing in
|
| 88 |
+
the hot path unless that producer kernel is part of the intended low-bit block.
|
| 89 |
+
|
| 90 |
+
Use the bias/GELU and residual variants to avoid returning to BF16
|
| 91 |
+
elementwise code between low-bit GEMMs. Stream-K variants are selected only
|
| 92 |
+
for the validated large down-projection shapes; unsupported shapes reject
|
| 93 |
+
rather than silently selecting a losing schedule.
|
| 94 |
+
|
| 95 |
+
## Validation
|
| 96 |
+
|
| 97 |
+
```bash
|
| 98 |
+
python fp4-gemm/tests/test_fp4_gemm.py --backend source --mode full
|
| 99 |
+
python fp4-gemm/tests/test_fp4_gemm.py --backend installed --mode full \
|
| 100 |
+
--artifact fp4-gemm/build/torch211-cxx11-cu128-x86_64-linux
|
| 101 |
+
python fp4-gemm/benchmarks/benchmark.py --backend installed --mode headline \
|
| 102 |
+
--artifact fp4-gemm/build/torch211-cxx11-cu128-x86_64-linux
|
| 103 |
+
|
| 104 |
+
# Thor model-shape gate
|
| 105 |
+
python fp4-gemm/tests/test_fp4_gemm.py --backend installed \
|
| 106 |
+
--mode thor-models \
|
| 107 |
+
--artifact fp4-gemm/build/torch211-cxx11-cu130-aarch64-linux
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
The correctness reference dequantizes the same FP4/SFA and FP4/SFB inputs used
|
| 111 |
+
by the kernel, then computes the PyTorch GEMM reference from those dequantized
|
| 112 |
+
low-bit values.
|
| 113 |
+
|
| 114 |
+
The producer gate also checks the BF16 direct entry byte-for-byte against the
|
| 115 |
+
established FP16 compatibility chain at decode widths 5120, 6144 and 17408,
|
| 116 |
+
plus multi-row activation and SFB layouts.
|
benchmarks/benchmark.py
CHANGED
|
@@ -185,12 +185,79 @@ def bench_bf16_producer(ops, native, k: int, warmup: int, iters: int):
|
|
| 185 |
}
|
| 186 |
|
| 187 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
def main() -> int:
|
| 189 |
parser = argparse.ArgumentParser()
|
| 190 |
parser.add_argument("--backend", choices=["source", "installed"], default="source")
|
| 191 |
parser.add_argument("--artifact", default=None)
|
| 192 |
parser.add_argument(
|
| 193 |
-
"--mode",
|
|
|
|
|
|
|
| 194 |
)
|
| 195 |
parser.add_argument("--warmup", type=int, default=20)
|
| 196 |
parser.add_argument("--iterations", type=int, default=100)
|
|
@@ -198,6 +265,31 @@ def main() -> int:
|
|
| 198 |
args = parser.parse_args()
|
| 199 |
|
| 200 |
helpers = load_helpers()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
native_root = Path(
|
| 202 |
os.environ.get("FLASHRT_NATIVE_ROOT", str(ROOT.parent / "official" / "FlashRT"))
|
| 203 |
)
|
|
@@ -206,11 +298,6 @@ def main() -> int:
|
|
| 206 |
import flash_rt.flash_rt_kernels as native
|
| 207 |
finally:
|
| 208 |
sys.path.pop(0)
|
| 209 |
-
ops = (
|
| 210 |
-
helpers.load_source_ops()
|
| 211 |
-
if args.backend == "source"
|
| 212 |
-
else helpers.load_installed_ops(args.artifact)
|
| 213 |
-
)
|
| 214 |
shapes = {
|
| 215 |
"small_m16_n128_k128": (16, 128, 128),
|
| 216 |
"small_m32_n256_k256": (32, 256, 256),
|
|
|
|
| 185 |
}
|
| 186 |
|
| 187 |
|
| 188 |
+
def bench_w4a16_vocab(ops, warmup: int, iters: int):
|
| 189 |
+
n, k = 152064, 5120
|
| 190 |
+
gen = torch.Generator(device="cuda").manual_seed(20260818)
|
| 191 |
+
weight = torch.randint(
|
| 192 |
+
0, 256, (n, k // 2), device="cuda", dtype=torch.uint8,
|
| 193 |
+
generator=gen,
|
| 194 |
+
)
|
| 195 |
+
weight_scale = (
|
| 196 |
+
torch.rand((n, k // 16), device="cuda", generator=gen) * 0.1 + 0.01
|
| 197 |
+
).to(torch.float8_e4m3fn)
|
| 198 |
+
global_scale = torch.tensor([0.00390625], device="cuda", dtype=torch.float32)
|
| 199 |
+
weight_marlin, scales_marlin, global_marlin, workspace = (
|
| 200 |
+
ops.adopt_w4a16_marlin(weight, weight_scale, global_scale)
|
| 201 |
+
)
|
| 202 |
+
|
| 203 |
+
vllm_ops = None
|
| 204 |
+
scalar_types = None
|
| 205 |
+
try:
|
| 206 |
+
from vllm import _custom_ops as vllm_ops
|
| 207 |
+
from vllm.scalar_type import scalar_types
|
| 208 |
+
except ImportError:
|
| 209 |
+
pass
|
| 210 |
+
|
| 211 |
+
rows = []
|
| 212 |
+
for m in (1, 7, 8, 16):
|
| 213 |
+
x = torch.randn((m, k), device="cuda", dtype=torch.bfloat16)
|
| 214 |
+
out = torch.empty((m, n), device="cuda", dtype=torch.bfloat16)
|
| 215 |
+
|
| 216 |
+
def flashrt_call():
|
| 217 |
+
ops.w4a16_marlin(
|
| 218 |
+
x, weight_marlin, scales_marlin, global_marlin, workspace, out
|
| 219 |
+
)
|
| 220 |
+
|
| 221 |
+
flashrt_call()
|
| 222 |
+
flashrt_us = measure(flashrt_call, warmup, iters)
|
| 223 |
+
row = {
|
| 224 |
+
"M": m,
|
| 225 |
+
"N": n,
|
| 226 |
+
"K": k,
|
| 227 |
+
"flashrt_us": flashrt_us,
|
| 228 |
+
"target_us": 400.0 if m == 8 else None,
|
| 229 |
+
"target_passed": bool(m != 8 or flashrt_us <= 400.0),
|
| 230 |
+
}
|
| 231 |
+
if vllm_ops is not None and scalar_types is not None:
|
| 232 |
+
native_out = torch.empty_like(out)
|
| 233 |
+
|
| 234 |
+
def native_call():
|
| 235 |
+
vllm_ops.marlin_gemm(
|
| 236 |
+
x, native_out, weight_marlin, None, scales_marlin, None,
|
| 237 |
+
global_marlin, None, None, None, workspace,
|
| 238 |
+
scalar_types.float4_e2m1f, m, n, k,
|
| 239 |
+
True, False, False, False,
|
| 240 |
+
)
|
| 241 |
+
|
| 242 |
+
native_call()
|
| 243 |
+
native_us = measure(native_call, warmup, iters)
|
| 244 |
+
row.update({
|
| 245 |
+
"vllm_native_us": native_us,
|
| 246 |
+
"wrapper_over_native": flashrt_us / native_us,
|
| 247 |
+
"exact_vs_vllm": bool(torch.equal(out, native_out)),
|
| 248 |
+
})
|
| 249 |
+
rows.append(row)
|
| 250 |
+
return rows
|
| 251 |
+
|
| 252 |
+
|
| 253 |
def main() -> int:
|
| 254 |
parser = argparse.ArgumentParser()
|
| 255 |
parser.add_argument("--backend", choices=["source", "installed"], default="source")
|
| 256 |
parser.add_argument("--artifact", default=None)
|
| 257 |
parser.add_argument(
|
| 258 |
+
"--mode",
|
| 259 |
+
choices=["smoke", "headline", "thor-models", "w4a16-vocab"],
|
| 260 |
+
default="headline",
|
| 261 |
)
|
| 262 |
parser.add_argument("--warmup", type=int, default=20)
|
| 263 |
parser.add_argument("--iterations", type=int, default=100)
|
|
|
|
| 265 |
args = parser.parse_args()
|
| 266 |
|
| 267 |
helpers = load_helpers()
|
| 268 |
+
ops = (
|
| 269 |
+
helpers.load_source_ops()
|
| 270 |
+
if args.backend == "source"
|
| 271 |
+
else helpers.load_installed_ops(args.artifact)
|
| 272 |
+
)
|
| 273 |
+
if args.mode == "w4a16-vocab":
|
| 274 |
+
rows = bench_w4a16_vocab(ops, args.warmup, args.iterations)
|
| 275 |
+
payload = {
|
| 276 |
+
"mode": args.mode,
|
| 277 |
+
"backend": args.backend,
|
| 278 |
+
"device": torch.cuda.get_device_name(),
|
| 279 |
+
"torch": torch.__version__,
|
| 280 |
+
"results": rows,
|
| 281 |
+
"passed": all(
|
| 282 |
+
row["target_passed"] and row.get("exact_vs_vllm", True)
|
| 283 |
+
for row in rows
|
| 284 |
+
),
|
| 285 |
+
}
|
| 286 |
+
print(json.dumps(payload, indent=2))
|
| 287 |
+
if args.json_out:
|
| 288 |
+
out = Path(args.json_out)
|
| 289 |
+
out.parent.mkdir(parents=True, exist_ok=True)
|
| 290 |
+
out.write_text(json.dumps(payload, indent=2) + "\n")
|
| 291 |
+
return 0 if payload["passed"] else 1
|
| 292 |
+
|
| 293 |
native_root = Path(
|
| 294 |
os.environ.get("FLASHRT_NATIVE_ROOT", str(ROOT.parent / "official" / "FlashRT"))
|
| 295 |
)
|
|
|
|
| 298 |
import flash_rt.flash_rt_kernels as native
|
| 299 |
finally:
|
| 300 |
sys.path.pop(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
shapes = {
|
| 302 |
"small_m16_n128_k128": (16, 128, 128),
|
| 303 |
"small_m32_n256_k256": (32, 256, 256),
|
build/torch211-cxx11-cu128-x86_64-linux/__init__.py
CHANGED
|
@@ -37,6 +37,11 @@ def capabilities() -> dict[str, object]:
|
|
| 37 |
"sm120_warpsplit_mrows": (1, 16),
|
| 38 |
"sm120_warpsplit_mrows_n_alignment": 8,
|
| 39 |
"sm120_warpsplit_mrows_k_alignment": "64 * warps",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
"errors": "exceptions",
|
| 41 |
}
|
| 42 |
|
|
@@ -172,6 +177,28 @@ def _m256_fake(a_packed, b_packed, sfa, sfb, workspace, out, alpha: float = 1.0)
|
|
| 172 |
return None
|
| 173 |
|
| 174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
@torch.library.register_fake(add_op_namespace_prefix("fp4_w4a16_linear_bf16"))
|
| 176 |
def _legacy_linear_fake(
|
| 177 |
a_packed: torch.Tensor,
|
|
@@ -771,6 +798,98 @@ def nvfp4_gemm_m256_bf16(
|
|
| 771 |
return out
|
| 772 |
|
| 773 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 774 |
def fp4_w4a16_linear_bf16(
|
| 775 |
a_packed: torch.Tensor,
|
| 776 |
b_packed: torch.Tensor,
|
|
@@ -932,6 +1051,8 @@ __all__ = [
|
|
| 932 |
"SUPPORTED_CUDA_CAPABILITIES",
|
| 933 |
"SUPPORTED_LAYOUTS",
|
| 934 |
"capabilities",
|
|
|
|
|
|
|
| 935 |
"aligned_fp4_dim",
|
| 936 |
"cutlass_fp4_gemm_geglu_il_hw_v10",
|
| 937 |
"dequantize_fp4_sfa_fp16",
|
|
@@ -947,6 +1068,7 @@ __all__ = [
|
|
| 947 |
"nvfp4_gemm_nvfp4",
|
| 948 |
"nvfp4_gemm_m256_bf16",
|
| 949 |
"nvfp4_gemm_m256_workspace_size",
|
|
|
|
| 950 |
"nvfp4_gemm_geglu_nvfp4_fp16",
|
| 951 |
"nvfp4_gemm_bias_gelu_nvfp4_fp16",
|
| 952 |
"nvfp4_gemm_bias_residual_fp16",
|
|
|
|
| 37 |
"sm120_warpsplit_mrows": (1, 16),
|
| 38 |
"sm120_warpsplit_mrows_n_alignment": 8,
|
| 39 |
"sm120_warpsplit_mrows_k_alignment": "64 * warps",
|
| 40 |
+
"sm120_w4a16_marlin_m": (1, 16),
|
| 41 |
+
"sm120_w4a16_marlin_n_alignment": 64,
|
| 42 |
+
"sm120_w4a16_marlin_k_alignment": 128,
|
| 43 |
+
"sm120_w4a16_marlin_weight_layout": "marlin-repacked-nvfp4",
|
| 44 |
+
"sm120_w4a16_marlin_scale_layout": "marlin-permuted-fp8-e4m3-block16",
|
| 45 |
"errors": "exceptions",
|
| 46 |
}
|
| 47 |
|
|
|
|
| 177 |
return None
|
| 178 |
|
| 179 |
|
| 180 |
+
@torch.library.register_fake(add_op_namespace_prefix("nvfp4_w4a16_marlin_bf16"))
|
| 181 |
+
def _w4a16_marlin_fake(x, weight, weight_scale, global_scale, workspace, out) -> None:
|
| 182 |
+
del weight, weight_scale, global_scale, workspace
|
| 183 |
+
if x.ndim != 2 or out.ndim != 2 or out.shape[0] != x.shape[0]:
|
| 184 |
+
raise RuntimeError("x/out must have shapes (M, K)/(M, N)")
|
| 185 |
+
if not 1 <= x.shape[0] <= 16:
|
| 186 |
+
raise RuntimeError("the Marlin W4A16 tier serves M in [1, 16]")
|
| 187 |
+
if x.shape[1] % 128 or out.shape[1] % 64:
|
| 188 |
+
raise RuntimeError("K/N must be divisible by 128/64")
|
| 189 |
+
return None
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
@torch.library.register_fake(add_op_namespace_prefix("nvfp4_w4a16_marlin_repack"))
|
| 193 |
+
def _w4a16_marlin_repack_fake(qweight_kn, weight_marlin) -> None:
|
| 194 |
+
if qweight_kn.ndim != 2:
|
| 195 |
+
raise RuntimeError("qweight_kn must have shape (K / 8, N)")
|
| 196 |
+
k, n = qweight_kn.shape[0] * 8, qweight_kn.shape[1]
|
| 197 |
+
if weight_marlin.shape != (k // 16, n * 2):
|
| 198 |
+
raise RuntimeError("weight_marlin must have shape (K / 16, 2 * N)")
|
| 199 |
+
return None
|
| 200 |
+
|
| 201 |
+
|
| 202 |
@torch.library.register_fake(add_op_namespace_prefix("fp4_w4a16_linear_bf16"))
|
| 203 |
def _legacy_linear_fake(
|
| 204 |
a_packed: torch.Tensor,
|
|
|
|
| 798 |
return out
|
| 799 |
|
| 800 |
|
| 801 |
+
def allocate_w4a16_marlin_workspace(device: torch.device | str) -> torch.Tensor:
|
| 802 |
+
"""Allocate the persistent Marlin lock workspace before graph capture."""
|
| 803 |
+
props = torch.cuda.get_device_properties(device)
|
| 804 |
+
return torch.zeros(props.multi_processor_count, device=device, dtype=torch.int32)
|
| 805 |
+
|
| 806 |
+
|
| 807 |
+
def adopt_nvfp4_w4a16_marlin(
|
| 808 |
+
weight_packed: torch.Tensor,
|
| 809 |
+
weight_scale: torch.Tensor,
|
| 810 |
+
weight_scale_2: torch.Tensor,
|
| 811 |
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 812 |
+
"""Convert standard ModelOpt NVFP4 tensors once at model bind time.
|
| 813 |
+
|
| 814 |
+
Inputs use ``weight[N,K/2]``, ``weight_scale[N,K/16]`` and scalar
|
| 815 |
+
``weight_scale_2``. Returned tensors are the three runtime operands plus a
|
| 816 |
+
zeroed lock workspace. This function is intentionally outside the graph
|
| 817 |
+
hot path.
|
| 818 |
+
"""
|
| 819 |
+
if weight_packed.dtype != torch.uint8 or weight_packed.ndim != 2:
|
| 820 |
+
raise ValueError("weight_packed must be uint8 with shape (N, K / 2)")
|
| 821 |
+
n, k = weight_packed.shape[0], weight_packed.shape[1] * 2
|
| 822 |
+
if k % 128 or n % 64:
|
| 823 |
+
raise ValueError("K/N must be divisible by 128/64")
|
| 824 |
+
if weight_scale.shape != (n, k // NVFP4_BLOCK_SIZE):
|
| 825 |
+
raise ValueError("weight_scale must have shape (N, K / 16)")
|
| 826 |
+
if weight_scale_2.numel() != 1:
|
| 827 |
+
raise ValueError("weight_scale_2 must contain one global scale")
|
| 828 |
+
|
| 829 |
+
qweight_kn = weight_packed.view(torch.int32).T.contiguous()
|
| 830 |
+
weight_marlin = torch.empty(
|
| 831 |
+
(k // 16, n * 2), device=weight_packed.device, dtype=torch.int32
|
| 832 |
+
)
|
| 833 |
+
ops.nvfp4_w4a16_marlin_repack(qweight_kn, weight_marlin)
|
| 834 |
+
|
| 835 |
+
scales = weight_scale.T.contiguous().to(torch.bfloat16)
|
| 836 |
+
scale_perm = [i + 8 * j for i in range(8) for j in range(8)]
|
| 837 |
+
scales = scales.reshape(-1, 64)[:, scale_perm].reshape(-1, n).contiguous()
|
| 838 |
+
|
| 839 |
+
# Match vLLM's NVFP4 Marlin contract exactly: scale permutation uses the
|
| 840 |
+
# model parameter dtype, then S0E5M3 encoding starts from FP16.
|
| 841 |
+
scales = scales.to(torch.float16)
|
| 842 |
+
scales = scales.view(-1, 4)[:, [0, 2, 1, 3]].reshape(scales.shape)
|
| 843 |
+
scaled = scales.float() * (2**7)
|
| 844 |
+
nonzero = scaled > 0
|
| 845 |
+
scale_factor = 1.0
|
| 846 |
+
if bool(nonzero.any()):
|
| 847 |
+
max_value = scaled[nonzero].max()
|
| 848 |
+
if bool(max_value < 448 * (2**7)):
|
| 849 |
+
scale_factor = float((448 * (2**7) / max_value).log2().floor().exp2())
|
| 850 |
+
if scale_factor > 1.0:
|
| 851 |
+
scales = (scales.float() * scale_factor).to(torch.float16)
|
| 852 |
+
scales = scales * (2**7)
|
| 853 |
+
scales[scales < 2] = 0
|
| 854 |
+
scales = (scales.view(torch.int16) << 1).view(torch.float8_e4m3fn)
|
| 855 |
+
scales = scales[:, 1::2].contiguous()
|
| 856 |
+
|
| 857 |
+
# Marlin's BF16 dequantizer uses exponent bias 126 and removes the seven
|
| 858 |
+
# bits introduced while encoding the special unsigned FP8 scale format.
|
| 859 |
+
global_scale = (
|
| 860 |
+
weight_scale_2.to(device=weight_packed.device, dtype=torch.float32)
|
| 861 |
+
* (2.0 ** 119)
|
| 862 |
+
/ scale_factor
|
| 863 |
+
).contiguous()
|
| 864 |
+
workspace = allocate_w4a16_marlin_workspace(weight_packed.device)
|
| 865 |
+
return weight_marlin, scales, global_scale, workspace
|
| 866 |
+
|
| 867 |
+
|
| 868 |
+
def nvfp4_w4a16_marlin_bf16(
|
| 869 |
+
x: torch.Tensor,
|
| 870 |
+
weight_marlin: torch.Tensor,
|
| 871 |
+
weight_scale_marlin: torch.Tensor,
|
| 872 |
+
weight_global_scale: torch.Tensor,
|
| 873 |
+
*,
|
| 874 |
+
workspace: torch.Tensor,
|
| 875 |
+
out: torch.Tensor | None = None,
|
| 876 |
+
) -> torch.Tensor:
|
| 877 |
+
"""Small-M BF16 x NVFP4 linear using bind-time Marlin layouts.
|
| 878 |
+
|
| 879 |
+
``weight_marlin``, ``weight_scale_marlin`` and
|
| 880 |
+
``weight_global_scale`` are the tensors produced by a standard NVFP4
|
| 881 |
+
Marlin adoption step. The operation supports M in [1, 16] and never
|
| 882 |
+
allocates internally, making it suitable for CUDA Graph replay.
|
| 883 |
+
"""
|
| 884 |
+
if out is None:
|
| 885 |
+
n = weight_scale_marlin.numel() // (x.shape[1] // NVFP4_BLOCK_SIZE)
|
| 886 |
+
out = torch.empty((x.shape[0], n), device=x.device, dtype=torch.bfloat16)
|
| 887 |
+
ops.nvfp4_w4a16_marlin_bf16(
|
| 888 |
+
x, weight_marlin, weight_scale_marlin, weight_global_scale, workspace, out
|
| 889 |
+
)
|
| 890 |
+
return out
|
| 891 |
+
|
| 892 |
+
|
| 893 |
def fp4_w4a16_linear_bf16(
|
| 894 |
a_packed: torch.Tensor,
|
| 895 |
b_packed: torch.Tensor,
|
|
|
|
| 1051 |
"SUPPORTED_CUDA_CAPABILITIES",
|
| 1052 |
"SUPPORTED_LAYOUTS",
|
| 1053 |
"capabilities",
|
| 1054 |
+
"allocate_w4a16_marlin_workspace",
|
| 1055 |
+
"adopt_nvfp4_w4a16_marlin",
|
| 1056 |
"aligned_fp4_dim",
|
| 1057 |
"cutlass_fp4_gemm_geglu_il_hw_v10",
|
| 1058 |
"dequantize_fp4_sfa_fp16",
|
|
|
|
| 1068 |
"nvfp4_gemm_nvfp4",
|
| 1069 |
"nvfp4_gemm_m256_bf16",
|
| 1070 |
"nvfp4_gemm_m256_workspace_size",
|
| 1071 |
+
"nvfp4_w4a16_marlin_bf16",
|
| 1072 |
"nvfp4_gemm_geglu_nvfp4_fp16",
|
| 1073 |
"nvfp4_gemm_bias_gelu_nvfp4_fp16",
|
| 1074 |
"nvfp4_gemm_bias_residual_fp16",
|
build/torch211-cxx11-cu128-x86_64-linux/{_fp4_gemm_cuda_dc57d15.abi3.so → _fp4_gemm_cuda_6971f7a.abi3.so}
RENAMED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f01d9ffadff9a04439f0a83b6095305d1e20d3903fd275624cd4b2c0001ceaaf
|
| 3 |
+
size 4915160
|
build/torch211-cxx11-cu128-x86_64-linux/_ops.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
import torch
|
| 2 |
-
from . import
|
| 3 |
-
ops = torch.ops.
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
-
return f"
|
|
|
|
| 1 |
import torch
|
| 2 |
+
from . import _fp4_gemm_cuda_6971f7a
|
| 3 |
+
ops = torch.ops._fp4_gemm_cuda_6971f7a
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
+
return f"_fp4_gemm_cuda_6971f7a::{op_name}"
|
build/torch211-cxx11-cu128-x86_64-linux/metadata.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"name": "fp4-gemm",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
|
@@ -13,9 +13,9 @@
|
|
| 13 |
"digest": {
|
| 14 |
"algorithm": "sha256",
|
| 15 |
"files": {
|
| 16 |
-
"__init__.py": "
|
| 17 |
-
"
|
| 18 |
-
"_ops.py": "
|
| 19 |
"fp4_gemm/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
|
| 20 |
}
|
| 21 |
},
|
|
@@ -26,7 +26,7 @@
|
|
| 26 |
"dirty": false
|
| 27 |
},
|
| 28 |
"kernel": {
|
| 29 |
-
"sha": "
|
| 30 |
"dirty": false
|
| 31 |
}
|
| 32 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "fp4-gemm",
|
| 3 |
+
"id": "_fp4_gemm_cuda_6971f7a",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
|
|
|
| 13 |
"digest": {
|
| 14 |
"algorithm": "sha256",
|
| 15 |
"files": {
|
| 16 |
+
"__init__.py": "0hDN2W6X3IMoWIi5BBufrnqrq2yj5N1/q9wBhCwBRxA=",
|
| 17 |
+
"_fp4_gemm_cuda_6971f7a.abi3.so": "8B2f+t/5oEQ58Kg7YJUwXR4g05A/0nViTNSywAAc6q8=",
|
| 18 |
+
"_ops.py": "D7InzLughnNL5JQowMPNYpSpNPRFeeD8mlgs38Xw03g=",
|
| 19 |
"fp4_gemm/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
|
| 20 |
}
|
| 21 |
},
|
|
|
|
| 26 |
"dirty": false
|
| 27 |
},
|
| 28 |
"kernel": {
|
| 29 |
+
"sha": "6971f7a37410ba0d6edae93b389dbcbe3ba924d5",
|
| 30 |
"dirty": false
|
| 31 |
}
|
| 32 |
}
|
build/torch211-cxx11-cu130-x86_64-linux/__init__.py
CHANGED
|
@@ -37,6 +37,11 @@ def capabilities() -> dict[str, object]:
|
|
| 37 |
"sm120_warpsplit_mrows": (1, 16),
|
| 38 |
"sm120_warpsplit_mrows_n_alignment": 8,
|
| 39 |
"sm120_warpsplit_mrows_k_alignment": "64 * warps",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
"errors": "exceptions",
|
| 41 |
}
|
| 42 |
|
|
@@ -172,6 +177,28 @@ def _m256_fake(a_packed, b_packed, sfa, sfb, workspace, out, alpha: float = 1.0)
|
|
| 172 |
return None
|
| 173 |
|
| 174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
@torch.library.register_fake(add_op_namespace_prefix("fp4_w4a16_linear_bf16"))
|
| 176 |
def _legacy_linear_fake(
|
| 177 |
a_packed: torch.Tensor,
|
|
@@ -771,6 +798,98 @@ def nvfp4_gemm_m256_bf16(
|
|
| 771 |
return out
|
| 772 |
|
| 773 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 774 |
def fp4_w4a16_linear_bf16(
|
| 775 |
a_packed: torch.Tensor,
|
| 776 |
b_packed: torch.Tensor,
|
|
@@ -932,6 +1051,8 @@ __all__ = [
|
|
| 932 |
"SUPPORTED_CUDA_CAPABILITIES",
|
| 933 |
"SUPPORTED_LAYOUTS",
|
| 934 |
"capabilities",
|
|
|
|
|
|
|
| 935 |
"aligned_fp4_dim",
|
| 936 |
"cutlass_fp4_gemm_geglu_il_hw_v10",
|
| 937 |
"dequantize_fp4_sfa_fp16",
|
|
@@ -947,6 +1068,7 @@ __all__ = [
|
|
| 947 |
"nvfp4_gemm_nvfp4",
|
| 948 |
"nvfp4_gemm_m256_bf16",
|
| 949 |
"nvfp4_gemm_m256_workspace_size",
|
|
|
|
| 950 |
"nvfp4_gemm_geglu_nvfp4_fp16",
|
| 951 |
"nvfp4_gemm_bias_gelu_nvfp4_fp16",
|
| 952 |
"nvfp4_gemm_bias_residual_fp16",
|
|
|
|
| 37 |
"sm120_warpsplit_mrows": (1, 16),
|
| 38 |
"sm120_warpsplit_mrows_n_alignment": 8,
|
| 39 |
"sm120_warpsplit_mrows_k_alignment": "64 * warps",
|
| 40 |
+
"sm120_w4a16_marlin_m": (1, 16),
|
| 41 |
+
"sm120_w4a16_marlin_n_alignment": 64,
|
| 42 |
+
"sm120_w4a16_marlin_k_alignment": 128,
|
| 43 |
+
"sm120_w4a16_marlin_weight_layout": "marlin-repacked-nvfp4",
|
| 44 |
+
"sm120_w4a16_marlin_scale_layout": "marlin-permuted-fp8-e4m3-block16",
|
| 45 |
"errors": "exceptions",
|
| 46 |
}
|
| 47 |
|
|
|
|
| 177 |
return None
|
| 178 |
|
| 179 |
|
| 180 |
+
@torch.library.register_fake(add_op_namespace_prefix("nvfp4_w4a16_marlin_bf16"))
|
| 181 |
+
def _w4a16_marlin_fake(x, weight, weight_scale, global_scale, workspace, out) -> None:
|
| 182 |
+
del weight, weight_scale, global_scale, workspace
|
| 183 |
+
if x.ndim != 2 or out.ndim != 2 or out.shape[0] != x.shape[0]:
|
| 184 |
+
raise RuntimeError("x/out must have shapes (M, K)/(M, N)")
|
| 185 |
+
if not 1 <= x.shape[0] <= 16:
|
| 186 |
+
raise RuntimeError("the Marlin W4A16 tier serves M in [1, 16]")
|
| 187 |
+
if x.shape[1] % 128 or out.shape[1] % 64:
|
| 188 |
+
raise RuntimeError("K/N must be divisible by 128/64")
|
| 189 |
+
return None
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
@torch.library.register_fake(add_op_namespace_prefix("nvfp4_w4a16_marlin_repack"))
|
| 193 |
+
def _w4a16_marlin_repack_fake(qweight_kn, weight_marlin) -> None:
|
| 194 |
+
if qweight_kn.ndim != 2:
|
| 195 |
+
raise RuntimeError("qweight_kn must have shape (K / 8, N)")
|
| 196 |
+
k, n = qweight_kn.shape[0] * 8, qweight_kn.shape[1]
|
| 197 |
+
if weight_marlin.shape != (k // 16, n * 2):
|
| 198 |
+
raise RuntimeError("weight_marlin must have shape (K / 16, 2 * N)")
|
| 199 |
+
return None
|
| 200 |
+
|
| 201 |
+
|
| 202 |
@torch.library.register_fake(add_op_namespace_prefix("fp4_w4a16_linear_bf16"))
|
| 203 |
def _legacy_linear_fake(
|
| 204 |
a_packed: torch.Tensor,
|
|
|
|
| 798 |
return out
|
| 799 |
|
| 800 |
|
| 801 |
+
def allocate_w4a16_marlin_workspace(device: torch.device | str) -> torch.Tensor:
|
| 802 |
+
"""Allocate the persistent Marlin lock workspace before graph capture."""
|
| 803 |
+
props = torch.cuda.get_device_properties(device)
|
| 804 |
+
return torch.zeros(props.multi_processor_count, device=device, dtype=torch.int32)
|
| 805 |
+
|
| 806 |
+
|
| 807 |
+
def adopt_nvfp4_w4a16_marlin(
|
| 808 |
+
weight_packed: torch.Tensor,
|
| 809 |
+
weight_scale: torch.Tensor,
|
| 810 |
+
weight_scale_2: torch.Tensor,
|
| 811 |
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 812 |
+
"""Convert standard ModelOpt NVFP4 tensors once at model bind time.
|
| 813 |
+
|
| 814 |
+
Inputs use ``weight[N,K/2]``, ``weight_scale[N,K/16]`` and scalar
|
| 815 |
+
``weight_scale_2``. Returned tensors are the three runtime operands plus a
|
| 816 |
+
zeroed lock workspace. This function is intentionally outside the graph
|
| 817 |
+
hot path.
|
| 818 |
+
"""
|
| 819 |
+
if weight_packed.dtype != torch.uint8 or weight_packed.ndim != 2:
|
| 820 |
+
raise ValueError("weight_packed must be uint8 with shape (N, K / 2)")
|
| 821 |
+
n, k = weight_packed.shape[0], weight_packed.shape[1] * 2
|
| 822 |
+
if k % 128 or n % 64:
|
| 823 |
+
raise ValueError("K/N must be divisible by 128/64")
|
| 824 |
+
if weight_scale.shape != (n, k // NVFP4_BLOCK_SIZE):
|
| 825 |
+
raise ValueError("weight_scale must have shape (N, K / 16)")
|
| 826 |
+
if weight_scale_2.numel() != 1:
|
| 827 |
+
raise ValueError("weight_scale_2 must contain one global scale")
|
| 828 |
+
|
| 829 |
+
qweight_kn = weight_packed.view(torch.int32).T.contiguous()
|
| 830 |
+
weight_marlin = torch.empty(
|
| 831 |
+
(k // 16, n * 2), device=weight_packed.device, dtype=torch.int32
|
| 832 |
+
)
|
| 833 |
+
ops.nvfp4_w4a16_marlin_repack(qweight_kn, weight_marlin)
|
| 834 |
+
|
| 835 |
+
scales = weight_scale.T.contiguous().to(torch.bfloat16)
|
| 836 |
+
scale_perm = [i + 8 * j for i in range(8) for j in range(8)]
|
| 837 |
+
scales = scales.reshape(-1, 64)[:, scale_perm].reshape(-1, n).contiguous()
|
| 838 |
+
|
| 839 |
+
# Match vLLM's NVFP4 Marlin contract exactly: scale permutation uses the
|
| 840 |
+
# model parameter dtype, then S0E5M3 encoding starts from FP16.
|
| 841 |
+
scales = scales.to(torch.float16)
|
| 842 |
+
scales = scales.view(-1, 4)[:, [0, 2, 1, 3]].reshape(scales.shape)
|
| 843 |
+
scaled = scales.float() * (2**7)
|
| 844 |
+
nonzero = scaled > 0
|
| 845 |
+
scale_factor = 1.0
|
| 846 |
+
if bool(nonzero.any()):
|
| 847 |
+
max_value = scaled[nonzero].max()
|
| 848 |
+
if bool(max_value < 448 * (2**7)):
|
| 849 |
+
scale_factor = float((448 * (2**7) / max_value).log2().floor().exp2())
|
| 850 |
+
if scale_factor > 1.0:
|
| 851 |
+
scales = (scales.float() * scale_factor).to(torch.float16)
|
| 852 |
+
scales = scales * (2**7)
|
| 853 |
+
scales[scales < 2] = 0
|
| 854 |
+
scales = (scales.view(torch.int16) << 1).view(torch.float8_e4m3fn)
|
| 855 |
+
scales = scales[:, 1::2].contiguous()
|
| 856 |
+
|
| 857 |
+
# Marlin's BF16 dequantizer uses exponent bias 126 and removes the seven
|
| 858 |
+
# bits introduced while encoding the special unsigned FP8 scale format.
|
| 859 |
+
global_scale = (
|
| 860 |
+
weight_scale_2.to(device=weight_packed.device, dtype=torch.float32)
|
| 861 |
+
* (2.0 ** 119)
|
| 862 |
+
/ scale_factor
|
| 863 |
+
).contiguous()
|
| 864 |
+
workspace = allocate_w4a16_marlin_workspace(weight_packed.device)
|
| 865 |
+
return weight_marlin, scales, global_scale, workspace
|
| 866 |
+
|
| 867 |
+
|
| 868 |
+
def nvfp4_w4a16_marlin_bf16(
|
| 869 |
+
x: torch.Tensor,
|
| 870 |
+
weight_marlin: torch.Tensor,
|
| 871 |
+
weight_scale_marlin: torch.Tensor,
|
| 872 |
+
weight_global_scale: torch.Tensor,
|
| 873 |
+
*,
|
| 874 |
+
workspace: torch.Tensor,
|
| 875 |
+
out: torch.Tensor | None = None,
|
| 876 |
+
) -> torch.Tensor:
|
| 877 |
+
"""Small-M BF16 x NVFP4 linear using bind-time Marlin layouts.
|
| 878 |
+
|
| 879 |
+
``weight_marlin``, ``weight_scale_marlin`` and
|
| 880 |
+
``weight_global_scale`` are the tensors produced by a standard NVFP4
|
| 881 |
+
Marlin adoption step. The operation supports M in [1, 16] and never
|
| 882 |
+
allocates internally, making it suitable for CUDA Graph replay.
|
| 883 |
+
"""
|
| 884 |
+
if out is None:
|
| 885 |
+
n = weight_scale_marlin.numel() // (x.shape[1] // NVFP4_BLOCK_SIZE)
|
| 886 |
+
out = torch.empty((x.shape[0], n), device=x.device, dtype=torch.bfloat16)
|
| 887 |
+
ops.nvfp4_w4a16_marlin_bf16(
|
| 888 |
+
x, weight_marlin, weight_scale_marlin, weight_global_scale, workspace, out
|
| 889 |
+
)
|
| 890 |
+
return out
|
| 891 |
+
|
| 892 |
+
|
| 893 |
def fp4_w4a16_linear_bf16(
|
| 894 |
a_packed: torch.Tensor,
|
| 895 |
b_packed: torch.Tensor,
|
|
|
|
| 1051 |
"SUPPORTED_CUDA_CAPABILITIES",
|
| 1052 |
"SUPPORTED_LAYOUTS",
|
| 1053 |
"capabilities",
|
| 1054 |
+
"allocate_w4a16_marlin_workspace",
|
| 1055 |
+
"adopt_nvfp4_w4a16_marlin",
|
| 1056 |
"aligned_fp4_dim",
|
| 1057 |
"cutlass_fp4_gemm_geglu_il_hw_v10",
|
| 1058 |
"dequantize_fp4_sfa_fp16",
|
|
|
|
| 1068 |
"nvfp4_gemm_nvfp4",
|
| 1069 |
"nvfp4_gemm_m256_bf16",
|
| 1070 |
"nvfp4_gemm_m256_workspace_size",
|
| 1071 |
+
"nvfp4_w4a16_marlin_bf16",
|
| 1072 |
"nvfp4_gemm_geglu_nvfp4_fp16",
|
| 1073 |
"nvfp4_gemm_bias_gelu_nvfp4_fp16",
|
| 1074 |
"nvfp4_gemm_bias_residual_fp16",
|
build/torch211-cxx11-cu130-x86_64-linux/{_fp4_gemm_cuda_dc57d15.abi3.so → _fp4_gemm_cuda_6971f7a.abi3.so}
RENAMED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:555ecae796a12b3fed4ebc261381ef859509f47967e7fc84901fbbca05b31923
|
| 3 |
+
size 10756144
|
build/torch211-cxx11-cu130-x86_64-linux/_ops.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
import torch
|
| 2 |
-
from . import
|
| 3 |
-
ops = torch.ops.
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
-
return f"
|
|
|
|
| 1 |
import torch
|
| 2 |
+
from . import _fp4_gemm_cuda_6971f7a
|
| 3 |
+
ops = torch.ops._fp4_gemm_cuda_6971f7a
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
+
return f"_fp4_gemm_cuda_6971f7a::{op_name}"
|
build/torch211-cxx11-cu130-x86_64-linux/metadata.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"name": "fp4-gemm",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
|
@@ -14,9 +14,9 @@
|
|
| 14 |
"digest": {
|
| 15 |
"algorithm": "sha256",
|
| 16 |
"files": {
|
| 17 |
-
"__init__.py": "
|
| 18 |
-
"
|
| 19 |
-
"_ops.py": "
|
| 20 |
"fp4_gemm/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
|
| 21 |
}
|
| 22 |
},
|
|
@@ -27,7 +27,7 @@
|
|
| 27 |
"dirty": false
|
| 28 |
},
|
| 29 |
"kernel": {
|
| 30 |
-
"sha": "
|
| 31 |
"dirty": false
|
| 32 |
}
|
| 33 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "fp4-gemm",
|
| 3 |
+
"id": "_fp4_gemm_cuda_6971f7a",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
|
|
|
| 14 |
"digest": {
|
| 15 |
"algorithm": "sha256",
|
| 16 |
"files": {
|
| 17 |
+
"__init__.py": "0hDN2W6X3IMoWIi5BBufrnqrq2yj5N1/q9wBhCwBRxA=",
|
| 18 |
+
"_fp4_gemm_cuda_6971f7a.abi3.so": "VV7K55ahKz/tTrwmE4HvhZUJ9Hln5/yEkB+7ygWzGSM=",
|
| 19 |
+
"_ops.py": "D7InzLughnNL5JQowMPNYpSpNPRFeeD8mlgs38Xw03g=",
|
| 20 |
"fp4_gemm/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
|
| 21 |
}
|
| 22 |
},
|
|
|
|
| 27 |
"dirty": false
|
| 28 |
},
|
| 29 |
"kernel": {
|
| 30 |
+
"sha": "6971f7a37410ba0d6edae93b389dbcbe3ba924d5",
|
| 31 |
"dirty": false
|
| 32 |
}
|
| 33 |
}
|
build/torch212-cxx11-cu130-x86_64-linux/__init__.py
CHANGED
|
@@ -37,6 +37,11 @@ def capabilities() -> dict[str, object]:
|
|
| 37 |
"sm120_warpsplit_mrows": (1, 16),
|
| 38 |
"sm120_warpsplit_mrows_n_alignment": 8,
|
| 39 |
"sm120_warpsplit_mrows_k_alignment": "64 * warps",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
"errors": "exceptions",
|
| 41 |
}
|
| 42 |
|
|
@@ -172,6 +177,28 @@ def _m256_fake(a_packed, b_packed, sfa, sfb, workspace, out, alpha: float = 1.0)
|
|
| 172 |
return None
|
| 173 |
|
| 174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
@torch.library.register_fake(add_op_namespace_prefix("fp4_w4a16_linear_bf16"))
|
| 176 |
def _legacy_linear_fake(
|
| 177 |
a_packed: torch.Tensor,
|
|
@@ -771,6 +798,98 @@ def nvfp4_gemm_m256_bf16(
|
|
| 771 |
return out
|
| 772 |
|
| 773 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 774 |
def fp4_w4a16_linear_bf16(
|
| 775 |
a_packed: torch.Tensor,
|
| 776 |
b_packed: torch.Tensor,
|
|
@@ -932,6 +1051,8 @@ __all__ = [
|
|
| 932 |
"SUPPORTED_CUDA_CAPABILITIES",
|
| 933 |
"SUPPORTED_LAYOUTS",
|
| 934 |
"capabilities",
|
|
|
|
|
|
|
| 935 |
"aligned_fp4_dim",
|
| 936 |
"cutlass_fp4_gemm_geglu_il_hw_v10",
|
| 937 |
"dequantize_fp4_sfa_fp16",
|
|
@@ -947,6 +1068,7 @@ __all__ = [
|
|
| 947 |
"nvfp4_gemm_nvfp4",
|
| 948 |
"nvfp4_gemm_m256_bf16",
|
| 949 |
"nvfp4_gemm_m256_workspace_size",
|
|
|
|
| 950 |
"nvfp4_gemm_geglu_nvfp4_fp16",
|
| 951 |
"nvfp4_gemm_bias_gelu_nvfp4_fp16",
|
| 952 |
"nvfp4_gemm_bias_residual_fp16",
|
|
|
|
| 37 |
"sm120_warpsplit_mrows": (1, 16),
|
| 38 |
"sm120_warpsplit_mrows_n_alignment": 8,
|
| 39 |
"sm120_warpsplit_mrows_k_alignment": "64 * warps",
|
| 40 |
+
"sm120_w4a16_marlin_m": (1, 16),
|
| 41 |
+
"sm120_w4a16_marlin_n_alignment": 64,
|
| 42 |
+
"sm120_w4a16_marlin_k_alignment": 128,
|
| 43 |
+
"sm120_w4a16_marlin_weight_layout": "marlin-repacked-nvfp4",
|
| 44 |
+
"sm120_w4a16_marlin_scale_layout": "marlin-permuted-fp8-e4m3-block16",
|
| 45 |
"errors": "exceptions",
|
| 46 |
}
|
| 47 |
|
|
|
|
| 177 |
return None
|
| 178 |
|
| 179 |
|
| 180 |
+
@torch.library.register_fake(add_op_namespace_prefix("nvfp4_w4a16_marlin_bf16"))
|
| 181 |
+
def _w4a16_marlin_fake(x, weight, weight_scale, global_scale, workspace, out) -> None:
|
| 182 |
+
del weight, weight_scale, global_scale, workspace
|
| 183 |
+
if x.ndim != 2 or out.ndim != 2 or out.shape[0] != x.shape[0]:
|
| 184 |
+
raise RuntimeError("x/out must have shapes (M, K)/(M, N)")
|
| 185 |
+
if not 1 <= x.shape[0] <= 16:
|
| 186 |
+
raise RuntimeError("the Marlin W4A16 tier serves M in [1, 16]")
|
| 187 |
+
if x.shape[1] % 128 or out.shape[1] % 64:
|
| 188 |
+
raise RuntimeError("K/N must be divisible by 128/64")
|
| 189 |
+
return None
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
@torch.library.register_fake(add_op_namespace_prefix("nvfp4_w4a16_marlin_repack"))
|
| 193 |
+
def _w4a16_marlin_repack_fake(qweight_kn, weight_marlin) -> None:
|
| 194 |
+
if qweight_kn.ndim != 2:
|
| 195 |
+
raise RuntimeError("qweight_kn must have shape (K / 8, N)")
|
| 196 |
+
k, n = qweight_kn.shape[0] * 8, qweight_kn.shape[1]
|
| 197 |
+
if weight_marlin.shape != (k // 16, n * 2):
|
| 198 |
+
raise RuntimeError("weight_marlin must have shape (K / 16, 2 * N)")
|
| 199 |
+
return None
|
| 200 |
+
|
| 201 |
+
|
| 202 |
@torch.library.register_fake(add_op_namespace_prefix("fp4_w4a16_linear_bf16"))
|
| 203 |
def _legacy_linear_fake(
|
| 204 |
a_packed: torch.Tensor,
|
|
|
|
| 798 |
return out
|
| 799 |
|
| 800 |
|
| 801 |
+
def allocate_w4a16_marlin_workspace(device: torch.device | str) -> torch.Tensor:
|
| 802 |
+
"""Allocate the persistent Marlin lock workspace before graph capture."""
|
| 803 |
+
props = torch.cuda.get_device_properties(device)
|
| 804 |
+
return torch.zeros(props.multi_processor_count, device=device, dtype=torch.int32)
|
| 805 |
+
|
| 806 |
+
|
| 807 |
+
def adopt_nvfp4_w4a16_marlin(
|
| 808 |
+
weight_packed: torch.Tensor,
|
| 809 |
+
weight_scale: torch.Tensor,
|
| 810 |
+
weight_scale_2: torch.Tensor,
|
| 811 |
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 812 |
+
"""Convert standard ModelOpt NVFP4 tensors once at model bind time.
|
| 813 |
+
|
| 814 |
+
Inputs use ``weight[N,K/2]``, ``weight_scale[N,K/16]`` and scalar
|
| 815 |
+
``weight_scale_2``. Returned tensors are the three runtime operands plus a
|
| 816 |
+
zeroed lock workspace. This function is intentionally outside the graph
|
| 817 |
+
hot path.
|
| 818 |
+
"""
|
| 819 |
+
if weight_packed.dtype != torch.uint8 or weight_packed.ndim != 2:
|
| 820 |
+
raise ValueError("weight_packed must be uint8 with shape (N, K / 2)")
|
| 821 |
+
n, k = weight_packed.shape[0], weight_packed.shape[1] * 2
|
| 822 |
+
if k % 128 or n % 64:
|
| 823 |
+
raise ValueError("K/N must be divisible by 128/64")
|
| 824 |
+
if weight_scale.shape != (n, k // NVFP4_BLOCK_SIZE):
|
| 825 |
+
raise ValueError("weight_scale must have shape (N, K / 16)")
|
| 826 |
+
if weight_scale_2.numel() != 1:
|
| 827 |
+
raise ValueError("weight_scale_2 must contain one global scale")
|
| 828 |
+
|
| 829 |
+
qweight_kn = weight_packed.view(torch.int32).T.contiguous()
|
| 830 |
+
weight_marlin = torch.empty(
|
| 831 |
+
(k // 16, n * 2), device=weight_packed.device, dtype=torch.int32
|
| 832 |
+
)
|
| 833 |
+
ops.nvfp4_w4a16_marlin_repack(qweight_kn, weight_marlin)
|
| 834 |
+
|
| 835 |
+
scales = weight_scale.T.contiguous().to(torch.bfloat16)
|
| 836 |
+
scale_perm = [i + 8 * j for i in range(8) for j in range(8)]
|
| 837 |
+
scales = scales.reshape(-1, 64)[:, scale_perm].reshape(-1, n).contiguous()
|
| 838 |
+
|
| 839 |
+
# Match vLLM's NVFP4 Marlin contract exactly: scale permutation uses the
|
| 840 |
+
# model parameter dtype, then S0E5M3 encoding starts from FP16.
|
| 841 |
+
scales = scales.to(torch.float16)
|
| 842 |
+
scales = scales.view(-1, 4)[:, [0, 2, 1, 3]].reshape(scales.shape)
|
| 843 |
+
scaled = scales.float() * (2**7)
|
| 844 |
+
nonzero = scaled > 0
|
| 845 |
+
scale_factor = 1.0
|
| 846 |
+
if bool(nonzero.any()):
|
| 847 |
+
max_value = scaled[nonzero].max()
|
| 848 |
+
if bool(max_value < 448 * (2**7)):
|
| 849 |
+
scale_factor = float((448 * (2**7) / max_value).log2().floor().exp2())
|
| 850 |
+
if scale_factor > 1.0:
|
| 851 |
+
scales = (scales.float() * scale_factor).to(torch.float16)
|
| 852 |
+
scales = scales * (2**7)
|
| 853 |
+
scales[scales < 2] = 0
|
| 854 |
+
scales = (scales.view(torch.int16) << 1).view(torch.float8_e4m3fn)
|
| 855 |
+
scales = scales[:, 1::2].contiguous()
|
| 856 |
+
|
| 857 |
+
# Marlin's BF16 dequantizer uses exponent bias 126 and removes the seven
|
| 858 |
+
# bits introduced while encoding the special unsigned FP8 scale format.
|
| 859 |
+
global_scale = (
|
| 860 |
+
weight_scale_2.to(device=weight_packed.device, dtype=torch.float32)
|
| 861 |
+
* (2.0 ** 119)
|
| 862 |
+
/ scale_factor
|
| 863 |
+
).contiguous()
|
| 864 |
+
workspace = allocate_w4a16_marlin_workspace(weight_packed.device)
|
| 865 |
+
return weight_marlin, scales, global_scale, workspace
|
| 866 |
+
|
| 867 |
+
|
| 868 |
+
def nvfp4_w4a16_marlin_bf16(
|
| 869 |
+
x: torch.Tensor,
|
| 870 |
+
weight_marlin: torch.Tensor,
|
| 871 |
+
weight_scale_marlin: torch.Tensor,
|
| 872 |
+
weight_global_scale: torch.Tensor,
|
| 873 |
+
*,
|
| 874 |
+
workspace: torch.Tensor,
|
| 875 |
+
out: torch.Tensor | None = None,
|
| 876 |
+
) -> torch.Tensor:
|
| 877 |
+
"""Small-M BF16 x NVFP4 linear using bind-time Marlin layouts.
|
| 878 |
+
|
| 879 |
+
``weight_marlin``, ``weight_scale_marlin`` and
|
| 880 |
+
``weight_global_scale`` are the tensors produced by a standard NVFP4
|
| 881 |
+
Marlin adoption step. The operation supports M in [1, 16] and never
|
| 882 |
+
allocates internally, making it suitable for CUDA Graph replay.
|
| 883 |
+
"""
|
| 884 |
+
if out is None:
|
| 885 |
+
n = weight_scale_marlin.numel() // (x.shape[1] // NVFP4_BLOCK_SIZE)
|
| 886 |
+
out = torch.empty((x.shape[0], n), device=x.device, dtype=torch.bfloat16)
|
| 887 |
+
ops.nvfp4_w4a16_marlin_bf16(
|
| 888 |
+
x, weight_marlin, weight_scale_marlin, weight_global_scale, workspace, out
|
| 889 |
+
)
|
| 890 |
+
return out
|
| 891 |
+
|
| 892 |
+
|
| 893 |
def fp4_w4a16_linear_bf16(
|
| 894 |
a_packed: torch.Tensor,
|
| 895 |
b_packed: torch.Tensor,
|
|
|
|
| 1051 |
"SUPPORTED_CUDA_CAPABILITIES",
|
| 1052 |
"SUPPORTED_LAYOUTS",
|
| 1053 |
"capabilities",
|
| 1054 |
+
"allocate_w4a16_marlin_workspace",
|
| 1055 |
+
"adopt_nvfp4_w4a16_marlin",
|
| 1056 |
"aligned_fp4_dim",
|
| 1057 |
"cutlass_fp4_gemm_geglu_il_hw_v10",
|
| 1058 |
"dequantize_fp4_sfa_fp16",
|
|
|
|
| 1068 |
"nvfp4_gemm_nvfp4",
|
| 1069 |
"nvfp4_gemm_m256_bf16",
|
| 1070 |
"nvfp4_gemm_m256_workspace_size",
|
| 1071 |
+
"nvfp4_w4a16_marlin_bf16",
|
| 1072 |
"nvfp4_gemm_geglu_nvfp4_fp16",
|
| 1073 |
"nvfp4_gemm_bias_gelu_nvfp4_fp16",
|
| 1074 |
"nvfp4_gemm_bias_residual_fp16",
|
build/torch212-cxx11-cu130-x86_64-linux/{_fp4_gemm_cuda_dc57d15.abi3.so → _fp4_gemm_cuda_6971f7a.abi3.so}
RENAMED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4ecd0fd14beafd0648b7353999f47e14168ca7a7e051d51527729b836e648548
|
| 3 |
+
size 10758024
|
build/torch212-cxx11-cu130-x86_64-linux/_ops.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
import torch
|
| 2 |
-
from . import
|
| 3 |
-
ops = torch.ops.
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
-
return f"
|
|
|
|
| 1 |
import torch
|
| 2 |
+
from . import _fp4_gemm_cuda_6971f7a
|
| 3 |
+
ops = torch.ops._fp4_gemm_cuda_6971f7a
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
+
return f"_fp4_gemm_cuda_6971f7a::{op_name}"
|
build/torch212-cxx11-cu130-x86_64-linux/metadata.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"name": "fp4-gemm",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
|
@@ -14,9 +14,9 @@
|
|
| 14 |
"digest": {
|
| 15 |
"algorithm": "sha256",
|
| 16 |
"files": {
|
| 17 |
-
"__init__.py": "
|
| 18 |
-
"
|
| 19 |
-
"_ops.py": "
|
| 20 |
"fp4_gemm/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
|
| 21 |
}
|
| 22 |
},
|
|
@@ -27,7 +27,7 @@
|
|
| 27 |
"dirty": false
|
| 28 |
},
|
| 29 |
"kernel": {
|
| 30 |
-
"sha": "
|
| 31 |
"dirty": false
|
| 32 |
}
|
| 33 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "fp4-gemm",
|
| 3 |
+
"id": "_fp4_gemm_cuda_6971f7a",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
|
|
|
| 14 |
"digest": {
|
| 15 |
"algorithm": "sha256",
|
| 16 |
"files": {
|
| 17 |
+
"__init__.py": "0hDN2W6X3IMoWIi5BBufrnqrq2yj5N1/q9wBhCwBRxA=",
|
| 18 |
+
"_fp4_gemm_cuda_6971f7a.abi3.so": "Ts0P0Uvq/QZItzU5mfR+FBaMp6fgUdUVJ3Kbg25khUg=",
|
| 19 |
+
"_ops.py": "D7InzLughnNL5JQowMPNYpSpNPRFeeD8mlgs38Xw03g=",
|
| 20 |
"fp4_gemm/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
|
| 21 |
}
|
| 22 |
},
|
|
|
|
| 27 |
"dirty": false
|
| 28 |
},
|
| 29 |
"kernel": {
|
| 30 |
+
"sha": "6971f7a37410ba0d6edae93b389dbcbe3ba924d5",
|
| 31 |
"dirty": false
|
| 32 |
}
|
| 33 |
}
|
build/torch212-cxx11-cu132-x86_64-linux/__init__.py
CHANGED
|
@@ -37,6 +37,11 @@ def capabilities() -> dict[str, object]:
|
|
| 37 |
"sm120_warpsplit_mrows": (1, 16),
|
| 38 |
"sm120_warpsplit_mrows_n_alignment": 8,
|
| 39 |
"sm120_warpsplit_mrows_k_alignment": "64 * warps",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
"errors": "exceptions",
|
| 41 |
}
|
| 42 |
|
|
@@ -172,6 +177,28 @@ def _m256_fake(a_packed, b_packed, sfa, sfb, workspace, out, alpha: float = 1.0)
|
|
| 172 |
return None
|
| 173 |
|
| 174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
@torch.library.register_fake(add_op_namespace_prefix("fp4_w4a16_linear_bf16"))
|
| 176 |
def _legacy_linear_fake(
|
| 177 |
a_packed: torch.Tensor,
|
|
@@ -771,6 +798,98 @@ def nvfp4_gemm_m256_bf16(
|
|
| 771 |
return out
|
| 772 |
|
| 773 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 774 |
def fp4_w4a16_linear_bf16(
|
| 775 |
a_packed: torch.Tensor,
|
| 776 |
b_packed: torch.Tensor,
|
|
@@ -932,6 +1051,8 @@ __all__ = [
|
|
| 932 |
"SUPPORTED_CUDA_CAPABILITIES",
|
| 933 |
"SUPPORTED_LAYOUTS",
|
| 934 |
"capabilities",
|
|
|
|
|
|
|
| 935 |
"aligned_fp4_dim",
|
| 936 |
"cutlass_fp4_gemm_geglu_il_hw_v10",
|
| 937 |
"dequantize_fp4_sfa_fp16",
|
|
@@ -947,6 +1068,7 @@ __all__ = [
|
|
| 947 |
"nvfp4_gemm_nvfp4",
|
| 948 |
"nvfp4_gemm_m256_bf16",
|
| 949 |
"nvfp4_gemm_m256_workspace_size",
|
|
|
|
| 950 |
"nvfp4_gemm_geglu_nvfp4_fp16",
|
| 951 |
"nvfp4_gemm_bias_gelu_nvfp4_fp16",
|
| 952 |
"nvfp4_gemm_bias_residual_fp16",
|
|
|
|
| 37 |
"sm120_warpsplit_mrows": (1, 16),
|
| 38 |
"sm120_warpsplit_mrows_n_alignment": 8,
|
| 39 |
"sm120_warpsplit_mrows_k_alignment": "64 * warps",
|
| 40 |
+
"sm120_w4a16_marlin_m": (1, 16),
|
| 41 |
+
"sm120_w4a16_marlin_n_alignment": 64,
|
| 42 |
+
"sm120_w4a16_marlin_k_alignment": 128,
|
| 43 |
+
"sm120_w4a16_marlin_weight_layout": "marlin-repacked-nvfp4",
|
| 44 |
+
"sm120_w4a16_marlin_scale_layout": "marlin-permuted-fp8-e4m3-block16",
|
| 45 |
"errors": "exceptions",
|
| 46 |
}
|
| 47 |
|
|
|
|
| 177 |
return None
|
| 178 |
|
| 179 |
|
| 180 |
+
@torch.library.register_fake(add_op_namespace_prefix("nvfp4_w4a16_marlin_bf16"))
|
| 181 |
+
def _w4a16_marlin_fake(x, weight, weight_scale, global_scale, workspace, out) -> None:
|
| 182 |
+
del weight, weight_scale, global_scale, workspace
|
| 183 |
+
if x.ndim != 2 or out.ndim != 2 or out.shape[0] != x.shape[0]:
|
| 184 |
+
raise RuntimeError("x/out must have shapes (M, K)/(M, N)")
|
| 185 |
+
if not 1 <= x.shape[0] <= 16:
|
| 186 |
+
raise RuntimeError("the Marlin W4A16 tier serves M in [1, 16]")
|
| 187 |
+
if x.shape[1] % 128 or out.shape[1] % 64:
|
| 188 |
+
raise RuntimeError("K/N must be divisible by 128/64")
|
| 189 |
+
return None
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
@torch.library.register_fake(add_op_namespace_prefix("nvfp4_w4a16_marlin_repack"))
|
| 193 |
+
def _w4a16_marlin_repack_fake(qweight_kn, weight_marlin) -> None:
|
| 194 |
+
if qweight_kn.ndim != 2:
|
| 195 |
+
raise RuntimeError("qweight_kn must have shape (K / 8, N)")
|
| 196 |
+
k, n = qweight_kn.shape[0] * 8, qweight_kn.shape[1]
|
| 197 |
+
if weight_marlin.shape != (k // 16, n * 2):
|
| 198 |
+
raise RuntimeError("weight_marlin must have shape (K / 16, 2 * N)")
|
| 199 |
+
return None
|
| 200 |
+
|
| 201 |
+
|
| 202 |
@torch.library.register_fake(add_op_namespace_prefix("fp4_w4a16_linear_bf16"))
|
| 203 |
def _legacy_linear_fake(
|
| 204 |
a_packed: torch.Tensor,
|
|
|
|
| 798 |
return out
|
| 799 |
|
| 800 |
|
| 801 |
+
def allocate_w4a16_marlin_workspace(device: torch.device | str) -> torch.Tensor:
|
| 802 |
+
"""Allocate the persistent Marlin lock workspace before graph capture."""
|
| 803 |
+
props = torch.cuda.get_device_properties(device)
|
| 804 |
+
return torch.zeros(props.multi_processor_count, device=device, dtype=torch.int32)
|
| 805 |
+
|
| 806 |
+
|
| 807 |
+
def adopt_nvfp4_w4a16_marlin(
|
| 808 |
+
weight_packed: torch.Tensor,
|
| 809 |
+
weight_scale: torch.Tensor,
|
| 810 |
+
weight_scale_2: torch.Tensor,
|
| 811 |
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 812 |
+
"""Convert standard ModelOpt NVFP4 tensors once at model bind time.
|
| 813 |
+
|
| 814 |
+
Inputs use ``weight[N,K/2]``, ``weight_scale[N,K/16]`` and scalar
|
| 815 |
+
``weight_scale_2``. Returned tensors are the three runtime operands plus a
|
| 816 |
+
zeroed lock workspace. This function is intentionally outside the graph
|
| 817 |
+
hot path.
|
| 818 |
+
"""
|
| 819 |
+
if weight_packed.dtype != torch.uint8 or weight_packed.ndim != 2:
|
| 820 |
+
raise ValueError("weight_packed must be uint8 with shape (N, K / 2)")
|
| 821 |
+
n, k = weight_packed.shape[0], weight_packed.shape[1] * 2
|
| 822 |
+
if k % 128 or n % 64:
|
| 823 |
+
raise ValueError("K/N must be divisible by 128/64")
|
| 824 |
+
if weight_scale.shape != (n, k // NVFP4_BLOCK_SIZE):
|
| 825 |
+
raise ValueError("weight_scale must have shape (N, K / 16)")
|
| 826 |
+
if weight_scale_2.numel() != 1:
|
| 827 |
+
raise ValueError("weight_scale_2 must contain one global scale")
|
| 828 |
+
|
| 829 |
+
qweight_kn = weight_packed.view(torch.int32).T.contiguous()
|
| 830 |
+
weight_marlin = torch.empty(
|
| 831 |
+
(k // 16, n * 2), device=weight_packed.device, dtype=torch.int32
|
| 832 |
+
)
|
| 833 |
+
ops.nvfp4_w4a16_marlin_repack(qweight_kn, weight_marlin)
|
| 834 |
+
|
| 835 |
+
scales = weight_scale.T.contiguous().to(torch.bfloat16)
|
| 836 |
+
scale_perm = [i + 8 * j for i in range(8) for j in range(8)]
|
| 837 |
+
scales = scales.reshape(-1, 64)[:, scale_perm].reshape(-1, n).contiguous()
|
| 838 |
+
|
| 839 |
+
# Match vLLM's NVFP4 Marlin contract exactly: scale permutation uses the
|
| 840 |
+
# model parameter dtype, then S0E5M3 encoding starts from FP16.
|
| 841 |
+
scales = scales.to(torch.float16)
|
| 842 |
+
scales = scales.view(-1, 4)[:, [0, 2, 1, 3]].reshape(scales.shape)
|
| 843 |
+
scaled = scales.float() * (2**7)
|
| 844 |
+
nonzero = scaled > 0
|
| 845 |
+
scale_factor = 1.0
|
| 846 |
+
if bool(nonzero.any()):
|
| 847 |
+
max_value = scaled[nonzero].max()
|
| 848 |
+
if bool(max_value < 448 * (2**7)):
|
| 849 |
+
scale_factor = float((448 * (2**7) / max_value).log2().floor().exp2())
|
| 850 |
+
if scale_factor > 1.0:
|
| 851 |
+
scales = (scales.float() * scale_factor).to(torch.float16)
|
| 852 |
+
scales = scales * (2**7)
|
| 853 |
+
scales[scales < 2] = 0
|
| 854 |
+
scales = (scales.view(torch.int16) << 1).view(torch.float8_e4m3fn)
|
| 855 |
+
scales = scales[:, 1::2].contiguous()
|
| 856 |
+
|
| 857 |
+
# Marlin's BF16 dequantizer uses exponent bias 126 and removes the seven
|
| 858 |
+
# bits introduced while encoding the special unsigned FP8 scale format.
|
| 859 |
+
global_scale = (
|
| 860 |
+
weight_scale_2.to(device=weight_packed.device, dtype=torch.float32)
|
| 861 |
+
* (2.0 ** 119)
|
| 862 |
+
/ scale_factor
|
| 863 |
+
).contiguous()
|
| 864 |
+
workspace = allocate_w4a16_marlin_workspace(weight_packed.device)
|
| 865 |
+
return weight_marlin, scales, global_scale, workspace
|
| 866 |
+
|
| 867 |
+
|
| 868 |
+
def nvfp4_w4a16_marlin_bf16(
|
| 869 |
+
x: torch.Tensor,
|
| 870 |
+
weight_marlin: torch.Tensor,
|
| 871 |
+
weight_scale_marlin: torch.Tensor,
|
| 872 |
+
weight_global_scale: torch.Tensor,
|
| 873 |
+
*,
|
| 874 |
+
workspace: torch.Tensor,
|
| 875 |
+
out: torch.Tensor | None = None,
|
| 876 |
+
) -> torch.Tensor:
|
| 877 |
+
"""Small-M BF16 x NVFP4 linear using bind-time Marlin layouts.
|
| 878 |
+
|
| 879 |
+
``weight_marlin``, ``weight_scale_marlin`` and
|
| 880 |
+
``weight_global_scale`` are the tensors produced by a standard NVFP4
|
| 881 |
+
Marlin adoption step. The operation supports M in [1, 16] and never
|
| 882 |
+
allocates internally, making it suitable for CUDA Graph replay.
|
| 883 |
+
"""
|
| 884 |
+
if out is None:
|
| 885 |
+
n = weight_scale_marlin.numel() // (x.shape[1] // NVFP4_BLOCK_SIZE)
|
| 886 |
+
out = torch.empty((x.shape[0], n), device=x.device, dtype=torch.bfloat16)
|
| 887 |
+
ops.nvfp4_w4a16_marlin_bf16(
|
| 888 |
+
x, weight_marlin, weight_scale_marlin, weight_global_scale, workspace, out
|
| 889 |
+
)
|
| 890 |
+
return out
|
| 891 |
+
|
| 892 |
+
|
| 893 |
def fp4_w4a16_linear_bf16(
|
| 894 |
a_packed: torch.Tensor,
|
| 895 |
b_packed: torch.Tensor,
|
|
|
|
| 1051 |
"SUPPORTED_CUDA_CAPABILITIES",
|
| 1052 |
"SUPPORTED_LAYOUTS",
|
| 1053 |
"capabilities",
|
| 1054 |
+
"allocate_w4a16_marlin_workspace",
|
| 1055 |
+
"adopt_nvfp4_w4a16_marlin",
|
| 1056 |
"aligned_fp4_dim",
|
| 1057 |
"cutlass_fp4_gemm_geglu_il_hw_v10",
|
| 1058 |
"dequantize_fp4_sfa_fp16",
|
|
|
|
| 1068 |
"nvfp4_gemm_nvfp4",
|
| 1069 |
"nvfp4_gemm_m256_bf16",
|
| 1070 |
"nvfp4_gemm_m256_workspace_size",
|
| 1071 |
+
"nvfp4_w4a16_marlin_bf16",
|
| 1072 |
"nvfp4_gemm_geglu_nvfp4_fp16",
|
| 1073 |
"nvfp4_gemm_bias_gelu_nvfp4_fp16",
|
| 1074 |
"nvfp4_gemm_bias_residual_fp16",
|
build/torch212-cxx11-cu132-x86_64-linux/{_fp4_gemm_cuda_dc57d15.abi3.so → _fp4_gemm_cuda_6971f7a.abi3.so}
RENAMED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8d2c4336526e99aec6d7644e1f9bbc3cc0175e0d9b63689e50d8639b58dd0daa
|
| 3 |
+
size 10663864
|
build/torch212-cxx11-cu132-x86_64-linux/_ops.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
import torch
|
| 2 |
-
from . import
|
| 3 |
-
ops = torch.ops.
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
-
return f"
|
|
|
|
| 1 |
import torch
|
| 2 |
+
from . import _fp4_gemm_cuda_6971f7a
|
| 3 |
+
ops = torch.ops._fp4_gemm_cuda_6971f7a
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
+
return f"_fp4_gemm_cuda_6971f7a::{op_name}"
|
build/torch212-cxx11-cu132-x86_64-linux/metadata.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"name": "fp4-gemm",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
|
@@ -14,9 +14,9 @@
|
|
| 14 |
"digest": {
|
| 15 |
"algorithm": "sha256",
|
| 16 |
"files": {
|
| 17 |
-
"__init__.py": "
|
| 18 |
-
"
|
| 19 |
-
"_ops.py": "
|
| 20 |
"fp4_gemm/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
|
| 21 |
}
|
| 22 |
},
|
|
@@ -27,7 +27,7 @@
|
|
| 27 |
"dirty": false
|
| 28 |
},
|
| 29 |
"kernel": {
|
| 30 |
-
"sha": "
|
| 31 |
"dirty": false
|
| 32 |
}
|
| 33 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "fp4-gemm",
|
| 3 |
+
"id": "_fp4_gemm_cuda_6971f7a",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
|
|
|
| 14 |
"digest": {
|
| 15 |
"algorithm": "sha256",
|
| 16 |
"files": {
|
| 17 |
+
"__init__.py": "0hDN2W6X3IMoWIi5BBufrnqrq2yj5N1/q9wBhCwBRxA=",
|
| 18 |
+
"_fp4_gemm_cuda_6971f7a.abi3.so": "jSxDNlJuma7G12ROH5u8PMAXXg2bY2ieUNhjm1jdDao=",
|
| 19 |
+
"_ops.py": "D7InzLughnNL5JQowMPNYpSpNPRFeeD8mlgs38Xw03g=",
|
| 20 |
"fp4_gemm/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
|
| 21 |
}
|
| 22 |
},
|
|
|
|
| 27 |
"dirty": false
|
| 28 |
},
|
| 29 |
"kernel": {
|
| 30 |
+
"sha": "6971f7a37410ba0d6edae93b389dbcbe3ba924d5",
|
| 31 |
"dirty": false
|
| 32 |
}
|
| 33 |
}
|
build/torch213-cxx11-cu130-x86_64-linux/__init__.py
CHANGED
|
@@ -37,6 +37,11 @@ def capabilities() -> dict[str, object]:
|
|
| 37 |
"sm120_warpsplit_mrows": (1, 16),
|
| 38 |
"sm120_warpsplit_mrows_n_alignment": 8,
|
| 39 |
"sm120_warpsplit_mrows_k_alignment": "64 * warps",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
"errors": "exceptions",
|
| 41 |
}
|
| 42 |
|
|
@@ -172,6 +177,28 @@ def _m256_fake(a_packed, b_packed, sfa, sfb, workspace, out, alpha: float = 1.0)
|
|
| 172 |
return None
|
| 173 |
|
| 174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
@torch.library.register_fake(add_op_namespace_prefix("fp4_w4a16_linear_bf16"))
|
| 176 |
def _legacy_linear_fake(
|
| 177 |
a_packed: torch.Tensor,
|
|
@@ -771,6 +798,98 @@ def nvfp4_gemm_m256_bf16(
|
|
| 771 |
return out
|
| 772 |
|
| 773 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 774 |
def fp4_w4a16_linear_bf16(
|
| 775 |
a_packed: torch.Tensor,
|
| 776 |
b_packed: torch.Tensor,
|
|
@@ -932,6 +1051,8 @@ __all__ = [
|
|
| 932 |
"SUPPORTED_CUDA_CAPABILITIES",
|
| 933 |
"SUPPORTED_LAYOUTS",
|
| 934 |
"capabilities",
|
|
|
|
|
|
|
| 935 |
"aligned_fp4_dim",
|
| 936 |
"cutlass_fp4_gemm_geglu_il_hw_v10",
|
| 937 |
"dequantize_fp4_sfa_fp16",
|
|
@@ -947,6 +1068,7 @@ __all__ = [
|
|
| 947 |
"nvfp4_gemm_nvfp4",
|
| 948 |
"nvfp4_gemm_m256_bf16",
|
| 949 |
"nvfp4_gemm_m256_workspace_size",
|
|
|
|
| 950 |
"nvfp4_gemm_geglu_nvfp4_fp16",
|
| 951 |
"nvfp4_gemm_bias_gelu_nvfp4_fp16",
|
| 952 |
"nvfp4_gemm_bias_residual_fp16",
|
|
|
|
| 37 |
"sm120_warpsplit_mrows": (1, 16),
|
| 38 |
"sm120_warpsplit_mrows_n_alignment": 8,
|
| 39 |
"sm120_warpsplit_mrows_k_alignment": "64 * warps",
|
| 40 |
+
"sm120_w4a16_marlin_m": (1, 16),
|
| 41 |
+
"sm120_w4a16_marlin_n_alignment": 64,
|
| 42 |
+
"sm120_w4a16_marlin_k_alignment": 128,
|
| 43 |
+
"sm120_w4a16_marlin_weight_layout": "marlin-repacked-nvfp4",
|
| 44 |
+
"sm120_w4a16_marlin_scale_layout": "marlin-permuted-fp8-e4m3-block16",
|
| 45 |
"errors": "exceptions",
|
| 46 |
}
|
| 47 |
|
|
|
|
| 177 |
return None
|
| 178 |
|
| 179 |
|
| 180 |
+
@torch.library.register_fake(add_op_namespace_prefix("nvfp4_w4a16_marlin_bf16"))
|
| 181 |
+
def _w4a16_marlin_fake(x, weight, weight_scale, global_scale, workspace, out) -> None:
|
| 182 |
+
del weight, weight_scale, global_scale, workspace
|
| 183 |
+
if x.ndim != 2 or out.ndim != 2 or out.shape[0] != x.shape[0]:
|
| 184 |
+
raise RuntimeError("x/out must have shapes (M, K)/(M, N)")
|
| 185 |
+
if not 1 <= x.shape[0] <= 16:
|
| 186 |
+
raise RuntimeError("the Marlin W4A16 tier serves M in [1, 16]")
|
| 187 |
+
if x.shape[1] % 128 or out.shape[1] % 64:
|
| 188 |
+
raise RuntimeError("K/N must be divisible by 128/64")
|
| 189 |
+
return None
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
@torch.library.register_fake(add_op_namespace_prefix("nvfp4_w4a16_marlin_repack"))
|
| 193 |
+
def _w4a16_marlin_repack_fake(qweight_kn, weight_marlin) -> None:
|
| 194 |
+
if qweight_kn.ndim != 2:
|
| 195 |
+
raise RuntimeError("qweight_kn must have shape (K / 8, N)")
|
| 196 |
+
k, n = qweight_kn.shape[0] * 8, qweight_kn.shape[1]
|
| 197 |
+
if weight_marlin.shape != (k // 16, n * 2):
|
| 198 |
+
raise RuntimeError("weight_marlin must have shape (K / 16, 2 * N)")
|
| 199 |
+
return None
|
| 200 |
+
|
| 201 |
+
|
| 202 |
@torch.library.register_fake(add_op_namespace_prefix("fp4_w4a16_linear_bf16"))
|
| 203 |
def _legacy_linear_fake(
|
| 204 |
a_packed: torch.Tensor,
|
|
|
|
| 798 |
return out
|
| 799 |
|
| 800 |
|
| 801 |
+
def allocate_w4a16_marlin_workspace(device: torch.device | str) -> torch.Tensor:
|
| 802 |
+
"""Allocate the persistent Marlin lock workspace before graph capture."""
|
| 803 |
+
props = torch.cuda.get_device_properties(device)
|
| 804 |
+
return torch.zeros(props.multi_processor_count, device=device, dtype=torch.int32)
|
| 805 |
+
|
| 806 |
+
|
| 807 |
+
def adopt_nvfp4_w4a16_marlin(
|
| 808 |
+
weight_packed: torch.Tensor,
|
| 809 |
+
weight_scale: torch.Tensor,
|
| 810 |
+
weight_scale_2: torch.Tensor,
|
| 811 |
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 812 |
+
"""Convert standard ModelOpt NVFP4 tensors once at model bind time.
|
| 813 |
+
|
| 814 |
+
Inputs use ``weight[N,K/2]``, ``weight_scale[N,K/16]`` and scalar
|
| 815 |
+
``weight_scale_2``. Returned tensors are the three runtime operands plus a
|
| 816 |
+
zeroed lock workspace. This function is intentionally outside the graph
|
| 817 |
+
hot path.
|
| 818 |
+
"""
|
| 819 |
+
if weight_packed.dtype != torch.uint8 or weight_packed.ndim != 2:
|
| 820 |
+
raise ValueError("weight_packed must be uint8 with shape (N, K / 2)")
|
| 821 |
+
n, k = weight_packed.shape[0], weight_packed.shape[1] * 2
|
| 822 |
+
if k % 128 or n % 64:
|
| 823 |
+
raise ValueError("K/N must be divisible by 128/64")
|
| 824 |
+
if weight_scale.shape != (n, k // NVFP4_BLOCK_SIZE):
|
| 825 |
+
raise ValueError("weight_scale must have shape (N, K / 16)")
|
| 826 |
+
if weight_scale_2.numel() != 1:
|
| 827 |
+
raise ValueError("weight_scale_2 must contain one global scale")
|
| 828 |
+
|
| 829 |
+
qweight_kn = weight_packed.view(torch.int32).T.contiguous()
|
| 830 |
+
weight_marlin = torch.empty(
|
| 831 |
+
(k // 16, n * 2), device=weight_packed.device, dtype=torch.int32
|
| 832 |
+
)
|
| 833 |
+
ops.nvfp4_w4a16_marlin_repack(qweight_kn, weight_marlin)
|
| 834 |
+
|
| 835 |
+
scales = weight_scale.T.contiguous().to(torch.bfloat16)
|
| 836 |
+
scale_perm = [i + 8 * j for i in range(8) for j in range(8)]
|
| 837 |
+
scales = scales.reshape(-1, 64)[:, scale_perm].reshape(-1, n).contiguous()
|
| 838 |
+
|
| 839 |
+
# Match vLLM's NVFP4 Marlin contract exactly: scale permutation uses the
|
| 840 |
+
# model parameter dtype, then S0E5M3 encoding starts from FP16.
|
| 841 |
+
scales = scales.to(torch.float16)
|
| 842 |
+
scales = scales.view(-1, 4)[:, [0, 2, 1, 3]].reshape(scales.shape)
|
| 843 |
+
scaled = scales.float() * (2**7)
|
| 844 |
+
nonzero = scaled > 0
|
| 845 |
+
scale_factor = 1.0
|
| 846 |
+
if bool(nonzero.any()):
|
| 847 |
+
max_value = scaled[nonzero].max()
|
| 848 |
+
if bool(max_value < 448 * (2**7)):
|
| 849 |
+
scale_factor = float((448 * (2**7) / max_value).log2().floor().exp2())
|
| 850 |
+
if scale_factor > 1.0:
|
| 851 |
+
scales = (scales.float() * scale_factor).to(torch.float16)
|
| 852 |
+
scales = scales * (2**7)
|
| 853 |
+
scales[scales < 2] = 0
|
| 854 |
+
scales = (scales.view(torch.int16) << 1).view(torch.float8_e4m3fn)
|
| 855 |
+
scales = scales[:, 1::2].contiguous()
|
| 856 |
+
|
| 857 |
+
# Marlin's BF16 dequantizer uses exponent bias 126 and removes the seven
|
| 858 |
+
# bits introduced while encoding the special unsigned FP8 scale format.
|
| 859 |
+
global_scale = (
|
| 860 |
+
weight_scale_2.to(device=weight_packed.device, dtype=torch.float32)
|
| 861 |
+
* (2.0 ** 119)
|
| 862 |
+
/ scale_factor
|
| 863 |
+
).contiguous()
|
| 864 |
+
workspace = allocate_w4a16_marlin_workspace(weight_packed.device)
|
| 865 |
+
return weight_marlin, scales, global_scale, workspace
|
| 866 |
+
|
| 867 |
+
|
| 868 |
+
def nvfp4_w4a16_marlin_bf16(
|
| 869 |
+
x: torch.Tensor,
|
| 870 |
+
weight_marlin: torch.Tensor,
|
| 871 |
+
weight_scale_marlin: torch.Tensor,
|
| 872 |
+
weight_global_scale: torch.Tensor,
|
| 873 |
+
*,
|
| 874 |
+
workspace: torch.Tensor,
|
| 875 |
+
out: torch.Tensor | None = None,
|
| 876 |
+
) -> torch.Tensor:
|
| 877 |
+
"""Small-M BF16 x NVFP4 linear using bind-time Marlin layouts.
|
| 878 |
+
|
| 879 |
+
``weight_marlin``, ``weight_scale_marlin`` and
|
| 880 |
+
``weight_global_scale`` are the tensors produced by a standard NVFP4
|
| 881 |
+
Marlin adoption step. The operation supports M in [1, 16] and never
|
| 882 |
+
allocates internally, making it suitable for CUDA Graph replay.
|
| 883 |
+
"""
|
| 884 |
+
if out is None:
|
| 885 |
+
n = weight_scale_marlin.numel() // (x.shape[1] // NVFP4_BLOCK_SIZE)
|
| 886 |
+
out = torch.empty((x.shape[0], n), device=x.device, dtype=torch.bfloat16)
|
| 887 |
+
ops.nvfp4_w4a16_marlin_bf16(
|
| 888 |
+
x, weight_marlin, weight_scale_marlin, weight_global_scale, workspace, out
|
| 889 |
+
)
|
| 890 |
+
return out
|
| 891 |
+
|
| 892 |
+
|
| 893 |
def fp4_w4a16_linear_bf16(
|
| 894 |
a_packed: torch.Tensor,
|
| 895 |
b_packed: torch.Tensor,
|
|
|
|
| 1051 |
"SUPPORTED_CUDA_CAPABILITIES",
|
| 1052 |
"SUPPORTED_LAYOUTS",
|
| 1053 |
"capabilities",
|
| 1054 |
+
"allocate_w4a16_marlin_workspace",
|
| 1055 |
+
"adopt_nvfp4_w4a16_marlin",
|
| 1056 |
"aligned_fp4_dim",
|
| 1057 |
"cutlass_fp4_gemm_geglu_il_hw_v10",
|
| 1058 |
"dequantize_fp4_sfa_fp16",
|
|
|
|
| 1068 |
"nvfp4_gemm_nvfp4",
|
| 1069 |
"nvfp4_gemm_m256_bf16",
|
| 1070 |
"nvfp4_gemm_m256_workspace_size",
|
| 1071 |
+
"nvfp4_w4a16_marlin_bf16",
|
| 1072 |
"nvfp4_gemm_geglu_nvfp4_fp16",
|
| 1073 |
"nvfp4_gemm_bias_gelu_nvfp4_fp16",
|
| 1074 |
"nvfp4_gemm_bias_residual_fp16",
|
build/torch213-cxx11-cu130-x86_64-linux/_fp4_gemm_cuda_6971f7a.abi3.so
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6075d51ae508759aacba4d16f7bf01c2f27c57de03afcb0745ffb073ee1c3320
|
| 3 |
+
size 10757864
|
build/torch213-cxx11-cu130-x86_64-linux/_fp4_gemm_cuda_dc57d15.abi3.so
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:a2e973560e69449ee79707984b7703d19e8f8ef9bba3902513887c57b64682c0
|
| 3 |
-
size 8566520
|
|
|
|
|
|
|
|
|
|
|
|
build/torch213-cxx11-cu130-x86_64-linux/_ops.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
import torch
|
| 2 |
-
from . import
|
| 3 |
-
ops = torch.ops.
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
-
return f"
|
|
|
|
| 1 |
import torch
|
| 2 |
+
from . import _fp4_gemm_cuda_6971f7a
|
| 3 |
+
ops = torch.ops._fp4_gemm_cuda_6971f7a
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
+
return f"_fp4_gemm_cuda_6971f7a::{op_name}"
|
build/torch213-cxx11-cu130-x86_64-linux/metadata.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"name": "fp4-gemm",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
|
@@ -14,9 +14,9 @@
|
|
| 14 |
"digest": {
|
| 15 |
"algorithm": "sha256",
|
| 16 |
"files": {
|
| 17 |
-
"__init__.py": "
|
| 18 |
-
"
|
| 19 |
-
"_ops.py": "
|
| 20 |
"fp4_gemm/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
|
| 21 |
}
|
| 22 |
},
|
|
@@ -27,7 +27,7 @@
|
|
| 27 |
"dirty": false
|
| 28 |
},
|
| 29 |
"kernel": {
|
| 30 |
-
"sha": "
|
| 31 |
"dirty": false
|
| 32 |
}
|
| 33 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "fp4-gemm",
|
| 3 |
+
"id": "_fp4_gemm_cuda_6971f7a",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
|
|
|
| 14 |
"digest": {
|
| 15 |
"algorithm": "sha256",
|
| 16 |
"files": {
|
| 17 |
+
"__init__.py": "0hDN2W6X3IMoWIi5BBufrnqrq2yj5N1/q9wBhCwBRxA=",
|
| 18 |
+
"_fp4_gemm_cuda_6971f7a.abi3.so": "YHXVGuUIdZqsuk0W978BwvJ8V94Dr8sHRf+wc+4cMyA=",
|
| 19 |
+
"_ops.py": "D7InzLughnNL5JQowMPNYpSpNPRFeeD8mlgs38Xw03g=",
|
| 20 |
"fp4_gemm/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
|
| 21 |
}
|
| 22 |
},
|
|
|
|
| 27 |
"dirty": false
|
| 28 |
},
|
| 29 |
"kernel": {
|
| 30 |
+
"sha": "6971f7a37410ba0d6edae93b389dbcbe3ba924d5",
|
| 31 |
"dirty": false
|
| 32 |
}
|
| 33 |
}
|
build/torch213-cxx11-cu132-x86_64-linux/__init__.py
CHANGED
|
@@ -37,6 +37,11 @@ def capabilities() -> dict[str, object]:
|
|
| 37 |
"sm120_warpsplit_mrows": (1, 16),
|
| 38 |
"sm120_warpsplit_mrows_n_alignment": 8,
|
| 39 |
"sm120_warpsplit_mrows_k_alignment": "64 * warps",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
"errors": "exceptions",
|
| 41 |
}
|
| 42 |
|
|
@@ -172,6 +177,28 @@ def _m256_fake(a_packed, b_packed, sfa, sfb, workspace, out, alpha: float = 1.0)
|
|
| 172 |
return None
|
| 173 |
|
| 174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
@torch.library.register_fake(add_op_namespace_prefix("fp4_w4a16_linear_bf16"))
|
| 176 |
def _legacy_linear_fake(
|
| 177 |
a_packed: torch.Tensor,
|
|
@@ -771,6 +798,98 @@ def nvfp4_gemm_m256_bf16(
|
|
| 771 |
return out
|
| 772 |
|
| 773 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 774 |
def fp4_w4a16_linear_bf16(
|
| 775 |
a_packed: torch.Tensor,
|
| 776 |
b_packed: torch.Tensor,
|
|
@@ -932,6 +1051,8 @@ __all__ = [
|
|
| 932 |
"SUPPORTED_CUDA_CAPABILITIES",
|
| 933 |
"SUPPORTED_LAYOUTS",
|
| 934 |
"capabilities",
|
|
|
|
|
|
|
| 935 |
"aligned_fp4_dim",
|
| 936 |
"cutlass_fp4_gemm_geglu_il_hw_v10",
|
| 937 |
"dequantize_fp4_sfa_fp16",
|
|
@@ -947,6 +1068,7 @@ __all__ = [
|
|
| 947 |
"nvfp4_gemm_nvfp4",
|
| 948 |
"nvfp4_gemm_m256_bf16",
|
| 949 |
"nvfp4_gemm_m256_workspace_size",
|
|
|
|
| 950 |
"nvfp4_gemm_geglu_nvfp4_fp16",
|
| 951 |
"nvfp4_gemm_bias_gelu_nvfp4_fp16",
|
| 952 |
"nvfp4_gemm_bias_residual_fp16",
|
|
|
|
| 37 |
"sm120_warpsplit_mrows": (1, 16),
|
| 38 |
"sm120_warpsplit_mrows_n_alignment": 8,
|
| 39 |
"sm120_warpsplit_mrows_k_alignment": "64 * warps",
|
| 40 |
+
"sm120_w4a16_marlin_m": (1, 16),
|
| 41 |
+
"sm120_w4a16_marlin_n_alignment": 64,
|
| 42 |
+
"sm120_w4a16_marlin_k_alignment": 128,
|
| 43 |
+
"sm120_w4a16_marlin_weight_layout": "marlin-repacked-nvfp4",
|
| 44 |
+
"sm120_w4a16_marlin_scale_layout": "marlin-permuted-fp8-e4m3-block16",
|
| 45 |
"errors": "exceptions",
|
| 46 |
}
|
| 47 |
|
|
|
|
| 177 |
return None
|
| 178 |
|
| 179 |
|
| 180 |
+
@torch.library.register_fake(add_op_namespace_prefix("nvfp4_w4a16_marlin_bf16"))
|
| 181 |
+
def _w4a16_marlin_fake(x, weight, weight_scale, global_scale, workspace, out) -> None:
|
| 182 |
+
del weight, weight_scale, global_scale, workspace
|
| 183 |
+
if x.ndim != 2 or out.ndim != 2 or out.shape[0] != x.shape[0]:
|
| 184 |
+
raise RuntimeError("x/out must have shapes (M, K)/(M, N)")
|
| 185 |
+
if not 1 <= x.shape[0] <= 16:
|
| 186 |
+
raise RuntimeError("the Marlin W4A16 tier serves M in [1, 16]")
|
| 187 |
+
if x.shape[1] % 128 or out.shape[1] % 64:
|
| 188 |
+
raise RuntimeError("K/N must be divisible by 128/64")
|
| 189 |
+
return None
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
@torch.library.register_fake(add_op_namespace_prefix("nvfp4_w4a16_marlin_repack"))
|
| 193 |
+
def _w4a16_marlin_repack_fake(qweight_kn, weight_marlin) -> None:
|
| 194 |
+
if qweight_kn.ndim != 2:
|
| 195 |
+
raise RuntimeError("qweight_kn must have shape (K / 8, N)")
|
| 196 |
+
k, n = qweight_kn.shape[0] * 8, qweight_kn.shape[1]
|
| 197 |
+
if weight_marlin.shape != (k // 16, n * 2):
|
| 198 |
+
raise RuntimeError("weight_marlin must have shape (K / 16, 2 * N)")
|
| 199 |
+
return None
|
| 200 |
+
|
| 201 |
+
|
| 202 |
@torch.library.register_fake(add_op_namespace_prefix("fp4_w4a16_linear_bf16"))
|
| 203 |
def _legacy_linear_fake(
|
| 204 |
a_packed: torch.Tensor,
|
|
|
|
| 798 |
return out
|
| 799 |
|
| 800 |
|
| 801 |
+
def allocate_w4a16_marlin_workspace(device: torch.device | str) -> torch.Tensor:
|
| 802 |
+
"""Allocate the persistent Marlin lock workspace before graph capture."""
|
| 803 |
+
props = torch.cuda.get_device_properties(device)
|
| 804 |
+
return torch.zeros(props.multi_processor_count, device=device, dtype=torch.int32)
|
| 805 |
+
|
| 806 |
+
|
| 807 |
+
def adopt_nvfp4_w4a16_marlin(
|
| 808 |
+
weight_packed: torch.Tensor,
|
| 809 |
+
weight_scale: torch.Tensor,
|
| 810 |
+
weight_scale_2: torch.Tensor,
|
| 811 |
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 812 |
+
"""Convert standard ModelOpt NVFP4 tensors once at model bind time.
|
| 813 |
+
|
| 814 |
+
Inputs use ``weight[N,K/2]``, ``weight_scale[N,K/16]`` and scalar
|
| 815 |
+
``weight_scale_2``. Returned tensors are the three runtime operands plus a
|
| 816 |
+
zeroed lock workspace. This function is intentionally outside the graph
|
| 817 |
+
hot path.
|
| 818 |
+
"""
|
| 819 |
+
if weight_packed.dtype != torch.uint8 or weight_packed.ndim != 2:
|
| 820 |
+
raise ValueError("weight_packed must be uint8 with shape (N, K / 2)")
|
| 821 |
+
n, k = weight_packed.shape[0], weight_packed.shape[1] * 2
|
| 822 |
+
if k % 128 or n % 64:
|
| 823 |
+
raise ValueError("K/N must be divisible by 128/64")
|
| 824 |
+
if weight_scale.shape != (n, k // NVFP4_BLOCK_SIZE):
|
| 825 |
+
raise ValueError("weight_scale must have shape (N, K / 16)")
|
| 826 |
+
if weight_scale_2.numel() != 1:
|
| 827 |
+
raise ValueError("weight_scale_2 must contain one global scale")
|
| 828 |
+
|
| 829 |
+
qweight_kn = weight_packed.view(torch.int32).T.contiguous()
|
| 830 |
+
weight_marlin = torch.empty(
|
| 831 |
+
(k // 16, n * 2), device=weight_packed.device, dtype=torch.int32
|
| 832 |
+
)
|
| 833 |
+
ops.nvfp4_w4a16_marlin_repack(qweight_kn, weight_marlin)
|
| 834 |
+
|
| 835 |
+
scales = weight_scale.T.contiguous().to(torch.bfloat16)
|
| 836 |
+
scale_perm = [i + 8 * j for i in range(8) for j in range(8)]
|
| 837 |
+
scales = scales.reshape(-1, 64)[:, scale_perm].reshape(-1, n).contiguous()
|
| 838 |
+
|
| 839 |
+
# Match vLLM's NVFP4 Marlin contract exactly: scale permutation uses the
|
| 840 |
+
# model parameter dtype, then S0E5M3 encoding starts from FP16.
|
| 841 |
+
scales = scales.to(torch.float16)
|
| 842 |
+
scales = scales.view(-1, 4)[:, [0, 2, 1, 3]].reshape(scales.shape)
|
| 843 |
+
scaled = scales.float() * (2**7)
|
| 844 |
+
nonzero = scaled > 0
|
| 845 |
+
scale_factor = 1.0
|
| 846 |
+
if bool(nonzero.any()):
|
| 847 |
+
max_value = scaled[nonzero].max()
|
| 848 |
+
if bool(max_value < 448 * (2**7)):
|
| 849 |
+
scale_factor = float((448 * (2**7) / max_value).log2().floor().exp2())
|
| 850 |
+
if scale_factor > 1.0:
|
| 851 |
+
scales = (scales.float() * scale_factor).to(torch.float16)
|
| 852 |
+
scales = scales * (2**7)
|
| 853 |
+
scales[scales < 2] = 0
|
| 854 |
+
scales = (scales.view(torch.int16) << 1).view(torch.float8_e4m3fn)
|
| 855 |
+
scales = scales[:, 1::2].contiguous()
|
| 856 |
+
|
| 857 |
+
# Marlin's BF16 dequantizer uses exponent bias 126 and removes the seven
|
| 858 |
+
# bits introduced while encoding the special unsigned FP8 scale format.
|
| 859 |
+
global_scale = (
|
| 860 |
+
weight_scale_2.to(device=weight_packed.device, dtype=torch.float32)
|
| 861 |
+
* (2.0 ** 119)
|
| 862 |
+
/ scale_factor
|
| 863 |
+
).contiguous()
|
| 864 |
+
workspace = allocate_w4a16_marlin_workspace(weight_packed.device)
|
| 865 |
+
return weight_marlin, scales, global_scale, workspace
|
| 866 |
+
|
| 867 |
+
|
| 868 |
+
def nvfp4_w4a16_marlin_bf16(
|
| 869 |
+
x: torch.Tensor,
|
| 870 |
+
weight_marlin: torch.Tensor,
|
| 871 |
+
weight_scale_marlin: torch.Tensor,
|
| 872 |
+
weight_global_scale: torch.Tensor,
|
| 873 |
+
*,
|
| 874 |
+
workspace: torch.Tensor,
|
| 875 |
+
out: torch.Tensor | None = None,
|
| 876 |
+
) -> torch.Tensor:
|
| 877 |
+
"""Small-M BF16 x NVFP4 linear using bind-time Marlin layouts.
|
| 878 |
+
|
| 879 |
+
``weight_marlin``, ``weight_scale_marlin`` and
|
| 880 |
+
``weight_global_scale`` are the tensors produced by a standard NVFP4
|
| 881 |
+
Marlin adoption step. The operation supports M in [1, 16] and never
|
| 882 |
+
allocates internally, making it suitable for CUDA Graph replay.
|
| 883 |
+
"""
|
| 884 |
+
if out is None:
|
| 885 |
+
n = weight_scale_marlin.numel() // (x.shape[1] // NVFP4_BLOCK_SIZE)
|
| 886 |
+
out = torch.empty((x.shape[0], n), device=x.device, dtype=torch.bfloat16)
|
| 887 |
+
ops.nvfp4_w4a16_marlin_bf16(
|
| 888 |
+
x, weight_marlin, weight_scale_marlin, weight_global_scale, workspace, out
|
| 889 |
+
)
|
| 890 |
+
return out
|
| 891 |
+
|
| 892 |
+
|
| 893 |
def fp4_w4a16_linear_bf16(
|
| 894 |
a_packed: torch.Tensor,
|
| 895 |
b_packed: torch.Tensor,
|
|
|
|
| 1051 |
"SUPPORTED_CUDA_CAPABILITIES",
|
| 1052 |
"SUPPORTED_LAYOUTS",
|
| 1053 |
"capabilities",
|
| 1054 |
+
"allocate_w4a16_marlin_workspace",
|
| 1055 |
+
"adopt_nvfp4_w4a16_marlin",
|
| 1056 |
"aligned_fp4_dim",
|
| 1057 |
"cutlass_fp4_gemm_geglu_il_hw_v10",
|
| 1058 |
"dequantize_fp4_sfa_fp16",
|
|
|
|
| 1068 |
"nvfp4_gemm_nvfp4",
|
| 1069 |
"nvfp4_gemm_m256_bf16",
|
| 1070 |
"nvfp4_gemm_m256_workspace_size",
|
| 1071 |
+
"nvfp4_w4a16_marlin_bf16",
|
| 1072 |
"nvfp4_gemm_geglu_nvfp4_fp16",
|
| 1073 |
"nvfp4_gemm_bias_gelu_nvfp4_fp16",
|
| 1074 |
"nvfp4_gemm_bias_residual_fp16",
|
build/torch213-cxx11-cu132-x86_64-linux/_fp4_gemm_cuda_6971f7a.abi3.so
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d89dfa19d243671a415f0fdaeeb8a6261c417bf7d4f45e5d661745387e2ef1f7
|
| 3 |
+
size 10663712
|
build/torch213-cxx11-cu132-x86_64-linux/_fp4_gemm_cuda_dc57d15.abi3.so
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:3c5c466015e9fda36acc0d9426f2c9281bb9e8af175afb1686edd37348a4f975
|
| 3 |
-
size 8550192
|
|
|
|
|
|
|
|
|
|
|
|
build/torch213-cxx11-cu132-x86_64-linux/_ops.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
import torch
|
| 2 |
-
from . import
|
| 3 |
-
ops = torch.ops.
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
-
return f"
|
|
|
|
| 1 |
import torch
|
| 2 |
+
from . import _fp4_gemm_cuda_6971f7a
|
| 3 |
+
ops = torch.ops._fp4_gemm_cuda_6971f7a
|
| 4 |
|
| 5 |
def add_op_namespace_prefix(op_name: str):
|
| 6 |
"""
|
| 7 |
Prefix op by namespace.
|
| 8 |
"""
|
| 9 |
+
return f"_fp4_gemm_cuda_6971f7a::{op_name}"
|
build/torch213-cxx11-cu132-x86_64-linux/metadata.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"name": "fp4-gemm",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
|
@@ -14,9 +14,9 @@
|
|
| 14 |
"digest": {
|
| 15 |
"algorithm": "sha256",
|
| 16 |
"files": {
|
| 17 |
-
"__init__.py": "
|
| 18 |
-
"
|
| 19 |
-
"_ops.py": "
|
| 20 |
"fp4_gemm/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
|
| 21 |
}
|
| 22 |
},
|
|
@@ -27,7 +27,7 @@
|
|
| 27 |
"dirty": false
|
| 28 |
},
|
| 29 |
"kernel": {
|
| 30 |
-
"sha": "
|
| 31 |
"dirty": false
|
| 32 |
}
|
| 33 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "fp4-gemm",
|
| 3 |
+
"id": "_fp4_gemm_cuda_6971f7a",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"python-depends": [],
|
|
|
|
| 14 |
"digest": {
|
| 15 |
"algorithm": "sha256",
|
| 16 |
"files": {
|
| 17 |
+
"__init__.py": "0hDN2W6X3IMoWIi5BBufrnqrq2yj5N1/q9wBhCwBRxA=",
|
| 18 |
+
"_fp4_gemm_cuda_6971f7a.abi3.so": "2J36GdJDZxpBXw/a7rimJhxBe/fU9F5dZhdFOH4u8fc=",
|
| 19 |
+
"_ops.py": "D7InzLughnNL5JQowMPNYpSpNPRFeeD8mlgs38Xw03g=",
|
| 20 |
"fp4_gemm/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
|
| 21 |
}
|
| 22 |
},
|
|
|
|
| 27 |
"dirty": false
|
| 28 |
},
|
| 29 |
"kernel": {
|
| 30 |
+
"sha": "6971f7a37410ba0d6edae93b389dbcbe3ba924d5",
|
| 31 |
"dirty": false
|
| 32 |
}
|
| 33 |
}
|