chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:31:40 +08:00
commit 4df75a30cd
234 changed files with 41407 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
<!--
Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES
Licensed under the Apache License, Version 2.0 (the "License").
You may not use this file except in compliance with the License.
To view a copy of this license, visit http://www.apache.org/licenses/LICENSE-2.0
No warranties are given. The work is provided "AS IS", without warranty of any kind, express or implied.
SPDX-License-Identifier: Apache-2.0
-->
# LongLive KV Dequant CUDA Extension
Build from this directory:
```bash
cd utils/kernel
OPENBLAS_NUM_THREADS=1 OMP_NUM_THREADS=1 MKL_NUM_THREADS=1 MAX_JOBS=4 \
python setup.py build_ext --inplace
```
Runtime import:
```python
from utils.kernel.kv_dequant import dequantize_kv_cache_fp4
```
`utils.quant.dequantize_kv_cache()` already calls this extension first and falls
back to the original Triton path if the extension is not built.
For direct calls, pass the same scale limits used by the QuantizedTensor's
`scale_rule`:
- `static_6`: `e2m1_max=6.0`, `e4m3_max=448.0`
- `static_4`: `e2m1_max=4.0`, `e4m3_max=448.0`
- `mse` / `l1_norm` / `abs_max` 4o6 modes: `e2m1_max=6.0`, `e4m3_max=256.0`
The normal `utils.quant.dequantize_kv_cache()` path reads these values from
`qt.scale_rule`, so manual selection is not needed there.
You can also pass `scale_rule` directly:
```python
out = dequantize_kv_cache_fp4(
values,
scale_factors,
amax,
num_heads=num_heads,
block_token_size=block_token_size,
dtype=torch.bfloat16,
scale_rule="static_6",
)
```
+10
View File
@@ -0,0 +1,10 @@
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# To view a copy of this license, visit http://www.apache.org/licenses/LICENSE-2.0
#
# No warranties are given. The work is provided "AS IS", without warranty of any kind, express or implied.
#
# SPDX-License-Identifier: Apache-2.0
"""Custom CUDA kernels used by LongLive."""
+20
View File
@@ -0,0 +1,20 @@
// Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES
//
// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// To view a copy of this license, visit http://www.apache.org/licenses/LICENSE-2.0
//
// No warranties are given. The work is provided "AS IS", without warranty of any kind, express or implied.
//
// SPDX-License-Identifier: Apache-2.0
#include <torch/extension.h>
TORCH_LIBRARY(longlive_kernels, m)
{
m.def("dequantize_kv_cache_fp4(Tensor[] values, Tensor[] scale_factors, Tensor[] amax, int num_heads, int block_token_size, int dtype_code, float e2m1_max, float e4m3_max) -> Tensor");
}
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m)
{
m.doc() = "LongLive custom CUDA kernels";
}
+82
View File
@@ -0,0 +1,82 @@
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# To view a copy of this license, visit http://www.apache.org/licenses/LICENSE-2.0
#
# No warranties are given. The work is provided "AS IS", without warranty of any kind, express or implied.
#
# SPDX-License-Identifier: Apache-2.0
import torch
try:
from . import longlive_kv_dequant_cuda # noqa: F401
except ImportError:
import longlive_kv_dequant_cuda # noqa: F401
def _dtype_to_code(dtype: torch.dtype) -> int:
if dtype == torch.bfloat16:
return 0
if dtype == torch.float16:
return 1
if dtype == torch.float32:
return 2
raise ValueError(f"Unsupported fused KV dequant dtype: {dtype}")
def scale_rule_to_fp4_limits(scale_rule) -> tuple[float, float]:
"""Return the dequant denominator limits used by FourOverSix ScaleRule."""
if hasattr(scale_rule, "max_allowed_e2m1_value") and hasattr(
scale_rule, "max_allowed_e4m3_value",
):
return (
float(scale_rule.max_allowed_e2m1_value()),
float(scale_rule.max_allowed_e4m3_value()),
)
normalized = str(scale_rule).lower()
if "." in normalized:
normalized = normalized.rsplit(".", 1)[-1]
normalized = normalized.strip().strip("\"'")
if normalized == "static_4":
return 4.0, 448.0
if normalized == "static_6":
return 6.0, 448.0
if normalized in {"mse", "mae", "l1_norm", "abs_max"}:
return 6.0, 256.0
raise ValueError(f"Unsupported FP4 scale_rule: {scale_rule}")
def dequantize_kv_cache_fp4(
values: list[torch.Tensor],
scale_factors: list[torch.Tensor],
amax: list[torch.Tensor],
*,
num_heads: int,
block_token_size: int,
dtype: torch.dtype,
e2m1_max: float | None = None,
e4m3_max: float | None = None,
scale_rule=None,
) -> torch.Tensor:
"""Dequantize multiple AR KV-cache chunks with one CUDA launch."""
if e2m1_max is None or e4m3_max is None:
if scale_rule is None:
raise ValueError(
"Either e2m1_max/e4m3_max or scale_rule must be provided.",
)
e2m1_max, e4m3_max = scale_rule_to_fp4_limits(scale_rule)
return torch.ops.longlive_kernels.dequantize_kv_cache_fp4.default(
values,
scale_factors,
amax,
num_heads,
block_token_size,
_dtype_to_code(dtype),
e2m1_max,
e4m3_max,
)
+244
View File
@@ -0,0 +1,244 @@
// Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES
//
// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// To view a copy of this license, visit http://www.apache.org/licenses/LICENSE-2.0
//
// No warranties are given. The work is provided "AS IS", without warranty of any kind, express or implied.
//
// SPDX-License-Identifier: Apache-2.0
#include <ATen/Dispatch.h>
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAException.h>
#include <c10/cuda/CUDAGuard.h>
#include <cuda_fp16.h>
#include <cuda_fp4.h>
#include <cuda_fp8.h>
#include <cuda_runtime.h>
#include <torch/extension.h>
#include <cstdint>
#include <vector>
namespace {
#define CHECK_CUDA_TENSOR(x) TORCH_CHECK((x).is_cuda(), #x " must be a CUDA tensor")
#define CHECK_CONTIGUOUS(x) TORCH_CHECK((x).is_contiguous(), #x " must be contiguous")
__device__ __constant__ float kE2M1ToFloat[16] = {
0.0f, 0.5f, 1.0f, 1.5f, 2.0f, 3.0f, 4.0f, 6.0f,
-0.0f, -0.5f, -1.0f, -1.5f, -2.0f, -3.0f, -4.0f, -6.0f,
};
// iter-37: hardware FP4→FP16x2 via CUDA 12.8+ built-in API
// __nv_cvt_fp4x2_to_halfraw2 (wraps cvt.rn.f16x2.e2m1x2 PTX instruction).
// Returns __half2_raw with 2 fp16 values from 1 packed byte.
__device__ __forceinline__ __half2_raw e2m1x2_to_halfraw2(uint8_t byte) {
return __nv_cvt_fp4x2_to_halfraw2(
static_cast<__nv_fp4x2_storage_t>(byte), __NV_E2M1);
}
__device__ __forceinline__ int64_t blocked_scale_index(
const int row,
const int scale_col,
const int scale_cols)
{
// Inverse of fouroversix.quantize.utils.to_blocked for a scale matrix
// shaped [rows_padded, scale_cols].
const int row_block = row / 128;
const int row_in_block = row - row_block * 128;
const int scale_col_block = scale_col / 4;
const int scale_col_in_block = scale_col - scale_col_block * 4;
const int scale_col_blocks = scale_cols / 4;
const int logical_block = row_block * scale_col_blocks + scale_col_block;
return (((int64_t)logical_block * 32 + (row_in_block & 31)) * 16
+ (row_in_block >> 5) * 4 + scale_col_in_block);
}
template <typename scalar_t>
__global__ void fp4_kv_dequant_kernel(
const uint64_t* __restrict__ value_ptrs,
const uint64_t* __restrict__ scale_ptrs,
const uint64_t* __restrict__ amax_ptrs,
scalar_t* __restrict__ output,
const int64_t total_packed_values,
const int block_token_size,
const int num_heads,
const int packed_cols,
const int scale_cols,
const float inv_global_scale_denom)
{
const int64_t packed_idx = (int64_t)blockIdx.x * blockDim.x + threadIdx.x;
if (packed_idx >= total_packed_values) {
return;
}
const int col_pair = packed_idx % packed_cols;
const int64_t global_row = packed_idx / packed_cols;
const int rows_per_cache_block = block_token_size * num_heads;
const int cache_block = global_row / rows_per_cache_block;
const int row_in_cache_block = global_row - (int64_t)cache_block * rows_per_cache_block;
const int token_in_block = row_in_cache_block / num_heads;
const int head = row_in_cache_block - token_in_block * num_heads;
const int out_token = cache_block * block_token_size + token_in_block;
const auto* values = reinterpret_cast<const uint8_t*>(value_ptrs[cache_block]);
const auto* scales = reinterpret_cast<const __nv_fp8_e4m3*>(scale_ptrs[cache_block]);
const auto* amax = reinterpret_cast<const float*>(amax_ptrs[cache_block]);
const uint8_t packed = values[(int64_t)row_in_cache_block * packed_cols + col_pair];
const int scale_col = (col_pair * 2) / 16;
const int64_t scale_idx = blocked_scale_index(row_in_cache_block, scale_col, scale_cols);
const float scale = static_cast<float>(scales[scale_idx]);
const float global_scale = amax[0] * inv_global_scale_denom;
// iter-37: hardware FP4→FP16x2 via CUDA 12.8 built-in (wraps cvt.rn.f16x2.e2m1x2).
const __half2_raw f16x2 = e2m1x2_to_halfraw2(packed);
// __half2_raw layout: .x = low nibble's fp16 (unsigned short), .y = high nibble's.
const float low = __half2float(__ushort_as_half(f16x2.x)) * scale * global_scale;
const float high = __half2float(__ushort_as_half(f16x2.y)) * scale * global_scale;
const int out_col = col_pair * 2;
const int64_t out_base = (((int64_t)out_token * num_heads + head) * (packed_cols * 2)) + out_col;
output[out_base] = static_cast<scalar_t>(low);
output[out_base + 1] = static_cast<scalar_t>(high);
}
at::ScalarType dtype_code_to_scalar_type(const int64_t dtype_code)
{
switch (dtype_code) {
case 0:
return at::ScalarType::BFloat16;
case 1:
return at::ScalarType::Half;
case 2:
return at::ScalarType::Float;
default:
TORCH_CHECK(false, "Unsupported KV dequant dtype code: ", dtype_code);
}
return at::ScalarType::Float;
}
at::Tensor make_device_pointer_tensor(at::TensorList tensors)
{
auto options = at::TensorOptions()
.dtype(at::ScalarType::Long)
.device(tensors.front().device());
at::Tensor ptrs = at::empty({static_cast<int64_t>(tensors.size())}, options);
std::vector<int64_t> host_ptrs(tensors.size());
for (size_t i = 0; i < tensors.size(); ++i) {
host_ptrs[i] = reinterpret_cast<int64_t>(tensors[i].data_ptr());
}
// The pointer table is tiny; use a synchronous copy so the temporary host
// vector cannot outlive an async H2D transfer.
C10_CUDA_CHECK(cudaMemcpy(
ptrs.data_ptr<int64_t>(),
host_ptrs.data(),
host_ptrs.size() * sizeof(int64_t),
cudaMemcpyHostToDevice));
return ptrs;
}
} // namespace
at::Tensor dequantize_kv_cache_fp4_cuda(
at::TensorList values,
at::TensorList scale_factors,
at::TensorList amax,
int64_t num_heads,
int64_t block_token_size,
int64_t dtype_code,
double e2m1_max,
double e4m3_max)
{
TORCH_CHECK(!values.empty(), "values must contain at least one cache block");
TORCH_CHECK(values.size() == scale_factors.size(),
"values and scale_factors must have the same length");
TORCH_CHECK(values.size() == amax.size(),
"values and amax must have the same length");
TORCH_CHECK(num_heads > 0, "num_heads must be positive");
TORCH_CHECK(block_token_size > 0, "block_token_size must be positive");
TORCH_CHECK(e2m1_max > 0.0 && e4m3_max > 0.0,
"e2m1_max and e4m3_max must be positive");
const auto device = values.front().device();
c10::cuda::CUDAGuard device_guard(device);
const int64_t max_blocks = static_cast<int64_t>(values.size());
const int64_t packed_cols = values.front().size(1);
const int64_t head_dim = packed_cols * 2;
const int64_t rows_padded = values.front().size(0);
const int64_t logical_rows = block_token_size * num_heads;
const int64_t scale_cols = head_dim / 16;
TORCH_CHECK(head_dim == 128, "KV dequant currently expects head_dim=128, got ", head_dim);
TORCH_CHECK(scale_cols % 4 == 0, "scale column count must be a multiple of 4");
TORCH_CHECK(rows_padded >= logical_rows,
"values rows are smaller than logical KV block rows");
TORCH_CHECK(rows_padded % 128 == 0, "values rows must be padded to a multiple of 128");
for (int64_t i = 0; i < max_blocks; ++i) {
CHECK_CUDA_TENSOR(values[i]);
CHECK_CUDA_TENSOR(scale_factors[i]);
CHECK_CUDA_TENSOR(amax[i]);
CHECK_CONTIGUOUS(values[i]);
CHECK_CONTIGUOUS(scale_factors[i]);
CHECK_CONTIGUOUS(amax[i]);
TORCH_CHECK(values[i].device() == device, "all values tensors must be on the same device");
TORCH_CHECK(scale_factors[i].device() == device,
"all scale_factors tensors must be on the same device");
TORCH_CHECK(amax[i].device() == device, "all amax tensors must be on the same device");
TORCH_CHECK(values[i].scalar_type() == at::ScalarType::Byte,
"values tensors must be uint8");
TORCH_CHECK(amax[i].scalar_type() == at::ScalarType::Float,
"amax tensors must be float32");
TORCH_CHECK(values[i].dim() == 2, "values tensors must be 2D");
TORCH_CHECK(values[i].size(0) == rows_padded && values[i].size(1) == packed_cols,
"all values tensors must have the same shape");
}
const auto out_dtype = dtype_code_to_scalar_type(dtype_code);
at::Tensor output = at::empty(
{1, max_blocks * block_token_size, num_heads, head_dim},
values.front().options().dtype(out_dtype));
cudaStream_t stream = at::cuda::getCurrentCUDAStream().stream();
at::Tensor value_ptrs = make_device_pointer_tensor(values);
at::Tensor scale_ptrs = make_device_pointer_tensor(scale_factors);
at::Tensor amax_ptrs = make_device_pointer_tensor(amax);
const int64_t total_packed_values = max_blocks * logical_rows * packed_cols;
const int threads = 256;
const dim3 blocks((total_packed_values + threads - 1) / threads);
const float inv_global_scale_denom = static_cast<float>(1.0 / (e2m1_max * e4m3_max));
AT_DISPATCH_FLOATING_TYPES_AND2(
at::ScalarType::Half,
at::ScalarType::BFloat16,
output.scalar_type(),
"fp4_kv_dequant_kernel",
[&] {
fp4_kv_dequant_kernel<scalar_t><<<blocks, threads, 0, stream>>>(
reinterpret_cast<const uint64_t*>(value_ptrs.data_ptr<int64_t>()),
reinterpret_cast<const uint64_t*>(scale_ptrs.data_ptr<int64_t>()),
reinterpret_cast<const uint64_t*>(amax_ptrs.data_ptr<int64_t>()),
output.data_ptr<scalar_t>(),
total_packed_values,
static_cast<int>(block_token_size),
static_cast<int>(num_heads),
static_cast<int>(packed_cols),
static_cast<int>(scale_cols),
inv_global_scale_denom);
});
C10_CUDA_KERNEL_LAUNCH_CHECK();
return output;
}
TORCH_LIBRARY_IMPL(longlive_kernels, CUDA, m)
{
m.impl("dequantize_kv_cache_fp4", &dequantize_kv_cache_fp4_cuda);
}
+41
View File
@@ -0,0 +1,41 @@
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# To view a copy of this license, visit http://www.apache.org/licenses/LICENSE-2.0
#
# No warranties are given. The work is provided "AS IS", without warranty of any kind, express or implied.
#
# SPDX-License-Identifier: Apache-2.0
from pathlib import Path
from setuptools import setup
from torch.utils.cpp_extension import BuildExtension, CUDAExtension
THIS_DIR = Path(__file__).resolve().parent
setup(
name="longlive_kv_dequant_cuda",
ext_modules=[
CUDAExtension(
name="longlive_kv_dequant_cuda",
sources=[
str(THIS_DIR / "kv_dequant.cpp"),
str(THIS_DIR / "kv_dequant_cuda.cu"),
],
extra_compile_args={
"cxx": ["-O3", "-std=c++17"],
"nvcc": [
"-O3",
"-std=c++17",
"--expt-relaxed-constexpr",
# iter-37: need sm_100a (Blackwell arch-specific) for
# cvt.rn.f16x2.e2m1x2 instruction. Plain sm_100 lacks it.
"-gencode=arch=compute_100a,code=sm_100a",
],
},
),
],
cmdclass={"build_ext": BuildExtension},
)