Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- lib/python3.12/site-packages/torchao/dtypes/__init__.py +64 -0
- lib/python3.12/site-packages/torchao/dtypes/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/__pycache__/_nf4tensor_api.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/__pycache__/affine_quantized_tensor.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/__pycache__/affine_quantized_tensor_ops.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/__pycache__/nf4tensor.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/__pycache__/utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/_nf4tensor_api.py +39 -0
- lib/python3.12/site-packages/torchao/dtypes/affine_quantized_tensor.py +507 -0
- lib/python3.12/site-packages/torchao/dtypes/affine_quantized_tensor_ops.py +580 -0
- lib/python3.12/site-packages/torchao/dtypes/floatx/__init__.py +17 -0
- lib/python3.12/site-packages/torchao/dtypes/floatx/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/floatx/__pycache__/cutlass_semi_sparse_layout.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/floatx/__pycache__/float8_layout.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/floatx/__pycache__/floatx_tensor_core_layout.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/floatx/cutlass_semi_sparse_layout.py +212 -0
- lib/python3.12/site-packages/torchao/dtypes/floatx/float8_layout.py +386 -0
- lib/python3.12/site-packages/torchao/dtypes/floatx/floatx_tensor_core_layout.py +666 -0
- lib/python3.12/site-packages/torchao/dtypes/nf4tensor.py +1086 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/__init__.py +51 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/bitpacking.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/block_sparse_layout.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/cutlass_int4_packed_layout.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/gemlite_layout.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/int4_cpu_layout.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/int4_xpu_layout.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/marlin_qqq_tensor.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/marlin_sparse_layout.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/packed_linear_int8_dynamic_activation_intx_weight_layout.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/plain_layout.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/q_dq_layout.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/semi_sparse_layout.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/tensor_core_tiled_layout.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/uint4_layout.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/uintx_layout.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/bitpacking.py +265 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/block_sparse_layout.py +233 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/cutlass_int4_packed_layout.py +224 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/gemlite_layout.py +403 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/int4_cpu_layout.py +354 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/int4_xpu_layout.py +445 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/marlin_qqq_tensor.py +351 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/marlin_sparse_layout.py +310 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/packed_linear_int8_dynamic_activation_intx_weight_layout.py +467 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/plain_layout.py +315 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/q_dq_layout.py +242 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/semi_sparse_layout.py +128 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/tensor_core_tiled_layout.py +527 -0
- lib/python3.12/site-packages/torchao/dtypes/uintx/uint4_layout.py +321 -0
lib/python3.12/site-packages/torchao/dtypes/__init__.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from . import affine_quantized_tensor_ops
|
| 2 |
+
from .affine_quantized_tensor import (
|
| 3 |
+
AffineQuantizedTensor,
|
| 4 |
+
to_affine_quantized_floatx,
|
| 5 |
+
to_affine_quantized_floatx_static,
|
| 6 |
+
# experimental, will be merged into floatx in the future
|
| 7 |
+
to_affine_quantized_fpx,
|
| 8 |
+
to_affine_quantized_intx,
|
| 9 |
+
to_affine_quantized_intx_static,
|
| 10 |
+
)
|
| 11 |
+
from .floatx import (
|
| 12 |
+
CutlassSemiSparseLayout,
|
| 13 |
+
Float8Layout,
|
| 14 |
+
)
|
| 15 |
+
from .nf4tensor import NF4Tensor, to_nf4
|
| 16 |
+
from .uintx import (
|
| 17 |
+
BlockSparseLayout,
|
| 18 |
+
CutlassInt4PackedLayout,
|
| 19 |
+
Int4CPULayout,
|
| 20 |
+
Int4XPULayout,
|
| 21 |
+
MarlinQQQLayout,
|
| 22 |
+
MarlinQQQTensor,
|
| 23 |
+
MarlinSparseLayout,
|
| 24 |
+
PackedLinearInt8DynamicActivationIntxWeightLayout,
|
| 25 |
+
QDQLayout,
|
| 26 |
+
SemiSparseLayout,
|
| 27 |
+
TensorCoreTiledLayout,
|
| 28 |
+
UintxLayout,
|
| 29 |
+
to_marlinqqq_quantized_intx,
|
| 30 |
+
)
|
| 31 |
+
from .utils import (
|
| 32 |
+
Layout,
|
| 33 |
+
PlainLayout,
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
__all__ = [
|
| 37 |
+
"NF4Tensor",
|
| 38 |
+
"to_nf4",
|
| 39 |
+
"AffineQuantizedTensor",
|
| 40 |
+
"to_affine_quantized_intx",
|
| 41 |
+
"to_affine_quantized_intx_static",
|
| 42 |
+
"to_affine_quantized_fpx",
|
| 43 |
+
"to_affine_quantized_floatx",
|
| 44 |
+
"to_affine_quantized_floatx_static",
|
| 45 |
+
"to_marlinqqq_quantized_intx",
|
| 46 |
+
"Layout",
|
| 47 |
+
"PlainLayout",
|
| 48 |
+
"SemiSparseLayout",
|
| 49 |
+
"TensorCoreTiledLayout",
|
| 50 |
+
"Float8Layout",
|
| 51 |
+
"MarlinSparseLayout",
|
| 52 |
+
"affine_quantized_tensor_ops",
|
| 53 |
+
"BlockSparseLayout",
|
| 54 |
+
"UintxLayout",
|
| 55 |
+
"MarlinQQQTensor",
|
| 56 |
+
"MarlinQQQLayout",
|
| 57 |
+
"Int4CPULayout",
|
| 58 |
+
"CutlassInt4PackedLayout",
|
| 59 |
+
"CutlassSemiSparseLayout",
|
| 60 |
+
"QDQLayout",
|
| 61 |
+
"PackedLinearInt8DynamicActivationIntxWeightLayout",
|
| 62 |
+
"to_affine_quantized_packed_linear_int8_dynamic_activation_intx_weight",
|
| 63 |
+
"Int4XPULayout",
|
| 64 |
+
]
|
lib/python3.12/site-packages/torchao/dtypes/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (1.38 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/__pycache__/_nf4tensor_api.cpython-312.pyc
ADDED
|
Binary file (1.65 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/__pycache__/affine_quantized_tensor.cpython-312.pyc
ADDED
|
Binary file (18.4 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/__pycache__/affine_quantized_tensor_ops.cpython-312.pyc
ADDED
|
Binary file (21.4 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/__pycache__/nf4tensor.cpython-312.pyc
ADDED
|
Binary file (48 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/__pycache__/utils.cpython-312.pyc
ADDED
|
Binary file (6.13 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/_nf4tensor_api.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD 3-Clause license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
from torchao.core.config import AOBaseConfig
|
| 9 |
+
from torchao.dtypes.nf4tensor import NF4Tensor
|
| 10 |
+
from torchao.quantization.transform_module import (
|
| 11 |
+
register_quantize_module_handler,
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class NF4WeightOnlyConfig(AOBaseConfig):
|
| 16 |
+
"""
|
| 17 |
+
Note: the file location of this workflow is temporary.
|
| 18 |
+
TODO(future PR): integrate this properly into torchao's directory structure
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
block_size: int = 64
|
| 22 |
+
scaler_block_size: int = 256
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# for bc
|
| 26 |
+
nf4_weight_only = NF4WeightOnlyConfig
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
@register_quantize_module_handler(NF4WeightOnlyConfig)
|
| 30 |
+
def _nf4_weight_only_transform(
|
| 31 |
+
module: torch.nn.Module,
|
| 32 |
+
config: NF4WeightOnlyConfig,
|
| 33 |
+
) -> torch.nn.Module:
|
| 34 |
+
block_size = config.block_size
|
| 35 |
+
scaler_block_size = config.scaler_block_size
|
| 36 |
+
|
| 37 |
+
new_weight = NF4Tensor.from_tensor(module.weight, block_size, scaler_block_size)
|
| 38 |
+
module.weight = torch.nn.Parameter(new_weight, requires_grad=False)
|
| 39 |
+
return module
|
lib/python3.12/site-packages/torchao/dtypes/affine_quantized_tensor.py
ADDED
|
@@ -0,0 +1,507 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD 3-Clause license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
import logging
|
| 7 |
+
import math
|
| 8 |
+
from typing import Optional, Tuple, Union
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
|
| 12 |
+
from torchao.dtypes.utils import (
|
| 13 |
+
AQTTensorImpl,
|
| 14 |
+
Layout,
|
| 15 |
+
PlainLayout,
|
| 16 |
+
)
|
| 17 |
+
from torchao.quantization.quant_primitives import (
|
| 18 |
+
FP8_TYPES,
|
| 19 |
+
MappingType,
|
| 20 |
+
ZeroPointDomain,
|
| 21 |
+
choose_qparams_affine,
|
| 22 |
+
choose_qparams_affine_floatx,
|
| 23 |
+
choose_qparams_and_quantize_affine_hqq,
|
| 24 |
+
dequantize_affine,
|
| 25 |
+
dequantize_affine_floatx,
|
| 26 |
+
quantize_affine,
|
| 27 |
+
quantize_affine_floatx,
|
| 28 |
+
)
|
| 29 |
+
from torchao.utils import (
|
| 30 |
+
TORCH_VERSION_AT_LEAST_2_5,
|
| 31 |
+
TorchAOBaseTensor,
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
logger = logging.getLogger(__name__)
|
| 35 |
+
aten = torch.ops.aten
|
| 36 |
+
|
| 37 |
+
__all__ = [
|
| 38 |
+
"AffineQuantizedTensor",
|
| 39 |
+
"register_layout",
|
| 40 |
+
"to_affine_quantized_intx",
|
| 41 |
+
"to_affine_quantized_floatx",
|
| 42 |
+
"to_affine_quantized_intx_static",
|
| 43 |
+
"to_affine_quantized_floatx_static",
|
| 44 |
+
"to_affine_quantized_fpx",
|
| 45 |
+
]
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
##############################
|
| 49 |
+
# Tensor Subclass Definition #
|
| 50 |
+
##############################
|
| 51 |
+
class AffineQuantizedTensor(TorchAOBaseTensor):
|
| 52 |
+
"""Affine quantized tensor subclass. Affine quantization means we quantize the floating point tensor with an affine transformation:
|
| 53 |
+
quantized_tensor = float_tensor / scale + zero_point
|
| 54 |
+
|
| 55 |
+
To see what happens during choose_qparams, quantization and dequantization for affine quantization,
|
| 56 |
+
please checkout https://github.com/pytorch/ao/blob/main/torchao/quantization/quant_primitives.py
|
| 57 |
+
and check the three quant primitive ops: choose_qparams_affine, quantize_affine qand dequantize_affine
|
| 58 |
+
|
| 59 |
+
The shape and dtype of the tensor subclass represent how the tensor subclass looks externally,
|
| 60 |
+
regardless of the internal representation's type or orientation.
|
| 61 |
+
|
| 62 |
+
fields:
|
| 63 |
+
- tensor_impl (AQTTensorImpl): tensor that serves as a general tensor impl storage for the quantized data,
|
| 64 |
+
e.g. storing plain tensors (int_data, scale, zero_point) or packed formats depending on device and operator/kernel
|
| 65 |
+
- block_size (Tuple[int, ...]): granularity of quantization, this means the size of the tensor elements that's sharing the same qparam
|
| 66 |
+
e.g. when size is the same as the input tensor dimension, we are using per tensor quantization
|
| 67 |
+
- shape (torch.Size): the shape for the original high precision Tensor
|
| 68 |
+
- quant_min (Optional[int]): minimum quantized value for the Tensor, if not specified, it will be derived from dtype of `int_data`
|
| 69 |
+
- quant_max (Optional[int]): maximum quantized value for the Tensor, if not specified, it will be derived from dtype of `int_data`
|
| 70 |
+
- zero_point_domain (ZeroPointDomain): the domain that zero_point is in, should be either integer or float
|
| 71 |
+
if zero_point is in integer domain, zero point is added to the quantized integer value during quantization
|
| 72 |
+
if zero_point is in floating point domain, zero point is subtracted from the floating point (unquantized) value during quantization
|
| 73 |
+
default is ZeroPointDomain.INT
|
| 74 |
+
- dtype: dtype for original high precision tensor, e.g. torch.float32
|
| 75 |
+
"""
|
| 76 |
+
|
| 77 |
+
@staticmethod
|
| 78 |
+
def __new__(
|
| 79 |
+
cls,
|
| 80 |
+
tensor_impl: AQTTensorImpl,
|
| 81 |
+
block_size: Tuple[int, ...],
|
| 82 |
+
shape: torch.Size,
|
| 83 |
+
quant_min: Optional[Union[int, float]] = None,
|
| 84 |
+
quant_max: Optional[Union[int, float]] = None,
|
| 85 |
+
zero_point_domain: ZeroPointDomain = ZeroPointDomain.INT,
|
| 86 |
+
dtype=None,
|
| 87 |
+
strides=None,
|
| 88 |
+
):
|
| 89 |
+
if zero_point_domain is None:
|
| 90 |
+
raise ValueError("please use ZeroPointDomain.NONE instead of None")
|
| 91 |
+
kwargs = {}
|
| 92 |
+
kwargs["device"] = tensor_impl.device
|
| 93 |
+
kwargs["layout"] = (
|
| 94 |
+
kwargs.get("layout") if kwargs.get("layout", False) else tensor_impl.layout
|
| 95 |
+
)
|
| 96 |
+
kwargs["dtype"] = dtype
|
| 97 |
+
if strides is not None:
|
| 98 |
+
kwargs["strides"] = strides
|
| 99 |
+
kwargs["requires_grad"] = False
|
| 100 |
+
return torch.Tensor._make_wrapper_subclass(cls, shape, **kwargs) # type: ignore[attr-defined]
|
| 101 |
+
|
| 102 |
+
def __init__(
|
| 103 |
+
self,
|
| 104 |
+
tensor_impl: AQTTensorImpl,
|
| 105 |
+
block_size: Tuple[int, ...],
|
| 106 |
+
shape: torch.Size,
|
| 107 |
+
quant_min: Optional[Union[int, float]] = None,
|
| 108 |
+
quant_max: Optional[Union[int, float]] = None,
|
| 109 |
+
zero_point_domain: ZeroPointDomain = ZeroPointDomain.INT,
|
| 110 |
+
dtype=None,
|
| 111 |
+
strides=None,
|
| 112 |
+
):
|
| 113 |
+
self.tensor_impl = tensor_impl
|
| 114 |
+
self.block_size = block_size
|
| 115 |
+
self.quant_min = quant_min
|
| 116 |
+
self.quant_max = quant_max
|
| 117 |
+
self.zero_point_domain = zero_point_domain
|
| 118 |
+
|
| 119 |
+
def __repr__(self):
|
| 120 |
+
return (
|
| 121 |
+
f"{self.__class__.__name__}(tensor_impl={self.tensor_impl}, block_size={self.block_size}, "
|
| 122 |
+
f"shape={self.shape}, device={self.device}, dtype={self.dtype}, requires_grad={self.requires_grad})"
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
def _quantization_type(self):
|
| 126 |
+
return f"shape={self.shape}, block_size={self.block_size}, device={self.device}, _layout={self._layout}, tensor_impl_dtype={self.tensor_impl.dtype}, quant_min={self.quant_min}, quant_max={self.quant_max}"
|
| 127 |
+
|
| 128 |
+
def dequantize(self, output_dtype: Optional[torch.dtype] = None) -> torch.Tensor:
|
| 129 |
+
if output_dtype is None:
|
| 130 |
+
output_dtype = self.dtype
|
| 131 |
+
|
| 132 |
+
from torchao.dtypes.floatx import FloatxTensorCoreLayout
|
| 133 |
+
|
| 134 |
+
if isinstance(self._layout, FloatxTensorCoreLayout):
|
| 135 |
+
int_data, scale = self.tensor_impl.get_plain()
|
| 136 |
+
return dequantize_affine_floatx(
|
| 137 |
+
int_data,
|
| 138 |
+
scale,
|
| 139 |
+
self._layout.ebits,
|
| 140 |
+
self._layout.mbits,
|
| 141 |
+
output_dtype=output_dtype,
|
| 142 |
+
)
|
| 143 |
+
else:
|
| 144 |
+
data, scale, zero_point = self.tensor_impl.get_plain()
|
| 145 |
+
dq = dequantize_affine(
|
| 146 |
+
data,
|
| 147 |
+
self.block_size,
|
| 148 |
+
scale,
|
| 149 |
+
zero_point,
|
| 150 |
+
data.dtype,
|
| 151 |
+
self.quant_min,
|
| 152 |
+
self.quant_max,
|
| 153 |
+
self.zero_point_domain,
|
| 154 |
+
output_dtype=output_dtype,
|
| 155 |
+
)
|
| 156 |
+
from torchao.dtypes.uintx import TensorCoreTiledLayout
|
| 157 |
+
|
| 158 |
+
if isinstance(self._layout, TensorCoreTiledLayout):
|
| 159 |
+
# need to return to original shape if tensor was padded
|
| 160 |
+
# in preprocessing
|
| 161 |
+
# TODO: we could add an API for this if there are more use cases
|
| 162 |
+
# (e.g. dequant_post_process) in TensorImpl or Layout
|
| 163 |
+
for dim, dim_size in enumerate(self.shape):
|
| 164 |
+
dq = dq.narrow(dim, 0, dim_size)
|
| 165 |
+
return dq
|
| 166 |
+
|
| 167 |
+
def __tensor_flatten__(self):
|
| 168 |
+
# This is used in rumtime to unwrap AffineQuantizedTensor activations.
|
| 169 |
+
# AffineQuantizedTensor has __torch_function__ override:
|
| 170 |
+
# Each getattr will go through it, which is up to 10x slower than default attribute access.
|
| 171 |
+
with torch._C.DisableTorchFunctionSubclass():
|
| 172 |
+
return ["tensor_impl"], [
|
| 173 |
+
self.block_size,
|
| 174 |
+
self.shape,
|
| 175 |
+
self.quant_min,
|
| 176 |
+
self.quant_max,
|
| 177 |
+
self.zero_point_domain,
|
| 178 |
+
self.dtype,
|
| 179 |
+
]
|
| 180 |
+
|
| 181 |
+
@classmethod
|
| 182 |
+
def __tensor_unflatten__(
|
| 183 |
+
cls, tensor_data_dict, tensor_attributes, outer_size, outer_stride
|
| 184 |
+
):
|
| 185 |
+
tensor_impl = tensor_data_dict["tensor_impl"]
|
| 186 |
+
block_size, shape, quant_min, quant_max, zero_point_domain, dtype = (
|
| 187 |
+
tensor_attributes
|
| 188 |
+
)
|
| 189 |
+
return cls(
|
| 190 |
+
tensor_impl,
|
| 191 |
+
block_size,
|
| 192 |
+
shape if outer_size is None else outer_size,
|
| 193 |
+
quant_min,
|
| 194 |
+
quant_max,
|
| 195 |
+
zero_point_domain,
|
| 196 |
+
dtype=dtype,
|
| 197 |
+
strides=outer_stride,
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
@classmethod
|
| 201 |
+
def from_hp_to_intx(
|
| 202 |
+
cls,
|
| 203 |
+
input_float: torch.Tensor,
|
| 204 |
+
mapping_type: MappingType,
|
| 205 |
+
block_size: Tuple[int, ...],
|
| 206 |
+
target_dtype: torch.dtype,
|
| 207 |
+
quant_min: Optional[int] = None,
|
| 208 |
+
quant_max: Optional[int] = None,
|
| 209 |
+
eps: Optional[float] = None,
|
| 210 |
+
scale_dtype: Optional[torch.dtype] = None,
|
| 211 |
+
zero_point_dtype: Optional[torch.dtype] = None,
|
| 212 |
+
preserve_zero: bool = True,
|
| 213 |
+
zero_point_domain: ZeroPointDomain = ZeroPointDomain.INT,
|
| 214 |
+
_layout: Layout = PlainLayout(),
|
| 215 |
+
use_hqq: bool = False,
|
| 216 |
+
):
|
| 217 |
+
"""Convert a high precision tensor to an integer affine quantized tensor."""
|
| 218 |
+
original_shape = input_float.shape
|
| 219 |
+
input_float = _layout.pre_process(input_float)
|
| 220 |
+
|
| 221 |
+
if use_hqq:
|
| 222 |
+
assert (
|
| 223 |
+
zero_point_domain == ZeroPointDomain.FLOAT
|
| 224 |
+
and mapping_type == MappingType.ASYMMETRIC
|
| 225 |
+
and quant_min == 0
|
| 226 |
+
), "Invalid input parameters for HQQ quantization."
|
| 227 |
+
nbits = int(math.log2(quant_max + 1))
|
| 228 |
+
axis = 1 if (block_size[0] == 1) else 0
|
| 229 |
+
group_size = max(block_size)
|
| 230 |
+
compute_dtype = (
|
| 231 |
+
zero_point_dtype
|
| 232 |
+
if (zero_point_dtype is not None)
|
| 233 |
+
else input_float.dtype
|
| 234 |
+
)
|
| 235 |
+
device = input_float.device
|
| 236 |
+
from torchao.dtypes import Int4CPULayout
|
| 237 |
+
from torchao.dtypes.uintx import TensorCoreTiledLayout
|
| 238 |
+
|
| 239 |
+
data, scale, zero_point, _ = choose_qparams_and_quantize_affine_hqq(
|
| 240 |
+
input_float,
|
| 241 |
+
nbits=nbits,
|
| 242 |
+
group_size=group_size,
|
| 243 |
+
axis=axis,
|
| 244 |
+
compute_dtype=compute_dtype,
|
| 245 |
+
device=device,
|
| 246 |
+
verbose=False,
|
| 247 |
+
raw_output=not isinstance(
|
| 248 |
+
_layout, (TensorCoreTiledLayout, PlainLayout, Int4CPULayout)
|
| 249 |
+
),
|
| 250 |
+
# raw_output=False is basically the 'convert to TensorCoreTiledLayout zero_point version' option (add scale*midpoint)
|
| 251 |
+
# note in choose_qparams_affine, preserve_zero = False does this same thing while also controlling whether
|
| 252 |
+
# zero is preserved.
|
| 253 |
+
# TODO uncouple preserve_zero and conversion of zero_point to TensorCoreTiledLayout version
|
| 254 |
+
# TODO move the conversion of zero_point out of quant_primitives and into TensorCoreTiledLayout.from_plain
|
| 255 |
+
# TODO change PlainLayout to use raw_output.
|
| 256 |
+
)
|
| 257 |
+
data = data.to(target_dtype)
|
| 258 |
+
else:
|
| 259 |
+
scale, zero_point = choose_qparams_affine(
|
| 260 |
+
input_float,
|
| 261 |
+
mapping_type,
|
| 262 |
+
block_size,
|
| 263 |
+
target_dtype,
|
| 264 |
+
quant_min,
|
| 265 |
+
quant_max,
|
| 266 |
+
eps,
|
| 267 |
+
scale_dtype,
|
| 268 |
+
zero_point_dtype,
|
| 269 |
+
preserve_zero,
|
| 270 |
+
zero_point_domain,
|
| 271 |
+
)
|
| 272 |
+
# choose_qparams_affine is a custom op that does support returning optional Tensors. We thus set the zero_point to None if its domain is None
|
| 273 |
+
if zero_point_domain == ZeroPointDomain.NONE:
|
| 274 |
+
zero_point = None
|
| 275 |
+
data = quantize_affine(
|
| 276 |
+
input_float,
|
| 277 |
+
block_size,
|
| 278 |
+
scale,
|
| 279 |
+
zero_point,
|
| 280 |
+
target_dtype,
|
| 281 |
+
quant_min,
|
| 282 |
+
quant_max,
|
| 283 |
+
zero_point_domain,
|
| 284 |
+
)
|
| 285 |
+
# Note: output will be uint8 tensor for sub byte tensors for now
|
| 286 |
+
|
| 287 |
+
data, scale, zero_point = _layout.post_process(
|
| 288 |
+
data, scale, zero_point, block_size
|
| 289 |
+
)
|
| 290 |
+
tensor_impl_ctr = get_tensor_impl_constructor(type(_layout))
|
| 291 |
+
tensor_impl = tensor_impl_ctr(data, scale, zero_point, _layout)
|
| 292 |
+
return cls(
|
| 293 |
+
tensor_impl,
|
| 294 |
+
block_size,
|
| 295 |
+
original_shape,
|
| 296 |
+
quant_min,
|
| 297 |
+
quant_max,
|
| 298 |
+
zero_point_domain,
|
| 299 |
+
dtype=input_float.dtype,
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
@classmethod
|
| 303 |
+
def from_hp_to_intx_static(
|
| 304 |
+
cls,
|
| 305 |
+
input_float: torch.Tensor,
|
| 306 |
+
scale: torch.Tensor,
|
| 307 |
+
zero_point: Optional[torch.Tensor],
|
| 308 |
+
block_size: Tuple[int, ...],
|
| 309 |
+
target_dtype: torch.dtype,
|
| 310 |
+
quant_min: Optional[int] = None,
|
| 311 |
+
quant_max: Optional[int] = None,
|
| 312 |
+
zero_point_domain: ZeroPointDomain = ZeroPointDomain.INT,
|
| 313 |
+
_layout: Layout = PlainLayout(),
|
| 314 |
+
):
|
| 315 |
+
"""Create an integer AffineQuantizedTensor from a high precision tensor using static parameters."""
|
| 316 |
+
if zero_point_domain is None:
|
| 317 |
+
raise ValueError("please use ZeroPointDomain.NONE instead of None")
|
| 318 |
+
elif zero_point_domain is ZeroPointDomain.NONE and zero_point is not None:
|
| 319 |
+
raise ValueError("zero_point should be None when zero_point_domain is NONE")
|
| 320 |
+
if target_dtype not in FP8_TYPES:
|
| 321 |
+
assert zero_point is not None, (
|
| 322 |
+
"zero_point must be specified for non-fp8 types"
|
| 323 |
+
)
|
| 324 |
+
original_shape = input_float.shape
|
| 325 |
+
input_float, scale, zero_point = _layout.pre_process_static(
|
| 326 |
+
input_float, scale, zero_point, block_size
|
| 327 |
+
)
|
| 328 |
+
|
| 329 |
+
int_data = quantize_affine(
|
| 330 |
+
input_float,
|
| 331 |
+
block_size,
|
| 332 |
+
scale,
|
| 333 |
+
zero_point,
|
| 334 |
+
target_dtype,
|
| 335 |
+
quant_min,
|
| 336 |
+
quant_max,
|
| 337 |
+
zero_point_domain,
|
| 338 |
+
)
|
| 339 |
+
|
| 340 |
+
int_data, scale, zero_point = _layout.post_process(
|
| 341 |
+
int_data,
|
| 342 |
+
scale,
|
| 343 |
+
zero_point,
|
| 344 |
+
block_size,
|
| 345 |
+
)
|
| 346 |
+
|
| 347 |
+
tensor_impl_ctr = get_tensor_impl_constructor(type(_layout))
|
| 348 |
+
tensor_impl = tensor_impl_ctr(int_data, scale, zero_point, _layout)
|
| 349 |
+
return cls(
|
| 350 |
+
tensor_impl,
|
| 351 |
+
block_size,
|
| 352 |
+
original_shape,
|
| 353 |
+
quant_min,
|
| 354 |
+
quant_max,
|
| 355 |
+
zero_point_domain,
|
| 356 |
+
dtype=input_float.dtype,
|
| 357 |
+
)
|
| 358 |
+
|
| 359 |
+
@classmethod
|
| 360 |
+
def from_hp_to_floatx(
|
| 361 |
+
cls,
|
| 362 |
+
input_float: torch.Tensor,
|
| 363 |
+
block_size: Tuple[int, ...],
|
| 364 |
+
target_dtype: torch.dtype,
|
| 365 |
+
_layout: Layout,
|
| 366 |
+
scale_dtype: Optional[torch.dtype] = None,
|
| 367 |
+
):
|
| 368 |
+
"""Convert a high precision tensor to a float8 quantized tensor."""
|
| 369 |
+
if target_dtype in FP8_TYPES:
|
| 370 |
+
return cls.from_hp_to_intx(
|
| 371 |
+
input_float=input_float,
|
| 372 |
+
mapping_type=MappingType.SYMMETRIC,
|
| 373 |
+
block_size=block_size,
|
| 374 |
+
target_dtype=target_dtype,
|
| 375 |
+
quant_min=math.ceil(torch.finfo(target_dtype).min),
|
| 376 |
+
quant_max=math.ceil(torch.finfo(target_dtype).max),
|
| 377 |
+
eps=torch.finfo(torch.float32).eps,
|
| 378 |
+
scale_dtype=scale_dtype,
|
| 379 |
+
zero_point_dtype=None,
|
| 380 |
+
preserve_zero=True,
|
| 381 |
+
zero_point_domain=ZeroPointDomain.NONE,
|
| 382 |
+
_layout=_layout,
|
| 383 |
+
use_hqq=False,
|
| 384 |
+
)
|
| 385 |
+
else:
|
| 386 |
+
raise NotImplementedError(
|
| 387 |
+
f"Unsupported dtype {target_dtype} for from_hp_to_floatx"
|
| 388 |
+
)
|
| 389 |
+
|
| 390 |
+
@classmethod
|
| 391 |
+
def from_hp_to_floatx_static(
|
| 392 |
+
cls,
|
| 393 |
+
input_float: torch.Tensor,
|
| 394 |
+
scale: torch.Tensor,
|
| 395 |
+
block_size: Tuple[int, ...],
|
| 396 |
+
target_dtype: torch.dtype,
|
| 397 |
+
_layout: Layout,
|
| 398 |
+
):
|
| 399 |
+
"""Create a float8 AffineQuantizedTensor from a high precision tensor using static parameters."""
|
| 400 |
+
if target_dtype in FP8_TYPES:
|
| 401 |
+
return cls.from_hp_to_intx_static(
|
| 402 |
+
input_float=input_float,
|
| 403 |
+
scale=scale,
|
| 404 |
+
zero_point=None,
|
| 405 |
+
block_size=block_size,
|
| 406 |
+
target_dtype=target_dtype,
|
| 407 |
+
quant_min=math.ceil(torch.finfo(target_dtype).min),
|
| 408 |
+
quant_max=math.ceil(torch.finfo(target_dtype).max),
|
| 409 |
+
zero_point_domain=ZeroPointDomain.NONE,
|
| 410 |
+
_layout=_layout,
|
| 411 |
+
)
|
| 412 |
+
else:
|
| 413 |
+
raise NotImplementedError(
|
| 414 |
+
f"Unsupported dtype {target_dtype} for from_hp_to_floatx_static"
|
| 415 |
+
)
|
| 416 |
+
|
| 417 |
+
@classmethod
|
| 418 |
+
def from_hp_to_fpx(
|
| 419 |
+
cls,
|
| 420 |
+
input_float: torch.Tensor,
|
| 421 |
+
_layout: Layout,
|
| 422 |
+
):
|
| 423 |
+
"""Create a floatx AffineQuantizedTensor from a high precision tensor. Floatx is represented as ebits and mbits, and supports the representation of float1-float7."""
|
| 424 |
+
from torchao.dtypes.floatx import FloatxTensorCoreLayout
|
| 425 |
+
|
| 426 |
+
assert isinstance(_layout, FloatxTensorCoreLayout), (
|
| 427 |
+
f"Only FloatxTensorCoreLayout is supported for floatx, got {_layout}"
|
| 428 |
+
)
|
| 429 |
+
original_shape = input_float.shape
|
| 430 |
+
input_float = _layout.pre_process(input_float)
|
| 431 |
+
# per axis quantization, where axis = 1
|
| 432 |
+
block_size = list(input_float.shape)
|
| 433 |
+
block_size[1] = 1
|
| 434 |
+
|
| 435 |
+
ebits, mbits = _layout.ebits, _layout.mbits
|
| 436 |
+
# Note: these ops are hardcoded to have per axis quantization (axis=1) right now
|
| 437 |
+
scale = choose_qparams_affine_floatx(input_float, ebits, mbits)
|
| 438 |
+
floatx_unpacked = quantize_affine_floatx(input_float, scale, ebits, mbits)
|
| 439 |
+
floatx_packed, scale, _ = _layout.post_process(
|
| 440 |
+
floatx_unpacked, scale, None, block_size
|
| 441 |
+
)
|
| 442 |
+
|
| 443 |
+
tensor_impl_ctr = get_tensor_impl_constructor(type(_layout))
|
| 444 |
+
tensor_impl = tensor_impl_ctr(floatx_packed, scale, None, _layout)
|
| 445 |
+
return cls(tensor_impl, block_size, original_shape, dtype=input_float.dtype)
|
| 446 |
+
|
| 447 |
+
@property
|
| 448 |
+
def _layout(self) -> Layout:
|
| 449 |
+
return self.tensor_impl._layout
|
| 450 |
+
|
| 451 |
+
def to(self, *args, **kwargs):
|
| 452 |
+
kwargs = self._get_to_kwargs(*args, **kwargs)
|
| 453 |
+
device = kwargs.pop("device")
|
| 454 |
+
return self.__class__(
|
| 455 |
+
self.tensor_impl.to(device),
|
| 456 |
+
self.block_size,
|
| 457 |
+
self.shape,
|
| 458 |
+
self.quant_min,
|
| 459 |
+
self.quant_max,
|
| 460 |
+
self.zero_point_domain,
|
| 461 |
+
**kwargs,
|
| 462 |
+
)
|
| 463 |
+
|
| 464 |
+
def _apply_fn_to_data(self, fn):
|
| 465 |
+
return self.__class__(
|
| 466 |
+
fn(self.tensor_impl),
|
| 467 |
+
self.block_size,
|
| 468 |
+
self.shape,
|
| 469 |
+
self.quant_min,
|
| 470 |
+
self.quant_max,
|
| 471 |
+
self.zero_point_domain,
|
| 472 |
+
dtype=self.dtype,
|
| 473 |
+
strides=self.stride(),
|
| 474 |
+
)
|
| 475 |
+
|
| 476 |
+
# following are the comments for __torch_function__/__torch_dispatch__, -> this is defined in affine_quantized_tensor_ops.py
|
| 477 |
+
# a bit later
|
| 478 |
+
# Note: we only added cpu path here for 8da4w, this is for executorch, in the future
|
| 479 |
+
# 1. we'll add cpu/cuda version (int4mm etc.)
|
| 480 |
+
# 2. we'll need to hide the 8da4w executorch version under things like layouts (we also have multiple impl for cpu kernel as Michael mentioned), so it will be something like
|
| 481 |
+
# cpu device + et laytout --> gives current 8da4w executorch representation
|
| 482 |
+
# cpu device + avx layout --> gives optimized kernel for 8da4w in avx cpu etc.
|
| 483 |
+
# cuda device + some layout --> gives cuda kernel
|
| 484 |
+
|
| 485 |
+
# two scenarios where we currently fall back to vanilla mm:
|
| 486 |
+
# 1 - when tensor is on CUDA: we'll add this later, we'll also enable dispatching to optimized
|
| 487 |
+
# kernels in CPU as well, see the note above
|
| 488 |
+
# 2 - we're given non-floats - quantizing long to int8 is crazy
|
| 489 |
+
|
| 490 |
+
|
| 491 |
+
######################################################
|
| 492 |
+
# Layout and TensorImpl Subclass Registration #
|
| 493 |
+
######################################################
|
| 494 |
+
register_layout = AffineQuantizedTensor.register_layout
|
| 495 |
+
get_tensor_impl_constructor = AffineQuantizedTensor.get_tensor_impl_constructor
|
| 496 |
+
|
| 497 |
+
|
| 498 |
+
to_affine_quantized_intx = AffineQuantizedTensor.from_hp_to_intx
|
| 499 |
+
to_affine_quantized_intx_static = AffineQuantizedTensor.from_hp_to_intx_static
|
| 500 |
+
to_affine_quantized_floatx = AffineQuantizedTensor.from_hp_to_floatx
|
| 501 |
+
to_affine_quantized_floatx_static = AffineQuantizedTensor.from_hp_to_floatx_static
|
| 502 |
+
# experimental will be merged in to floatx
|
| 503 |
+
to_affine_quantized_fpx = AffineQuantizedTensor.from_hp_to_fpx
|
| 504 |
+
|
| 505 |
+
if TORCH_VERSION_AT_LEAST_2_5:
|
| 506 |
+
# Allow a model with AffineQuantizedTensor weights to be loaded with `weights_only=True`
|
| 507 |
+
torch.serialization.add_safe_globals([AffineQuantizedTensor])
|
lib/python3.12/site-packages/torchao/dtypes/affine_quantized_tensor_ops.py
ADDED
|
@@ -0,0 +1,580 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD 3-Clause license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
import logging
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
from torch.utils._python_dispatch import return_and_correct_aliasing
|
| 10 |
+
|
| 11 |
+
from torchao.dtypes.affine_quantized_tensor import (
|
| 12 |
+
AffineQuantizedTensor,
|
| 13 |
+
)
|
| 14 |
+
from torchao.dtypes.floatx.cutlass_semi_sparse_layout import (
|
| 15 |
+
_linear_fp8_act_fp8_weight_sparse_cutlass_check,
|
| 16 |
+
_linear_fp8_act_fp8_weight_sparse_cutlass_impl,
|
| 17 |
+
)
|
| 18 |
+
from torchao.dtypes.floatx.float8_layout import (
|
| 19 |
+
_linear_fp8_act_fp8_weight_check,
|
| 20 |
+
_linear_fp8_act_fp8_weight_impl,
|
| 21 |
+
_linear_fp_act_fp8_weight_check,
|
| 22 |
+
_linear_fp_act_fp8_weight_impl,
|
| 23 |
+
)
|
| 24 |
+
from torchao.dtypes.floatx.floatx_tensor_core_layout import (
|
| 25 |
+
_linear_f16_bf16_act_floatx_weight_check,
|
| 26 |
+
_linear_f16_bf16_act_floatx_weight_impl,
|
| 27 |
+
)
|
| 28 |
+
from torchao.dtypes.uintx.block_sparse_layout import (
|
| 29 |
+
_linear_int8_act_int8_weight_block_sparse_check,
|
| 30 |
+
_linear_int8_act_int8_weight_block_sparse_impl,
|
| 31 |
+
)
|
| 32 |
+
from torchao.dtypes.uintx.cutlass_int4_packed_layout import (
|
| 33 |
+
_linear_int4_act_int4_weight_cutlass_check,
|
| 34 |
+
_linear_int4_act_int4_weight_cutlass_impl,
|
| 35 |
+
_linear_int8_act_int4_weight_cutlass_check,
|
| 36 |
+
_linear_int8_act_int4_weight_cutlass_impl,
|
| 37 |
+
)
|
| 38 |
+
from torchao.dtypes.uintx.gemlite_layout import (
|
| 39 |
+
_linear_fp_act_int4_weight_gemlite_check,
|
| 40 |
+
_linear_fp_act_int4_weight_gemlite_impl,
|
| 41 |
+
)
|
| 42 |
+
from torchao.dtypes.uintx.int4_cpu_layout import (
|
| 43 |
+
_linear_fp_act_uint4_weight_cpu_check,
|
| 44 |
+
_linear_fp_act_uint4_weight_cpu_impl,
|
| 45 |
+
)
|
| 46 |
+
from torchao.dtypes.uintx.int4_xpu_layout import (
|
| 47 |
+
_linear_bf16_act_uint4_weight_float_zero_check,
|
| 48 |
+
_linear_bf16_act_uint4_weight_float_zero_impl,
|
| 49 |
+
_linear_bf16_act_uint4_weight_int8_zero_check,
|
| 50 |
+
_linear_bf16_act_uint4_weight_int8_zero_impl,
|
| 51 |
+
)
|
| 52 |
+
from torchao.dtypes.uintx.marlin_qqq_tensor import (
|
| 53 |
+
_linear_int8_act_int4_weight_marlin_qqq_check,
|
| 54 |
+
_linear_int8_act_int4_weight_marlin_qqq_impl,
|
| 55 |
+
)
|
| 56 |
+
from torchao.dtypes.uintx.marlin_sparse_layout import (
|
| 57 |
+
_linear_fp_act_int4_weight_sparse_marlin_check,
|
| 58 |
+
_linear_fp_act_int4_weight_sparse_marlin_impl,
|
| 59 |
+
)
|
| 60 |
+
from torchao.dtypes.uintx.packed_linear_int8_dynamic_activation_intx_weight_layout import (
|
| 61 |
+
_linear_check as _linear_int8_act_intx_weight_packed_check,
|
| 62 |
+
)
|
| 63 |
+
from torchao.dtypes.uintx.packed_linear_int8_dynamic_activation_intx_weight_layout import (
|
| 64 |
+
_linear_impl as _linear_int8_act_intx_weight_packed_impl,
|
| 65 |
+
)
|
| 66 |
+
from torchao.dtypes.uintx.plain_layout import (
|
| 67 |
+
PlainAQTTensorImpl,
|
| 68 |
+
_linear_fp_act_int8_weight_check,
|
| 69 |
+
_linear_fp_act_int8_weight_impl,
|
| 70 |
+
_linear_int8_act_int8_weight_check,
|
| 71 |
+
_linear_int8_act_int8_weight_impl,
|
| 72 |
+
)
|
| 73 |
+
from torchao.dtypes.uintx.q_dq_layout import (
|
| 74 |
+
_embedding_check as _embedding_q_dq_check,
|
| 75 |
+
)
|
| 76 |
+
from torchao.dtypes.uintx.q_dq_layout import (
|
| 77 |
+
_embedding_impl as _embedding_q_dq_impl,
|
| 78 |
+
)
|
| 79 |
+
from torchao.dtypes.uintx.q_dq_layout import (
|
| 80 |
+
_linear_check as _linear_q_dq_check,
|
| 81 |
+
)
|
| 82 |
+
from torchao.dtypes.uintx.q_dq_layout import (
|
| 83 |
+
_linear_impl as _linear_q_dq_impl,
|
| 84 |
+
)
|
| 85 |
+
from torchao.dtypes.uintx.semi_sparse_layout import (
|
| 86 |
+
_linear_int8_act_int8_weight_semi_structured_sparse_check,
|
| 87 |
+
_linear_int8_act_int8_weight_semi_structured_sparse_impl,
|
| 88 |
+
)
|
| 89 |
+
from torchao.dtypes.uintx.tensor_core_tiled_layout import (
|
| 90 |
+
_linear_bf16_act_uint4_weight_check,
|
| 91 |
+
_linear_bf16_act_uint4_weight_impl,
|
| 92 |
+
)
|
| 93 |
+
from torchao.quantization.quant_primitives import dequantize_affine
|
| 94 |
+
from torchao.utils import (
|
| 95 |
+
fill_defaults,
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
logger = logging.getLogger(__name__)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
aten = torch.ops.aten
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
_AQT_QLINEAR_DISPATCH_TABLE = {}
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def register_aqt_quantized_linear_dispatch(dispatch_condition, impl):
|
| 108 |
+
"""Register a dispatch for quantized linear op with dispatch_condition function and impl function
|
| 109 |
+
both takes three arguments:
|
| 110 |
+
input_tensor: dimension is (M1, M2, ..., in_features)
|
| 111 |
+
weight_tensor: dimension is (out_features, in_features)
|
| 112 |
+
bias: dimension is (out_features,)
|
| 113 |
+
so that these can be shared by F.linear, aten.mm, aten.addmm dispatches
|
| 114 |
+
|
| 115 |
+
Args:
|
| 116 |
+
`dispatch_condition` (Callable[[torch.Tensor, torch.Tensor, torch.Tensor], bool]: the dispatch
|
| 117 |
+
condition for a specialized quantized linear implementation, e.g. bfloat16 activation + uint4 weight
|
| 118 |
+
`impl` (Callable[[torch.Tensor, torch.Tensor, torch.Tensor], torch.Tensor]: the specialized
|
| 119 |
+
quantized linear implementation
|
| 120 |
+
"""
|
| 121 |
+
_AQT_QLINEAR_DISPATCH_TABLE[dispatch_condition] = impl
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def deregister_aqt_quantized_linear_dispatch(dispatch_condition):
|
| 125 |
+
if dispatch_condition in _AQT_QLINEAR_DISPATCH_TABLE:
|
| 126 |
+
del _AQT_QLINEAR_DISPATCH_TABLE[dispatch_condition]
|
| 127 |
+
else:
|
| 128 |
+
logger.warn(
|
| 129 |
+
f"Attempting to remove non-existant dispatch condition {dispatch_condition}"
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def _same_metadata(self: AffineQuantizedTensor, src: AffineQuantizedTensor):
|
| 134 |
+
return (
|
| 135 |
+
isinstance(self, AffineQuantizedTensor)
|
| 136 |
+
and isinstance(src, AffineQuantizedTensor)
|
| 137 |
+
and all(
|
| 138 |
+
[
|
| 139 |
+
getattr(self, attr) == getattr(src, attr)
|
| 140 |
+
for attr in [
|
| 141 |
+
"block_size",
|
| 142 |
+
"shape",
|
| 143 |
+
"quant_min",
|
| 144 |
+
"quant_max",
|
| 145 |
+
"zero_point_domain",
|
| 146 |
+
"dtype",
|
| 147 |
+
]
|
| 148 |
+
]
|
| 149 |
+
)
|
| 150 |
+
and isinstance(self.tensor_impl, type(src.tensor_impl))
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
class QuantizedLinearNotImplementedError(NotImplementedError):
|
| 155 |
+
"""Thin wrapper around NotImplementedError to make it easier to catch this error in the dispatch table"""
|
| 156 |
+
|
| 157 |
+
pass
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
# input_tensor: dimension is (M1, M2, ..., in_features)
|
| 161 |
+
# weight_tensor: dimension is (out_features, in_features)
|
| 162 |
+
# bias: dimension is (out_features,)
|
| 163 |
+
@staticmethod
|
| 164 |
+
def _quantized_linear_op(input_tensor, weight_tensor, bias):
|
| 165 |
+
for dispatch_condition, impl in _AQT_QLINEAR_DISPATCH_TABLE.items():
|
| 166 |
+
if dispatch_condition(input_tensor, weight_tensor, bias):
|
| 167 |
+
return impl(input_tensor, weight_tensor, bias)
|
| 168 |
+
raise QuantizedLinearNotImplementedError(
|
| 169 |
+
"No specialized dispatch found for quantized linear op"
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
# Attach the _quantized_linear_op to the AffineQuantizedTensor class
|
| 174 |
+
AffineQuantizedTensor._quantized_linear_op = _quantized_linear_op
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
# _register_aqt_quantized_linear_dispatches function has a list of (dispatch_condition, implementation) functions, defined in their dtype layout classes, that takes the following args:
|
| 178 |
+
# input_tensor: dimension is (M1, M2, ..., in_features)
|
| 179 |
+
# weight_tensor: dimension is (out_features, in_features)
|
| 180 |
+
# bias: dimension is (out_features,)
|
| 181 |
+
# so that these can be shared by F.linear, aten.mm, aten.addmm dispatches
|
| 182 |
+
def _register_aqt_quantized_linear_dispatches():
|
| 183 |
+
for dispatch_condition, impl in [
|
| 184 |
+
(_linear_int8_act_int8_weight_check, _linear_int8_act_int8_weight_impl),
|
| 185 |
+
(
|
| 186 |
+
_linear_int8_act_int8_weight_semi_structured_sparse_check,
|
| 187 |
+
_linear_int8_act_int8_weight_semi_structured_sparse_impl,
|
| 188 |
+
),
|
| 189 |
+
(
|
| 190 |
+
_linear_int8_act_int8_weight_block_sparse_check,
|
| 191 |
+
_linear_int8_act_int8_weight_block_sparse_impl,
|
| 192 |
+
),
|
| 193 |
+
(_linear_fp8_act_fp8_weight_check, _linear_fp8_act_fp8_weight_impl),
|
| 194 |
+
(_linear_fp_act_fp8_weight_check, _linear_fp_act_fp8_weight_impl),
|
| 195 |
+
(_linear_bf16_act_uint4_weight_check, _linear_bf16_act_uint4_weight_impl),
|
| 196 |
+
(_linear_fp_act_int8_weight_check, _linear_fp_act_int8_weight_impl),
|
| 197 |
+
(
|
| 198 |
+
_linear_f16_bf16_act_floatx_weight_check,
|
| 199 |
+
_linear_f16_bf16_act_floatx_weight_impl,
|
| 200 |
+
),
|
| 201 |
+
(
|
| 202 |
+
_linear_fp_act_int4_weight_sparse_marlin_check,
|
| 203 |
+
_linear_fp_act_int4_weight_sparse_marlin_impl,
|
| 204 |
+
),
|
| 205 |
+
(
|
| 206 |
+
_linear_int8_act_int4_weight_marlin_qqq_check,
|
| 207 |
+
_linear_int8_act_int4_weight_marlin_qqq_impl,
|
| 208 |
+
),
|
| 209 |
+
(
|
| 210 |
+
_linear_fp_act_int4_weight_gemlite_check,
|
| 211 |
+
_linear_fp_act_int4_weight_gemlite_impl,
|
| 212 |
+
),
|
| 213 |
+
(
|
| 214 |
+
_linear_int8_act_int4_weight_cutlass_check,
|
| 215 |
+
_linear_int8_act_int4_weight_cutlass_impl,
|
| 216 |
+
),
|
| 217 |
+
(
|
| 218 |
+
_linear_int4_act_int4_weight_cutlass_check,
|
| 219 |
+
_linear_int4_act_int4_weight_cutlass_impl,
|
| 220 |
+
),
|
| 221 |
+
(
|
| 222 |
+
_linear_fp8_act_fp8_weight_sparse_cutlass_check,
|
| 223 |
+
_linear_fp8_act_fp8_weight_sparse_cutlass_impl,
|
| 224 |
+
),
|
| 225 |
+
(
|
| 226 |
+
_linear_fp_act_uint4_weight_cpu_check,
|
| 227 |
+
_linear_fp_act_uint4_weight_cpu_impl,
|
| 228 |
+
),
|
| 229 |
+
(
|
| 230 |
+
_linear_int8_act_intx_weight_packed_check,
|
| 231 |
+
_linear_int8_act_intx_weight_packed_impl,
|
| 232 |
+
),
|
| 233 |
+
(
|
| 234 |
+
_linear_q_dq_check,
|
| 235 |
+
_linear_q_dq_impl,
|
| 236 |
+
),
|
| 237 |
+
(
|
| 238 |
+
_linear_bf16_act_uint4_weight_int8_zero_check,
|
| 239 |
+
_linear_bf16_act_uint4_weight_int8_zero_impl,
|
| 240 |
+
),
|
| 241 |
+
(
|
| 242 |
+
_linear_bf16_act_uint4_weight_float_zero_check,
|
| 243 |
+
_linear_bf16_act_uint4_weight_float_zero_impl,
|
| 244 |
+
),
|
| 245 |
+
]:
|
| 246 |
+
register_aqt_quantized_linear_dispatch(dispatch_condition, impl)
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
_register_aqt_quantized_linear_dispatches()
|
| 250 |
+
|
| 251 |
+
implements = AffineQuantizedTensor.implements
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
@implements([torch.nn.functional.linear, aten.linear.default])
|
| 255 |
+
def _(func, types, args, kwargs):
|
| 256 |
+
input_tensor, weight_tensor, bias = (
|
| 257 |
+
args[0],
|
| 258 |
+
args[1],
|
| 259 |
+
args[2] if len(args) > 2 else None,
|
| 260 |
+
)
|
| 261 |
+
if not input_tensor.is_floating_point():
|
| 262 |
+
raise NotImplementedError(
|
| 263 |
+
f"{func} is not implemented for non floating point input"
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
# using try/except here so that we can have a general fallback when input_tensor/weight_tensor
|
| 267 |
+
# is not picked up by any of the dispatch paths in `_quantized_linear_op`, this allows us to
|
| 268 |
+
# make the branches easier to understand in `_quantized_linear_op`
|
| 269 |
+
try:
|
| 270 |
+
return weight_tensor._quantized_linear_op(input_tensor, weight_tensor, bias)
|
| 271 |
+
except QuantizedLinearNotImplementedError as e:
|
| 272 |
+
# fallback path is only called when user did not specify a specfic quantized linear implementation with `_layout.quantized_linear_impl`
|
| 273 |
+
if (
|
| 274 |
+
isinstance(weight_tensor, AffineQuantizedTensor)
|
| 275 |
+
and hasattr(weight_tensor._layout, "quantized_linear_impl")
|
| 276 |
+
and weight_tensor._layout.quantized_linear_impl is not None
|
| 277 |
+
):
|
| 278 |
+
raise e
|
| 279 |
+
|
| 280 |
+
if isinstance(input_tensor, AffineQuantizedTensor):
|
| 281 |
+
input_tensor = input_tensor.dequantize()
|
| 282 |
+
if isinstance(weight_tensor, AffineQuantizedTensor):
|
| 283 |
+
weight_tensor = weight_tensor.dequantize()
|
| 284 |
+
return torch.nn.functional.linear(input_tensor, weight_tensor, bias)
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
@implements(torch.nn.functional.embedding)
|
| 288 |
+
def _(func, types, args, kwargs):
|
| 289 |
+
if _embedding_q_dq_check(args, kwargs):
|
| 290 |
+
return _embedding_q_dq_impl(args, kwargs)
|
| 291 |
+
|
| 292 |
+
# new_arg1 = args[1].dequantize()
|
| 293 |
+
# return torch.nn.embedding(args[0], new_arg1, *args[2:], **kwargs)
|
| 294 |
+
assert isinstance(args[1].tensor_impl, PlainAQTTensorImpl), (
|
| 295 |
+
f"embedding only works with PlainAQTTensorImpl but got {type(args[1].tensor_impl)}"
|
| 296 |
+
)
|
| 297 |
+
assert (
|
| 298 |
+
kwargs["padding_idx"] is None
|
| 299 |
+
and kwargs["max_norm"] is None
|
| 300 |
+
and not kwargs["scale_grad_by_freq"]
|
| 301 |
+
and not kwargs["sparse"]
|
| 302 |
+
and kwargs["norm_type"] == 2.0
|
| 303 |
+
)
|
| 304 |
+
idx = args[0]
|
| 305 |
+
int_data, scale, zero_point = args[1].tensor_impl.get_plain()
|
| 306 |
+
|
| 307 |
+
sliced_data, sliced_scale, sliced_zero_point = (
|
| 308 |
+
int_data[idx],
|
| 309 |
+
scale[idx],
|
| 310 |
+
zero_point[idx],
|
| 311 |
+
)
|
| 312 |
+
# Block size is expecting 2 dimensions [1, group size] but
|
| 313 |
+
# batchsize or other dims gets added to sliced_data, sliced_scale and sliced_zero_point so
|
| 314 |
+
# we need to increase block size to correct dim
|
| 315 |
+
new_blocks = idx.dim() - 1
|
| 316 |
+
return dequantize_affine(
|
| 317 |
+
sliced_data,
|
| 318 |
+
new_blocks * [1] + list(args[1].block_size),
|
| 319 |
+
sliced_scale,
|
| 320 |
+
sliced_zero_point,
|
| 321 |
+
sliced_data.dtype,
|
| 322 |
+
args[1].quant_min,
|
| 323 |
+
args[1].quant_max,
|
| 324 |
+
args[1].zero_point_domain,
|
| 325 |
+
output_dtype=sliced_scale.dtype,
|
| 326 |
+
)
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
@implements(aten.addmm.default)
|
| 330 |
+
def _(func, types, args, kwargs):
|
| 331 |
+
input_tensor, weight_tensor, bias = (
|
| 332 |
+
args[1],
|
| 333 |
+
args[2],
|
| 334 |
+
args[0],
|
| 335 |
+
)
|
| 336 |
+
if not input_tensor.is_floating_point():
|
| 337 |
+
raise NotImplementedError(
|
| 338 |
+
f"{func} is not implemented for non floating point input"
|
| 339 |
+
)
|
| 340 |
+
|
| 341 |
+
assert input_tensor.shape[-1] == weight_tensor.shape[0], (
|
| 342 |
+
f"need mat1 shape: {input_tensor.shape} final dim"
|
| 343 |
+
f"to match mat2 shape: {weight_tensor.shape} first dim"
|
| 344 |
+
)
|
| 345 |
+
|
| 346 |
+
# using try/except here so that we can have a general fallback when input_tensor/weight_tensor
|
| 347 |
+
# is not picked up by any of the dispatch paths in `_quantized_linear_op`, this allows us to
|
| 348 |
+
# make the branches easier to understand in `_quantized_linear_op`
|
| 349 |
+
try:
|
| 350 |
+
transposed_weight_tensor = weight_tensor.t()
|
| 351 |
+
return weight_tensor._quantized_linear_op(
|
| 352 |
+
input_tensor, transposed_weight_tensor, bias
|
| 353 |
+
)
|
| 354 |
+
except QuantizedLinearNotImplementedError as e:
|
| 355 |
+
# fallback path is only called when user did not specify a specfic quantized linear implementation with `_layout.quantized_linear_impl`
|
| 356 |
+
if (
|
| 357 |
+
isinstance(weight_tensor, AffineQuantizedTensor)
|
| 358 |
+
and hasattr(weight_tensor._layout, "quantized_linear_impl")
|
| 359 |
+
and weight_tensor._layout.quantized_linear_impl is not None
|
| 360 |
+
):
|
| 361 |
+
raise e
|
| 362 |
+
|
| 363 |
+
if isinstance(input_tensor, AffineQuantizedTensor):
|
| 364 |
+
input_tensor = input_tensor.dequantize()
|
| 365 |
+
if isinstance(weight_tensor, AffineQuantizedTensor):
|
| 366 |
+
weight_tensor = weight_tensor.dequantize()
|
| 367 |
+
return func(bias, input_tensor, weight_tensor)
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
@implements(aten.mm.default)
|
| 371 |
+
def _(func, types, args, kwargs):
|
| 372 |
+
input_tensor, weight_tensor, bias = (args[0], args[1], None)
|
| 373 |
+
if not input_tensor.is_floating_point():
|
| 374 |
+
raise NotImplementedError(
|
| 375 |
+
f"{func} is not implemented for non floating point input"
|
| 376 |
+
)
|
| 377 |
+
|
| 378 |
+
assert input_tensor.shape[-1] == weight_tensor.shape[0], (
|
| 379 |
+
f"need mat1 shape: {input_tensor.shape} final dim"
|
| 380 |
+
f"to match mat2 shape: {weight_tensor.shape} first dim"
|
| 381 |
+
)
|
| 382 |
+
|
| 383 |
+
try:
|
| 384 |
+
transposed_weight_tensor = weight_tensor.t()
|
| 385 |
+
return weight_tensor._quantized_linear_op(
|
| 386 |
+
input_tensor, transposed_weight_tensor, bias
|
| 387 |
+
)
|
| 388 |
+
except QuantizedLinearNotImplementedError as e:
|
| 389 |
+
# fallback path is only called when user did not specify a specfic quantized linear implementation with `_layout.quantized_linear_impl`
|
| 390 |
+
if (
|
| 391 |
+
isinstance(weight_tensor, AffineQuantizedTensor)
|
| 392 |
+
and hasattr(weight_tensor._layout, "quantized_linear_impl")
|
| 393 |
+
and weight_tensor._layout.quantized_linear_impl is not None
|
| 394 |
+
):
|
| 395 |
+
raise e
|
| 396 |
+
|
| 397 |
+
if isinstance(input_tensor, AffineQuantizedTensor):
|
| 398 |
+
input_tensor = input_tensor.dequantize()
|
| 399 |
+
if isinstance(weight_tensor, AffineQuantizedTensor):
|
| 400 |
+
weight_tensor = weight_tensor.dequantize()
|
| 401 |
+
return func(input_tensor, weight_tensor)
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
@implements([aten.detach.default, aten.alias.default])
|
| 405 |
+
def _(func, types, args, kwargs):
|
| 406 |
+
return return_and_correct_aliasing(
|
| 407 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.detach)
|
| 408 |
+
)
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
@implements(aten.clone.default)
|
| 412 |
+
def _(func, types, args, kwargs):
|
| 413 |
+
return return_and_correct_aliasing(
|
| 414 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.clone)
|
| 415 |
+
)
|
| 416 |
+
|
| 417 |
+
|
| 418 |
+
@implements(aten.copy_.default)
|
| 419 |
+
def _(func, types, args, kwargs):
|
| 420 |
+
return return_and_correct_aliasing(
|
| 421 |
+
func, args, kwargs, args[1]._apply_fn_to_data(torch.clone)
|
| 422 |
+
)
|
| 423 |
+
|
| 424 |
+
|
| 425 |
+
@implements(aten._to_copy.default)
|
| 426 |
+
def _(func, types, args, kwargs):
|
| 427 |
+
return return_and_correct_aliasing(
|
| 428 |
+
func,
|
| 429 |
+
args,
|
| 430 |
+
kwargs,
|
| 431 |
+
args[0].to(*args[1:], **kwargs)._apply_fn_to_data(torch.clone),
|
| 432 |
+
)
|
| 433 |
+
|
| 434 |
+
|
| 435 |
+
@implements(aten.copy_.default)
|
| 436 |
+
def _(func, types, args, kwargs):
|
| 437 |
+
self = args[0]
|
| 438 |
+
src = args[1]
|
| 439 |
+
if _same_metadata(self, src):
|
| 440 |
+
self_tensors = self.__tensor_flatten__()[0]
|
| 441 |
+
for tensor_name in self_tensors:
|
| 442 |
+
getattr(self, tensor_name).copy_(getattr(src, tensor_name))
|
| 443 |
+
return
|
| 444 |
+
raise ValueError(
|
| 445 |
+
f"Not supported args for copy_ due to metadata mistach: {args[0], args[1]}"
|
| 446 |
+
)
|
| 447 |
+
|
| 448 |
+
|
| 449 |
+
@implements(aten.t.default)
|
| 450 |
+
def _(func, types, args, kwargs):
|
| 451 |
+
block_size = args[0].block_size
|
| 452 |
+
assert len(block_size) == 2
|
| 453 |
+
transposed_block_size = (block_size[1], block_size[0])
|
| 454 |
+
tensor = args[0]
|
| 455 |
+
shape = tensor.shape[::-1]
|
| 456 |
+
new = tensor.__class__(
|
| 457 |
+
tensor.tensor_impl.t(),
|
| 458 |
+
transposed_block_size,
|
| 459 |
+
shape,
|
| 460 |
+
tensor.quant_min,
|
| 461 |
+
tensor.quant_max,
|
| 462 |
+
tensor.zero_point_domain,
|
| 463 |
+
dtype=tensor.dtype,
|
| 464 |
+
strides=tensor.stride(),
|
| 465 |
+
)
|
| 466 |
+
return return_and_correct_aliasing(func, args, kwargs, new)
|
| 467 |
+
|
| 468 |
+
|
| 469 |
+
@implements(aten.slice.Tensor)
|
| 470 |
+
def _(func, types, args, kwargs):
|
| 471 |
+
self, dim, start, end, step = fill_defaults(args, 5, [0, None, None, 1])
|
| 472 |
+
assert step == 1
|
| 473 |
+
assert dim == 0 or dim == 1, f"Only dim==0 or 1 are supported, got: {dim}"
|
| 474 |
+
if end >= self.shape[dim]:
|
| 475 |
+
end = self.shape[dim]
|
| 476 |
+
shape = list(self.shape)
|
| 477 |
+
shape[dim] = end - start
|
| 478 |
+
block_size = self.block_size
|
| 479 |
+
assert len(block_size) in [
|
| 480 |
+
2,
|
| 481 |
+
3,
|
| 482 |
+
], f"Slice only works for 2 and 3d block_size right now, got: {block_size}"
|
| 483 |
+
# with slice, some shape dimension might be smaller than block_size dimension, so
|
| 484 |
+
# we need to make sure there is no overflow
|
| 485 |
+
if len(block_size) == 2:
|
| 486 |
+
block_size = (min(shape[0], block_size[0]), min(shape[1], block_size[1]))
|
| 487 |
+
|
| 488 |
+
new = self.__class__(
|
| 489 |
+
aten.slice.Tensor(self.tensor_impl, dim, start, end, step),
|
| 490 |
+
block_size,
|
| 491 |
+
shape,
|
| 492 |
+
self.quant_min,
|
| 493 |
+
self.quant_max,
|
| 494 |
+
self.zero_point_domain,
|
| 495 |
+
dtype=self.dtype,
|
| 496 |
+
strides=self.stride() if len(block_size) == 2 else None,
|
| 497 |
+
)
|
| 498 |
+
return return_and_correct_aliasing(func, args, kwargs, new)
|
| 499 |
+
|
| 500 |
+
|
| 501 |
+
@implements(aten.index.Tensor)
|
| 502 |
+
def _(func, types, args, kwargs):
|
| 503 |
+
self, indices = args
|
| 504 |
+
assert len(indices) == 1, (
|
| 505 |
+
f"op {func} currently only implemented for single dimensional indexing but got indices: {indices}"
|
| 506 |
+
)
|
| 507 |
+
new_tensor_impl = aten.index.Tensor(self.tensor_impl, indices)
|
| 508 |
+
shape = tuple([indices[0].numel(), *self.shape[1:]])
|
| 509 |
+
|
| 510 |
+
block_size = self.block_size
|
| 511 |
+
new = self.__class__(
|
| 512 |
+
new_tensor_impl,
|
| 513 |
+
block_size,
|
| 514 |
+
shape,
|
| 515 |
+
self.quant_min,
|
| 516 |
+
self.quant_max,
|
| 517 |
+
self.zero_point_domain,
|
| 518 |
+
dtype=self.dtype,
|
| 519 |
+
)
|
| 520 |
+
return return_and_correct_aliasing(func, args, kwargs, new)
|
| 521 |
+
|
| 522 |
+
|
| 523 |
+
@implements(aten.select.int)
|
| 524 |
+
def _(func, types, args, kwargs):
|
| 525 |
+
self, dim, index = fill_defaults(args, 3, [0, 0])
|
| 526 |
+
assert dim == 0, f"op {func} currently only implemented for dim=0 but got dim={dim}"
|
| 527 |
+
assert self.dim() == 3, (
|
| 528 |
+
f"op {func} currently only implemented for 3 dimensional tensors but got shape={self.shape}"
|
| 529 |
+
)
|
| 530 |
+
|
| 531 |
+
new_tensor_impl = aten.select.int(self.tensor_impl, dim, index)
|
| 532 |
+
|
| 533 |
+
shape = self.shape[1:]
|
| 534 |
+
block_size = self.block_size[1:]
|
| 535 |
+
new = self.__class__(
|
| 536 |
+
new_tensor_impl,
|
| 537 |
+
block_size,
|
| 538 |
+
shape,
|
| 539 |
+
self.quant_min,
|
| 540 |
+
self.quant_max,
|
| 541 |
+
self.zero_point_domain,
|
| 542 |
+
dtype=self.dtype,
|
| 543 |
+
)
|
| 544 |
+
return return_and_correct_aliasing(func, args, kwargs, new)
|
| 545 |
+
|
| 546 |
+
|
| 547 |
+
# this is needed for DTensor.from_local() and for flattening tensor
|
| 548 |
+
@implements(aten.view.default)
|
| 549 |
+
def _(func, types, args, kwargs):
|
| 550 |
+
self, shape = args
|
| 551 |
+
|
| 552 |
+
if tuple(self.shape) == tuple(shape):
|
| 553 |
+
return self.__class__(
|
| 554 |
+
self.tensor_impl,
|
| 555 |
+
self.block_size,
|
| 556 |
+
self.shape,
|
| 557 |
+
self.quant_min,
|
| 558 |
+
self.quant_max,
|
| 559 |
+
self.zero_point_domain,
|
| 560 |
+
dtype=self.dtype,
|
| 561 |
+
strides=self.stride(),
|
| 562 |
+
)
|
| 563 |
+
|
| 564 |
+
if len(shape) == 1 and shape[0] == -1:
|
| 565 |
+
assert len(self.block_size) == 2 and self.block_size[0] == 1
|
| 566 |
+
block_size = (self.block_size[1],)
|
| 567 |
+
return self.__class__(
|
| 568 |
+
self.tensor_impl,
|
| 569 |
+
block_size,
|
| 570 |
+
(self.numel(),),
|
| 571 |
+
self.quant_min,
|
| 572 |
+
self.quant_max,
|
| 573 |
+
self.zero_point_domain,
|
| 574 |
+
dtype=self.dtype,
|
| 575 |
+
strides=self.stride(),
|
| 576 |
+
)
|
| 577 |
+
|
| 578 |
+
raise ValueError(
|
| 579 |
+
f"{self.__class__.__name__} only supports .view() with same shape or shape=[-1]"
|
| 580 |
+
)
|
lib/python3.12/site-packages/torchao/dtypes/floatx/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .cutlass_semi_sparse_layout import (
|
| 2 |
+
CutlassSemiSparseLayout,
|
| 3 |
+
)
|
| 4 |
+
from .float8_layout import Float8Layout
|
| 5 |
+
from .floatx_tensor_core_layout import (
|
| 6 |
+
FloatxTensorCoreLayout,
|
| 7 |
+
from_scaled_tc_floatx,
|
| 8 |
+
to_scaled_tc_floatx,
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
__all__ = [
|
| 12 |
+
"FloatxTensorCoreLayout",
|
| 13 |
+
"to_scaled_tc_floatx",
|
| 14 |
+
"from_scaled_tc_floatx",
|
| 15 |
+
"Float8Layout",
|
| 16 |
+
"CutlassSemiSparseLayout",
|
| 17 |
+
]
|
lib/python3.12/site-packages/torchao/dtypes/floatx/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (525 Bytes). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/floatx/__pycache__/cutlass_semi_sparse_layout.cpython-312.pyc
ADDED
|
Binary file (10.5 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/floatx/__pycache__/float8_layout.cpython-312.pyc
ADDED
|
Binary file (17.1 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/floatx/__pycache__/floatx_tensor_core_layout.cpython-312.pyc
ADDED
|
Binary file (22.7 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/floatx/cutlass_semi_sparse_layout.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD 3-Clause license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
from typing import Optional
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
from torch.utils._python_dispatch import (
|
| 11 |
+
return_and_correct_aliasing,
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
from torchao.dtypes.affine_quantized_tensor import (
|
| 15 |
+
AffineQuantizedTensor,
|
| 16 |
+
register_layout,
|
| 17 |
+
)
|
| 18 |
+
from torchao.dtypes.utils import AQTTensorImpl, Layout
|
| 19 |
+
from torchao.ops import (
|
| 20 |
+
rowwise_scaled_linear_sparse_cutlass_f8f8,
|
| 21 |
+
to_sparse_semi_structured_cutlass_sm9x_f8,
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
aten = torch.ops.aten
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def _same_metadata(
|
| 28 |
+
self: "CutlassSemiSparseTensorImpl", src: "CutlassSemiSparseTensorImpl"
|
| 29 |
+
) -> bool:
|
| 30 |
+
return (
|
| 31 |
+
isinstance(self, CutlassSemiSparseTensorImpl)
|
| 32 |
+
and isinstance(src, CutlassSemiSparseTensorImpl)
|
| 33 |
+
and self.shape == src.shape
|
| 34 |
+
and self.sparse.shape == src.sparse.shape
|
| 35 |
+
and self.meta.shape == src.meta.shape
|
| 36 |
+
and self.scale.shape == src.scale.shape
|
| 37 |
+
and type(self._layout) == type(src._layout)
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
@dataclass(frozen=True)
|
| 42 |
+
class CutlassSemiSparseLayout(Layout):
|
| 43 |
+
"""Layout class for float8 2:4 sparsity layout for affine quantized tensor, for cutlass kernel."""
|
| 44 |
+
|
| 45 |
+
def pre_process(self, dense: torch.Tensor) -> torch.Tensor:
|
| 46 |
+
# prune to 2:4 if not already
|
| 47 |
+
from torchao.sparsity.utils import mask_creator
|
| 48 |
+
|
| 49 |
+
return dense * mask_creator(dense).bool()
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
@register_layout(CutlassSemiSparseLayout)
|
| 53 |
+
class CutlassSemiSparseTensorImpl(AQTTensorImpl):
|
| 54 |
+
@staticmethod
|
| 55 |
+
def __new__(
|
| 56 |
+
cls,
|
| 57 |
+
sparse: torch.Tensor,
|
| 58 |
+
meta: torch.Tensor,
|
| 59 |
+
scale: torch.Tensor,
|
| 60 |
+
_layout: Layout,
|
| 61 |
+
):
|
| 62 |
+
kwargs = {}
|
| 63 |
+
kwargs["device"] = sparse.device
|
| 64 |
+
kwargs["layout"] = (
|
| 65 |
+
kwargs.get("layout") if kwargs.get("layout", False) else sparse.layout
|
| 66 |
+
)
|
| 67 |
+
kwargs["dtype"] = sparse.dtype
|
| 68 |
+
kwargs["requires_grad"] = False
|
| 69 |
+
shape = (sparse.shape[0], 2 * sparse.shape[-1])
|
| 70 |
+
return torch.Tensor._make_wrapper_subclass(cls, shape, **kwargs) # type: ignore[attr-defined]
|
| 71 |
+
|
| 72 |
+
def __init__(
|
| 73 |
+
self,
|
| 74 |
+
sparse: torch.Tensor,
|
| 75 |
+
meta: torch.Tensor,
|
| 76 |
+
scale: torch.Tensor,
|
| 77 |
+
_layout: Layout,
|
| 78 |
+
):
|
| 79 |
+
self.sparse = sparse
|
| 80 |
+
self.meta = meta
|
| 81 |
+
self.scale = scale
|
| 82 |
+
self._layout = _layout
|
| 83 |
+
|
| 84 |
+
@classmethod
|
| 85 |
+
def __torch_dispatch__(cls, func, types, args, kwargs):
|
| 86 |
+
kwargs = {} if kwargs is None else kwargs
|
| 87 |
+
|
| 88 |
+
if func is aten.detach.default:
|
| 89 |
+
return return_and_correct_aliasing(
|
| 90 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.detach)
|
| 91 |
+
)
|
| 92 |
+
elif func is aten.copy_.default:
|
| 93 |
+
self = args[0]
|
| 94 |
+
src = args[1]
|
| 95 |
+
if _same_metadata(self, src):
|
| 96 |
+
self_tensors = self.__tensor_flatten__()[0]
|
| 97 |
+
for tensor_name in self_tensors:
|
| 98 |
+
getattr(self, tensor_name).copy_(getattr(src, tensor_name))
|
| 99 |
+
return
|
| 100 |
+
raise ValueError(
|
| 101 |
+
f"Not supported args for copy_ due to metadata mistach: {args[0], args[1]}"
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
raise NotImplementedError(
|
| 105 |
+
f"CutlassSemiSparseTensorImpl dispatch: attempting to run {func}, this is not supported"
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
def __tensor_flatten__(self):
|
| 109 |
+
return ["sparse", "meta", "scale"], [self._layout]
|
| 110 |
+
|
| 111 |
+
@classmethod
|
| 112 |
+
def __tensor_unflatten__(
|
| 113 |
+
cls, tensor_data_dict, tensor_attributes, outer_size, outer_stride
|
| 114 |
+
):
|
| 115 |
+
sparse = tensor_data_dict["sparse"]
|
| 116 |
+
meta = tensor_data_dict["meta"]
|
| 117 |
+
scale = tensor_data_dict["scale"]
|
| 118 |
+
(_layout,) = tensor_attributes
|
| 119 |
+
return cls(sparse, meta, scale, _layout)
|
| 120 |
+
|
| 121 |
+
def get_plain(self):
|
| 122 |
+
# No support in CUTLASS to convert back to dense from sparse
|
| 123 |
+
# semi-structured format, so multiplying with identity matrix,
|
| 124 |
+
# and using identity scale factors, for the conversion.
|
| 125 |
+
cols = self.shape[1]
|
| 126 |
+
input = torch.eye(cols, dtype=self.sparse.dtype, device=self.sparse.device)
|
| 127 |
+
input_scale = torch.ones(
|
| 128 |
+
(cols,), dtype=self.scale.dtype, device=self.sparse.device
|
| 129 |
+
)
|
| 130 |
+
sparse_scale = torch.ones_like(self.scale)
|
| 131 |
+
out_dtype = torch.bfloat16
|
| 132 |
+
dense = (
|
| 133 |
+
rowwise_scaled_linear_sparse_cutlass_f8f8(
|
| 134 |
+
input,
|
| 135 |
+
input_scale,
|
| 136 |
+
self.sparse,
|
| 137 |
+
self.meta,
|
| 138 |
+
sparse_scale,
|
| 139 |
+
out_dtype=out_dtype,
|
| 140 |
+
)
|
| 141 |
+
.to(self.dtype)
|
| 142 |
+
.t()
|
| 143 |
+
.contiguous()
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
return dense, self.scale, None
|
| 147 |
+
|
| 148 |
+
@classmethod
|
| 149 |
+
def from_plain(
|
| 150 |
+
cls,
|
| 151 |
+
dense: torch.Tensor,
|
| 152 |
+
scale: torch.Tensor,
|
| 153 |
+
zero_point: Optional[torch.Tensor],
|
| 154 |
+
_layout: Layout,
|
| 155 |
+
):
|
| 156 |
+
assert zero_point is None or torch.all(zero_point == 0)
|
| 157 |
+
|
| 158 |
+
sparse, meta = to_sparse_semi_structured_cutlass_sm9x_f8(dense)
|
| 159 |
+
|
| 160 |
+
return cls(
|
| 161 |
+
sparse,
|
| 162 |
+
meta,
|
| 163 |
+
scale,
|
| 164 |
+
_layout,
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
def get_layout(self) -> Layout:
|
| 168 |
+
return self._layout
|
| 169 |
+
|
| 170 |
+
def _apply_fn_to_data(self, fn):
|
| 171 |
+
self.sparse = fn(self.sparse)
|
| 172 |
+
self.meta = fn(self.meta)
|
| 173 |
+
self.scale = fn(self.scale)
|
| 174 |
+
return self
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
def _linear_fp8_act_fp8_weight_sparse_cutlass_check(input_tensor, weight_tensor, bias):
|
| 178 |
+
from torchao.dtypes.floatx import Float8Layout
|
| 179 |
+
|
| 180 |
+
return (
|
| 181 |
+
isinstance(input_tensor, AffineQuantizedTensor)
|
| 182 |
+
and isinstance(input_tensor._layout, Float8Layout)
|
| 183 |
+
and input_tensor.dtype in (torch.float16, torch.bfloat16)
|
| 184 |
+
and len(input_tensor.shape) >= 2
|
| 185 |
+
and input_tensor.tensor_impl.scale.dtype == torch.float32
|
| 186 |
+
and len(input_tensor.tensor_impl.scale.shape) == len(input_tensor.shape) - 1
|
| 187 |
+
and isinstance(weight_tensor, AffineQuantizedTensor)
|
| 188 |
+
and isinstance(weight_tensor._layout, CutlassSemiSparseLayout)
|
| 189 |
+
and weight_tensor.dtype == input_tensor.dtype
|
| 190 |
+
and len(weight_tensor.shape) == 2
|
| 191 |
+
and weight_tensor.tensor_impl.scale.dtype == torch.float32
|
| 192 |
+
and len(weight_tensor.tensor_impl.scale.shape) == 1
|
| 193 |
+
and (bias is None or bias.dtype == input_tensor.dtype)
|
| 194 |
+
and (bias is None or len(bias.shape) == 1)
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def _linear_fp8_act_fp8_weight_sparse_cutlass_impl(input_tensor, weight_tensor, bias):
|
| 199 |
+
from torchao.ops import rowwise_scaled_linear_sparse_cutlass_f8f8
|
| 200 |
+
|
| 201 |
+
input = input_tensor.tensor_impl.float8_data
|
| 202 |
+
input_scale = input_tensor.tensor_impl.scale
|
| 203 |
+
weight = weight_tensor.tensor_impl.sparse
|
| 204 |
+
weight_meta = weight_tensor.tensor_impl.meta
|
| 205 |
+
weight_scale = weight_tensor.tensor_impl.scale
|
| 206 |
+
out_dtype = input_tensor.dtype
|
| 207 |
+
|
| 208 |
+
out = rowwise_scaled_linear_sparse_cutlass_f8f8(
|
| 209 |
+
input, input_scale, weight, weight_meta, weight_scale, bias, out_dtype
|
| 210 |
+
)
|
| 211 |
+
|
| 212 |
+
return out
|
lib/python3.12/site-packages/torchao/dtypes/floatx/float8_layout.py
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD 3-Clause license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
from typing import Optional, Tuple, Union
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
from torch.utils._python_dispatch import (
|
| 11 |
+
is_traceable_wrapper_subclass,
|
| 12 |
+
return_and_correct_aliasing,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
from torchao.dtypes.affine_quantized_tensor import (
|
| 16 |
+
AffineQuantizedTensor,
|
| 17 |
+
register_layout,
|
| 18 |
+
)
|
| 19 |
+
from torchao.dtypes.utils import AQTTensorImpl, Layout, get_out_shape
|
| 20 |
+
from torchao.float8.inference import (
|
| 21 |
+
Float8MMConfig,
|
| 22 |
+
_is_rowwise_scaled,
|
| 23 |
+
addmm_float8_unwrapped_inference,
|
| 24 |
+
preprocess_data,
|
| 25 |
+
)
|
| 26 |
+
from torchao.utils import _is_float8_type, fill_defaults
|
| 27 |
+
|
| 28 |
+
aten = torch.ops.aten
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _same_metadata(self: "Float8AQTTensorImpl", src: "Float8AQTTensorImpl") -> bool:
|
| 32 |
+
# Special handling for transposed attribute
|
| 33 |
+
transposed_match = (self.transposed == src.transposed) or (
|
| 34 |
+
self.transposed is False and src.transposed is None
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
return (
|
| 38 |
+
isinstance(self, Float8AQTTensorImpl)
|
| 39 |
+
and isinstance(src, Float8AQTTensorImpl)
|
| 40 |
+
and self.shape == src.shape
|
| 41 |
+
and self.float8_data.shape == src.float8_data.shape
|
| 42 |
+
and self.scale.shape == src.scale.shape
|
| 43 |
+
and transposed_match
|
| 44 |
+
and type(self._layout) == type(src._layout)
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
@dataclass(frozen=True)
|
| 49 |
+
class Float8Layout(Layout):
|
| 50 |
+
"""Represents the layout configuration for Float8 affine quantized tensors.
|
| 51 |
+
|
| 52 |
+
Attributes:
|
| 53 |
+
mm_config (Optional[Float8MMConfig]): Configuration for matrix multiplication operations involving Float8 tensors. If None, default settings are used.
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
mm_config: Optional[Float8MMConfig] = None
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
_fallback_warning_shown = False
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
@register_layout(Float8Layout)
|
| 63 |
+
class Float8AQTTensorImpl(AQTTensorImpl):
|
| 64 |
+
"""
|
| 65 |
+
TensorImpl for float8 layout affine quantized tensor
|
| 66 |
+
|
| 67 |
+
Note: technically we should not create a new layout for float8 we should merge this into
|
| 68 |
+
plain layout
|
| 69 |
+
"""
|
| 70 |
+
|
| 71 |
+
float8_data: torch.Tensor
|
| 72 |
+
scale: torch.Tensor
|
| 73 |
+
transposed: bool
|
| 74 |
+
|
| 75 |
+
def __new__(
|
| 76 |
+
cls,
|
| 77 |
+
float8_data: torch.Tensor,
|
| 78 |
+
scale: torch.Tensor,
|
| 79 |
+
transposed: bool,
|
| 80 |
+
_layout: Layout,
|
| 81 |
+
):
|
| 82 |
+
kwargs = {}
|
| 83 |
+
kwargs["device"] = float8_data.device
|
| 84 |
+
kwargs["layout"] = (
|
| 85 |
+
kwargs.get("layout") if kwargs.get("layout", False) else float8_data.layout
|
| 86 |
+
)
|
| 87 |
+
kwargs["dtype"] = float8_data.dtype
|
| 88 |
+
kwargs["requires_grad"] = False
|
| 89 |
+
shape = float8_data.shape
|
| 90 |
+
return torch.Tensor._make_wrapper_subclass(cls, shape, **kwargs) # type: ignore[attr-defined]
|
| 91 |
+
|
| 92 |
+
def __init__(
|
| 93 |
+
self,
|
| 94 |
+
float8_data: torch.Tensor,
|
| 95 |
+
scale: torch.Tensor,
|
| 96 |
+
transposed: bool,
|
| 97 |
+
_layout: Layout,
|
| 98 |
+
):
|
| 99 |
+
self.float8_data = float8_data
|
| 100 |
+
self.scale = scale
|
| 101 |
+
self.transposed = transposed
|
| 102 |
+
self._layout = _layout
|
| 103 |
+
|
| 104 |
+
def _apply_fn_to_data(self, fn):
|
| 105 |
+
"""Applys a fn to all tensor components stored on this class"""
|
| 106 |
+
global _fallback_warning_shown
|
| 107 |
+
|
| 108 |
+
try:
|
| 109 |
+
return self.__class__(
|
| 110 |
+
fn(self.float8_data),
|
| 111 |
+
fn(self.scale),
|
| 112 |
+
self.transposed,
|
| 113 |
+
self._layout,
|
| 114 |
+
)
|
| 115 |
+
except RuntimeError as e:
|
| 116 |
+
if '"index_cuda" not implemented for ' in str(e):
|
| 117 |
+
if not _fallback_warning_shown:
|
| 118 |
+
import warnings
|
| 119 |
+
|
| 120 |
+
warnings.warn(
|
| 121 |
+
f"When trying to index Float8AQTTensorImpl, got known error {e}, will use slower fallback but "
|
| 122 |
+
+ "note: You can torch.compile the model to avoid this problem.",
|
| 123 |
+
UserWarning,
|
| 124 |
+
)
|
| 125 |
+
_fallback_warning_shown = True
|
| 126 |
+
|
| 127 |
+
return self.__class__( # do indexing in bfloat16 then convert back
|
| 128 |
+
fn(self.float8_data.to(torch.bfloat16)).to(self.float8_data.dtype),
|
| 129 |
+
fn(self.scale),
|
| 130 |
+
self.transposed,
|
| 131 |
+
self._layout,
|
| 132 |
+
)
|
| 133 |
+
else:
|
| 134 |
+
raise e
|
| 135 |
+
|
| 136 |
+
def to(self, *args, **kwargs):
|
| 137 |
+
kwargs = self._get_to_kwargs(*args, **kwargs)
|
| 138 |
+
return self.__class__(
|
| 139 |
+
self.float8_data.to(kwargs["device"]),
|
| 140 |
+
self.scale.to(kwargs["device"]),
|
| 141 |
+
self.transposed,
|
| 142 |
+
self._layout,
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
def __tensor_flatten__(self):
|
| 146 |
+
return ["float8_data", "scale"], [self.transposed, self._layout]
|
| 147 |
+
|
| 148 |
+
@classmethod
|
| 149 |
+
def __tensor_unflatten__(
|
| 150 |
+
cls, tensor_data_dict, tensor_attributes, outer_size, outer_stride
|
| 151 |
+
):
|
| 152 |
+
float8_data, scale = tensor_data_dict["float8_data"], tensor_data_dict["scale"]
|
| 153 |
+
(
|
| 154 |
+
transposed,
|
| 155 |
+
_layout,
|
| 156 |
+
) = tensor_attributes
|
| 157 |
+
return cls(float8_data, scale, transposed, _layout)
|
| 158 |
+
|
| 159 |
+
@classmethod
|
| 160 |
+
def __torch_dispatch__(cls, func, types, args, kwargs):
|
| 161 |
+
kwargs = {} if kwargs is None else kwargs
|
| 162 |
+
|
| 163 |
+
if func is aten.detach.default:
|
| 164 |
+
return return_and_correct_aliasing(
|
| 165 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.detach)
|
| 166 |
+
)
|
| 167 |
+
elif func is aten.clone.default:
|
| 168 |
+
return return_and_correct_aliasing(
|
| 169 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.clone)
|
| 170 |
+
)
|
| 171 |
+
elif func is aten.t.default:
|
| 172 |
+
"""we don't need to repack the weight and just rely on external
|
| 173 |
+
shape being changed and record the status of transpose/no-transpose
|
| 174 |
+
"""
|
| 175 |
+
args[0].transposed = not args[0].transposed
|
| 176 |
+
return return_and_correct_aliasing(func, args, kwargs, args[0])
|
| 177 |
+
elif func is aten.copy_.default:
|
| 178 |
+
self = args[0]
|
| 179 |
+
src = args[1]
|
| 180 |
+
if _same_metadata(self, src):
|
| 181 |
+
self_tensors = self.__tensor_flatten__()[0]
|
| 182 |
+
for tensor_name in self_tensors:
|
| 183 |
+
getattr(self, tensor_name).copy_(getattr(src, tensor_name))
|
| 184 |
+
return
|
| 185 |
+
raise ValueError(
|
| 186 |
+
f"Not supported args for copy_ due to metadata mistach: {args[0], args[1]}"
|
| 187 |
+
)
|
| 188 |
+
elif func in [aten.select.int, aten.index.Tensor]:
|
| 189 |
+
return return_and_correct_aliasing(
|
| 190 |
+
func,
|
| 191 |
+
args,
|
| 192 |
+
kwargs,
|
| 193 |
+
args[0]._apply_fn_to_data(lambda x: func(x, *args[1:], **kwargs)),
|
| 194 |
+
)
|
| 195 |
+
elif func is aten.slice.Tensor:
|
| 196 |
+
self, dim, start, end, step = fill_defaults(args, 5, [0, None, None, 1])
|
| 197 |
+
if dim == 0:
|
| 198 |
+
# TODO: scale replecation should be dependent on block size
|
| 199 |
+
if self.scale.ndim == 1:
|
| 200 |
+
return return_and_correct_aliasing(
|
| 201 |
+
func,
|
| 202 |
+
args,
|
| 203 |
+
kwargs,
|
| 204 |
+
args[0]._apply_fn_to_data(
|
| 205 |
+
lambda x: aten.slice.Tensor(x, dim, start, end, step)
|
| 206 |
+
),
|
| 207 |
+
)
|
| 208 |
+
elif self.scale.ndim == 0:
|
| 209 |
+
return return_and_correct_aliasing(
|
| 210 |
+
func,
|
| 211 |
+
args,
|
| 212 |
+
kwargs,
|
| 213 |
+
Float8AQTTensorImpl(
|
| 214 |
+
aten.slice.Tensor(self.float8_data, dim, start, end, step),
|
| 215 |
+
self.scale,
|
| 216 |
+
None,
|
| 217 |
+
self._layout,
|
| 218 |
+
),
|
| 219 |
+
)
|
| 220 |
+
else:
|
| 221 |
+
raise NotImplementedError(
|
| 222 |
+
f"Float8AQTTensorImpl dispatch: attempting to run {func}, with scale ndim={dim}, that is not supported"
|
| 223 |
+
)
|
| 224 |
+
elif dim == 1:
|
| 225 |
+
return return_and_correct_aliasing(
|
| 226 |
+
func,
|
| 227 |
+
args,
|
| 228 |
+
kwargs,
|
| 229 |
+
Float8AQTTensorImpl(
|
| 230 |
+
aten.slice.Tensor(
|
| 231 |
+
self.float8_data, dim, start, end, step
|
| 232 |
+
).contiguous(),
|
| 233 |
+
self.scale,
|
| 234 |
+
None,
|
| 235 |
+
self._layout,
|
| 236 |
+
),
|
| 237 |
+
)
|
| 238 |
+
else:
|
| 239 |
+
raise NotImplementedError(
|
| 240 |
+
f"Float8AQTTensorImpl dispatch: attempting to run {func}, with dim={dim}, that is not supported"
|
| 241 |
+
)
|
| 242 |
+
else:
|
| 243 |
+
raise NotImplementedError(
|
| 244 |
+
f"Float8AQTTensorImpl dispatch: attempting to run {func}, this is not supported"
|
| 245 |
+
)
|
| 246 |
+
|
| 247 |
+
__torch_function__ = torch._C._disabled_torch_function_impl
|
| 248 |
+
|
| 249 |
+
def get_plain(self) -> Tuple[torch.Tensor, torch.Tensor, Optional[torch.Tensor]]:
|
| 250 |
+
return self.float8_data, self.scale, None
|
| 251 |
+
|
| 252 |
+
def get_layout(self) -> Layout:
|
| 253 |
+
return self._layout
|
| 254 |
+
|
| 255 |
+
@classmethod
|
| 256 |
+
def from_plain(
|
| 257 |
+
cls,
|
| 258 |
+
data: torch.Tensor,
|
| 259 |
+
scale: torch.Tensor,
|
| 260 |
+
zero_point: Optional[torch.Tensor],
|
| 261 |
+
_layout: Layout,
|
| 262 |
+
):
|
| 263 |
+
"""Main entrypoint for constructing Float8TensorImpl"""
|
| 264 |
+
assert _is_float8_type(data.dtype), (
|
| 265 |
+
f"Float8 TensorImpl must be constructed from float8 dtype but got {data.dtype}"
|
| 266 |
+
)
|
| 267 |
+
assert isinstance(_layout, Float8Layout), (
|
| 268 |
+
f"Float8 TensorImpl must be constructed from Float8Layout but got {_layout}"
|
| 269 |
+
)
|
| 270 |
+
return cls(data, scale, False, _layout)
|
| 271 |
+
|
| 272 |
+
def __repr__(self):
|
| 273 |
+
float8_data, scale, _ = self.get_plain()
|
| 274 |
+
_layout = self.get_layout()
|
| 275 |
+
return (
|
| 276 |
+
f"{self.__class__.__name__}(\n"
|
| 277 |
+
f"float8_data={float8_data},\n"
|
| 278 |
+
f"scale={scale},\n"
|
| 279 |
+
f"transposed={self.transposed}, "
|
| 280 |
+
f"_layout={_layout})"
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
##########################
|
| 285 |
+
# Float8 Dispatch Kernels
|
| 286 |
+
##########################
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
def _linear_fp8_act_fp8_weight_check(
|
| 290 |
+
input_tensor: Union[torch.Tensor, "AffineQuantizedTensor"],
|
| 291 |
+
weight_tensor: Union[torch.Tensor, "AffineQuantizedTensor"],
|
| 292 |
+
bias: Optional[torch.Tensor],
|
| 293 |
+
) -> bool:
|
| 294 |
+
def check_aqt(aqt: Union[torch.Tensor, AffineQuantizedTensor]) -> bool:
|
| 295 |
+
return (
|
| 296 |
+
isinstance(aqt, AffineQuantizedTensor)
|
| 297 |
+
and isinstance(aqt._layout, Float8Layout)
|
| 298 |
+
and aqt.tensor_impl.dtype in [torch.float8_e4m3fn, torch.float8_e5m2]
|
| 299 |
+
and (aqt.shape == aqt.block_size or _is_rowwise_scaled(aqt))
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
return check_aqt(input_tensor) and check_aqt(weight_tensor)
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
def preprocess_scale(input_scale: torch.Tensor, input_shape: Tuple[int]):
|
| 306 |
+
"""Ensures input tensor is correctly formated for _scaled_mm"""
|
| 307 |
+
input_scale = input_scale.unsqueeze(-1)
|
| 308 |
+
|
| 309 |
+
if input_scale.dim() > 2:
|
| 310 |
+
input_scale = input_scale.reshape(-1, input_scale.shape[-1])
|
| 311 |
+
|
| 312 |
+
return input_scale
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
def _linear_fp8_act_fp8_weight_impl(
|
| 316 |
+
input_tensor: "AffineQuantizedTensor",
|
| 317 |
+
weight_tensor: "AffineQuantizedTensor",
|
| 318 |
+
bias: Optional[torch.Tensor],
|
| 319 |
+
):
|
| 320 |
+
"""Implements matmul between FP8 input and FP8 weight with compute using _scaled_mm"""
|
| 321 |
+
scaled_mm_config = weight_tensor._layout.mm_config
|
| 322 |
+
assert scaled_mm_config is not None
|
| 323 |
+
out_shape = get_out_shape(input_tensor.shape, weight_tensor.shape)
|
| 324 |
+
|
| 325 |
+
# Weight tensor preprocessing
|
| 326 |
+
w_tensor_impl = weight_tensor.tensor_impl
|
| 327 |
+
assert not w_tensor_impl.transposed, "Weight tensor must be contiguous"
|
| 328 |
+
w_data = w_tensor_impl.float8_data
|
| 329 |
+
w_scale = w_tensor_impl.scale
|
| 330 |
+
|
| 331 |
+
# Input tensor preprocessing
|
| 332 |
+
inpt_data = input_tensor.tensor_impl.float8_data
|
| 333 |
+
input_scale = input_tensor.tensor_impl.scale
|
| 334 |
+
# Handle case where input tensor is more than 2D
|
| 335 |
+
inpt_data = inpt_data.reshape(-1, inpt_data.shape[-1])
|
| 336 |
+
|
| 337 |
+
# Handle rowwise case
|
| 338 |
+
if _is_rowwise_scaled(weight_tensor):
|
| 339 |
+
assert _is_rowwise_scaled(input_tensor), (
|
| 340 |
+
"Input tensor must be rowwise block size"
|
| 341 |
+
)
|
| 342 |
+
w_scale = w_scale.unsqueeze(-1).T
|
| 343 |
+
input_scale = preprocess_scale(input_scale, input_tensor.shape)
|
| 344 |
+
|
| 345 |
+
# Preprocess data
|
| 346 |
+
inpt_data, w_data = preprocess_data(inpt_data, w_data.T, scaled_mm_config)
|
| 347 |
+
|
| 348 |
+
# Perform the computation
|
| 349 |
+
return addmm_float8_unwrapped_inference(
|
| 350 |
+
inpt_data,
|
| 351 |
+
input_scale,
|
| 352 |
+
w_data,
|
| 353 |
+
w_scale,
|
| 354 |
+
output_dtype=input_tensor.dtype,
|
| 355 |
+
bias=bias,
|
| 356 |
+
use_fast_accum=scaled_mm_config.use_fast_accum,
|
| 357 |
+
).reshape(out_shape)
|
| 358 |
+
|
| 359 |
+
|
| 360 |
+
def _linear_fp_act_fp8_weight_check(
|
| 361 |
+
input_tensor: Union[torch.Tensor, "AffineQuantizedTensor"],
|
| 362 |
+
weight_tensor: Union[torch.Tensor, "AffineQuantizedTensor"],
|
| 363 |
+
bias: Optional[torch.Tensor],
|
| 364 |
+
) -> bool:
|
| 365 |
+
return (
|
| 366 |
+
# input is native float tensor
|
| 367 |
+
not is_traceable_wrapper_subclass(input_tensor)
|
| 368 |
+
and input_tensor.is_floating_point()
|
| 369 |
+
and
|
| 370 |
+
# weight is float8 quantized affine quantized tensor
|
| 371 |
+
isinstance(weight_tensor, AffineQuantizedTensor)
|
| 372 |
+
and isinstance(weight_tensor._layout, Float8Layout)
|
| 373 |
+
and weight_tensor.tensor_impl.dtype in [torch.float8_e4m3fn, torch.float8_e5m2]
|
| 374 |
+
and (
|
| 375 |
+
weight_tensor.shape == weight_tensor.block_size
|
| 376 |
+
or _is_rowwise_scaled(weight_tensor)
|
| 377 |
+
)
|
| 378 |
+
)
|
| 379 |
+
|
| 380 |
+
|
| 381 |
+
def _linear_fp_act_fp8_weight_impl(
|
| 382 |
+
input_tensor: torch.Tensor,
|
| 383 |
+
weight_tensor: "AffineQuantizedTensor",
|
| 384 |
+
bias: Optional[torch.Tensor],
|
| 385 |
+
):
|
| 386 |
+
return torch.nn.functional.linear(input_tensor, weight_tensor.dequantize(), bias)
|
lib/python3.12/site-packages/torchao/dtypes/floatx/floatx_tensor_core_layout.py
ADDED
|
@@ -0,0 +1,666 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD 3-Clause license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
from functools import reduce
|
| 8 |
+
from typing import Optional, Tuple
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
from torch import Tensor
|
| 12 |
+
from torch.utils._python_dispatch import (
|
| 13 |
+
is_traceable_wrapper_subclass,
|
| 14 |
+
return_and_correct_aliasing,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
from torchao.dtypes.affine_quantized_tensor import (
|
| 18 |
+
AffineQuantizedTensor,
|
| 19 |
+
register_layout,
|
| 20 |
+
)
|
| 21 |
+
from torchao.dtypes.utils import (
|
| 22 |
+
AQTTensorImpl,
|
| 23 |
+
Layout,
|
| 24 |
+
)
|
| 25 |
+
from torchao.prototype.custom_fp_utils import (
|
| 26 |
+
_f32_to_floatx_unpacked,
|
| 27 |
+
_floatx_unpacked_to_f32,
|
| 28 |
+
_n_ones,
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
aten = torch.ops.aten
|
| 32 |
+
_ONES_TABLE = [_n_ones(i) for i in range(8)]
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def _pack(x: Tensor, n_bits: int) -> Tensor:
|
| 36 |
+
return reduce(
|
| 37 |
+
torch.bitwise_or,
|
| 38 |
+
[
|
| 39 |
+
x[..., i :: (8 // n_bits)] << (8 - (i + 1) * n_bits)
|
| 40 |
+
for i in range(8 // n_bits)
|
| 41 |
+
],
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def _unpack(x: Tensor, n_bits: int) -> Tensor:
|
| 46 |
+
return torch.stack(
|
| 47 |
+
[
|
| 48 |
+
(x >> (8 - (i + 1) * n_bits)) & ((1 << n_bits) - 1)
|
| 49 |
+
for i in range(8 // n_bits)
|
| 50 |
+
],
|
| 51 |
+
dim=-1,
|
| 52 |
+
).flatten(-2)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
# https://github.com/usyd-fsalab/fp6_llm/blob/5df6737cca32f604e957e3f63f03ccc2e4d1df0d/fp6_llm/csrc/utils/weight_prepacking.h#L87-L116
|
| 56 |
+
def _bit_interleave(x: Tensor, n_bits: int, undo: bool = False) -> Tensor:
|
| 57 |
+
# the original code unpacks/packs the values from/to uint32 while we unpack/pack the values from/to uint8
|
| 58 |
+
# thus, we need to reverse byte order within a uint32 word.
|
| 59 |
+
x = x.reshape(-1, 4).flip(1)
|
| 60 |
+
|
| 61 |
+
x = _unpack(x, n_bits)
|
| 62 |
+
x = x.view(-1, 4 * (8 // n_bits))
|
| 63 |
+
|
| 64 |
+
if not undo:
|
| 65 |
+
bit_order = {
|
| 66 |
+
1: [
|
| 67 |
+
1,
|
| 68 |
+
5,
|
| 69 |
+
9,
|
| 70 |
+
13,
|
| 71 |
+
17,
|
| 72 |
+
21,
|
| 73 |
+
25,
|
| 74 |
+
29,
|
| 75 |
+
3,
|
| 76 |
+
7,
|
| 77 |
+
11,
|
| 78 |
+
15,
|
| 79 |
+
19,
|
| 80 |
+
23,
|
| 81 |
+
27,
|
| 82 |
+
31,
|
| 83 |
+
0,
|
| 84 |
+
4,
|
| 85 |
+
8,
|
| 86 |
+
12,
|
| 87 |
+
16,
|
| 88 |
+
20,
|
| 89 |
+
24,
|
| 90 |
+
28,
|
| 91 |
+
2,
|
| 92 |
+
6,
|
| 93 |
+
10,
|
| 94 |
+
14,
|
| 95 |
+
18,
|
| 96 |
+
22,
|
| 97 |
+
26,
|
| 98 |
+
30,
|
| 99 |
+
],
|
| 100 |
+
2: [1, 5, 9, 13, 3, 7, 11, 15, 0, 4, 8, 12, 2, 6, 10, 14],
|
| 101 |
+
4: [1, 5, 3, 7, 0, 4, 2, 6],
|
| 102 |
+
}[n_bits]
|
| 103 |
+
|
| 104 |
+
else:
|
| 105 |
+
# this is inverse of the above, obtained by running
|
| 106 |
+
# [v.index(i) for i in range(len(v))]
|
| 107 |
+
bit_order = {
|
| 108 |
+
1: [
|
| 109 |
+
16,
|
| 110 |
+
0,
|
| 111 |
+
24,
|
| 112 |
+
8,
|
| 113 |
+
17,
|
| 114 |
+
1,
|
| 115 |
+
25,
|
| 116 |
+
9,
|
| 117 |
+
18,
|
| 118 |
+
2,
|
| 119 |
+
26,
|
| 120 |
+
10,
|
| 121 |
+
19,
|
| 122 |
+
3,
|
| 123 |
+
27,
|
| 124 |
+
11,
|
| 125 |
+
20,
|
| 126 |
+
4,
|
| 127 |
+
28,
|
| 128 |
+
12,
|
| 129 |
+
21,
|
| 130 |
+
5,
|
| 131 |
+
29,
|
| 132 |
+
13,
|
| 133 |
+
22,
|
| 134 |
+
6,
|
| 135 |
+
30,
|
| 136 |
+
14,
|
| 137 |
+
23,
|
| 138 |
+
7,
|
| 139 |
+
31,
|
| 140 |
+
15,
|
| 141 |
+
],
|
| 142 |
+
2: [8, 0, 12, 4, 9, 1, 13, 5, 10, 2, 14, 6, 11, 3, 15, 7],
|
| 143 |
+
4: [4, 0, 6, 2, 5, 1, 7, 3],
|
| 144 |
+
}[n_bits]
|
| 145 |
+
|
| 146 |
+
x = x[:, bit_order]
|
| 147 |
+
x = _pack(x, n_bits)
|
| 148 |
+
|
| 149 |
+
# reverse byte order within a uint32 word again.
|
| 150 |
+
x = x.reshape(-1, 4).flip(1)
|
| 151 |
+
return x.flatten()
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
# this is a literal adaptation of FP6-LLM ahead-of-time bit-level pre-packing
|
| 155 |
+
# https://github.com/usyd-fsalab/fp6_llm/blob/5df6737cca32f604e957e3f63f03ccc2e4d1df0d/fp6_llm/csrc/utils/weight_prepacking.h
|
| 156 |
+
def _pack_tc_floatx(tensor: Tensor, nbits: int) -> Tensor:
|
| 157 |
+
assert tensor.ndim == 2, tensor.dtype == torch.uint8
|
| 158 |
+
M, N = tensor.shape
|
| 159 |
+
assert (M % 64 == 0) and (N % 64 == 0)
|
| 160 |
+
|
| 161 |
+
# Pass 1 from original code
|
| 162 |
+
tensor = tensor.view(M // 64, 4, 2, 8, N // 16, 2, 8)
|
| 163 |
+
tensor = tensor.permute(0, 4, 1, 5, 2, 3, 6)
|
| 164 |
+
tensor = tensor.reshape(-1, 32, 2)
|
| 165 |
+
tensor = tensor.permute(1, 0, 2)
|
| 166 |
+
tensor = tensor.flatten()
|
| 167 |
+
|
| 168 |
+
used_bits = 0
|
| 169 |
+
fragments = []
|
| 170 |
+
|
| 171 |
+
for y in [1, 2, 4]:
|
| 172 |
+
if nbits & y:
|
| 173 |
+
mask = (1 << y) - 1
|
| 174 |
+
tensor_ybit = (tensor >> (nbits - used_bits - y)) & mask
|
| 175 |
+
tensor_ybit = _pack(tensor_ybit, y)
|
| 176 |
+
|
| 177 |
+
tensor_ybit = (
|
| 178 |
+
tensor_ybit.view(32, -1, 4).permute(1, 0, 2).flip(2)
|
| 179 |
+
) # Pass 2 from original code
|
| 180 |
+
tensor_ybit = _bit_interleave(
|
| 181 |
+
tensor_ybit.flatten(), y
|
| 182 |
+
) # Pass 3 from original code
|
| 183 |
+
fragments.append(tensor_ybit)
|
| 184 |
+
used_bits += y
|
| 185 |
+
|
| 186 |
+
return torch.cat(fragments, dim=0).view(M, -1)
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
# more optimized version of _pack_tc_floatx() for FP6 by merging ops
|
| 190 |
+
def _pack_tc_fp6(tensor: Tensor) -> Tensor:
|
| 191 |
+
assert tensor.ndim == 2, tensor.dtype == torch.uint8
|
| 192 |
+
M, N = tensor.shape
|
| 193 |
+
assert (M % 64 == 0) and (N % 64 == 0)
|
| 194 |
+
|
| 195 |
+
tensor = tensor.view(M // 64, 2, 2, 2, 8, N // 16, 2, 8)
|
| 196 |
+
tensor = tensor.flip(3)
|
| 197 |
+
|
| 198 |
+
tensor_2bit = (tensor >> 4) & 0b11
|
| 199 |
+
tensor_2bit = tensor_2bit.permute(0, 5, 1, 4, 7, 3, 2, 6)
|
| 200 |
+
tensor_2bit = _pack(tensor_2bit.flatten(), 2)
|
| 201 |
+
|
| 202 |
+
tensor_4bit = tensor & 0b1111
|
| 203 |
+
tensor_4bit = tensor_4bit.permute(0, 5, 1, 2, 4, 7, 3, 6)
|
| 204 |
+
tensor_4bit = _pack(tensor_4bit.flatten(), 4)
|
| 205 |
+
|
| 206 |
+
return torch.cat([tensor_2bit, tensor_4bit], dim=0).view(M, -1)
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
# currently only optimize for TC-FP6 packing
|
| 210 |
+
def pack_tc_floatx(tensor: Tensor, nbits: int) -> Tensor:
|
| 211 |
+
if nbits == 6:
|
| 212 |
+
return _pack_tc_fp6(tensor)
|
| 213 |
+
return _pack_tc_floatx(tensor, nbits)
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def to_scaled_tc_floatx(
|
| 217 |
+
tensor: Tensor, ebits: int, mbits: int
|
| 218 |
+
) -> Tuple[Tensor, Tensor]:
|
| 219 |
+
# _n_ones() is not compatible with torch.compile() due to << operator
|
| 220 |
+
# https://github.com/pytorch/pytorch/issues/119152
|
| 221 |
+
# exp_bias = _n_ones(ebits - 1)
|
| 222 |
+
# max_normal = 2 ** (_n_ones(ebits) - exp_bias) * (_n_ones(mbits + 1) / (2 ** mbits))
|
| 223 |
+
|
| 224 |
+
# workaround: global lookup table
|
| 225 |
+
exp_bias = _ONES_TABLE[ebits - 1]
|
| 226 |
+
max_normal = 2 ** (_ONES_TABLE[ebits] - exp_bias) * (
|
| 227 |
+
_ONES_TABLE[mbits + 1] / (2**mbits)
|
| 228 |
+
)
|
| 229 |
+
|
| 230 |
+
dtype = tensor.dtype
|
| 231 |
+
tensor = tensor.float()
|
| 232 |
+
scale = tensor.abs().amax(1).clamp(min=1e-12) / max_normal
|
| 233 |
+
tensor_floatx = _f32_to_floatx_unpacked(tensor / scale.view(-1, 1), ebits, mbits)
|
| 234 |
+
tensor_tc_floatx = pack_tc_floatx(tensor_floatx, 1 + ebits + mbits)
|
| 235 |
+
return tensor_tc_floatx, scale.to(dtype)
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
# inverse of _pack_tc_floatx()
|
| 239 |
+
def _unpack_tc_floatx(tensor: Tensor, nbits: int) -> Tensor:
|
| 240 |
+
assert tensor.ndim == 2 and tensor.dtype == torch.uint8
|
| 241 |
+
M = tensor.shape[0]
|
| 242 |
+
size = tensor.numel()
|
| 243 |
+
tensor = tensor.flatten()
|
| 244 |
+
offset = 0
|
| 245 |
+
used_bits = 0
|
| 246 |
+
|
| 247 |
+
tensor_floatx = None
|
| 248 |
+
|
| 249 |
+
for y in [1, 2, 4]:
|
| 250 |
+
if nbits & y:
|
| 251 |
+
size_ybit = size // nbits * y
|
| 252 |
+
tensor_ybit = tensor[offset : offset + size_ybit]
|
| 253 |
+
offset += size_ybit
|
| 254 |
+
|
| 255 |
+
tensor_ybit = _bit_interleave(tensor_ybit, y, undo=True) # undo Pass 3
|
| 256 |
+
tensor_ybit = (
|
| 257 |
+
tensor_ybit.view(-1, 32, 4).flip(2).permute(1, 0, 2)
|
| 258 |
+
) # undo Pass 2
|
| 259 |
+
|
| 260 |
+
tensor_ybit = _unpack(tensor_ybit.flatten(), y)
|
| 261 |
+
tensor_ybit = tensor_ybit << (nbits - used_bits - y)
|
| 262 |
+
used_bits += y
|
| 263 |
+
|
| 264 |
+
if tensor_floatx is None:
|
| 265 |
+
tensor_floatx = tensor_ybit
|
| 266 |
+
else:
|
| 267 |
+
tensor_floatx |= tensor_ybit
|
| 268 |
+
|
| 269 |
+
# undo Pass 1
|
| 270 |
+
tensor_floatx = tensor_floatx.view(32, -1, 2).permute(1, 0, 2)
|
| 271 |
+
tensor_floatx = tensor_floatx.reshape(M // 64, -1, 4, 2, 2, 8, 8)
|
| 272 |
+
tensor_floatx = tensor_floatx.permute(0, 2, 4, 5, 1, 3, 6)
|
| 273 |
+
tensor_floatx = tensor_floatx.reshape(M, -1)
|
| 274 |
+
return tensor_floatx
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
# more optimized version of _unpack_tc_floatx() for FP6 by merging ops
|
| 278 |
+
# inverse of _unpack_tc_fp6()
|
| 279 |
+
def _unpack_tc_fp6(tensor: Tensor) -> Tensor:
|
| 280 |
+
assert tensor.ndim == 2 and tensor.dtype == torch.uint8
|
| 281 |
+
M = tensor.shape[0]
|
| 282 |
+
N = tensor.shape[1] // 3 * 4
|
| 283 |
+
assert (M % 64 == 0) and (N % 64 == 0)
|
| 284 |
+
size_2bit = M * N // 4
|
| 285 |
+
size_4bit = M * N // 2
|
| 286 |
+
tensor = tensor.view(-1)
|
| 287 |
+
assert tensor.numel() == size_2bit + size_4bit
|
| 288 |
+
|
| 289 |
+
tensor_2bit, tensor_4bit = tensor.split([size_2bit, size_4bit])
|
| 290 |
+
|
| 291 |
+
tensor_2bit = _unpack(tensor_2bit, 2)
|
| 292 |
+
tensor_2bit = tensor_2bit.view(M // 64, N // 16, 2, 8, 8, 2, 2, 2)
|
| 293 |
+
tensor_2bit = tensor_2bit.permute(0, 2, 6, 5, 3, 1, 7, 4)
|
| 294 |
+
|
| 295 |
+
tensor_4bit = _unpack(tensor_4bit, 4)
|
| 296 |
+
tensor_4bit = tensor_4bit.view(M // 64, N // 16, 2, 2, 8, 8, 2, 2)
|
| 297 |
+
tensor_4bit = tensor_4bit.permute(0, 2, 3, 6, 4, 1, 7, 5)
|
| 298 |
+
|
| 299 |
+
tensor_fp6 = (tensor_2bit << 4) | tensor_4bit
|
| 300 |
+
tensor_fp6 = tensor_fp6.flip(3).reshape(M, N)
|
| 301 |
+
return tensor_fp6
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
def unpack_tc_floatx(tensor: Tensor, nbits: int) -> Tensor:
|
| 305 |
+
if nbits == 6:
|
| 306 |
+
return _unpack_tc_fp6(tensor)
|
| 307 |
+
return _unpack_tc_floatx(tensor, nbits)
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
def from_scaled_tc_floatx(tensor: Tensor, ebits: int, mbits: int, scale=None) -> Tensor:
|
| 311 |
+
floatx_unpacked = unpack_tc_floatx(tensor, 1 + ebits + mbits)
|
| 312 |
+
tensor = _floatx_unpacked_to_f32(floatx_unpacked, ebits, mbits)
|
| 313 |
+
if scale is not None:
|
| 314 |
+
tensor = tensor * scale.float().view(-1, 1)
|
| 315 |
+
return tensor
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
# https://github.com/microsoft/DeepSpeed/blob/3a3a6db3332e339cc9fd94efd4982f6d60635a3d/deepspeed/inference/v2/kernels/core_ops/cuda_linear/cuda_linear.py
|
| 319 |
+
_SPLIT_K_MAP = [
|
| 320 |
+
{ # tokens: [1, 64]
|
| 321 |
+
3072: 18,
|
| 322 |
+
4096: 13,
|
| 323 |
+
5120: 10,
|
| 324 |
+
6144: 9,
|
| 325 |
+
8192: 6,
|
| 326 |
+
10240: 5,
|
| 327 |
+
14336: 7,
|
| 328 |
+
28672: 7,
|
| 329 |
+
57344: 7,
|
| 330 |
+
},
|
| 331 |
+
{ # tokens: [65:128]
|
| 332 |
+
3072: 9,
|
| 333 |
+
4096: 6,
|
| 334 |
+
5120: 5,
|
| 335 |
+
6144: 9,
|
| 336 |
+
8192: 3,
|
| 337 |
+
10240: 5,
|
| 338 |
+
14336: 7,
|
| 339 |
+
28672: 7,
|
| 340 |
+
57344: 6,
|
| 341 |
+
},
|
| 342 |
+
{ # tokens: [129:192]
|
| 343 |
+
3072: 6,
|
| 344 |
+
4096: 4,
|
| 345 |
+
5120: 7,
|
| 346 |
+
6144: 3,
|
| 347 |
+
8192: 2,
|
| 348 |
+
10240: 5,
|
| 349 |
+
14336: 5,
|
| 350 |
+
28672: 5,
|
| 351 |
+
57344: 4,
|
| 352 |
+
},
|
| 353 |
+
{ # tokens: [193:256]
|
| 354 |
+
3072: 9,
|
| 355 |
+
4096: 3,
|
| 356 |
+
5120: 5,
|
| 357 |
+
6144: 2,
|
| 358 |
+
8192: 5,
|
| 359 |
+
10240: 4,
|
| 360 |
+
14336: 8,
|
| 361 |
+
28672: 6,
|
| 362 |
+
57344: 4,
|
| 363 |
+
},
|
| 364 |
+
{ # tokens: [257:320]
|
| 365 |
+
3072: 7,
|
| 366 |
+
4096: 5,
|
| 367 |
+
5120: 2,
|
| 368 |
+
6144: 5,
|
| 369 |
+
8192: 4,
|
| 370 |
+
10240: 1,
|
| 371 |
+
14336: 3,
|
| 372 |
+
28672: 3,
|
| 373 |
+
57344: 4,
|
| 374 |
+
},
|
| 375 |
+
{ # tokens: [321:384]
|
| 376 |
+
3072: 3,
|
| 377 |
+
4096: 2,
|
| 378 |
+
5120: 5,
|
| 379 |
+
6144: 3,
|
| 380 |
+
8192: 1,
|
| 381 |
+
10240: 8,
|
| 382 |
+
14336: 3,
|
| 383 |
+
28672: 4,
|
| 384 |
+
57344: 3,
|
| 385 |
+
},
|
| 386 |
+
{ # tokens: [385:448]
|
| 387 |
+
3072: 5,
|
| 388 |
+
4096: 7,
|
| 389 |
+
5120: 3,
|
| 390 |
+
6144: 5,
|
| 391 |
+
8192: 7,
|
| 392 |
+
10240: 3,
|
| 393 |
+
14336: 1,
|
| 394 |
+
28672: 1,
|
| 395 |
+
57344: 3,
|
| 396 |
+
},
|
| 397 |
+
{ # tokens: [449:512]
|
| 398 |
+
3072: 2,
|
| 399 |
+
4096: 5,
|
| 400 |
+
5120: 4,
|
| 401 |
+
6144: 1,
|
| 402 |
+
8192: 5,
|
| 403 |
+
10240: 2,
|
| 404 |
+
14336: 6,
|
| 405 |
+
28672: 4,
|
| 406 |
+
57344: 1,
|
| 407 |
+
},
|
| 408 |
+
{ # tokens: [513:576]
|
| 409 |
+
3072: 2,
|
| 410 |
+
4096: 3,
|
| 411 |
+
5120: 1,
|
| 412 |
+
6144: 1,
|
| 413 |
+
8192: 3,
|
| 414 |
+
10240: 3,
|
| 415 |
+
14336: 3,
|
| 416 |
+
28672: 1,
|
| 417 |
+
57344: 1,
|
| 418 |
+
},
|
| 419 |
+
{ # tokens: [577:640]
|
| 420 |
+
3072: 5,
|
| 421 |
+
4096: 4,
|
| 422 |
+
5120: 1,
|
| 423 |
+
6144: 4,
|
| 424 |
+
8192: 2,
|
| 425 |
+
10240: 1,
|
| 426 |
+
14336: 1,
|
| 427 |
+
28672: 1,
|
| 428 |
+
57344: 1,
|
| 429 |
+
},
|
| 430 |
+
{ # tokens: [641:704]
|
| 431 |
+
3072: 3,
|
| 432 |
+
4096: 1,
|
| 433 |
+
5120: 2,
|
| 434 |
+
6144: 2,
|
| 435 |
+
8192: 1,
|
| 436 |
+
10240: 2,
|
| 437 |
+
14336: 1,
|
| 438 |
+
28672: 1,
|
| 439 |
+
57344: 1,
|
| 440 |
+
},
|
| 441 |
+
{ # tokens: [705:768]
|
| 442 |
+
3072: 3,
|
| 443 |
+
4096: 1,
|
| 444 |
+
5120: 3,
|
| 445 |
+
6144: 2,
|
| 446 |
+
8192: 1,
|
| 447 |
+
10240: 1,
|
| 448 |
+
14336: 1,
|
| 449 |
+
28672: 1,
|
| 450 |
+
57344: 1,
|
| 451 |
+
},
|
| 452 |
+
]
|
| 453 |
+
|
| 454 |
+
|
| 455 |
+
# quantization api integrations
|
| 456 |
+
@dataclass(frozen=True)
|
| 457 |
+
class FloatxTensorCoreLayout(Layout):
|
| 458 |
+
"""FloatxTensorCoreLayout is a data class that defines the layout for a tensor with a specific number of exponent bits (ebits) and mantissa bits (mbits).
|
| 459 |
+
This layout is used in the context of quantization and packing of tensors optimized for TensorCore operations.
|
| 460 |
+
"""
|
| 461 |
+
|
| 462 |
+
ebits: int
|
| 463 |
+
mbits: int
|
| 464 |
+
|
| 465 |
+
|
| 466 |
+
@register_layout(FloatxTensorCoreLayout)
|
| 467 |
+
class FloatxTensorCoreAQTTensorImpl(AQTTensorImpl):
|
| 468 |
+
"""FloatxTensorCoreAQTTensorImpl represents a Tensor with dtype floatx(ebits=a, mbits=b),
|
| 469 |
+
it has a internal tensor field of "packed_floatx_data", which is packed from the
|
| 470 |
+
uint8 unpacked data (the output of `quantize_affine_floatx` operator)
|
| 471 |
+
|
| 472 |
+
The packing is optimized for TensorCore, from the fp6-llm paper: https://arxiv.org/abs/2401.14112
|
| 473 |
+
github repo: https://github.com/usyd-fsalab/fp6_llm, now renamed to quant-llm
|
| 474 |
+
|
| 475 |
+
At a high level packing is done by grouping bits into 1 bit fragments (shards), 2 bit fragments and
|
| 476 |
+
4 bit fragments each fragments are packed separately and concatenated together.
|
| 477 |
+
For example for 6 bit dtype, we can extract the first 4 bits for all elements and pack them together
|
| 478 |
+
in a fragment, and extract the last 2 bits for all elements and pack them into fragment, in the end
|
| 479 |
+
we concatenate the fragments together.
|
| 480 |
+
|
| 481 |
+
If original Tensor shape is (M, N), and the data is in nbit, the shape of the packed data will be
|
| 482 |
+
(M, N // 8 * nbit)
|
| 483 |
+
|
| 484 |
+
FloatxTensorCoreAQTTensorImpl.from_plain takes an unpacked uint8 floatx Tensor of shape (M, N), with format of
|
| 485 |
+
(zero padding bits + sign bit + exponent bits + mantissa bits), e.g. 00SEEEMM for fp6_e3_m2
|
| 486 |
+
it will then pack the weight and instantiate the FloatxTensorCoreAQTTensorImpl tensor
|
| 487 |
+
FloatxTensorCoreAQTTensorImpl.__init__() takes a packed floatx Tensor of shape (M, N // 8 * nbit)
|
| 488 |
+
"""
|
| 489 |
+
|
| 490 |
+
def __new__(
|
| 491 |
+
cls,
|
| 492 |
+
packed_floatx_data: torch.Tensor,
|
| 493 |
+
scale: torch.Tensor,
|
| 494 |
+
_layout: Layout,
|
| 495 |
+
):
|
| 496 |
+
assert packed_floatx_data.ndim == 2
|
| 497 |
+
assert packed_floatx_data.dtype == torch.uint8
|
| 498 |
+
shape = (
|
| 499 |
+
packed_floatx_data.shape[0],
|
| 500 |
+
packed_floatx_data.shape[1] // (1 + _layout.ebits + _layout.mbits) * 8,
|
| 501 |
+
)
|
| 502 |
+
kwargs = {}
|
| 503 |
+
kwargs["device"] = packed_floatx_data.device
|
| 504 |
+
kwargs["layout"] = (
|
| 505 |
+
kwargs.get("layout")
|
| 506 |
+
if kwargs.get("layout", False)
|
| 507 |
+
else packed_floatx_data.layout
|
| 508 |
+
)
|
| 509 |
+
kwargs["dtype"] = packed_floatx_data.dtype
|
| 510 |
+
kwargs["requires_grad"] = False
|
| 511 |
+
return torch.Tensor._make_wrapper_subclass(cls, shape, **kwargs) # type: ignore[attr-defined]
|
| 512 |
+
|
| 513 |
+
def __init__(
|
| 514 |
+
self,
|
| 515 |
+
packed_floatx_data: torch.Tensor,
|
| 516 |
+
scale: torch.Tensor,
|
| 517 |
+
_layout: Layout,
|
| 518 |
+
):
|
| 519 |
+
self.packed_floatx_data = packed_floatx_data
|
| 520 |
+
self.scale = scale
|
| 521 |
+
self._layout = _layout
|
| 522 |
+
|
| 523 |
+
def __tensor_flatten__(self):
|
| 524 |
+
return ["packed_floatx_data", "scale"], [self._layout]
|
| 525 |
+
|
| 526 |
+
@classmethod
|
| 527 |
+
def __tensor_unflatten__(
|
| 528 |
+
cls, tensor_data_dict, tensor_attributes, outer_size, outer_stride
|
| 529 |
+
):
|
| 530 |
+
packed_floatx_data, scale = (
|
| 531 |
+
tensor_data_dict["packed_floatx_data"],
|
| 532 |
+
tensor_data_dict["scale"],
|
| 533 |
+
)
|
| 534 |
+
(_layout,) = tensor_attributes
|
| 535 |
+
return cls(packed_floatx_data, scale, _layout)
|
| 536 |
+
|
| 537 |
+
def get_plain(self) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 538 |
+
unpacked_floatx_data = unpack_tc_floatx(
|
| 539 |
+
self.packed_floatx_data, 1 + self._layout.ebits + self._layout.mbits
|
| 540 |
+
)
|
| 541 |
+
return unpacked_floatx_data, self.scale
|
| 542 |
+
|
| 543 |
+
@classmethod
|
| 544 |
+
def from_plain(
|
| 545 |
+
cls,
|
| 546 |
+
unpacked_floatx_data: torch.Tensor,
|
| 547 |
+
scale: torch.Tensor,
|
| 548 |
+
zero_point: Optional[torch.Tensor],
|
| 549 |
+
_layout: Layout,
|
| 550 |
+
):
|
| 551 |
+
"""
|
| 552 |
+
Format for `unpacked_floatx_data` will be:
|
| 553 |
+
zero padding bits | sign bit | exponent bits | mantissa bits
|
| 554 |
+
|
| 555 |
+
For example for fp6_e3_m2, the format will be: `00SEEEMM`, where S is sign bit, E is exponent
|
| 556 |
+
bit, M is mantissa bit
|
| 557 |
+
"""
|
| 558 |
+
assert isinstance(_layout, FloatxTensorCoreLayout)
|
| 559 |
+
packed_floatx_data = pack_tc_floatx(
|
| 560 |
+
unpacked_floatx_data, 1 + _layout.ebits + _layout.mbits
|
| 561 |
+
)
|
| 562 |
+
return cls(packed_floatx_data, scale, _layout)
|
| 563 |
+
|
| 564 |
+
def __repr__(self):
|
| 565 |
+
unpacked_floatx_data, scale = self.get_plain()
|
| 566 |
+
_layout = self.get_layout()
|
| 567 |
+
return f"{self.__class__.__name__}(unpacked_floatx_data={unpacked_floatx_data}, scale={scale}, _layout={_layout})"
|
| 568 |
+
|
| 569 |
+
def _apply_fn_to_data(self, fn):
|
| 570 |
+
return self.__class__(
|
| 571 |
+
fn(self.packed_floatx_data),
|
| 572 |
+
fn(self.scale),
|
| 573 |
+
self._layout,
|
| 574 |
+
)
|
| 575 |
+
|
| 576 |
+
def to(self, *args, **kwargs):
|
| 577 |
+
kwargs = self._get_to_kwargs(*args, **kwargs)
|
| 578 |
+
device = kwargs.pop("device")
|
| 579 |
+
return self.__class__(
|
| 580 |
+
self.packed_floatx_data.to(device),
|
| 581 |
+
self.scale.to(device),
|
| 582 |
+
self._layout,
|
| 583 |
+
)
|
| 584 |
+
|
| 585 |
+
@classmethod
|
| 586 |
+
def __torch_dispatch__(cls, func, types, args, kwargs):
|
| 587 |
+
kwargs = {} if kwargs is None else kwargs
|
| 588 |
+
|
| 589 |
+
if func is aten.detach.default:
|
| 590 |
+
return return_and_correct_aliasing(
|
| 591 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.detach)
|
| 592 |
+
)
|
| 593 |
+
elif func is aten.clone.default:
|
| 594 |
+
return return_and_correct_aliasing(
|
| 595 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.clone)
|
| 596 |
+
)
|
| 597 |
+
elif func is aten._to_copy.default:
|
| 598 |
+
return return_and_correct_aliasing(
|
| 599 |
+
func,
|
| 600 |
+
args,
|
| 601 |
+
kwargs,
|
| 602 |
+
args[0]._apply_fn_to_data(
|
| 603 |
+
lambda x: x.to(device=kwargs.pop("device", None))
|
| 604 |
+
),
|
| 605 |
+
)
|
| 606 |
+
|
| 607 |
+
raise NotImplementedError(
|
| 608 |
+
f"FloatxTensorCoreAQTTensorImpl dispatch: attempting to run {func}, this is not supported"
|
| 609 |
+
)
|
| 610 |
+
|
| 611 |
+
__torch_function__ = torch._C._disabled_torch_function_impl
|
| 612 |
+
|
| 613 |
+
def get_layout(self) -> Layout:
|
| 614 |
+
return self._layout
|
| 615 |
+
|
| 616 |
+
|
| 617 |
+
def _linear_f16_bf16_act_floatx_weight_check(input_tensor, weight_tensor, bias):
|
| 618 |
+
from torchao.dtypes.floatx import FloatxTensorCoreLayout
|
| 619 |
+
|
| 620 |
+
return (
|
| 621 |
+
# input is native float32 tensor
|
| 622 |
+
not is_traceable_wrapper_subclass(input_tensor)
|
| 623 |
+
and input_tensor.is_floating_point()
|
| 624 |
+
and input_tensor.dtype in (torch.float16, torch.bfloat16)
|
| 625 |
+
and
|
| 626 |
+
# weight is floatx Tensor
|
| 627 |
+
isinstance(weight_tensor, AffineQuantizedTensor)
|
| 628 |
+
and isinstance(weight_tensor._layout, FloatxTensorCoreLayout)
|
| 629 |
+
and (
|
| 630 |
+
# weight is using fp6 quantization
|
| 631 |
+
(weight_tensor._layout.ebits == 3 and weight_tensor._layout.mbits == 2)
|
| 632 |
+
or (weight_tensor._layout.ebits == 2 and weight_tensor._layout.mbits == 3)
|
| 633 |
+
or
|
| 634 |
+
# weight is using fp5 quantization
|
| 635 |
+
(weight_tensor._layout.ebits == 2 and weight_tensor._layout.mbits == 2)
|
| 636 |
+
or (weight_tensor._layout.ebits == 3 and weight_tensor._layout.mbits == 1)
|
| 637 |
+
)
|
| 638 |
+
)
|
| 639 |
+
|
| 640 |
+
|
| 641 |
+
def _linear_f16_bf16_act_floatx_weight_impl(input_tensor, weight_tensor, bias):
|
| 642 |
+
from torchao.ops import quant_llm_linear
|
| 643 |
+
|
| 644 |
+
act = input_tensor
|
| 645 |
+
weight = weight_tensor
|
| 646 |
+
|
| 647 |
+
out_dim, in_dim = weight.shape
|
| 648 |
+
act_reshaped = act.view(-1, in_dim)
|
| 649 |
+
|
| 650 |
+
# https://github.com/microsoft/DeepSpeed/blob/3a3a6db3332e339cc9fd94efd4982f6d60635a3d/deepspeed/inference/v2/kernels/core_ops/cuda_linear/cuda_linear.py
|
| 651 |
+
bsize = act_reshaped.shape[0]
|
| 652 |
+
splitK = _SPLIT_K_MAP[(bsize - 1) // 64].get(out_dim, 1) if bsize <= 768 else 1
|
| 653 |
+
|
| 654 |
+
out = quant_llm_linear(
|
| 655 |
+
weight._layout.ebits,
|
| 656 |
+
weight._layout.mbits,
|
| 657 |
+
act_reshaped,
|
| 658 |
+
weight.tensor_impl.packed_floatx_data,
|
| 659 |
+
weight.tensor_impl.scale,
|
| 660 |
+
splitK=splitK,
|
| 661 |
+
)
|
| 662 |
+
|
| 663 |
+
if bias is not None:
|
| 664 |
+
out += bias
|
| 665 |
+
|
| 666 |
+
return out.view(*act.shape[:-1], out_dim).to(act.dtype)
|
lib/python3.12/site-packages/torchao/dtypes/nf4tensor.py
ADDED
|
@@ -0,0 +1,1086 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD 3-Clause license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
import functools
|
| 7 |
+
import math
|
| 8 |
+
import sys
|
| 9 |
+
from dataclasses import dataclass, replace
|
| 10 |
+
from enum import Enum, auto
|
| 11 |
+
from typing import Any, Dict, Optional, Tuple, Union
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
import torch.nn.functional as F
|
| 15 |
+
from torch._prims_common import make_contiguous_strides_for
|
| 16 |
+
from torch.distributed.device_mesh import DeviceMesh
|
| 17 |
+
|
| 18 |
+
from torchao.utils import TORCH_VERSION_AT_LEAST_2_5
|
| 19 |
+
|
| 20 |
+
aten = torch.ops.aten
|
| 21 |
+
|
| 22 |
+
c10d_functional = torch.ops.c10d_functional
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
NF4_OPS_TABLE: Dict[Any, Any] = {}
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
_INNER_TENSOR_NAMES_FOR_SHARDING = [
|
| 29 |
+
"quantized_scalers",
|
| 30 |
+
"quantization_factor",
|
| 31 |
+
"quantized_data",
|
| 32 |
+
]
|
| 33 |
+
|
| 34 |
+
# Note: Quantize in Chunks
|
| 35 |
+
# During quantization to NF4, one of the steps to convert from the original float number
|
| 36 |
+
# to the index of the nearest value in the NF4 format. This can cause a large memory spike
|
| 37 |
+
# Due to intermediates of the quantization process. Instead we process the original
|
| 38 |
+
# tensor in chunks. This is a tradeoff between memory and speed. This number seems to
|
| 39 |
+
# strike a good balance between memory and speed
|
| 40 |
+
CHUNK_SIZE = 1024**2
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def same_metadata(a: "NF4Tensor", b: "NF4Tensor"):
|
| 44 |
+
both_nf4 = isinstance(a, NF4Tensor) and isinstance(b, NF4Tensor)
|
| 45 |
+
return (
|
| 46 |
+
both_nf4
|
| 47 |
+
and a.block_size == b.block_size
|
| 48 |
+
and a.scaler_block_size == b.scaler_block_size
|
| 49 |
+
and a.n_blocks == b.n_blocks
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def implements(aten_ops):
|
| 54 |
+
"""Use this decorator to implement a function for an aten op in __torch_dispatch__"""
|
| 55 |
+
|
| 56 |
+
def decorator(func):
|
| 57 |
+
for op in aten_ops:
|
| 58 |
+
NF4_OPS_TABLE[op] = func
|
| 59 |
+
return func
|
| 60 |
+
|
| 61 |
+
return decorator
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def construct_nf4_args(nf4tensor: "NF4Tensor", kwargs: Optional[Dict[str, Any]] = None):
|
| 65 |
+
if kwargs is None:
|
| 66 |
+
kwargs = {}
|
| 67 |
+
tensor_meta = SubclassTensorArgs(
|
| 68 |
+
kwargs.get("size", nf4tensor.size()),
|
| 69 |
+
kwargs.get("stride", nf4tensor.stride()),
|
| 70 |
+
kwargs.get("storage_offset", nf4tensor.storage_offset()),
|
| 71 |
+
kwargs.get("dtype", nf4tensor.dtype),
|
| 72 |
+
kwargs.get("device", nf4tensor.device),
|
| 73 |
+
kwargs.get("requires_grad", nf4tensor.requires_grad),
|
| 74 |
+
)
|
| 75 |
+
return (
|
| 76 |
+
tensor_meta,
|
| 77 |
+
kwargs.get("block_size", nf4tensor.block_size),
|
| 78 |
+
kwargs.get("n_blocks", nf4tensor.n_blocks),
|
| 79 |
+
kwargs.get("scaler_block_size", nf4tensor.scaler_block_size),
|
| 80 |
+
kwargs.get("quantized_scalers", nf4tensor.quantized_scalers),
|
| 81 |
+
kwargs.get("quantization_factor", nf4tensor.quantization_factor),
|
| 82 |
+
kwargs.get("scaler_mean", nf4tensor.scaler_mean),
|
| 83 |
+
kwargs.get("quantized_data", nf4tensor.quantized_data),
|
| 84 |
+
kwargs.get("nf4", nf4tensor.nf4),
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
# __torch_dispatch__ utils: apply aten op to inner tensors
|
| 89 |
+
def apply_to_inner_tensors(nf4tensor: "NF4Tensor", aten_op, args, kwargs):
|
| 90 |
+
attr_to_tensor = {}
|
| 91 |
+
for attr in _INNER_TENSOR_NAMES_FOR_SHARDING:
|
| 92 |
+
attr_to_tensor[attr] = aten_op(getattr(nf4tensor, attr), *args, **kwargs)
|
| 93 |
+
return attr_to_tensor
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
# __torch_function__ utils: call tensor ops from inner tensors
|
| 97 |
+
def call_from_inner_tensors(nf4tensor: "NF4Tensor", method_name: str, args, kwargs):
|
| 98 |
+
attr_to_tensor = {}
|
| 99 |
+
for attr in _INNER_TENSOR_NAMES_FOR_SHARDING:
|
| 100 |
+
inner_tensor = getattr(nf4tensor, attr)
|
| 101 |
+
func = getattr(inner_tensor, method_name)
|
| 102 |
+
attr_to_tensor[attr] = func(*args, **kwargs)
|
| 103 |
+
return attr_to_tensor
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
class CompareOp(Enum):
|
| 107 |
+
EQ = auto()
|
| 108 |
+
LT = auto()
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def expect_num_of_args(op: CompareOp, num: int, msg: str):
|
| 112 |
+
def decorator(func):
|
| 113 |
+
@functools.wraps(func)
|
| 114 |
+
def wrapper(aten_op, args, kwargs=None):
|
| 115 |
+
if op == CompareOp.LT and not (len(args) < num):
|
| 116 |
+
raise NotImplementedError(msg)
|
| 117 |
+
return func(aten_op, args, kwargs)
|
| 118 |
+
|
| 119 |
+
return wrapper
|
| 120 |
+
|
| 121 |
+
return decorator
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def expect_arg_value_at_k(k: int, op: CompareOp, value: Any, msg: str):
|
| 125 |
+
def decorator(func):
|
| 126 |
+
@functools.wraps(func)
|
| 127 |
+
def wrapper(aten_op, args, kwargs=None):
|
| 128 |
+
if op == CompareOp.EQ and not (args[k] == value):
|
| 129 |
+
raise NotImplementedError(msg + str(args[k]))
|
| 130 |
+
return func(aten_op, args, kwargs)
|
| 131 |
+
|
| 132 |
+
return wrapper
|
| 133 |
+
|
| 134 |
+
return decorator
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def expect_args_len_at_k(k: int, op: CompareOp, value: Any, msg: str):
|
| 138 |
+
def decorator(func):
|
| 139 |
+
@functools.wraps(func)
|
| 140 |
+
def wrapper(aten_op, args, kwargs=None):
|
| 141 |
+
if op == CompareOp.LT and not (len(args[k]) < value):
|
| 142 |
+
raise NotImplementedError(msg + str(len(args[k])))
|
| 143 |
+
elif op == CompareOp.EQ and not (len(args[k]) == value):
|
| 144 |
+
raise NotImplementedError(msg + str(len(args[k])))
|
| 145 |
+
return func(aten_op, args, kwargs)
|
| 146 |
+
|
| 147 |
+
return wrapper
|
| 148 |
+
|
| 149 |
+
return decorator
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
@implements([torch.ops.aten.detach])
|
| 153 |
+
def noop_detach(func, *args, **kwargs):
|
| 154 |
+
return args[0][0]
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
@implements([torch.ops.aten.clone.default])
|
| 158 |
+
def clone(func, *args, **kwargs):
|
| 159 |
+
return to_nf4(args[0][0].get_original_weight())
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
@implements(
|
| 163 |
+
[
|
| 164 |
+
aten.detach.default,
|
| 165 |
+
]
|
| 166 |
+
)
|
| 167 |
+
def nf4_detach(aten_op, args, kwargs=None):
|
| 168 |
+
nf4tensor = args[0]
|
| 169 |
+
updated_attrs = apply_to_inner_tensors(nf4tensor, aten_op, args[1:], kwargs)
|
| 170 |
+
return NF4Tensor(*construct_nf4_args(nf4tensor, updated_attrs))
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
@implements(
|
| 174 |
+
[
|
| 175 |
+
aten.empty_like.default,
|
| 176 |
+
]
|
| 177 |
+
)
|
| 178 |
+
def nf4_empty_like(aten_op, args, kwargs=None):
|
| 179 |
+
nf4tensor = args[0]
|
| 180 |
+
updated_attrs = apply_to_inner_tensors(nf4tensor, aten_op, args[1:], kwargs)
|
| 181 |
+
if kwargs is not None and len(kwargs):
|
| 182 |
+
for key, value in kwargs.items():
|
| 183 |
+
updated_attrs[key] = value
|
| 184 |
+
return NF4Tensor(*construct_nf4_args(nf4tensor, updated_attrs))
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
@implements(
|
| 188 |
+
[
|
| 189 |
+
aten.split.Tensor,
|
| 190 |
+
]
|
| 191 |
+
)
|
| 192 |
+
def nf4_split(aten_op, args, kwargs=None):
|
| 193 |
+
if len(args) == 3 and args[2] != 0:
|
| 194 |
+
raise NotImplementedError(f"aten.split(NF4Tensor, dim={args[2]})")
|
| 195 |
+
nf4tensor = args[0]
|
| 196 |
+
num_chunks = nf4tensor.size(0) // args[1]
|
| 197 |
+
|
| 198 |
+
attr_to_chunks = {}
|
| 199 |
+
for attr in _INNER_TENSOR_NAMES_FOR_SHARDING:
|
| 200 |
+
inner_tensor = getattr(nf4tensor, attr)
|
| 201 |
+
assert inner_tensor.numel() % num_chunks == 0, (
|
| 202 |
+
f"{attr}.numel() not divisible by {num_chunks}"
|
| 203 |
+
)
|
| 204 |
+
chunks = aten_op(inner_tensor, inner_tensor.numel() // num_chunks, **kwargs)
|
| 205 |
+
attr_to_chunks[attr] = chunks
|
| 206 |
+
|
| 207 |
+
orig_dim = nf4tensor.dim()
|
| 208 |
+
if orig_dim == 1:
|
| 209 |
+
chunked_size = (nf4tensor.size(0) // num_chunks,)
|
| 210 |
+
elif orig_dim == 2:
|
| 211 |
+
chunked_size = (nf4tensor.size(0) // num_chunks, nf4tensor.size(1))
|
| 212 |
+
else:
|
| 213 |
+
chunked_size = ()
|
| 214 |
+
raise NotImplementedError(
|
| 215 |
+
f"aten.split(NF4Tensor) wherer NF4Tensor.dim() = {orig_dim}"
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
nf4_chunks = []
|
| 219 |
+
for idx in range(num_chunks):
|
| 220 |
+
updated_attrs = {"size": chunked_size}
|
| 221 |
+
for attr, chunks in attr_to_chunks.items():
|
| 222 |
+
updated_attrs[attr] = chunks[idx]
|
| 223 |
+
nf4_chunks.append(NF4Tensor(*construct_nf4_args(nf4tensor, updated_attrs)))
|
| 224 |
+
return nf4_chunks
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
@implements(
|
| 228 |
+
[
|
| 229 |
+
aten.new_zeros.default,
|
| 230 |
+
]
|
| 231 |
+
)
|
| 232 |
+
@expect_args_len_at_k(1, CompareOp.LT, 3, "aten.view(NF4Tensor) with len(size)=")
|
| 233 |
+
def nf4_new_zeros(aten_op, args, kwargs=None):
|
| 234 |
+
nf4tensor = args[0]
|
| 235 |
+
new_size = tuple(args[1])
|
| 236 |
+
|
| 237 |
+
if nf4tensor.numel() % math.prod(new_size) != 0:
|
| 238 |
+
raise NotImplementedError(f"aten.new_zeros(NF4Tensor) with new size {new_size}")
|
| 239 |
+
ratio = nf4tensor.numel() // math.prod(new_size)
|
| 240 |
+
|
| 241 |
+
updated_attrs = {}
|
| 242 |
+
for attr in _INNER_TENSOR_NAMES_FOR_SHARDING:
|
| 243 |
+
inner_tensor = getattr(nf4tensor, attr)
|
| 244 |
+
assert inner_tensor.size(0) % ratio == 0, (
|
| 245 |
+
f"{attr}.numel() must be divisible by {ratio}"
|
| 246 |
+
)
|
| 247 |
+
inner_tensor = aten_op(inner_tensor, [inner_tensor.size(0) // ratio], **kwargs)
|
| 248 |
+
updated_attrs[attr] = inner_tensor
|
| 249 |
+
updated_attrs["size"] = new_size
|
| 250 |
+
|
| 251 |
+
return NF4Tensor(*construct_nf4_args(nf4tensor, updated_attrs))
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
@implements(
|
| 255 |
+
[
|
| 256 |
+
aten.slice.Tensor,
|
| 257 |
+
]
|
| 258 |
+
)
|
| 259 |
+
@expect_num_of_args(CompareOp.LT, 5, "aten.slice(NF4Tensor) with customized step")
|
| 260 |
+
@expect_arg_value_at_k(1, CompareOp.EQ, 0, "aten.slice(NF4Tensor) with dim=")
|
| 261 |
+
@expect_arg_value_at_k(2, CompareOp.EQ, 0, "aten.slice(NF4Tensor) with start=")
|
| 262 |
+
def nf4_slice(aten_op, args, kwargs=None):
|
| 263 |
+
nf4tensor = args[0]
|
| 264 |
+
# for tensor 512 x 512, tensor[:, :512] dispatch to
|
| 265 |
+
# aten.slice(dim = 0, end=sys.maxsize)
|
| 266 |
+
if args[3] not in [nf4tensor.size(0), sys.maxsize]:
|
| 267 |
+
raise NotImplementedError(f"aten.slice(NF4Tensor) with end={args[3]}")
|
| 268 |
+
return NF4Tensor(*construct_nf4_args(nf4tensor))
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
@implements(
|
| 272 |
+
[
|
| 273 |
+
aten.view.default,
|
| 274 |
+
]
|
| 275 |
+
)
|
| 276 |
+
@expect_args_len_at_k(1, CompareOp.EQ, 1, "aten.view(NF4Tensor) with len(size)=")
|
| 277 |
+
def nf4_view(aten_op, args, kwargs=None):
|
| 278 |
+
nf4tensor = args[0]
|
| 279 |
+
size = args[1]
|
| 280 |
+
if size[0] != -1:
|
| 281 |
+
raise NotImplementedError(f"aten.view(NF4Tensor) with size={size}")
|
| 282 |
+
updated_attrs = apply_to_inner_tensors(nf4tensor, aten_op, args[1:], kwargs)
|
| 283 |
+
updated_attrs.update(
|
| 284 |
+
{
|
| 285 |
+
"size": [nf4tensor.numel()],
|
| 286 |
+
"stride": (1,),
|
| 287 |
+
}
|
| 288 |
+
)
|
| 289 |
+
return NF4Tensor(*construct_nf4_args(nf4tensor, updated_attrs))
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
@implements(
|
| 293 |
+
[
|
| 294 |
+
aten.as_strided.default,
|
| 295 |
+
]
|
| 296 |
+
)
|
| 297 |
+
@expect_args_len_at_k(
|
| 298 |
+
1, CompareOp.LT, 3, "aten.as_strided(NF4Tensor) only support dim <= 2 but got dim="
|
| 299 |
+
)
|
| 300 |
+
def nf4_as_strided(aten_op, args, kwargs=None):
|
| 301 |
+
nf4tensor = args[0]
|
| 302 |
+
size = args[1]
|
| 303 |
+
stride = tuple(args[2])
|
| 304 |
+
storage_offset = args[3]
|
| 305 |
+
if math.prod(size) != nf4tensor.numel():
|
| 306 |
+
raise NotImplementedError(
|
| 307 |
+
f"aten.as_strided(NF4Tensor) different numel={nf4tensor.numel()} and size={size}"
|
| 308 |
+
)
|
| 309 |
+
if stride != make_contiguous_strides_for(size):
|
| 310 |
+
raise NotImplementedError(
|
| 311 |
+
f"aten.as_strided(NF4Tensor) only support continuous stride={make_contiguous_strides_for(size)} but got stride={stride}"
|
| 312 |
+
)
|
| 313 |
+
if nf4tensor.storage_offset() != storage_offset:
|
| 314 |
+
raise NotImplementedError(
|
| 315 |
+
f"aten.as_strided(NF4Tensor) only support original storage offset {nf4tensor.storage_offset()} but got {storage_offset}"
|
| 316 |
+
)
|
| 317 |
+
kwargs = {
|
| 318 |
+
"size": torch.Size(size),
|
| 319 |
+
"stride": stride,
|
| 320 |
+
"storage_offset": storage_offset,
|
| 321 |
+
}
|
| 322 |
+
return NF4Tensor(*construct_nf4_args(nf4tensor, kwargs))
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
@implements([torch.ops.aten._to_copy.default])
|
| 326 |
+
def _to_copy(func, *args, **kwargs):
|
| 327 |
+
if not args[0][0].is_contiguous():
|
| 328 |
+
assert args[0][0].t().is_contiguous()
|
| 329 |
+
return func(args[0][0].t()).t()
|
| 330 |
+
out = args[0][0].get_original_weight().to(args[1]["dtype"])
|
| 331 |
+
if "device" in args[1]:
|
| 332 |
+
out = out.to(args[1]["device"])
|
| 333 |
+
return out
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
@implements([torch.ops.aten.to.dtype])
|
| 337 |
+
def to_dtype(func, *args, **kwargs):
|
| 338 |
+
if not args[0][0].is_contiguous():
|
| 339 |
+
assert args[0][0].t().is_contiguous()
|
| 340 |
+
return torch.ops.aten.to.dtype(args[0][0].t(), args[0][1]).t()
|
| 341 |
+
return args[0][0].get_original_weight().to(args[0][1])
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
@implements([torch.ops.aten.t.default])
|
| 345 |
+
def t_default(func, *args, **kwargs):
|
| 346 |
+
a = args[0][0]
|
| 347 |
+
tensor_meta = SubclassTensorArgs(
|
| 348 |
+
a.size(),
|
| 349 |
+
(a.stride(1), a.stride(0)),
|
| 350 |
+
a.storage_offset(),
|
| 351 |
+
a.dtype,
|
| 352 |
+
a.device,
|
| 353 |
+
a.requires_grad,
|
| 354 |
+
)
|
| 355 |
+
b = NF4Tensor(
|
| 356 |
+
tensor_meta,
|
| 357 |
+
a.block_size,
|
| 358 |
+
a.n_blocks,
|
| 359 |
+
a.scaler_block_size,
|
| 360 |
+
a.quantized_scalers,
|
| 361 |
+
a.quantization_factor,
|
| 362 |
+
a.scaler_mean,
|
| 363 |
+
a.quantized_data,
|
| 364 |
+
a.nf4,
|
| 365 |
+
)
|
| 366 |
+
return b
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
@implements([torch.ops.aten.mm.default])
|
| 370 |
+
def mm_default(func, *args, **kwargs):
|
| 371 |
+
return linear_nf4(args[0][0], args[0][1])
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
@implements(
|
| 375 |
+
[
|
| 376 |
+
aten.copy_.default,
|
| 377 |
+
]
|
| 378 |
+
)
|
| 379 |
+
def copy_(func, *args, **kwargs):
|
| 380 |
+
original: NF4Tensor = args[0][0]
|
| 381 |
+
copy_in: torch.Tensor = args[0][1]
|
| 382 |
+
|
| 383 |
+
# Base Case
|
| 384 |
+
|
| 385 |
+
if same_metadata(original, copy_in):
|
| 386 |
+
original_tensors = original.__tensor_flatten__()[0]
|
| 387 |
+
for tensor_name in original_tensors:
|
| 388 |
+
getattr(original, tensor_name).copy_(getattr(copy_in, tensor_name))
|
| 389 |
+
return
|
| 390 |
+
|
| 391 |
+
# Convert Non NF4Tensor into NF4 for copy in
|
| 392 |
+
if not isinstance(copy_in, NF4Tensor):
|
| 393 |
+
copy_in_nf4 = NF4Tensor.from_tensor(
|
| 394 |
+
copy_in.to(original.device), original.block_size, original.scaler_block_size
|
| 395 |
+
)
|
| 396 |
+
return original.copy_(copy_in_nf4)
|
| 397 |
+
|
| 398 |
+
# Other Tensor is not a NF4Tensor
|
| 399 |
+
full_precision = copy_in.get_original_weight()
|
| 400 |
+
same_meta_nf4 = NF4Tensor.from_tensor(
|
| 401 |
+
full_precision, original.block_size, original.scaler_block_size
|
| 402 |
+
)
|
| 403 |
+
return original.copy_(same_meta_nf4)
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
@implements(
|
| 407 |
+
[
|
| 408 |
+
aten.is_pinned.default,
|
| 409 |
+
]
|
| 410 |
+
)
|
| 411 |
+
def nf4_is_pinned(aten_op, args, kwargs=None):
|
| 412 |
+
nf4tensor = args[0]
|
| 413 |
+
for attr in _INNER_TENSOR_NAMES_FOR_SHARDING:
|
| 414 |
+
inner_tensor = getattr(nf4tensor, attr)
|
| 415 |
+
if not aten_op(inner_tensor, *(args[1:]), **kwargs):
|
| 416 |
+
return False
|
| 417 |
+
return True
|
| 418 |
+
|
| 419 |
+
|
| 420 |
+
@implements(
|
| 421 |
+
[
|
| 422 |
+
aten._pin_memory.default,
|
| 423 |
+
]
|
| 424 |
+
)
|
| 425 |
+
def nf4_pin_memory(aten_op, args, kwargs=None):
|
| 426 |
+
nf4tensor = args[0]
|
| 427 |
+
updated_attrs = apply_to_inner_tensors(nf4tensor, aten_op, args[1:], kwargs)
|
| 428 |
+
return NF4Tensor(*construct_nf4_args(nf4tensor, updated_attrs))
|
| 429 |
+
|
| 430 |
+
|
| 431 |
+
@implements(
|
| 432 |
+
[
|
| 433 |
+
aten.cat.default,
|
| 434 |
+
]
|
| 435 |
+
)
|
| 436 |
+
def nf4_cat(aten_op: torch._ops.OpOverload, args, kwargs=None):
|
| 437 |
+
tensors_to_cat = args[0]
|
| 438 |
+
assert all(isinstance(t, torch.Tensor) for t in tensors_to_cat)
|
| 439 |
+
remaining_args = args[1:]
|
| 440 |
+
|
| 441 |
+
ts = []
|
| 442 |
+
for t in tensors_to_cat:
|
| 443 |
+
assert isinstance(t, torch.Tensor)
|
| 444 |
+
|
| 445 |
+
if isinstance(t, NF4Tensor):
|
| 446 |
+
ts.append(t.get_original_weight())
|
| 447 |
+
else:
|
| 448 |
+
ts.append(t)
|
| 449 |
+
|
| 450 |
+
dtype = ts[0].dtype
|
| 451 |
+
assert all(t.dtype == dtype for t in ts)
|
| 452 |
+
|
| 453 |
+
if kwargs is None:
|
| 454 |
+
kwargs = {}
|
| 455 |
+
|
| 456 |
+
tensors = aten_op(ts, *remaining_args, **kwargs)
|
| 457 |
+
return tensors
|
| 458 |
+
|
| 459 |
+
|
| 460 |
+
@dataclass(frozen=True)
|
| 461 |
+
class SubclassTensorArgs:
|
| 462 |
+
original_shape: torch.Size
|
| 463 |
+
|
| 464 |
+
original_strides: Tuple
|
| 465 |
+
storage_offset: int
|
| 466 |
+
dtype: torch.dtype
|
| 467 |
+
device: torch.device
|
| 468 |
+
requires_grad: bool
|
| 469 |
+
|
| 470 |
+
|
| 471 |
+
def get_block_absmax(input_tensor: torch.Tensor, block_size: int) -> torch.Tensor:
|
| 472 |
+
"""Iterate through a flattened tensor getting the absmax scalers for each block
|
| 473 |
+
|
| 474 |
+
Args:
|
| 475 |
+
input_tensor: Input tensor to get scalers for
|
| 476 |
+
block_size: Block size for the scanning window
|
| 477 |
+
Returns:
|
| 478 |
+
torch.Tensor: Tensor of scalers for each block
|
| 479 |
+
"""
|
| 480 |
+
assert input_tensor.dim() == 1, "Input tensor must be flattened"
|
| 481 |
+
assert (input_tensor.numel() % block_size) == 0, (
|
| 482 |
+
f"Input tensor must be divisible by block size, got {input_tensor.numel()} and {block_size}"
|
| 483 |
+
)
|
| 484 |
+
|
| 485 |
+
n_blocks = input_tensor.numel() // block_size
|
| 486 |
+
blocks = input_tensor.view(n_blocks, block_size)
|
| 487 |
+
block_scalers = blocks.abs().max(dim=1).values
|
| 488 |
+
return block_scalers
|
| 489 |
+
|
| 490 |
+
|
| 491 |
+
class NF4Tensor(torch.Tensor):
|
| 492 |
+
"""NF4Tensor class for converting a weight to the QLoRA NF4 format"""
|
| 493 |
+
|
| 494 |
+
@torch._dynamo.disable
|
| 495 |
+
def __new__(
|
| 496 |
+
cls,
|
| 497 |
+
# Args related for base tensor construction
|
| 498 |
+
tensor_meta: SubclassTensorArgs,
|
| 499 |
+
# Args stored on the instance
|
| 500 |
+
block_size: int,
|
| 501 |
+
n_blocks: int,
|
| 502 |
+
scaler_block_size: int,
|
| 503 |
+
quantized_scalers: torch.Tensor,
|
| 504 |
+
quantization_factor: torch.Tensor,
|
| 505 |
+
scaler_mean: torch.Tensor,
|
| 506 |
+
quantized_data: torch.Tensor,
|
| 507 |
+
nf4: torch.Tensor,
|
| 508 |
+
):
|
| 509 |
+
"""Create a new NF4Tensor object
|
| 510 |
+
Args:
|
| 511 |
+
tensor_meta: Metadata for the tensor
|
| 512 |
+
block_size: Size of the quantization block
|
| 513 |
+
n_blocks: Number of blocks to cover the full tensor
|
| 514 |
+
scaler_block_size: Block size for the scalar quantization
|
| 515 |
+
quantized_scalers: Quantized scalers data' represented a uint8 tensor
|
| 516 |
+
quantization_factor: Quantization factor, single scalar represented as torch.Tensor
|
| 517 |
+
scaler_mean: Mean of the scalers
|
| 518 |
+
quantized_data: Quantized data represented as uint8 tensor
|
| 519 |
+
nf4: NF4 tensor LUT for the quantization and dequantization
|
| 520 |
+
|
| 521 |
+
"""
|
| 522 |
+
|
| 523 |
+
nf4tensor = torch.Tensor._make_wrapper_subclass(
|
| 524 |
+
cls,
|
| 525 |
+
tensor_meta.original_shape,
|
| 526 |
+
tensor_meta.original_strides,
|
| 527 |
+
tensor_meta.storage_offset,
|
| 528 |
+
# Picked some floating dtype, but we need dtype extensibility
|
| 529 |
+
dtype=tensor_meta.dtype,
|
| 530 |
+
device=tensor_meta.device,
|
| 531 |
+
requires_grad=tensor_meta.requires_grad,
|
| 532 |
+
)
|
| 533 |
+
return nf4tensor
|
| 534 |
+
|
| 535 |
+
@torch._dynamo.disable
|
| 536 |
+
def __init__(
|
| 537 |
+
self,
|
| 538 |
+
tensor_meta: SubclassTensorArgs,
|
| 539 |
+
block_size: int,
|
| 540 |
+
n_blocks: int,
|
| 541 |
+
scaler_block_size: int,
|
| 542 |
+
quantized_scalers: torch.Tensor,
|
| 543 |
+
quantization_factor: torch.Tensor,
|
| 544 |
+
scaler_mean: torch.Tensor,
|
| 545 |
+
quantized_data: torch.Tensor,
|
| 546 |
+
nf4: torch.Tensor,
|
| 547 |
+
):
|
| 548 |
+
"""Initialize the NF4Tensor class"""
|
| 549 |
+
self.block_size = block_size
|
| 550 |
+
self.n_blocks = n_blocks
|
| 551 |
+
self.scaler_block_size = scaler_block_size
|
| 552 |
+
self.quantized_scalers = quantized_scalers
|
| 553 |
+
self.quantization_factor = quantization_factor
|
| 554 |
+
self.scaler_mean = scaler_mean
|
| 555 |
+
self.quantized_data = quantized_data
|
| 556 |
+
self.nf4 = nf4
|
| 557 |
+
|
| 558 |
+
@classmethod
|
| 559 |
+
@torch.no_grad()
|
| 560 |
+
def from_tensor(
|
| 561 |
+
cls,
|
| 562 |
+
input_tensor: torch.Tensor,
|
| 563 |
+
block_size: int,
|
| 564 |
+
scaler_block_size: int,
|
| 565 |
+
):
|
| 566 |
+
assert input_tensor.dim() <= 2, (
|
| 567 |
+
f"expect input tensor dim <= 2 but got dim = {input_tensor.dim()}"
|
| 568 |
+
)
|
| 569 |
+
assert input_tensor.numel() % block_size == 0, (
|
| 570 |
+
f"Input tensor must be divisible by block size, got {input_tensor.numel()} and {block_size}"
|
| 571 |
+
)
|
| 572 |
+
assert input_tensor.is_contiguous, "Input tensor must be contiguous!"
|
| 573 |
+
# I think I want do this
|
| 574 |
+
# assert not input_tensor.requires_grad, "Input tensor must not require grad"
|
| 575 |
+
device = input_tensor.device
|
| 576 |
+
# Cache the tensor on the class def
|
| 577 |
+
nf4 = torch.tensor(
|
| 578 |
+
[
|
| 579 |
+
-1.0000,
|
| 580 |
+
-0.6962,
|
| 581 |
+
-0.5251,
|
| 582 |
+
-0.3949,
|
| 583 |
+
-0.2844,
|
| 584 |
+
-0.1848,
|
| 585 |
+
-0.0911,
|
| 586 |
+
0.0000,
|
| 587 |
+
0.0796,
|
| 588 |
+
0.1609,
|
| 589 |
+
0.2461,
|
| 590 |
+
0.3379,
|
| 591 |
+
0.4407,
|
| 592 |
+
0.5626,
|
| 593 |
+
0.7230,
|
| 594 |
+
1.0000,
|
| 595 |
+
],
|
| 596 |
+
device=device,
|
| 597 |
+
dtype=input_tensor.dtype,
|
| 598 |
+
)
|
| 599 |
+
n_blocks = input_tensor.numel() // block_size
|
| 600 |
+
# Double quantization
|
| 601 |
+
(
|
| 602 |
+
quantized_scalers,
|
| 603 |
+
quantization_factor,
|
| 604 |
+
scaler_mean,
|
| 605 |
+
) = cls.double_quantize_scalers(
|
| 606 |
+
input_tensor.flatten(), block_size, scaler_block_size
|
| 607 |
+
)
|
| 608 |
+
quantized_data = cls.convert_to_norm_float_weight(
|
| 609 |
+
input_tensor, n_blocks, block_size, nf4
|
| 610 |
+
)
|
| 611 |
+
tensor_meta = SubclassTensorArgs(
|
| 612 |
+
input_tensor.size(),
|
| 613 |
+
input_tensor.stride(),
|
| 614 |
+
input_tensor.storage_offset(),
|
| 615 |
+
input_tensor.dtype,
|
| 616 |
+
input_tensor.device,
|
| 617 |
+
input_tensor.requires_grad,
|
| 618 |
+
)
|
| 619 |
+
return cls(
|
| 620 |
+
tensor_meta,
|
| 621 |
+
block_size,
|
| 622 |
+
n_blocks,
|
| 623 |
+
scaler_block_size,
|
| 624 |
+
quantized_scalers,
|
| 625 |
+
quantization_factor,
|
| 626 |
+
scaler_mean,
|
| 627 |
+
quantized_data,
|
| 628 |
+
nf4=nf4,
|
| 629 |
+
)
|
| 630 |
+
|
| 631 |
+
@staticmethod
|
| 632 |
+
def double_quantize_scalers(
|
| 633 |
+
input_tensor: torch.Tensor,
|
| 634 |
+
block_size: int,
|
| 635 |
+
scaler_block_size: int,
|
| 636 |
+
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 637 |
+
"""Used to achieve the double quantization of the scalers
|
| 638 |
+
We take the input tensor first calculate the absmax quantization factors for each block.
|
| 639 |
+
We then find the mean of our positive absmax scalers. We subtract this mean from the scalers
|
| 640 |
+
And then we calculate the absmax quantization factors for each block again. We then quantize the scalers to int8.
|
| 641 |
+
|
| 642 |
+
Args:
|
| 643 |
+
input_tensor: Input tensor to convert to QLoRA format, typically a weight tensor
|
| 644 |
+
|
| 645 |
+
Returns:
|
| 646 |
+
torch.Tensor: Tensor of per_block quantization factors stored in int8 format
|
| 647 |
+
size: (n_blocks)
|
| 648 |
+
torch.Tensor: Tensor of per_scaler_block quantization factors stored in int16 format
|
| 649 |
+
size: (n_scaler_blocks)
|
| 650 |
+
"""
|
| 651 |
+
assert input_tensor.dim() == 1, "Input tensor must be flattened"
|
| 652 |
+
assert (input_tensor.numel() % scaler_block_size) == 0, (
|
| 653 |
+
f"Input tensor must be divisible by block size, got {input_tensor.numel()} and {scaler_block_size}"
|
| 654 |
+
)
|
| 655 |
+
|
| 656 |
+
# First round of quantization
|
| 657 |
+
# Produces: A tensor of size (n_blocks) of input_tensor.dtype
|
| 658 |
+
scalers_1 = get_block_absmax(input_tensor, block_size)
|
| 659 |
+
scalers_1_mean = scalers_1.mean()
|
| 660 |
+
scalers_1 = scalers_1 - scalers_1_mean
|
| 661 |
+
# Second round of quantization
|
| 662 |
+
assert scalers_1.numel() % scaler_block_size == 0, (
|
| 663 |
+
f"Number of scalers must be divisible by scaler block size, got {scalers_1.numel()} scaler_block_size {scaler_block_size} "
|
| 664 |
+
)
|
| 665 |
+
n_scaler_blocks = scalers_1.numel() // scaler_block_size
|
| 666 |
+
scaler_blocks = scalers_1.view(n_scaler_blocks, scaler_block_size)
|
| 667 |
+
|
| 668 |
+
scaler_absmax = get_block_absmax(scalers_1, scaler_block_size)
|
| 669 |
+
scaler_absmax = scaler_absmax.unsqueeze(-1).expand(
|
| 670 |
+
n_scaler_blocks, scaler_block_size
|
| 671 |
+
)
|
| 672 |
+
|
| 673 |
+
quantization_factor = 256 / (2 * scaler_absmax)
|
| 674 |
+
# Length equal to weight numel // block_size
|
| 675 |
+
quantized_scaler_blocks = scaler_blocks * quantization_factor
|
| 676 |
+
quantized_scaler_blocks = quantized_scaler_blocks.round()
|
| 677 |
+
quantized_scaler_blocks = quantized_scaler_blocks.clamp(-128, 127)
|
| 678 |
+
|
| 679 |
+
# This is needed to make sure that quantization_factor remains a repeated view of n_scaler_blocks
|
| 680 |
+
# For some reason the 127/scaler_absmax realizes n_scaler entries when only n_scaler_blocks are needed
|
| 681 |
+
# The following will grab the first entry for the n_scaler_blocks which is the same across the scaler_block_size
|
| 682 |
+
|
| 683 |
+
quantization_factor = quantization_factor[:, 0]
|
| 684 |
+
|
| 685 |
+
return (
|
| 686 |
+
quantized_scaler_blocks.flatten().to(torch.int8),
|
| 687 |
+
quantization_factor.view(n_scaler_blocks).contiguous(),
|
| 688 |
+
scalers_1_mean,
|
| 689 |
+
)
|
| 690 |
+
|
| 691 |
+
def dequantize_scalers(
|
| 692 |
+
self,
|
| 693 |
+
input_tensor: torch.Tensor,
|
| 694 |
+
quantization_factor: torch.Tensor,
|
| 695 |
+
scaler_block_size: int,
|
| 696 |
+
) -> torch.Tensor:
|
| 697 |
+
"""Used to unpack the double quantized scalers
|
| 698 |
+
|
| 699 |
+
Args:
|
| 700 |
+
input_tensor: Input tensor to convert to QLoRA format this is the quantized scalers in int8 format
|
| 701 |
+
quantization_factor: Tensor of per_scaler_block quantization factors stored in inpt_weight.dtype
|
| 702 |
+
scaler_block_size: Scaler block size to use for double quantization.
|
| 703 |
+
|
| 704 |
+
"""
|
| 705 |
+
assert input_tensor.dim() == 1, "Input tensor must be flattened"
|
| 706 |
+
assert (input_tensor.numel() % scaler_block_size) == 0, (
|
| 707 |
+
f"Input tensor must be divisible by block size, got {input_tensor.numel()} and {scaler_block_size}"
|
| 708 |
+
)
|
| 709 |
+
n_scaler_blocks = input_tensor.numel() // scaler_block_size
|
| 710 |
+
input_tensor = input_tensor.view(n_scaler_blocks, scaler_block_size)
|
| 711 |
+
dequantized = (input_tensor / quantization_factor.unsqueeze(-1)).flatten().to(
|
| 712 |
+
self.dtype
|
| 713 |
+
) + self.scaler_mean
|
| 714 |
+
return dequantized
|
| 715 |
+
|
| 716 |
+
@staticmethod
|
| 717 |
+
def convert_to_norm_float_weight(
|
| 718 |
+
input_tensor: torch.Tensor, n_blocks: int, block_size: int, nf4: torch.Tensor
|
| 719 |
+
) -> torch.Tensor:
|
| 720 |
+
"""Convert a tensor to the normalized float weight format"""
|
| 721 |
+
flattened_tensor = input_tensor.flatten()
|
| 722 |
+
# Since we are using uint8 we will encode 2 entries per byte
|
| 723 |
+
numel = input_tensor.numel()
|
| 724 |
+
assert numel % 2 == 0, (
|
| 725 |
+
"Number of elements must be even just to not have to think about the end"
|
| 726 |
+
)
|
| 727 |
+
# Reshape the flattened tensor into blocks of size self.block_size
|
| 728 |
+
blocks = flattened_tensor.view(n_blocks, block_size)
|
| 729 |
+
|
| 730 |
+
# Scale the blocks
|
| 731 |
+
scalers = get_block_absmax(input_tensor.flatten(), block_size)
|
| 732 |
+
scales = scalers.unsqueeze(-1).expand(n_blocks, block_size)
|
| 733 |
+
scaled_blocks = blocks / scales
|
| 734 |
+
|
| 735 |
+
# Returns a flattened tensor with each element quantized to nf4 index
|
| 736 |
+
# See Note: Quantize in Chunks
|
| 737 |
+
quantized_blocks = torch.empty(
|
| 738 |
+
numel, dtype=torch.uint8, device=input_tensor.device
|
| 739 |
+
)
|
| 740 |
+
flattened = scaled_blocks.flatten()
|
| 741 |
+
for chunk_num in range(math.ceil(numel / CHUNK_SIZE)):
|
| 742 |
+
start = chunk_num * CHUNK_SIZE
|
| 743 |
+
end = min(start + CHUNK_SIZE, numel)
|
| 744 |
+
quantized_blocks[start:end] = NF4Tensor.quantize_tensor_nearest(
|
| 745 |
+
flattened[start:end], nf4
|
| 746 |
+
).to(torch.uint8)
|
| 747 |
+
|
| 748 |
+
# Combine the quantized elements into uint8 values
|
| 749 |
+
# This lays out two consecutive elements in the same byte
|
| 750 |
+
# [a, b, c, d] -> [ab, cd]
|
| 751 |
+
# The size of combined blocks will be half the size of the original tensor
|
| 752 |
+
combined_blocks = quantized_blocks[::2] << 4 | quantized_blocks[1::2]
|
| 753 |
+
|
| 754 |
+
return combined_blocks.to(torch.uint8)
|
| 755 |
+
|
| 756 |
+
def get_original_weight(self) -> torch.Tensor:
|
| 757 |
+
"""Get the original weight from the normalized float weight format"""
|
| 758 |
+
# Since we are using uint8 we will decode 2 entries per byte
|
| 759 |
+
# Shift elements down 4 and select out the bottom 4 bits
|
| 760 |
+
first_elements = (self.quantized_data >> 4).to(torch.long)
|
| 761 |
+
second_elements = (self.quantized_data & 0b1111).to(torch.long)
|
| 762 |
+
|
| 763 |
+
# Dequantize every element
|
| 764 |
+
dequantized_first = self.dequantize(first_elements, self.nf4)
|
| 765 |
+
dequantized_second = self.dequantize(second_elements, self.nf4)
|
| 766 |
+
|
| 767 |
+
# Build up matrix of scalers repeated for each element in the block
|
| 768 |
+
# Since first and second elements make up a full block
|
| 769 |
+
# we expand out to half the size of the full block
|
| 770 |
+
scalers = self.dequantize_scalers(
|
| 771 |
+
self.quantized_scalers, self.quantization_factor, self.scaler_block_size
|
| 772 |
+
)
|
| 773 |
+
repeated = scalers.unsqueeze(-1).expand(scalers.size(0), self.block_size // 2)
|
| 774 |
+
|
| 775 |
+
scaled_first = dequantized_first * repeated.flatten()
|
| 776 |
+
scaled_second = dequantized_second * repeated.flatten()
|
| 777 |
+
|
| 778 |
+
# Flip them to be vertical and them stack them together horizontally
|
| 779 |
+
# Upon flattening this will interleave the elements
|
| 780 |
+
scaled_first = scaled_first.unsqueeze(-1).transpose(0, 1)
|
| 781 |
+
scaled_second = scaled_second.unsqueeze(-1).transpose(0, 1)
|
| 782 |
+
return torch.stack([scaled_first, scaled_second], dim=-1).reshape(self.shape)
|
| 783 |
+
|
| 784 |
+
@staticmethod
|
| 785 |
+
def quantize_tensor_nearest(value: torch.Tensor, nf4: torch.Tensor) -> torch.Tensor:
|
| 786 |
+
"""Quantize a float16 tensor to nf4 format to nearest and not rounded up"""
|
| 787 |
+
value = value.unsqueeze(-1) # (numel, 1)
|
| 788 |
+
# Compare the value tensor with the nf4 tensor element-wise
|
| 789 |
+
diff = (value - nf4).abs()
|
| 790 |
+
closest_nf4 = diff.min(dim=-1).indices
|
| 791 |
+
return closest_nf4
|
| 792 |
+
|
| 793 |
+
@staticmethod
|
| 794 |
+
def dequantize(value: torch.Tensor, nf4: torch.Tensor) -> torch.Tensor:
|
| 795 |
+
"""Dequantize a nf4 value to bfloat16 format"""
|
| 796 |
+
# return nf4.index_select(0, value)
|
| 797 |
+
return nf4[value]
|
| 798 |
+
|
| 799 |
+
def __repr__(self) -> str:
|
| 800 |
+
return f"Quantized Data: {self.quantized_data}\nScalers: {self.quantized_scalers}\n"
|
| 801 |
+
|
| 802 |
+
def __str__(self) -> str:
|
| 803 |
+
return f"NF4Tensor({self.shape}, {self.block_size})"
|
| 804 |
+
|
| 805 |
+
def __tensor_flatten__(self):
|
| 806 |
+
tensor_meta = SubclassTensorArgs(
|
| 807 |
+
self.shape,
|
| 808 |
+
self.stride(),
|
| 809 |
+
self.storage_offset(),
|
| 810 |
+
self.dtype,
|
| 811 |
+
self.device,
|
| 812 |
+
self.requires_grad,
|
| 813 |
+
)
|
| 814 |
+
ctx = {
|
| 815 |
+
"block_size": self.block_size,
|
| 816 |
+
"n_blocks": self.n_blocks,
|
| 817 |
+
"scaler_block_size": self.scaler_block_size,
|
| 818 |
+
"tensor_meta": tensor_meta,
|
| 819 |
+
}
|
| 820 |
+
return [
|
| 821 |
+
"quantized_data",
|
| 822 |
+
"scaler_mean",
|
| 823 |
+
"quantization_factor",
|
| 824 |
+
"quantized_scalers",
|
| 825 |
+
"nf4",
|
| 826 |
+
], ctx
|
| 827 |
+
|
| 828 |
+
@staticmethod
|
| 829 |
+
def __tensor_unflatten__(inner_tensors: Dict, metadata, outer_size, outer_stride):
|
| 830 |
+
assert len(inner_tensors) == 5, "Expected 5 inner tensors"
|
| 831 |
+
return NF4Tensor(
|
| 832 |
+
metadata["tensor_meta"],
|
| 833 |
+
metadata["block_size"],
|
| 834 |
+
metadata["n_blocks"],
|
| 835 |
+
metadata["scaler_block_size"],
|
| 836 |
+
inner_tensors["quantized_scalers"],
|
| 837 |
+
inner_tensors["quantization_factor"],
|
| 838 |
+
inner_tensors["scaler_mean"],
|
| 839 |
+
inner_tensors["quantized_data"],
|
| 840 |
+
inner_tensors["nf4"],
|
| 841 |
+
)
|
| 842 |
+
|
| 843 |
+
@classmethod
|
| 844 |
+
@torch._dynamo.disable
|
| 845 |
+
def __torch_dispatch__(cls, func, types, args, kwargs=None):
|
| 846 |
+
"""TODO we are not supporting torch dispatch at the moment
|
| 847 |
+
instead we have created a Autograd.Function to handle the linear
|
| 848 |
+
"""
|
| 849 |
+
# All ops in the NF4_OPS_TABLE expect NF4 Tensors as inputs
|
| 850 |
+
# And don't support mixed tensor subclasses. This will trigger the handler for
|
| 851 |
+
# the next type in the dispatch list
|
| 852 |
+
|
| 853 |
+
def allowed_subclasses(type):
|
| 854 |
+
return (
|
| 855 |
+
issubclass(cls, type)
|
| 856 |
+
or issubclass(torch._subclasses.fake_tensor.FakeTensor, type)
|
| 857 |
+
or issubclass(
|
| 858 |
+
torch._subclasses.functional_tensor.FunctionalTensor, type
|
| 859 |
+
)
|
| 860 |
+
)
|
| 861 |
+
|
| 862 |
+
if not all(allowed_subclasses(t) for t in types):
|
| 863 |
+
return NotImplemented("Up to the next one to handle")
|
| 864 |
+
|
| 865 |
+
if func in NF4_OPS_TABLE:
|
| 866 |
+
return NF4_OPS_TABLE[func](func, args, kwargs)
|
| 867 |
+
raise NotImplementedError(
|
| 868 |
+
f"NF4Tensor dispatch: attempting to run {func}, this is not supported"
|
| 869 |
+
)
|
| 870 |
+
|
| 871 |
+
# Do not force the Float8Tensor type on the returned tensor
|
| 872 |
+
|
| 873 |
+
@classmethod
|
| 874 |
+
def __torch_function__(cls, func, types, args=(), kwargs=None):
|
| 875 |
+
if kwargs is None:
|
| 876 |
+
kwargs = {}
|
| 877 |
+
|
| 878 |
+
try:
|
| 879 |
+
if func in NF4_TORCH_FUNCTIONS:
|
| 880 |
+
return NF4_TORCH_FUNCTIONS[func](*args, **kwargs)
|
| 881 |
+
except NotImplementedError:
|
| 882 |
+
pass
|
| 883 |
+
|
| 884 |
+
with torch._C.DisableTorchFunctionSubclass():
|
| 885 |
+
return func(*args, **kwargs)
|
| 886 |
+
|
| 887 |
+
def fsdp_pre_all_gather(
|
| 888 |
+
self, mesh: DeviceMesh
|
| 889 |
+
) -> Tuple[Tuple[torch.Tensor, ...], Any]:
|
| 890 |
+
return (
|
| 891 |
+
self.quantized_scalers,
|
| 892 |
+
self.quantization_factor,
|
| 893 |
+
self.quantized_data,
|
| 894 |
+
), (
|
| 895 |
+
SubclassTensorArgs(
|
| 896 |
+
self.size(),
|
| 897 |
+
self.stride(),
|
| 898 |
+
self.storage_offset(),
|
| 899 |
+
self.dtype,
|
| 900 |
+
self.device,
|
| 901 |
+
self.requires_grad,
|
| 902 |
+
),
|
| 903 |
+
self.block_size,
|
| 904 |
+
self.n_blocks,
|
| 905 |
+
self.scaler_block_size,
|
| 906 |
+
self.scaler_mean,
|
| 907 |
+
self.nf4,
|
| 908 |
+
mesh.get_group().size(),
|
| 909 |
+
)
|
| 910 |
+
|
| 911 |
+
def fsdp_post_all_gather(
|
| 912 |
+
self,
|
| 913 |
+
all_gather_outputs: Tuple[torch.Tensor, ...],
|
| 914 |
+
metadata: Any,
|
| 915 |
+
param_dtype: torch.dtype,
|
| 916 |
+
*,
|
| 917 |
+
out: Optional[torch.Tensor] = None,
|
| 918 |
+
) -> Union[Tuple[torch.Tensor, Tuple[torch.Tensor, ...]], None]:
|
| 919 |
+
(quantized_scalers, quantization_factor, quantized_data) = all_gather_outputs
|
| 920 |
+
(
|
| 921 |
+
tensor_meta,
|
| 922 |
+
block_size,
|
| 923 |
+
n_blocks,
|
| 924 |
+
scaler_block_size,
|
| 925 |
+
scaler_mean,
|
| 926 |
+
nf4,
|
| 927 |
+
pg_size,
|
| 928 |
+
) = metadata
|
| 929 |
+
if len(tensor_meta.original_shape) != 2:
|
| 930 |
+
raise NotImplementedError(
|
| 931 |
+
f"only support 2D shape but got dim={len(tensor_meta.original_shape)}"
|
| 932 |
+
)
|
| 933 |
+
|
| 934 |
+
new_shape = torch.Size(
|
| 935 |
+
(tensor_meta.original_shape[0] * pg_size, tensor_meta.original_shape[1])
|
| 936 |
+
)
|
| 937 |
+
new_tensor_meta = replace(tensor_meta, original_shape=new_shape)
|
| 938 |
+
if out is not None:
|
| 939 |
+
# TODO: add param dtype for mixed precision
|
| 940 |
+
assert isinstance(out, NF4Tensor), f"{type(out)}"
|
| 941 |
+
assert (
|
| 942 |
+
quantized_scalers.untyped_storage().data_ptr()
|
| 943 |
+
== out.quantized_scalers.untyped_storage().data_ptr()
|
| 944 |
+
and quantization_factor.untyped_storage().data_ptr()
|
| 945 |
+
== out.quantization_factor.untyped_storage().data_ptr()
|
| 946 |
+
and quantized_data.untyped_storage().data_ptr()
|
| 947 |
+
== out.quantized_data.untyped_storage().data_ptr()
|
| 948 |
+
), "Expects out's data to be the all-gather output"
|
| 949 |
+
return
|
| 950 |
+
|
| 951 |
+
return nf4_constructor(
|
| 952 |
+
new_tensor_meta,
|
| 953 |
+
block_size,
|
| 954 |
+
n_blocks,
|
| 955 |
+
scaler_block_size,
|
| 956 |
+
quantized_scalers,
|
| 957 |
+
quantization_factor,
|
| 958 |
+
scaler_mean,
|
| 959 |
+
quantized_data,
|
| 960 |
+
nf4,
|
| 961 |
+
), (quantized_scalers, quantization_factor, quantized_data)
|
| 962 |
+
|
| 963 |
+
|
| 964 |
+
class LinearNF4(torch.autograd.Function):
|
| 965 |
+
@staticmethod
|
| 966 |
+
def forward(ctx, input: torch.Tensor, weight: NF4Tensor):
|
| 967 |
+
"""Save the quantized nf4 weight for backward pass"""
|
| 968 |
+
ctx.save_for_backward(weight)
|
| 969 |
+
return F.linear(input, weight.to(input.dtype))
|
| 970 |
+
|
| 971 |
+
@staticmethod
|
| 972 |
+
def backward(ctx, grad_output):
|
| 973 |
+
"""The nf4 weight will never require grad so we can just return the grad_output @ weight.to(grad_output.dtype)"""
|
| 974 |
+
weight: NF4Tensor = ctx.saved_tensors[0]
|
| 975 |
+
return grad_output @ weight.to(grad_output.dtype), None
|
| 976 |
+
|
| 977 |
+
|
| 978 |
+
def linear_nf4(input: torch.Tensor, weight: NF4Tensor) -> torch.Tensor:
|
| 979 |
+
"""Apply a linear operation with the NF4Tensor weight
|
| 980 |
+
|
| 981 |
+
Args:
|
| 982 |
+
input: Input tensor
|
| 983 |
+
weight: NF4Tensor weight
|
| 984 |
+
"""
|
| 985 |
+
return LinearNF4.apply(input, weight)
|
| 986 |
+
|
| 987 |
+
|
| 988 |
+
def to_nf4(tensor, block_size: int = 64, scaler_block_size: int = 256):
|
| 989 |
+
"""Convert a given tensor to normalized float 4-bit tensor."""
|
| 990 |
+
return NF4Tensor.from_tensor(tensor, block_size, scaler_block_size)
|
| 991 |
+
|
| 992 |
+
|
| 993 |
+
NF4_TORCH_FUNCTIONS = {}
|
| 994 |
+
|
| 995 |
+
|
| 996 |
+
def implements_torch_function(torch_function):
|
| 997 |
+
def decorator(func):
|
| 998 |
+
functools.update_wrapper(func, torch_function)
|
| 999 |
+
NF4_TORCH_FUNCTIONS[torch_function] = func
|
| 1000 |
+
return func
|
| 1001 |
+
|
| 1002 |
+
return decorator
|
| 1003 |
+
|
| 1004 |
+
|
| 1005 |
+
@implements_torch_function(torch.Tensor.to)
|
| 1006 |
+
def function_to_dtype(*args, **kwargs):
|
| 1007 |
+
tensor = args[0]
|
| 1008 |
+
device, dtype, non_blocking, convert_to_format = torch._C._nn._parse_to(
|
| 1009 |
+
*args[1:], **kwargs
|
| 1010 |
+
)
|
| 1011 |
+
|
| 1012 |
+
# dtype is specified -> dequantize
|
| 1013 |
+
if dtype is not None:
|
| 1014 |
+
return tensor.get_original_weight().to(
|
| 1015 |
+
device, dtype, non_blocking, memory_format=convert_to_format
|
| 1016 |
+
)
|
| 1017 |
+
|
| 1018 |
+
# dtype is not specified -> keep NF4
|
| 1019 |
+
updated_attrs = dict(device=device)
|
| 1020 |
+
tensor_attrs, _ = tensor.__tensor_flatten__()
|
| 1021 |
+
for attr in tensor_attrs:
|
| 1022 |
+
inner_tensor = getattr(tensor, attr)
|
| 1023 |
+
updated_attrs[attr] = inner_tensor.to(
|
| 1024 |
+
device, dtype, non_blocking, memory_format=convert_to_format
|
| 1025 |
+
)
|
| 1026 |
+
return NF4Tensor(*construct_nf4_args(tensor, updated_attrs))
|
| 1027 |
+
|
| 1028 |
+
|
| 1029 |
+
@implements_torch_function(torch.Tensor.cpu)
|
| 1030 |
+
def function_cpu(*args, **kwargs):
|
| 1031 |
+
# Tensor.cpu(self, memory_format)
|
| 1032 |
+
return args[0].to("cpu", *args[1:], **kwargs)
|
| 1033 |
+
|
| 1034 |
+
|
| 1035 |
+
@implements_torch_function(torch.Tensor.cuda)
|
| 1036 |
+
def function_cuda(*args, **kwargs):
|
| 1037 |
+
# Tensor.cuda(self, device, non_blocking, memory_format)
|
| 1038 |
+
tensor = args[0]
|
| 1039 |
+
updated_attrs = dict()
|
| 1040 |
+
tensor_attrs, _ = tensor.__tensor_flatten__()
|
| 1041 |
+
for attr in tensor_attrs:
|
| 1042 |
+
inner_tensor = getattr(tensor, attr)
|
| 1043 |
+
updated_attrs[attr] = inner_tensor.cuda(*args[1:], **kwargs)
|
| 1044 |
+
updated_attrs["device"] = updated_attrs[tensor_attrs[0]].device
|
| 1045 |
+
return NF4Tensor(*construct_nf4_args(tensor, updated_attrs))
|
| 1046 |
+
|
| 1047 |
+
|
| 1048 |
+
@implements_torch_function(F.linear)
|
| 1049 |
+
def _(*args, **kwargs):
|
| 1050 |
+
input = args[0]
|
| 1051 |
+
weight = args[1]
|
| 1052 |
+
bias = args[2] if len(args) > 2 else None
|
| 1053 |
+
out = LinearNF4.apply(input, weight)
|
| 1054 |
+
if bias is not None:
|
| 1055 |
+
out = out + bias
|
| 1056 |
+
return out
|
| 1057 |
+
|
| 1058 |
+
|
| 1059 |
+
@torch._dynamo.allow_in_graph
|
| 1060 |
+
def nf4_constructor(
|
| 1061 |
+
tensor_meta: SubclassTensorArgs,
|
| 1062 |
+
block_size: int,
|
| 1063 |
+
n_blocks: int,
|
| 1064 |
+
scaler_block_size: int,
|
| 1065 |
+
quantized_scalers: torch.Tensor,
|
| 1066 |
+
quantization_factor: torch.Tensor,
|
| 1067 |
+
scaler_mean: torch.Tensor,
|
| 1068 |
+
quantized_data: torch.Tensor,
|
| 1069 |
+
nf4: torch.Tensor,
|
| 1070 |
+
):
|
| 1071 |
+
return NF4Tensor(
|
| 1072 |
+
tensor_meta,
|
| 1073 |
+
block_size,
|
| 1074 |
+
n_blocks,
|
| 1075 |
+
scaler_block_size,
|
| 1076 |
+
quantized_scalers,
|
| 1077 |
+
quantization_factor,
|
| 1078 |
+
scaler_mean,
|
| 1079 |
+
quantized_data,
|
| 1080 |
+
nf4,
|
| 1081 |
+
)
|
| 1082 |
+
|
| 1083 |
+
|
| 1084 |
+
if TORCH_VERSION_AT_LEAST_2_5:
|
| 1085 |
+
torch.serialization.add_safe_globals([NF4Tensor])
|
| 1086 |
+
torch.serialization.add_safe_globals([NF4Tensor])
|
lib/python3.12/site-packages/torchao/dtypes/uintx/__init__.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .block_sparse_layout import (
|
| 2 |
+
BlockSparseLayout,
|
| 3 |
+
)
|
| 4 |
+
from .cutlass_int4_packed_layout import (
|
| 5 |
+
CutlassInt4PackedLayout,
|
| 6 |
+
)
|
| 7 |
+
from .int4_cpu_layout import (
|
| 8 |
+
Int4CPULayout,
|
| 9 |
+
)
|
| 10 |
+
from .int4_xpu_layout import (
|
| 11 |
+
Int4XPULayout,
|
| 12 |
+
)
|
| 13 |
+
from .marlin_qqq_tensor import (
|
| 14 |
+
MarlinQQQLayout,
|
| 15 |
+
MarlinQQQTensor,
|
| 16 |
+
to_marlinqqq_quantized_intx,
|
| 17 |
+
)
|
| 18 |
+
from .marlin_sparse_layout import (
|
| 19 |
+
MarlinSparseLayout,
|
| 20 |
+
)
|
| 21 |
+
from .packed_linear_int8_dynamic_activation_intx_weight_layout import (
|
| 22 |
+
PackedLinearInt8DynamicActivationIntxWeightLayout,
|
| 23 |
+
)
|
| 24 |
+
from .q_dq_layout import (
|
| 25 |
+
QDQLayout,
|
| 26 |
+
)
|
| 27 |
+
from .semi_sparse_layout import (
|
| 28 |
+
SemiSparseLayout,
|
| 29 |
+
)
|
| 30 |
+
from .tensor_core_tiled_layout import (
|
| 31 |
+
TensorCoreTiledLayout,
|
| 32 |
+
)
|
| 33 |
+
from .uintx_layout import (
|
| 34 |
+
UintxLayout,
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
__all__ = [
|
| 38 |
+
"UintxLayout",
|
| 39 |
+
"BlockSparseLayout",
|
| 40 |
+
"MarlinSparseLayout",
|
| 41 |
+
"SemiSparseLayout",
|
| 42 |
+
"TensorCoreTiledLayout",
|
| 43 |
+
"Int4CPULayout",
|
| 44 |
+
"MarlinQQQLayout",
|
| 45 |
+
"MarlinQQQTensor",
|
| 46 |
+
"to_marlinqqq_quantized_intx",
|
| 47 |
+
"CutlassInt4PackedLayout",
|
| 48 |
+
"PackedLinearInt8DynamicActivationIntxWeightLayout",
|
| 49 |
+
"QDQLayout",
|
| 50 |
+
"Int4XPULayout",
|
| 51 |
+
]
|
lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (1.11 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/bitpacking.cpython-312.pyc
ADDED
|
Binary file (10.4 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/block_sparse_layout.cpython-312.pyc
ADDED
|
Binary file (10.2 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/cutlass_int4_packed_layout.cpython-312.pyc
ADDED
|
Binary file (11.7 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/gemlite_layout.cpython-312.pyc
ADDED
|
Binary file (17.3 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/int4_cpu_layout.cpython-312.pyc
ADDED
|
Binary file (17 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/int4_xpu_layout.cpython-312.pyc
ADDED
|
Binary file (20.7 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/marlin_qqq_tensor.cpython-312.pyc
ADDED
|
Binary file (14.6 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/marlin_sparse_layout.cpython-312.pyc
ADDED
|
Binary file (12.4 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/packed_linear_int8_dynamic_activation_intx_weight_layout.cpython-312.pyc
ADDED
|
Binary file (22.4 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/plain_layout.cpython-312.pyc
ADDED
|
Binary file (15.3 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/q_dq_layout.cpython-312.pyc
ADDED
|
Binary file (11.8 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/semi_sparse_layout.cpython-312.pyc
ADDED
|
Binary file (6.64 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/tensor_core_tiled_layout.cpython-312.pyc
ADDED
|
Binary file (24.7 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/uint4_layout.cpython-312.pyc
ADDED
|
Binary file (17.8 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/uintx_layout.cpython-312.pyc
ADDED
|
Binary file (13 kB). View file
|
|
|
lib/python3.12/site-packages/torchao/dtypes/uintx/bitpacking.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD 3-Clause license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
from functools import reduce
|
| 7 |
+
from typing import List, Optional
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
# for selecting the shards from 8 bits
|
| 12 |
+
maskbits = {
|
| 13 |
+
1: (0x01,),
|
| 14 |
+
2: (0x03,),
|
| 15 |
+
3: (0x03, 0x04),
|
| 16 |
+
4: (0x0F,),
|
| 17 |
+
5: (0x0F, 0x10),
|
| 18 |
+
6: (0x0F, 0x30),
|
| 19 |
+
7: (0x0F, 0x30, 0x40),
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
unpack_mask = {
|
| 23 |
+
1: (0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80),
|
| 24 |
+
2: (0x03, 0x0C, 0x30, 0xC0),
|
| 25 |
+
4: (0x0F, 0xF0),
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
# size of each shard
|
| 29 |
+
numbits = {
|
| 30 |
+
1: (1,),
|
| 31 |
+
2: (2,),
|
| 32 |
+
3: (2, 1),
|
| 33 |
+
4: (4,),
|
| 34 |
+
5: (4, 1),
|
| 35 |
+
6: (4, 2),
|
| 36 |
+
7: (4, 2, 1),
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
# shift amount for each shard
|
| 40 |
+
shifts = {
|
| 41 |
+
1: (0,),
|
| 42 |
+
2: (0,),
|
| 43 |
+
3: (0, 2),
|
| 44 |
+
4: (0,),
|
| 45 |
+
5: (0, 4),
|
| 46 |
+
6: (0, 4),
|
| 47 |
+
7: (0, 4, 6),
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
# for shifting groups left but right if shift is negative
|
| 52 |
+
def abs_lsh(data, shift):
|
| 53 |
+
if shift == 0:
|
| 54 |
+
return data
|
| 55 |
+
elif shift < 0:
|
| 56 |
+
return data >> -shift
|
| 57 |
+
else:
|
| 58 |
+
return data << shift
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
# inverse of abs_lsh for unpacking
|
| 62 |
+
def abs_rsh(data, shift):
|
| 63 |
+
if shift == 0:
|
| 64 |
+
return data
|
| 65 |
+
elif shift < 0:
|
| 66 |
+
return data << -shift
|
| 67 |
+
else:
|
| 68 |
+
return data >> shift
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def pack_cpu(
|
| 72 |
+
data: torch.Tensor, elem_size: int, dim: Optional[int] = -1
|
| 73 |
+
) -> List[torch.Tensor]:
|
| 74 |
+
"""
|
| 75 |
+
Inputs:
|
| 76 |
+
data: a tensor of sub byte elements in uint8
|
| 77 |
+
elem_size: the size in bits of the elements to pack
|
| 78 |
+
dim: the dimension to pack along
|
| 79 |
+
Returns: a list of packed shards
|
| 80 |
+
|
| 81 |
+
==================================================================================================
|
| 82 |
+
given an array such as [0x30,0x29,0x17,0x5,0x20,0x16,0x9,0x22] which are 8 uint6 elements
|
| 83 |
+
first seperate into two shards: the upper 2 bits and the lower 4 bits by using a mask (0x30 and 0x0f respectively)
|
| 84 |
+
2 bit shard:
|
| 85 |
+
mask: 0x30
|
| 86 |
+
[0x30, 0x20, 0x10, 0x00, 0x00, 0x10, 0x00, 0x20 ]
|
| 87 |
+
[0b00110000, 0b00100000, 0b00010000, 0b00000000, 0b00100000, 0b00010000, 0b00000000, 0b00100000]
|
| 88 |
+
|
| 89 |
+
Group elements into subsets that will be shifted to the same position within the 8bit container
|
| 90 |
+
group1 >> 4, group2 >> 2, group3 >> 0, group4 << 2
|
| 91 |
+
|
| 92 |
+
[0b00000011, 0b00000010, 0b00000100, 0b00000000, 0b00100000, 0b00010000, 0b00000000, 0b10000000]
|
| 93 |
+
|------ group 1 ------| |------ group 2 ------| |------ group 3 ------| |------ group 4 ------|
|
| 94 |
+
|
| 95 |
+
Finally bitwise-or the groups together
|
| 96 |
+
[0b00000011, 0b00000010,
|
| 97 |
+
0b00000100, 0b00000000,
|
| 98 |
+
0b00100000, 0b00010000,
|
| 99 |
+
0b00000000, 0b01000000]
|
| 100 |
+
|
| 101 |
+
[0b00100111, 0b10010010]
|
| 102 |
+
==================================================================================================
|
| 103 |
+
Similarly for 4 bit shards:
|
| 104 |
+
mask: 0x0f
|
| 105 |
+
[0x00, 0x09, 0x07, 0x05, 0x00, 0x16, 0x9, 0x02]
|
| 106 |
+
[0b00000000, 0b00001001, 0b00000111, 0b00000101, 0b00000000, 0b00000110, 0b00001001, 0b00000010]
|
| 107 |
+
|
| 108 |
+
group1 << 0, group2 << 4
|
| 109 |
+
[0b00000000, 0b00001001, 0b00000111, 0b00000101, 0b00000000, 0b01100000, 0b10010000, 0b00100000]
|
| 110 |
+
|------------------ group 1 ------------------| |------------------ group 2 ------------------|
|
| 111 |
+
|
| 112 |
+
bitwise-or:
|
| 113 |
+
[0b00000000, 0b00001001, 0b00000111, 0b00000101,
|
| 114 |
+
0b00000000, 0b01100000, 0b10010000, 0b00100000]
|
| 115 |
+
|
| 116 |
+
[0b00000000, 0b01101001, 0b10010111, 0b00100101]
|
| 117 |
+
==================================================================================================
|
| 118 |
+
After pack, data went from 8 elements to 6: [[0, 105, 151, 37], [39, 146]]
|
| 119 |
+
In general this means pack reduces input tensor size from n * 8 to n * elem_size
|
| 120 |
+
"""
|
| 121 |
+
torch._assert(
|
| 122 |
+
data.shape[dim] % 8 == 0,
|
| 123 |
+
f"pack dimension size ({data.shape[dim]}) is not divisble by scale",
|
| 124 |
+
)
|
| 125 |
+
torch._assert(data.dtype == torch.uint8, "data must be uint8")
|
| 126 |
+
output_shape = list(data.shape)
|
| 127 |
+
|
| 128 |
+
output = []
|
| 129 |
+
for i in range(len(numbits[elem_size])):
|
| 130 |
+
output_shape[dim] = data.shape[dim] * numbits[elem_size][i] // 8
|
| 131 |
+
shard = torch.zeros(output_shape, dtype=torch.uint8, device=data.device)
|
| 132 |
+
bit_size = numbits[elem_size][i]
|
| 133 |
+
rel_pos = shifts[elem_size][i]
|
| 134 |
+
bits = data & maskbits[elem_size][i]
|
| 135 |
+
scale = 8 // bit_size
|
| 136 |
+
slice_len = bits.shape[dim] // scale
|
| 137 |
+
for j in range(scale):
|
| 138 |
+
bit_slice = bits.narrow(dim, slice_len * j, slice_len)
|
| 139 |
+
shard = torch.bitwise_or(shard, abs_lsh(bit_slice, j * bit_size - rel_pos))
|
| 140 |
+
output.append(shard)
|
| 141 |
+
return output
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def unpack_cpu(
|
| 145 |
+
data: List[torch.Tensor], elem_size: int, dim: Optional[int] = -1
|
| 146 |
+
) -> torch.Tensor:
|
| 147 |
+
"""
|
| 148 |
+
Unpacks small dtype elements from a larger dtype.
|
| 149 |
+
|
| 150 |
+
Inputs:
|
| 151 |
+
data: - a list of packed shards
|
| 152 |
+
elem_size: the size in bits of the elements to unpack
|
| 153 |
+
dim: the dimension to unpack along
|
| 154 |
+
|
| 155 |
+
Returns: torch.Tensor - a tensor of the unpacked elements.
|
| 156 |
+
"""
|
| 157 |
+
# define the output tensor
|
| 158 |
+
output_shape = list(data[0].shape)
|
| 159 |
+
output_shape[dim] = data[0].shape[dim] * 8 // numbits[elem_size][0]
|
| 160 |
+
output = torch.zeros(output_shape, dtype=torch.uint8, device=data[0].device)
|
| 161 |
+
|
| 162 |
+
for i in range(len(numbits[elem_size])):
|
| 163 |
+
# define variables for the current shard
|
| 164 |
+
bit_size = numbits[elem_size][i]
|
| 165 |
+
rel_pos = shifts[elem_size][i]
|
| 166 |
+
scale = 8 // bit_size
|
| 167 |
+
group_size = bit_size * output_shape[dim] // 8
|
| 168 |
+
# mask and shift every group of bits to the correct position
|
| 169 |
+
for j in range(scale):
|
| 170 |
+
output_narrow = output.narrow(dim, j * group_size, group_size)
|
| 171 |
+
group = data[i] & unpack_mask[bit_size][j]
|
| 172 |
+
output_narrow.copy_(
|
| 173 |
+
torch.bitwise_or(output_narrow, abs_rsh(group, j * bit_size - rel_pos))
|
| 174 |
+
)
|
| 175 |
+
return output
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
# these are faster on the GPU
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def _pack(data, elem_size, scale, dim):
|
| 182 |
+
"""
|
| 183 |
+
Inner for loop from above pack function
|
| 184 |
+
"""
|
| 185 |
+
packed_shape = list(data.shape)
|
| 186 |
+
packed_shape[dim] = packed_shape[dim] // scale
|
| 187 |
+
|
| 188 |
+
packed = torch.zeros(packed_shape, dtype=data.dtype, device=data.device)
|
| 189 |
+
|
| 190 |
+
for i in range(scale):
|
| 191 |
+
narrow_slice = data.narrow(
|
| 192 |
+
dim, data.shape[dim] * i // scale, data.shape[dim] // scale
|
| 193 |
+
)
|
| 194 |
+
packed |= narrow_slice << (elem_size * i)
|
| 195 |
+
|
| 196 |
+
return packed
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def _unpack(data, element_size, scale, dim):
|
| 200 |
+
"""
|
| 201 |
+
Inner for loop from above unpack function
|
| 202 |
+
"""
|
| 203 |
+
unpacked_shape = list(data.shape)
|
| 204 |
+
unpacked_shape[dim] *= scale
|
| 205 |
+
|
| 206 |
+
nbits = (1 << element_size) - 1 # mask for the last element_size bits
|
| 207 |
+
|
| 208 |
+
unpacked_data = torch.zeros(unpacked_shape, dtype=data.dtype, device=data.device)
|
| 209 |
+
|
| 210 |
+
for i in range(scale):
|
| 211 |
+
shift_amt = element_size * i
|
| 212 |
+
unpacked_data.narrow(
|
| 213 |
+
dim,
|
| 214 |
+
unpacked_data.shape[dim] * i // scale,
|
| 215 |
+
unpacked_data.shape[dim] // scale,
|
| 216 |
+
).copy_((data >> shift_amt) & nbits)
|
| 217 |
+
|
| 218 |
+
return unpacked_data
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def pack(
|
| 222 |
+
data: torch.Tensor, elem_size: int, dim: Optional[int] = -1
|
| 223 |
+
) -> List[torch.Tensor]:
|
| 224 |
+
"""
|
| 225 |
+
a less branching but more compute version so better for gpu
|
| 226 |
+
"""
|
| 227 |
+
torch._assert(
|
| 228 |
+
data.shape[dim] % 8 == 0,
|
| 229 |
+
f"pack dimension size ({data.shape[dim]}) is not divisble by scale",
|
| 230 |
+
)
|
| 231 |
+
torch._assert(data.dtype == torch.uint8, "data must be uint8")
|
| 232 |
+
container_size = 8
|
| 233 |
+
shards = [
|
| 234 |
+
(data & maskbits[elem_size][i]) >> shifts[elem_size][i]
|
| 235 |
+
for i in range(len(maskbits[elem_size]))
|
| 236 |
+
]
|
| 237 |
+
return tuple(
|
| 238 |
+
[
|
| 239 |
+
_pack(
|
| 240 |
+
shards[i],
|
| 241 |
+
numbits[elem_size][i],
|
| 242 |
+
container_size // numbits[elem_size][i],
|
| 243 |
+
dim,
|
| 244 |
+
)
|
| 245 |
+
for i in range(len(maskbits[elem_size]))
|
| 246 |
+
]
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
def unpack(
|
| 251 |
+
data: List[torch.Tensor], elem_size: int, dim: Optional[int] = 0
|
| 252 |
+
) -> torch.Tensor:
|
| 253 |
+
"""
|
| 254 |
+
a less branching but more compute version so better for gpu
|
| 255 |
+
"""
|
| 256 |
+
container_size = 8
|
| 257 |
+
# unpack each 4,2,1 bit shard and unshift them back to the correct position
|
| 258 |
+
data = [
|
| 259 |
+
_unpack(
|
| 260 |
+
data[i], numbits[elem_size][i], container_size // numbits[elem_size][i], dim
|
| 261 |
+
)
|
| 262 |
+
<< shifts[elem_size][i]
|
| 263 |
+
for i in range(len(data))
|
| 264 |
+
]
|
| 265 |
+
return reduce(torch.bitwise_or, data)
|
lib/python3.12/site-packages/torchao/dtypes/uintx/block_sparse_layout.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD 3-Clause license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
import logging
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
from typing import Optional, Tuple
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
from torch.utils._python_dispatch import (
|
| 12 |
+
return_and_correct_aliasing,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
from torchao.dtypes.affine_quantized_tensor import (
|
| 16 |
+
AffineQuantizedTensor,
|
| 17 |
+
register_layout,
|
| 18 |
+
)
|
| 19 |
+
from torchao.dtypes.uintx.plain_layout import (
|
| 20 |
+
PlainAQTTensorImpl,
|
| 21 |
+
_aqt_is_int8_reduced_range,
|
| 22 |
+
)
|
| 23 |
+
from torchao.dtypes.utils import (
|
| 24 |
+
Layout,
|
| 25 |
+
PlainLayout,
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
logger = logging.getLogger(__name__)
|
| 29 |
+
|
| 30 |
+
aten = torch.ops.aten
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@dataclass(frozen=True)
|
| 34 |
+
class BlockSparseLayout(Layout):
|
| 35 |
+
"""BlockSparseLayout is a data class that represents the layout of a block sparse matrix.
|
| 36 |
+
|
| 37 |
+
Attributes:
|
| 38 |
+
blocksize (int): The size of the blocks in the sparse matrix. Default is 64.
|
| 39 |
+
"""
|
| 40 |
+
|
| 41 |
+
blocksize: int = 64
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
@register_layout(BlockSparseLayout)
|
| 45 |
+
class BlockSparseAQTTensorImpl(PlainAQTTensorImpl):
|
| 46 |
+
bsr_crow_indices: Optional[torch.Tensor]
|
| 47 |
+
bsr_col_indices: Optional[torch.Tensor]
|
| 48 |
+
bsr_values: Optional[torch.Tensor]
|
| 49 |
+
scale: Optional[torch.Tensor]
|
| 50 |
+
zero_point: Optional[torch.Tensor]
|
| 51 |
+
|
| 52 |
+
__slots__ = [
|
| 53 |
+
"bsr_crow_indices",
|
| 54 |
+
"bsr_col_indices",
|
| 55 |
+
"bsr_values",
|
| 56 |
+
"scale",
|
| 57 |
+
"zero_point",
|
| 58 |
+
]
|
| 59 |
+
|
| 60 |
+
@staticmethod
|
| 61 |
+
def __new__( # noqa: PYI034
|
| 62 |
+
cls,
|
| 63 |
+
shape: torch.Size,
|
| 64 |
+
bsr_crow_indices: Optional[torch.Tensor],
|
| 65 |
+
bsr_col_indices: Optional[torch.Tensor],
|
| 66 |
+
bsr_values: Optional[torch.Tensor],
|
| 67 |
+
scale: Optional[torch.Tensor],
|
| 68 |
+
zero_point: Optional[torch.Tensor],
|
| 69 |
+
_layout: Layout,
|
| 70 |
+
requires_grad: bool = False,
|
| 71 |
+
):
|
| 72 |
+
if bsr_values is None:
|
| 73 |
+
raise ValueError("bsr values must be provided!")
|
| 74 |
+
else:
|
| 75 |
+
previous_tensor = bsr_values
|
| 76 |
+
|
| 77 |
+
kwargs = {
|
| 78 |
+
"device": previous_tensor.device,
|
| 79 |
+
"dtype": previous_tensor.dtype,
|
| 80 |
+
"layout": previous_tensor.layout,
|
| 81 |
+
"requires_grad": requires_grad,
|
| 82 |
+
}
|
| 83 |
+
return torch.Tensor._make_wrapper_subclass(cls, shape, **kwargs) # type: ignore[attr-defined]
|
| 84 |
+
|
| 85 |
+
def __init__( # noqa: PYI034
|
| 86 |
+
self,
|
| 87 |
+
shape: torch.Size,
|
| 88 |
+
bsr_crow_indices: Optional[torch.Tensor],
|
| 89 |
+
bsr_col_indices: Optional[torch.Tensor],
|
| 90 |
+
bsr_values: Optional[torch.Tensor],
|
| 91 |
+
scale: Optional[torch.Tensor],
|
| 92 |
+
zero_point: Optional[torch.Tensor],
|
| 93 |
+
_layout: Layout,
|
| 94 |
+
requires_grad: bool = False,
|
| 95 |
+
):
|
| 96 |
+
self.bsr_crow_indices = bsr_crow_indices
|
| 97 |
+
self.bsr_col_indices = bsr_col_indices
|
| 98 |
+
self.bsr_values = bsr_values
|
| 99 |
+
self.scale = scale
|
| 100 |
+
self.zero_point = zero_point
|
| 101 |
+
self._layout = _layout
|
| 102 |
+
|
| 103 |
+
def __tensor_flatten__(self):
|
| 104 |
+
inner_tensors = list(
|
| 105 |
+
filter(lambda x: getattr(self, x) is not None, self.__slots__)
|
| 106 |
+
)
|
| 107 |
+
tensor_meta = (self.shape, self._layout, self.requires_grad)
|
| 108 |
+
return inner_tensors, tensor_meta
|
| 109 |
+
|
| 110 |
+
@classmethod
|
| 111 |
+
def __tensor_unflatten__(
|
| 112 |
+
cls,
|
| 113 |
+
inner_tensors,
|
| 114 |
+
tensor_meta: Tuple[torch.Size, bool],
|
| 115 |
+
outer_size,
|
| 116 |
+
outer_stride,
|
| 117 |
+
) -> torch.Tensor:
|
| 118 |
+
shape, _layout, requires_grad = tensor_meta
|
| 119 |
+
return cls(
|
| 120 |
+
shape=shape,
|
| 121 |
+
bsr_crow_indices=inner_tensors.get("bsr_crow_indices", None),
|
| 122 |
+
bsr_col_indices=inner_tensors.get("bsr_col_indices", None),
|
| 123 |
+
bsr_values=inner_tensors.get("bsr_values", None),
|
| 124 |
+
scale=inner_tensors.get("scale", None),
|
| 125 |
+
zero_point=inner_tensors.get("zero_point", None),
|
| 126 |
+
_layout=_layout,
|
| 127 |
+
requires_grad=requires_grad,
|
| 128 |
+
)
|
| 129 |
+
|
| 130 |
+
@classmethod
|
| 131 |
+
def from_plain(cls, int_data, scale, zero_point, _layout):
|
| 132 |
+
bsr_tensor = int_data.to_sparse_bsr(_layout.blocksize)
|
| 133 |
+
return cls(
|
| 134 |
+
shape=int_data.shape,
|
| 135 |
+
bsr_crow_indices=bsr_tensor.crow_indices(),
|
| 136 |
+
bsr_col_indices=bsr_tensor.col_indices(),
|
| 137 |
+
bsr_values=bsr_tensor.values(),
|
| 138 |
+
scale=scale,
|
| 139 |
+
zero_point=zero_point,
|
| 140 |
+
_layout=_layout,
|
| 141 |
+
requires_grad=False,
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
def get_plain(self):
|
| 145 |
+
int_data_expanded = torch.ops.blocksparse.bsr_to_dense(
|
| 146 |
+
self.crow_indices(),
|
| 147 |
+
self.col_indices(),
|
| 148 |
+
self.values(),
|
| 149 |
+
self.shape[0],
|
| 150 |
+
self.shape[1],
|
| 151 |
+
)
|
| 152 |
+
return int_data_expanded, self.scale, self.zero_point
|
| 153 |
+
|
| 154 |
+
def _apply_fn_to_data(self, func):
|
| 155 |
+
return self.__class__(
|
| 156 |
+
shape=self.shape,
|
| 157 |
+
bsr_crow_indices=func(self.bsr_crow_indices),
|
| 158 |
+
bsr_col_indices=func(self.bsr_col_indices),
|
| 159 |
+
bsr_values=func(self.bsr_values),
|
| 160 |
+
scale=self.scale,
|
| 161 |
+
zero_point=self.zero_point,
|
| 162 |
+
_layout=self._layout,
|
| 163 |
+
requires_grad=self.requires_grad,
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
@classmethod
|
| 167 |
+
def __torch_dispatch__(cls, func, types, args, kwargs):
|
| 168 |
+
kwargs = {} if kwargs is None else kwargs
|
| 169 |
+
|
| 170 |
+
if func is aten.detach.default:
|
| 171 |
+
return return_and_correct_aliasing(
|
| 172 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.detach)
|
| 173 |
+
)
|
| 174 |
+
if func is aten.clone.default:
|
| 175 |
+
return return_and_correct_aliasing(
|
| 176 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.clone)
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
# Need the following for bsr specific functions
|
| 180 |
+
if func is aten.crow_indices.default:
|
| 181 |
+
return args[0].bsr_crow_indices.detach()
|
| 182 |
+
|
| 183 |
+
if func is aten.col_indices.default:
|
| 184 |
+
return args[0].bsr_col_indices.detach()
|
| 185 |
+
|
| 186 |
+
if func is aten.values.default:
|
| 187 |
+
return args[0].bsr_values.detach()
|
| 188 |
+
|
| 189 |
+
if func is aten._nnz.default:
|
| 190 |
+
return args[0].bsr_values.shape[0]
|
| 191 |
+
|
| 192 |
+
raise NotImplementedError(
|
| 193 |
+
f"BlockSparseAQTTensorImpl dispatch: attempting to run {func}, this is not supported"
|
| 194 |
+
)
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def _linear_int8_act_int8_weight_block_sparse_check(input_tensor, weight_tensor, bias):
|
| 198 |
+
return (
|
| 199 |
+
isinstance(input_tensor, AffineQuantizedTensor)
|
| 200 |
+
and _aqt_is_int8_reduced_range(input_tensor)
|
| 201 |
+
and isinstance(weight_tensor, AffineQuantizedTensor)
|
| 202 |
+
and weight_tensor.is_cuda
|
| 203 |
+
and input_tensor.dtype == weight_tensor.dtype
|
| 204 |
+
and isinstance(input_tensor._layout, PlainLayout)
|
| 205 |
+
and isinstance(weight_tensor._layout, BlockSparseLayout)
|
| 206 |
+
)
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def _linear_int8_act_int8_weight_block_sparse_impl(input_tensor, weight_tensor, bias):
|
| 210 |
+
x_vals_int8 = input_tensor.tensor_impl.int_data
|
| 211 |
+
x_scales = input_tensor.tensor_impl.scale
|
| 212 |
+
w_vals = weight_tensor.tensor_impl
|
| 213 |
+
w_scales = weight_tensor.tensor_impl.scale
|
| 214 |
+
tmp = x_vals_int8.reshape(-1, x_vals_int8.shape[-1])
|
| 215 |
+
tmp_t = tmp.t()
|
| 216 |
+
|
| 217 |
+
y = torch.ops.blocksparse.int_addmm(
|
| 218 |
+
w_vals.crow_indices(),
|
| 219 |
+
w_vals.col_indices(),
|
| 220 |
+
w_vals.values(),
|
| 221 |
+
tmp_t,
|
| 222 |
+
w_scales,
|
| 223 |
+
x_scales.reshape(-1),
|
| 224 |
+
)
|
| 225 |
+
y_shape = (*x_vals_int8.shape[:-1], w_scales.shape[-1])
|
| 226 |
+
y = y.reshape(*y_shape)
|
| 227 |
+
|
| 228 |
+
# can downcast only at the very end
|
| 229 |
+
output_dtype = input_tensor.dtype
|
| 230 |
+
y = y.to(output_dtype)
|
| 231 |
+
if bias is not None:
|
| 232 |
+
y += bias
|
| 233 |
+
return y
|
lib/python3.12/site-packages/torchao/dtypes/uintx/cutlass_int4_packed_layout.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD 3-Clause license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
from typing import Optional
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
from torch.utils._python_dispatch import (
|
| 11 |
+
return_and_correct_aliasing,
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
from torchao.dtypes.affine_quantized_tensor import (
|
| 15 |
+
AffineQuantizedTensor,
|
| 16 |
+
register_layout,
|
| 17 |
+
)
|
| 18 |
+
from torchao.dtypes.uintx.plain_layout import (
|
| 19 |
+
_aqt_is_int8,
|
| 20 |
+
)
|
| 21 |
+
from torchao.dtypes.utils import AQTTensorImpl, Layout, PlainLayout
|
| 22 |
+
|
| 23 |
+
aten = torch.ops.aten
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def _aqt_is_int4(aqt):
|
| 27 |
+
"""Check if an AffineQuantizedTensor is int4 quantized Tensor"""
|
| 28 |
+
# TODO: use torch.int4
|
| 29 |
+
return (
|
| 30 |
+
aqt.tensor_impl.dtype == torch.int8
|
| 31 |
+
and aqt.quant_min == -8
|
| 32 |
+
and aqt.quant_max == 7
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _same_metadata(self: "Int4PackedTensorImpl", src: "Int4PackedTensorImpl") -> bool:
|
| 37 |
+
return (
|
| 38 |
+
isinstance(self, Int4PackedTensorImpl)
|
| 39 |
+
and isinstance(src, Int4PackedTensorImpl)
|
| 40 |
+
and self.shape == src.shape
|
| 41 |
+
and self.int_data.shape == src.int_data.shape
|
| 42 |
+
and self.scale.shape == src.scale.shape
|
| 43 |
+
and type(self._layout) == type(src._layout)
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
@dataclass(frozen=True)
|
| 48 |
+
class CutlassInt4PackedLayout(Layout):
|
| 49 |
+
"""Layout class for int4 packed layout for affine quantized tensor, for cutlass kernel."""
|
| 50 |
+
|
| 51 |
+
pass
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
@register_layout(CutlassInt4PackedLayout)
|
| 55 |
+
class Int4PackedTensorImpl(AQTTensorImpl):
|
| 56 |
+
"""
|
| 57 |
+
TensorImpl storage class for int4 packed layout for affine quantized tensor.
|
| 58 |
+
"""
|
| 59 |
+
|
| 60 |
+
@staticmethod
|
| 61 |
+
def __new__(
|
| 62 |
+
cls,
|
| 63 |
+
int_data: torch.Tensor,
|
| 64 |
+
scale: torch.Tensor,
|
| 65 |
+
_layout: Layout,
|
| 66 |
+
):
|
| 67 |
+
kwargs = {}
|
| 68 |
+
kwargs["device"] = int_data.device
|
| 69 |
+
kwargs["layout"] = (
|
| 70 |
+
kwargs.get("layout") if kwargs.get("layout", False) else int_data.layout
|
| 71 |
+
)
|
| 72 |
+
kwargs["dtype"] = int_data.dtype
|
| 73 |
+
kwargs["requires_grad"] = False
|
| 74 |
+
shape = int_data.shape
|
| 75 |
+
return torch.Tensor._make_wrapper_subclass(cls, shape, **kwargs) # type: ignore[attr-defined]
|
| 76 |
+
|
| 77 |
+
def __init__(
|
| 78 |
+
self,
|
| 79 |
+
int_data: torch.Tensor,
|
| 80 |
+
scale: torch.Tensor,
|
| 81 |
+
_layout: Layout,
|
| 82 |
+
):
|
| 83 |
+
self.int_data = int_data
|
| 84 |
+
self.scale = scale
|
| 85 |
+
self._layout = _layout
|
| 86 |
+
|
| 87 |
+
@classmethod
|
| 88 |
+
def __torch_dispatch__(cls, func, types, args, kwargs):
|
| 89 |
+
kwargs = {} if kwargs is None else kwargs
|
| 90 |
+
|
| 91 |
+
if func is aten.detach.default:
|
| 92 |
+
return return_and_correct_aliasing(
|
| 93 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.detach)
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
elif func is aten.copy_.default:
|
| 97 |
+
self = args[0]
|
| 98 |
+
src = args[1]
|
| 99 |
+
if _same_metadata(self, src):
|
| 100 |
+
self_tensors = self.__tensor_flatten__()[0]
|
| 101 |
+
for tensor_name in self_tensors:
|
| 102 |
+
getattr(self, tensor_name).copy_(getattr(src, tensor_name))
|
| 103 |
+
return
|
| 104 |
+
raise ValueError(
|
| 105 |
+
f"Not supported args for copy_ due to metadata mistach: {args[0], args[1]}"
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
raise NotImplementedError(
|
| 109 |
+
f"Int4PackedTensorImpl dispatch: attempting to run {func}, this is not supported"
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
def __tensor_flatten__(self):
|
| 113 |
+
return ["int_data", "scale"], [self._layout]
|
| 114 |
+
|
| 115 |
+
@classmethod
|
| 116 |
+
def __tensor_unflatten__(
|
| 117 |
+
cls, tensor_data_dict, tensor_attributes, outer_size, outer_stride
|
| 118 |
+
):
|
| 119 |
+
int_data = tensor_data_dict["int_data"]
|
| 120 |
+
scale = tensor_data_dict["scale"]
|
| 121 |
+
(_layout,) = tensor_attributes
|
| 122 |
+
return cls(int_data, scale, _layout)
|
| 123 |
+
|
| 124 |
+
def get_plain(self):
|
| 125 |
+
int_data = torch.stack(
|
| 126 |
+
((self.int_data << 4) >> 4, self.int_data >> 4), dim=-1
|
| 127 |
+
).view(self.int_data.shape[:-1] + (2 * self.int_data.shape[-1],))
|
| 128 |
+
return int_data, self.scale, None
|
| 129 |
+
|
| 130 |
+
@classmethod
|
| 131 |
+
def from_plain(
|
| 132 |
+
cls,
|
| 133 |
+
int_data: torch.Tensor,
|
| 134 |
+
scale: torch.Tensor,
|
| 135 |
+
zero_point: Optional[torch.Tensor],
|
| 136 |
+
_layout: Layout,
|
| 137 |
+
):
|
| 138 |
+
assert zero_point is None or torch.all(zero_point == 0)
|
| 139 |
+
int_data_s4 = ((int_data[..., 1::2] & 0xF) << 4) | (int_data[..., 0::2] & 0xF)
|
| 140 |
+
return cls(
|
| 141 |
+
int_data_s4,
|
| 142 |
+
scale,
|
| 143 |
+
_layout,
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
def get_layout(self) -> Layout:
|
| 147 |
+
return self._layout
|
| 148 |
+
|
| 149 |
+
def _apply_fn_to_data(self, fn):
|
| 150 |
+
self.int_data = fn(self.int_data)
|
| 151 |
+
self.scale = fn(self.scale)
|
| 152 |
+
return self
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def _linear_int8_act_int4_weight_cutlass_check(input_tensor, weight_tensor, bias):
|
| 156 |
+
return (
|
| 157 |
+
isinstance(input_tensor, AffineQuantizedTensor)
|
| 158 |
+
and isinstance(input_tensor._layout, PlainLayout)
|
| 159 |
+
and _aqt_is_int8(input_tensor)
|
| 160 |
+
and input_tensor.dtype in (torch.float16, torch.bfloat16)
|
| 161 |
+
and len(input_tensor.shape) >= 2
|
| 162 |
+
and input_tensor.tensor_impl.scale.dtype == torch.float32
|
| 163 |
+
and len(input_tensor.tensor_impl.scale.shape) == len(input_tensor.shape) - 1
|
| 164 |
+
and isinstance(weight_tensor, AffineQuantizedTensor)
|
| 165 |
+
and isinstance(weight_tensor._layout, CutlassInt4PackedLayout)
|
| 166 |
+
and _aqt_is_int4(weight_tensor)
|
| 167 |
+
and weight_tensor.dtype == input_tensor.dtype
|
| 168 |
+
and len(weight_tensor.shape) == 2
|
| 169 |
+
and weight_tensor.tensor_impl.scale.dtype == torch.float32
|
| 170 |
+
and len(weight_tensor.tensor_impl.scale.shape) == 1
|
| 171 |
+
and (bias is None or bias.dtype == input_tensor.dtype)
|
| 172 |
+
and (bias is None or len(bias.shape) == 1)
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def _linear_int8_act_int4_weight_cutlass_impl(input_tensor, weight_tensor, bias):
|
| 177 |
+
from torchao.ops import rowwise_scaled_linear_cutlass_s8s4
|
| 178 |
+
|
| 179 |
+
weight = weight_tensor.tensor_impl.int_data
|
| 180 |
+
weight_scale = weight_tensor.tensor_impl.scale
|
| 181 |
+
input = input_tensor.tensor_impl.int_data
|
| 182 |
+
input_scale = input_tensor.tensor_impl.scale
|
| 183 |
+
out_dtype = input_tensor.dtype
|
| 184 |
+
|
| 185 |
+
out = rowwise_scaled_linear_cutlass_s8s4(
|
| 186 |
+
input, input_scale, weight, weight_scale, bias, out_dtype
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
return out
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def _linear_int4_act_int4_weight_cutlass_check(input_tensor, weight_tensor, bias):
|
| 193 |
+
return (
|
| 194 |
+
isinstance(input_tensor, AffineQuantizedTensor)
|
| 195 |
+
and isinstance(input_tensor._layout, CutlassInt4PackedLayout)
|
| 196 |
+
and _aqt_is_int4(input_tensor)
|
| 197 |
+
and input_tensor.dtype in (torch.float16, torch.bfloat16)
|
| 198 |
+
and len(input_tensor.shape) >= 2
|
| 199 |
+
and input_tensor.tensor_impl.scale.dtype == torch.float32
|
| 200 |
+
and len(input_tensor.tensor_impl.scale.shape) == len(input_tensor.shape) - 1
|
| 201 |
+
and isinstance(weight_tensor, AffineQuantizedTensor)
|
| 202 |
+
and isinstance(weight_tensor._layout, CutlassInt4PackedLayout)
|
| 203 |
+
and _aqt_is_int4(weight_tensor)
|
| 204 |
+
and weight_tensor.dtype == input_tensor.dtype
|
| 205 |
+
and len(weight_tensor.shape) == 2
|
| 206 |
+
and weight_tensor.tensor_impl.scale.dtype == torch.float32
|
| 207 |
+
and len(weight_tensor.tensor_impl.scale.shape) == 1
|
| 208 |
+
)
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def _linear_int4_act_int4_weight_cutlass_impl(input_tensor, weight_tensor, bias):
|
| 212 |
+
from torchao.ops import rowwise_scaled_linear_cutlass_s4s4
|
| 213 |
+
|
| 214 |
+
weight = weight_tensor.tensor_impl.int_data
|
| 215 |
+
weight_scale = weight_tensor.tensor_impl.scale
|
| 216 |
+
input = input_tensor.tensor_impl.int_data
|
| 217 |
+
input_scale = input_tensor.tensor_impl.scale
|
| 218 |
+
out_dtype = input_tensor.dtype
|
| 219 |
+
|
| 220 |
+
out = rowwise_scaled_linear_cutlass_s4s4(
|
| 221 |
+
input, input_scale, weight, weight_scale, bias, out_dtype
|
| 222 |
+
)
|
| 223 |
+
|
| 224 |
+
return out
|
lib/python3.12/site-packages/torchao/dtypes/uintx/gemlite_layout.py
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD 3-Clause license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
from typing import Dict, Optional, Tuple
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
from torch.utils._python_dispatch import (
|
| 11 |
+
is_traceable_wrapper_subclass,
|
| 12 |
+
return_and_correct_aliasing,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
from torchao.dtypes.affine_quantized_tensor import (
|
| 16 |
+
AffineQuantizedTensor,
|
| 17 |
+
register_layout,
|
| 18 |
+
)
|
| 19 |
+
from torchao.dtypes.uintx.tensor_core_tiled_layout import TensorCoreTiledAQTTensorImpl
|
| 20 |
+
from torchao.dtypes.utils import Layout
|
| 21 |
+
from torchao.utils import fill_defaults
|
| 22 |
+
|
| 23 |
+
try:
|
| 24 |
+
import gemlite
|
| 25 |
+
from gemlite.core import GemLiteLinearTriton
|
| 26 |
+
except:
|
| 27 |
+
gemlite = None
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
aten = torch.ops.aten
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _same_metadata(
|
| 34 |
+
self: "GemliteAQTTensorImpl",
|
| 35 |
+
src: "GemliteAQTTensorImpl",
|
| 36 |
+
) -> bool:
|
| 37 |
+
kwargs_match = len(self.gemlite_kwargs) == len(src.gemlite_kwargs)
|
| 38 |
+
for k, v in self.gemlite_kwargs.items():
|
| 39 |
+
if k != "scale_activations":
|
| 40 |
+
kwargs_match = kwargs_match and (v == src.gemlite_kwargs[k])
|
| 41 |
+
|
| 42 |
+
return (
|
| 43 |
+
isinstance(self, GemliteAQTTensorImpl)
|
| 44 |
+
and isinstance(src, GemliteAQTTensorImpl)
|
| 45 |
+
and self.shape == src.shape
|
| 46 |
+
and self.packed_weight.shape == src.packed_weight.shape
|
| 47 |
+
and self.scale.shape == src.scale.shape
|
| 48 |
+
and self.zero_point.shape == src.zero_point.shape
|
| 49 |
+
and kwargs_match
|
| 50 |
+
and type(self._layout) == type(src._layout)
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def scale_activations_no_scaling(x):
|
| 55 |
+
return x, None
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def scale_activations_int8(x):
|
| 59 |
+
x_shape = x.shape
|
| 60 |
+
out_x = x.view(-1, x.shape[-1])
|
| 61 |
+
scaled_x = torch.abs(out_x).amax(axis=1, keepdim=True) / 127
|
| 62 |
+
out_x = torch.round(out_x / scaled_x).to(dtype=torch.int8)
|
| 63 |
+
return out_x.view(x_shape), scaled_x
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def get_gemlite_quant_kwargs(bit_width, group_size, dtype):
|
| 67 |
+
from torchao.quantization.quant_primitives import MappingType, ZeroPointDomain
|
| 68 |
+
|
| 69 |
+
kwargs = {}
|
| 70 |
+
if bit_width != 8:
|
| 71 |
+
kwargs["mapping_type"] = MappingType.ASYMMETRIC
|
| 72 |
+
kwargs["block_size"] = (1, group_size)
|
| 73 |
+
kwargs["target_dtype"] = torch.uint8
|
| 74 |
+
kwargs["eps"] = 1e-6
|
| 75 |
+
kwargs["quant_min"] = 0
|
| 76 |
+
kwargs["quant_max"] = (2**bit_width) - 1
|
| 77 |
+
kwargs["eps"] = 1e-6
|
| 78 |
+
kwargs["zero_point_dtype"] = dtype
|
| 79 |
+
kwargs["zero_point_domain"] = ZeroPointDomain.FLOAT
|
| 80 |
+
elif bit_width == 8:
|
| 81 |
+
kwargs["mapping_type"] = MappingType.SYMMETRIC
|
| 82 |
+
kwargs["block_size"] = (1, group_size)
|
| 83 |
+
kwargs["target_dtype"] = torch.int8
|
| 84 |
+
kwargs["quant_min"] = -128
|
| 85 |
+
kwargs["quant_max"] = 127
|
| 86 |
+
kwargs["eps"] = 1e-5
|
| 87 |
+
kwargs["zero_point_dtype"] = None
|
| 88 |
+
kwargs["zero_point_domain"] = ZeroPointDomain.NONE
|
| 89 |
+
return kwargs
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def get_gemlite_aqt_kwargs(
|
| 93 |
+
weight,
|
| 94 |
+
group_size=64,
|
| 95 |
+
bit_width=4,
|
| 96 |
+
packing_bitwidth=32,
|
| 97 |
+
contiguous=None,
|
| 98 |
+
use_hqq=True,
|
| 99 |
+
):
|
| 100 |
+
if gemlite is None:
|
| 101 |
+
raise ImportError(
|
| 102 |
+
"Unable to import 'gemlite'. Please ensure it is installed correctly. You can install it with: pip install gemlite"
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
assert bit_width in [
|
| 106 |
+
4,
|
| 107 |
+
8,
|
| 108 |
+
], f"gemlite only works with bit_width 4,8 but got {bit_width}"
|
| 109 |
+
assert packing_bitwidth in [
|
| 110 |
+
8,
|
| 111 |
+
16,
|
| 112 |
+
32,
|
| 113 |
+
None,
|
| 114 |
+
], f"gemlite needs packing_bitwidth in [8, 16, 32] but got {packing_bitwidth}"
|
| 115 |
+
assert weight.dtype in [torch.float16, torch.bfloat16], (
|
| 116 |
+
f"gemlite only works with dtype torch.float16 or torch.bfloat16 but got {weight.dtype}"
|
| 117 |
+
)
|
| 118 |
+
assert group_size in [32, 64, 128, 256, 512, 1024, None]
|
| 119 |
+
assert group_size is None or bit_width != 8, (
|
| 120 |
+
"gemlite only works with group_size=None for bit_width=8"
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
out_features, in_features = weight.shape
|
| 124 |
+
group_size = in_features if group_size is None else group_size
|
| 125 |
+
|
| 126 |
+
aqt_kwargs = get_gemlite_quant_kwargs(bit_width, group_size, weight.dtype)
|
| 127 |
+
aqt_kwargs["_layout"] = GemlitePackedLayout(
|
| 128 |
+
group_size=group_size,
|
| 129 |
+
bit_width=bit_width,
|
| 130 |
+
packing_bitwidth=packing_bitwidth,
|
| 131 |
+
contiguous=contiguous,
|
| 132 |
+
)
|
| 133 |
+
aqt_kwargs["use_hqq"] = use_hqq
|
| 134 |
+
return aqt_kwargs
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
@dataclass(frozen=True)
|
| 138 |
+
class GemlitePackedLayout(Layout):
|
| 139 |
+
group_size: Optional[int] = 64
|
| 140 |
+
bit_width: int = 4
|
| 141 |
+
packing_bitwidth: int = None
|
| 142 |
+
contiguous: bool = None
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
@register_layout(GemlitePackedLayout)
|
| 146 |
+
class GemliteAQTTensorImpl(TensorCoreTiledAQTTensorImpl):
|
| 147 |
+
def __new__(
|
| 148 |
+
cls,
|
| 149 |
+
packed_weight: torch.Tensor,
|
| 150 |
+
scale: torch.Tensor,
|
| 151 |
+
zero_point: torch.Tensor,
|
| 152 |
+
gemlite_kwargs: Dict,
|
| 153 |
+
_layout: Layout,
|
| 154 |
+
):
|
| 155 |
+
kwargs = {}
|
| 156 |
+
kwargs["device"] = packed_weight.device
|
| 157 |
+
kwargs["layout"] = (
|
| 158 |
+
kwargs.get("layout")
|
| 159 |
+
if kwargs.get("layout", False)
|
| 160 |
+
else packed_weight.layout
|
| 161 |
+
)
|
| 162 |
+
kwargs["dtype"] = packed_weight.dtype
|
| 163 |
+
kwargs["requires_grad"] = False
|
| 164 |
+
shape = packed_weight.shape
|
| 165 |
+
return torch.Tensor._make_wrapper_subclass(cls, shape, **kwargs) # type: ignore[attr-defined]
|
| 166 |
+
|
| 167 |
+
def __init__(
|
| 168 |
+
self,
|
| 169 |
+
packed_weight: torch.Tensor,
|
| 170 |
+
scale: torch.Tensor,
|
| 171 |
+
zero_point: torch.Tensor,
|
| 172 |
+
gemlite_kwargs: Dict,
|
| 173 |
+
_layout: Layout,
|
| 174 |
+
):
|
| 175 |
+
self.packed_weight = packed_weight
|
| 176 |
+
self.scale = scale
|
| 177 |
+
self.zero_point = zero_point
|
| 178 |
+
self.gemlite_kwargs = gemlite_kwargs
|
| 179 |
+
self._layout = _layout
|
| 180 |
+
|
| 181 |
+
def __tensor_flatten__(self):
|
| 182 |
+
return ["packed_weight", "scale", "zero_point"], [
|
| 183 |
+
self._layout,
|
| 184 |
+
self.gemlite_kwargs,
|
| 185 |
+
]
|
| 186 |
+
|
| 187 |
+
@classmethod
|
| 188 |
+
def __tensor_unflatten__(
|
| 189 |
+
cls, tensor_data_dict, tensor_attributes, outer_size, outer_stride
|
| 190 |
+
):
|
| 191 |
+
packed_weight, scale, zero_point = (
|
| 192 |
+
tensor_data_dict["packed_weight"],
|
| 193 |
+
tensor_data_dict["scale"],
|
| 194 |
+
tensor_data_dict["zero_point"],
|
| 195 |
+
)
|
| 196 |
+
_layout, gemlite_kwargs = tensor_attributes
|
| 197 |
+
return cls(packed_weight, scale, zero_point, gemlite_kwargs, _layout)
|
| 198 |
+
|
| 199 |
+
@classmethod
|
| 200 |
+
def from_plain(
|
| 201 |
+
cls,
|
| 202 |
+
int_data: torch.Tensor,
|
| 203 |
+
scale: torch.Tensor,
|
| 204 |
+
zero_point: Optional[torch.Tensor],
|
| 205 |
+
_layout: Layout,
|
| 206 |
+
):
|
| 207 |
+
assert isinstance(_layout, GemlitePackedLayout), (
|
| 208 |
+
f"GemliteAQTTensorImpl only works with GemliteLinearTriton but got {_layout}"
|
| 209 |
+
)
|
| 210 |
+
device = int_data.device
|
| 211 |
+
if device.type != "cuda":
|
| 212 |
+
int_data = (
|
| 213 |
+
int_data.cuda()
|
| 214 |
+
) # We need int_data on cuda device because of Triton packing
|
| 215 |
+
|
| 216 |
+
group_size, bit_width = _layout.group_size, _layout.bit_width
|
| 217 |
+
out_features, in_features = int_data.shape
|
| 218 |
+
|
| 219 |
+
gemlite_linear = gemlite.helper.A16Wn(device=int_data.device).from_weights(
|
| 220 |
+
int_data, scale, zero_point, bit_width, group_size, bias=None
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
gemlite_kwargs = {
|
| 224 |
+
"out_features": out_features,
|
| 225 |
+
"scaled_activations": gemlite_linear.scaled_activations,
|
| 226 |
+
"meta_args": gemlite_linear.get_meta_args(),
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
packed_weight, scale, zero_point = gemlite_linear.get_tensor_args()
|
| 230 |
+
packed_weight = packed_weight.to(device)
|
| 231 |
+
|
| 232 |
+
return cls(packed_weight, scale, zero_point, gemlite_kwargs, _layout)
|
| 233 |
+
|
| 234 |
+
def to(self, *args, **kwargs):
|
| 235 |
+
kwargs = self._get_to_kwargs(*args, **kwargs)
|
| 236 |
+
device = kwargs["device"]
|
| 237 |
+
return self.__class__(
|
| 238 |
+
self.packed_weight.to(device),
|
| 239 |
+
self.scale.to(device),
|
| 240 |
+
self.zero_point.to(device),
|
| 241 |
+
self.gemlite_kwargs,
|
| 242 |
+
self._layout,
|
| 243 |
+
)
|
| 244 |
+
|
| 245 |
+
def _apply_fn_to_data(self, fn):
|
| 246 |
+
return self.__class__(
|
| 247 |
+
fn(self.packed_weight),
|
| 248 |
+
fn(self.scale),
|
| 249 |
+
fn(self.zero_point),
|
| 250 |
+
self.gemlite_kwargs,
|
| 251 |
+
self._layout,
|
| 252 |
+
)
|
| 253 |
+
|
| 254 |
+
def get_plain(self) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 255 |
+
device = self.packed_weight.device
|
| 256 |
+
elements_per_sample = self._layout.packing_bitwidth // self._layout.bit_width
|
| 257 |
+
in_features = (
|
| 258 |
+
self.packed_weight.numel() * elements_per_sample
|
| 259 |
+
) // self.gemlite_kwargs["out_features"]
|
| 260 |
+
int_data = (
|
| 261 |
+
gemlite.bitpack.unpack_over_rows(
|
| 262 |
+
self.packed_weight.cuda(),
|
| 263 |
+
W_nbits=self._layout.bit_width,
|
| 264 |
+
num_output_rows=in_features,
|
| 265 |
+
dtype=torch.uint8,
|
| 266 |
+
)
|
| 267 |
+
.t()
|
| 268 |
+
.contiguous()
|
| 269 |
+
).to(device)
|
| 270 |
+
scale = self.scale.t().contiguous()
|
| 271 |
+
zero_point = self.zero_point.t().contiguous()
|
| 272 |
+
|
| 273 |
+
return int_data, scale, zero_point
|
| 274 |
+
|
| 275 |
+
@classmethod
|
| 276 |
+
def __torch_dispatch__(cls, func, types, args, kwargs):
|
| 277 |
+
kwargs = {} if kwargs is None else kwargs
|
| 278 |
+
|
| 279 |
+
# we don't handle transpose operations and just ignore them. In practice the only
|
| 280 |
+
# reason a transpsoe should occur is because the functional linear
|
| 281 |
+
# op can decompose into e.g. transpose + addmm so since we want
|
| 282 |
+
# to use the gemlite matmul kernel, which expects teh weight to be passed in as is,
|
| 283 |
+
# we ignore the transpose
|
| 284 |
+
if func is aten.detach.default or func is aten.t.default:
|
| 285 |
+
return return_and_correct_aliasing(
|
| 286 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.detach)
|
| 287 |
+
)
|
| 288 |
+
|
| 289 |
+
if func is aten.clone.default:
|
| 290 |
+
return return_and_correct_aliasing(
|
| 291 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.clone)
|
| 292 |
+
)
|
| 293 |
+
|
| 294 |
+
if func is aten.slice.Tensor:
|
| 295 |
+
self, dim, start, end, step = fill_defaults(args, 5, [0, None, None, 1])
|
| 296 |
+
assert step == 1, "Only step == 1 is supported in slicing right now"
|
| 297 |
+
|
| 298 |
+
if dim in [0, 1]:
|
| 299 |
+
int_data, scale, zero_point = self.get_plain()
|
| 300 |
+
data_len = int_data.shape[dim]
|
| 301 |
+
scale_len = scale.shape[dim]
|
| 302 |
+
ratio = data_len / scale_len
|
| 303 |
+
start_scale = int(start / ratio)
|
| 304 |
+
end_scale = int(end / ratio)
|
| 305 |
+
|
| 306 |
+
int_data = aten.slice.Tensor(int_data, dim, start, end, step)
|
| 307 |
+
scale = aten.slice.Tensor(scale, dim, start_scale, end_scale, step)
|
| 308 |
+
if zero_point is not None and zero_point.numel() > 0:
|
| 309 |
+
zero_point = aten.slice.Tensor(
|
| 310 |
+
zero_point, dim, start_scale, end_scale, step
|
| 311 |
+
)
|
| 312 |
+
else:
|
| 313 |
+
zero_point = None
|
| 314 |
+
# this is to handle padding
|
| 315 |
+
int_data, scale, zero_point = self._layout.post_process(
|
| 316 |
+
int_data, scale, zero_point, self.block_size
|
| 317 |
+
)
|
| 318 |
+
|
| 319 |
+
sliced = self.from_plain(
|
| 320 |
+
int_data, scale, zero_point, self._layout
|
| 321 |
+
) # Will be transposed again
|
| 322 |
+
|
| 323 |
+
return return_and_correct_aliasing(func, args, kwargs, sliced)
|
| 324 |
+
|
| 325 |
+
else:
|
| 326 |
+
raise NotImplementedError(
|
| 327 |
+
f"GemliteAQTTensorImpl dispatch: attempting to run {func}, with dim={dim}, that is not supported"
|
| 328 |
+
)
|
| 329 |
+
|
| 330 |
+
elif func is aten.copy_.default:
|
| 331 |
+
self = args[0]
|
| 332 |
+
src = args[1]
|
| 333 |
+
if _same_metadata(self, src):
|
| 334 |
+
self_tensors = self.__tensor_flatten__()[0]
|
| 335 |
+
for tensor_name in self_tensors:
|
| 336 |
+
getattr(self, tensor_name).copy_(getattr(src, tensor_name))
|
| 337 |
+
return
|
| 338 |
+
raise ValueError(
|
| 339 |
+
f"Not supported args for copy_ due to metadata mistach: {args[0], args[1]}"
|
| 340 |
+
)
|
| 341 |
+
|
| 342 |
+
raise NotImplementedError(
|
| 343 |
+
f"GemliteAQTTensorImpl dispatch: attempting to run {func}, this is not supported"
|
| 344 |
+
)
|
| 345 |
+
|
| 346 |
+
__torch_function__ = torch._C._disabled_torch_function_impl
|
| 347 |
+
|
| 348 |
+
def get_layout(self) -> Layout:
|
| 349 |
+
return self._layout
|
| 350 |
+
|
| 351 |
+
@property
|
| 352 |
+
def block_size(self):
|
| 353 |
+
return (1, self._layout.group_size)
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
# logic taken from gemlite's core.py
|
| 357 |
+
def _matmul_type_fn(batch_size: int, bit_width: int) -> str:
|
| 358 |
+
if batch_size > 64:
|
| 359 |
+
return "GEMM"
|
| 360 |
+
elif batch_size > 1:
|
| 361 |
+
return "GEMM_SPLITK"
|
| 362 |
+
else:
|
| 363 |
+
return gemlite.core.get_default_gemv(bit_width)
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
def _linear_fp_act_int4_weight_gemlite_impl(input_tensor, weight_tensor, bias=None):
|
| 367 |
+
if hasattr(weight_tensor, "tensor_impl"):
|
| 368 |
+
weight_impl = weight_tensor.tensor_impl
|
| 369 |
+
else:
|
| 370 |
+
weight_impl = weight_tensor
|
| 371 |
+
|
| 372 |
+
batch_size = input_tensor.view(-1, input_tensor.shape[-1]).shape[0]
|
| 373 |
+
matmul_type = _matmul_type_fn(batch_size, weight_impl._layout.bit_width)
|
| 374 |
+
|
| 375 |
+
if weight_impl.gemlite_kwargs["scaled_activations"]:
|
| 376 |
+
scale_activations = scale_activations_int8
|
| 377 |
+
else:
|
| 378 |
+
scale_activations = scale_activations_no_scaling
|
| 379 |
+
|
| 380 |
+
return GemLiteLinearTriton.forward_functional(
|
| 381 |
+
x=input_tensor,
|
| 382 |
+
bias=bias,
|
| 383 |
+
matmul_type=matmul_type,
|
| 384 |
+
out_features=weight_impl.gemlite_kwargs["out_features"],
|
| 385 |
+
scale_activations=scale_activations,
|
| 386 |
+
meta_args=weight_impl.gemlite_kwargs["meta_args"],
|
| 387 |
+
tensor_args=(
|
| 388 |
+
weight_impl.packed_weight,
|
| 389 |
+
weight_impl.scale,
|
| 390 |
+
weight_impl.zero_point,
|
| 391 |
+
),
|
| 392 |
+
)
|
| 393 |
+
|
| 394 |
+
|
| 395 |
+
def _linear_fp_act_int4_weight_gemlite_check(input_tensor, weight_tensor, bias):
|
| 396 |
+
return (
|
| 397 |
+
# input is native fp16 tensor
|
| 398 |
+
not is_traceable_wrapper_subclass(input_tensor)
|
| 399 |
+
# and input_tensor.dtype in [torch.float16, torch.bfloat16]
|
| 400 |
+
# weight is gemlite layout
|
| 401 |
+
and isinstance(weight_tensor, AffineQuantizedTensor)
|
| 402 |
+
and isinstance(weight_tensor._layout, GemlitePackedLayout)
|
| 403 |
+
)
|
lib/python3.12/site-packages/torchao/dtypes/uintx/int4_cpu_layout.py
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD 3-Clause license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
from typing import Optional, Tuple
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
from torch.utils._python_dispatch import (
|
| 11 |
+
is_traceable_wrapper_subclass,
|
| 12 |
+
return_and_correct_aliasing,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
from torchao.dtypes.affine_quantized_tensor import (
|
| 16 |
+
AffineQuantizedTensor,
|
| 17 |
+
register_layout,
|
| 18 |
+
)
|
| 19 |
+
from torchao.dtypes.utils import AQTTensorImpl, Layout, is_device
|
| 20 |
+
from torchao.quantization.quant_primitives import ZeroPointDomain
|
| 21 |
+
from torchao.utils import (
|
| 22 |
+
TORCH_VERSION_AT_LEAST_2_5,
|
| 23 |
+
TORCH_VERSION_AT_LEAST_2_6,
|
| 24 |
+
fill_defaults,
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
aten = torch.ops.aten
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
@dataclass(frozen=True)
|
| 31 |
+
class Int4CPULayout(Layout):
|
| 32 |
+
"""Layout class for int4 CPU layout for affine quantized tensor, used by tinygemm kernels `_weight_int4pack_mm_for_cpu`.
|
| 33 |
+
Only for PyTorch version at least 2.6
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
pass
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
@register_layout(Int4CPULayout)
|
| 40 |
+
class Int4CPUAQTTensorImpl(AQTTensorImpl):
|
| 41 |
+
"""TensorImpl for int4 CPU layout for affine quantized tensor, this is for int4 only,
|
| 42 |
+
used by tinygemm kernels `_weight_int4pack_mm_for_cpu`
|
| 43 |
+
It stores the original tensor of dimension [n][k] (int32 dtype) as packed weight of 2-d tensor of
|
| 44 |
+
dimension: [n][k / 2] (uint8 dtype)
|
| 45 |
+
(unpacked Tensor shape is n * k)
|
| 46 |
+
Note: we also pack scale and zero point together here for tinygemm kernel
|
| 47 |
+
Note: technically Int4 CPU layout should be the layout for the underlying packed weight
|
| 48 |
+
(int Tensor) but since the scale and zero_point are also packed into the same tensor here which is not used
|
| 49 |
+
in plain layout, we just created a layout for AQT right now, this could be improved if we split out
|
| 50 |
+
int4 aqt into a separate tensor subclass
|
| 51 |
+
fields:
|
| 52 |
+
packed_weight (torch.Tensor): the 2-d packed tensor in a Int4 CPU layout
|
| 53 |
+
scale_and_zero (torch.Tensor): the combined scale Tensor used to map between floating point tensor to quantized tensor and zero_point Tensor
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
def __new__(
|
| 57 |
+
cls,
|
| 58 |
+
packed_weight: torch.Tensor,
|
| 59 |
+
scale_and_zero: torch.Tensor,
|
| 60 |
+
transposed: bool,
|
| 61 |
+
_layout: Layout,
|
| 62 |
+
):
|
| 63 |
+
kwargs = {}
|
| 64 |
+
kwargs["device"] = packed_weight.device
|
| 65 |
+
kwargs["layout"] = (
|
| 66 |
+
kwargs.get("layout")
|
| 67 |
+
if kwargs.get("layout", False)
|
| 68 |
+
else packed_weight.layout
|
| 69 |
+
)
|
| 70 |
+
kwargs["dtype"] = packed_weight.dtype
|
| 71 |
+
kwargs["requires_grad"] = False
|
| 72 |
+
shape = packed_weight.shape
|
| 73 |
+
return torch.Tensor._make_wrapper_subclass(cls, shape, **kwargs) # type: ignore[attr-defined]
|
| 74 |
+
|
| 75 |
+
def __init__(
|
| 76 |
+
self,
|
| 77 |
+
packed_weight: torch.Tensor,
|
| 78 |
+
scale_and_zero: torch.Tensor,
|
| 79 |
+
transposed: bool,
|
| 80 |
+
_layout: Layout,
|
| 81 |
+
):
|
| 82 |
+
self.packed_weight = packed_weight
|
| 83 |
+
self.scale_and_zero = scale_and_zero
|
| 84 |
+
self.transposed = False
|
| 85 |
+
self._layout = _layout
|
| 86 |
+
|
| 87 |
+
def __tensor_flatten__(self):
|
| 88 |
+
return ["packed_weight", "scale_and_zero"], [self.transposed, self._layout]
|
| 89 |
+
|
| 90 |
+
@classmethod
|
| 91 |
+
def __tensor_unflatten__(
|
| 92 |
+
cls, tensor_data_dict, tensor_attributes, outer_size, outer_stride
|
| 93 |
+
):
|
| 94 |
+
packed_weight, scale_and_zero = (
|
| 95 |
+
tensor_data_dict["packed_weight"],
|
| 96 |
+
tensor_data_dict["scale_and_zero"],
|
| 97 |
+
)
|
| 98 |
+
(
|
| 99 |
+
transposed,
|
| 100 |
+
_layout,
|
| 101 |
+
) = tensor_attributes
|
| 102 |
+
return cls(packed_weight, scale_and_zero, transposed, _layout)
|
| 103 |
+
|
| 104 |
+
@classmethod
|
| 105 |
+
def from_plain(
|
| 106 |
+
cls,
|
| 107 |
+
int_data: torch.Tensor,
|
| 108 |
+
scale: torch.Tensor,
|
| 109 |
+
zero_point: Optional[torch.Tensor],
|
| 110 |
+
_layout: Layout,
|
| 111 |
+
):
|
| 112 |
+
assert isinstance(_layout, Int4CPULayout)
|
| 113 |
+
|
| 114 |
+
if TORCH_VERSION_AT_LEAST_2_6:
|
| 115 |
+
assert int_data.dtype == torch.int32, (
|
| 116 |
+
"torch.ops.aten._convert_weight_to_int4pack_for_cpu expects `int32` dtype"
|
| 117 |
+
)
|
| 118 |
+
packed_weight = torch.ops.aten._convert_weight_to_int4pack_for_cpu(
|
| 119 |
+
int_data,
|
| 120 |
+
1, # TODO:remove
|
| 121 |
+
)
|
| 122 |
+
elif TORCH_VERSION_AT_LEAST_2_5:
|
| 123 |
+
int_data = (int_data[::, ::2] << 4 | int_data[::, 1::2]).to(torch.uint8)
|
| 124 |
+
assert int_data.dtype == torch.uint8, (
|
| 125 |
+
"torch.ops.aten._convert_weight_to_int4pack in torch 2.5 expects `uint8` dtype"
|
| 126 |
+
)
|
| 127 |
+
packed_weight = torch.ops.aten._convert_weight_to_int4pack(
|
| 128 |
+
int_data, _layout.inner_k_tiles
|
| 129 |
+
)
|
| 130 |
+
else:
|
| 131 |
+
assert int_data.dtype == torch.int32, (
|
| 132 |
+
"torch.ops.aten._convert_weight_to_int4pack in torch 2.4 expects `int32` dtype"
|
| 133 |
+
)
|
| 134 |
+
packed_weight = torch.ops.aten._convert_weight_to_int4pack(
|
| 135 |
+
int_data, _layout.inner_k_tiles
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
scale = scale.reshape(int_data.shape[0], -1)
|
| 139 |
+
zero_point = zero_point.reshape(int_data.shape[0], -1)
|
| 140 |
+
from torchao.quantization.utils import pack_tinygemm_scales_and_zeros
|
| 141 |
+
|
| 142 |
+
scale_and_zero = pack_tinygemm_scales_and_zeros(scale, zero_point, scale.dtype)
|
| 143 |
+
return cls(packed_weight, scale_and_zero, False, _layout)
|
| 144 |
+
|
| 145 |
+
def to(self, *args, **kwargs):
|
| 146 |
+
kwargs = self._get_to_kwargs(*args, **kwargs)
|
| 147 |
+
device = kwargs["device"]
|
| 148 |
+
if not is_device(torch.device(self.device).type, device):
|
| 149 |
+
raise ValueError(
|
| 150 |
+
f"Int4CPUAQTTensorImpl does not support conversion from {self.device} to {device}"
|
| 151 |
+
)
|
| 152 |
+
return self.__class__(
|
| 153 |
+
self.packed_weight.to(device),
|
| 154 |
+
self.scale_and_zero.to(device),
|
| 155 |
+
self.transposed,
|
| 156 |
+
self._layout,
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
def _apply_fn_to_data(self, fn):
|
| 160 |
+
return self.__class__(
|
| 161 |
+
fn(self.packed_weight),
|
| 162 |
+
fn(self.scale_and_zero),
|
| 163 |
+
self.transposed,
|
| 164 |
+
self._layout,
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
@classmethod
|
| 168 |
+
def __torch_dispatch__(cls, func, types, args, kwargs):
|
| 169 |
+
kwargs = {} if kwargs is None else kwargs
|
| 170 |
+
|
| 171 |
+
if func is aten.detach.default:
|
| 172 |
+
return return_and_correct_aliasing(
|
| 173 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.detach)
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
if func is aten.clone.default:
|
| 177 |
+
return return_and_correct_aliasing(
|
| 178 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.clone)
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
if func is aten.t.default:
|
| 182 |
+
"""we don't need to repack the weight and just rely on external
|
| 183 |
+
shape being changed and record the status of transpose/no-transpose
|
| 184 |
+
"""
|
| 185 |
+
transposed = Int4CPUAQTTensorImpl(
|
| 186 |
+
args[0].packed_weight,
|
| 187 |
+
args[0].scale_and_zero,
|
| 188 |
+
not args[0].transposed,
|
| 189 |
+
args[0]._layout,
|
| 190 |
+
)
|
| 191 |
+
return return_and_correct_aliasing(func, args, kwargs, transposed)
|
| 192 |
+
|
| 193 |
+
if func is aten.slice.Tensor:
|
| 194 |
+
self, dim, start, end, step = fill_defaults(args, 5, [0, None, None, 1])
|
| 195 |
+
if dim in [0, 1]:
|
| 196 |
+
assert step == 1, "Only step == 1 is supported in slicing right now"
|
| 197 |
+
int_data, scale, zero_point = self.get_plain()
|
| 198 |
+
data_len = int_data.shape[dim]
|
| 199 |
+
scale_len = scale.shape[dim]
|
| 200 |
+
ratio = data_len / scale_len
|
| 201 |
+
start_scale = int(start / ratio)
|
| 202 |
+
end_scale = int(end / ratio)
|
| 203 |
+
|
| 204 |
+
int_data = aten.slice.Tensor(int_data, dim, start, end, step)
|
| 205 |
+
scale = aten.slice.Tensor(scale, dim, start_scale, end_scale, step)
|
| 206 |
+
zero_point = aten.slice.Tensor(
|
| 207 |
+
zero_point, dim, start_scale, end_scale, step
|
| 208 |
+
)
|
| 209 |
+
# this is to handle padding
|
| 210 |
+
int_data, scale, zero_point = self._layout.post_process(
|
| 211 |
+
int_data, scale, zero_point, self.block_size
|
| 212 |
+
)
|
| 213 |
+
sliced = self.from_plain(int_data, scale, zero_point, self._layout)
|
| 214 |
+
return return_and_correct_aliasing(func, args, kwargs, sliced)
|
| 215 |
+
else:
|
| 216 |
+
raise NotImplementedError(
|
| 217 |
+
f"Int4CPUAQTTensorImpl dispatch: attempting to run {func}, with dim={dim}, that is not supported"
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
raise NotImplementedError(
|
| 221 |
+
f"Int4CPUAQTTensorImpl dispatch: attempting to run {func}, this is not supported"
|
| 222 |
+
)
|
| 223 |
+
|
| 224 |
+
__torch_function__ = torch._C._disabled_torch_function_impl
|
| 225 |
+
|
| 226 |
+
@property
|
| 227 |
+
def block_size(self):
|
| 228 |
+
from torchao.quantization.utils import unpack_tinygemm_scales_and_zeros
|
| 229 |
+
|
| 230 |
+
scale, zero = unpack_tinygemm_scales_and_zeros(self.scale_and_zero)
|
| 231 |
+
cur_shape = self.shape
|
| 232 |
+
assert len(cur_shape) == 4
|
| 233 |
+
inner_k_tiles = cur_shape[-1] * 2
|
| 234 |
+
original_shape = (cur_shape[0] * 8, cur_shape[1] * (inner_k_tiles * 16))
|
| 235 |
+
groupsize = int(original_shape[1] / scale.shape[-2])
|
| 236 |
+
return (1, groupsize)
|
| 237 |
+
|
| 238 |
+
def get_plain(self) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 239 |
+
from torchao.quantization.quant_primitives import (
|
| 240 |
+
ZeroPointDomain,
|
| 241 |
+
quantize_affine,
|
| 242 |
+
)
|
| 243 |
+
from torchao.quantization.utils import unpack_tinygemm_scales_and_zeros
|
| 244 |
+
|
| 245 |
+
scale, zero = unpack_tinygemm_scales_and_zeros(self.scale_and_zero)
|
| 246 |
+
|
| 247 |
+
cur_shape = self.shape
|
| 248 |
+
assert len(cur_shape) == 2
|
| 249 |
+
original_shape = (cur_shape[0], cur_shape[1] * 2)
|
| 250 |
+
eye_shape = original_shape[1]
|
| 251 |
+
groupsize = int(original_shape[1] / scale.shape[-2])
|
| 252 |
+
block_size = (1, groupsize)
|
| 253 |
+
device = self.device
|
| 254 |
+
original_dtype = self.scale_and_zero.dtype
|
| 255 |
+
target_dtype = torch.int32
|
| 256 |
+
quant_min = 0
|
| 257 |
+
quant_max = 15
|
| 258 |
+
zero_point_domain = ZeroPointDomain.FLOAT
|
| 259 |
+
assert len(block_size) == 2 and block_size[0] == 1
|
| 260 |
+
dequantized = torch.ops.aten._weight_int4pack_mm_for_cpu(
|
| 261 |
+
torch.eye(eye_shape, device=device, dtype=original_dtype),
|
| 262 |
+
self.packed_weight,
|
| 263 |
+
groupsize,
|
| 264 |
+
self.scale_and_zero,
|
| 265 |
+
)
|
| 266 |
+
dequantized = dequantized.t().contiguous()
|
| 267 |
+
# TODO: move this to `unpack_tinygemm_scales_and_zeros`?
|
| 268 |
+
scale = scale.reshape(scale.shape[:-1]).contiguous()
|
| 269 |
+
zero = zero.reshape(zero.shape[:-1]).contiguous()
|
| 270 |
+
int_data = quantize_affine(
|
| 271 |
+
dequantized,
|
| 272 |
+
block_size,
|
| 273 |
+
scale,
|
| 274 |
+
zero,
|
| 275 |
+
target_dtype,
|
| 276 |
+
quant_min,
|
| 277 |
+
quant_max,
|
| 278 |
+
zero_point_domain,
|
| 279 |
+
)
|
| 280 |
+
return int_data, scale, zero
|
| 281 |
+
|
| 282 |
+
def get_layout(self) -> Layout:
|
| 283 |
+
return self._layout
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
def _aqt_is_uint4(aqt):
|
| 287 |
+
"""Check if an AffineQuantizedTensor is uint4 quantized Tensor"""
|
| 288 |
+
return (
|
| 289 |
+
aqt.tensor_impl.dtype == torch.uint8
|
| 290 |
+
and aqt.quant_min == 0
|
| 291 |
+
and aqt.quant_max == 15
|
| 292 |
+
)
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
def _is_float(dtype):
|
| 296 |
+
return dtype in (torch.float, torch.half, torch.bfloat16)
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
def _linear_fp_act_uint4_weight_cpu_check(input_tensor, weight_tensor, bias):
|
| 300 |
+
return (
|
| 301 |
+
TORCH_VERSION_AT_LEAST_2_6
|
| 302 |
+
and is_device(input_tensor.device.type, "cpu")
|
| 303 |
+
and is_device(weight_tensor.device.type, "cpu")
|
| 304 |
+
and (bias is None or is_device(bias.device.type, "cpu"))
|
| 305 |
+
and not is_traceable_wrapper_subclass(input_tensor)
|
| 306 |
+
and _is_float(input_tensor.dtype)
|
| 307 |
+
and isinstance(weight_tensor, AffineQuantizedTensor)
|
| 308 |
+
and _aqt_is_uint4(weight_tensor)
|
| 309 |
+
and _is_float(weight_tensor.dtype)
|
| 310 |
+
and len(weight_tensor.shape) == 2
|
| 311 |
+
and weight_tensor.zero_point_domain == ZeroPointDomain.FLOAT
|
| 312 |
+
and isinstance(weight_tensor._layout, Int4CPULayout)
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
def _linear_fp_act_uint4_weight_cpu_impl(input_tensor, weight_tensor, bias):
|
| 317 |
+
assert TORCH_VERSION_AT_LEAST_2_6, (
|
| 318 |
+
f"Requires PyTorch version at least 2.6, but got: {torch.__version__}"
|
| 319 |
+
)
|
| 320 |
+
assert is_device(input_tensor.device.type, "cpu"), (
|
| 321 |
+
f"For CPU device only but got: {input_tensor.device}"
|
| 322 |
+
)
|
| 323 |
+
assert weight_tensor.block_size[0] == 1, (
|
| 324 |
+
f"Requires groupwise quantization, got block_size: {weight_tensor.block_size}"
|
| 325 |
+
)
|
| 326 |
+
assert input_tensor.shape[-1] == weight_tensor.shape[1], (
|
| 327 |
+
f"need input_tensor shape: {input_tensor.shape} final"
|
| 328 |
+
f"dim to match weight_tensor shape: {weight_tensor.shape} second dim "
|
| 329 |
+
)
|
| 330 |
+
|
| 331 |
+
act_mat = input_tensor
|
| 332 |
+
packed_weight = weight_tensor.tensor_impl.packed_weight
|
| 333 |
+
scale_and_zero = weight_tensor.tensor_impl.scale_and_zero
|
| 334 |
+
|
| 335 |
+
orig_act_size = act_mat.size()
|
| 336 |
+
orig_dtype = act_mat.dtype
|
| 337 |
+
|
| 338 |
+
# reshape to 2D
|
| 339 |
+
act_mat = act_mat.reshape(-1, act_mat.shape[-1])
|
| 340 |
+
|
| 341 |
+
# groupwise int4 quantization
|
| 342 |
+
groupsize = weight_tensor.block_size[1]
|
| 343 |
+
y = torch.ops.aten._weight_int4pack_mm_for_cpu(
|
| 344 |
+
act_mat.contiguous(), packed_weight, groupsize, scale_and_zero
|
| 345 |
+
)
|
| 346 |
+
|
| 347 |
+
# remove out_feature padding
|
| 348 |
+
orig_out_features = weight_tensor.shape[-2]
|
| 349 |
+
y = y[:, :orig_out_features]
|
| 350 |
+
y = y.reshape(*orig_act_size[:-1], orig_out_features)
|
| 351 |
+
|
| 352 |
+
if bias is not None:
|
| 353 |
+
y += bias
|
| 354 |
+
return y.to(orig_dtype)
|
lib/python3.12/site-packages/torchao/dtypes/uintx/int4_xpu_layout.py
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dataclasses import dataclass
|
| 2 |
+
from typing import Optional, Tuple
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
from torch.utils._python_dispatch import (
|
| 6 |
+
is_traceable_wrapper_subclass,
|
| 7 |
+
return_and_correct_aliasing,
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
from torchao.dtypes.affine_quantized_tensor import (
|
| 11 |
+
AffineQuantizedTensor,
|
| 12 |
+
register_layout,
|
| 13 |
+
)
|
| 14 |
+
from torchao.dtypes.utils import AQTTensorImpl, Layout, is_device
|
| 15 |
+
from torchao.quantization.quant_primitives import ZeroPointDomain
|
| 16 |
+
from torchao.utils import (
|
| 17 |
+
TORCH_VERSION_AT_LEAST_2_8,
|
| 18 |
+
fill_defaults,
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
aten = torch.ops.aten
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def _aqt_is_xpu_layout_uint4(aqt):
|
| 25 |
+
"""Check if an AffineQuantizedTensor is uint4 quantized Tensor"""
|
| 26 |
+
# TODO: use torch.uint4
|
| 27 |
+
return (
|
| 28 |
+
aqt.tensor_impl.dtype == torch.int32
|
| 29 |
+
and aqt.quant_min == 0
|
| 30 |
+
and aqt.quant_max == 15
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _linear_bf16_act_uint4_weight_float_zero_check(input_tensor, weight_tensor, bias):
|
| 35 |
+
return (
|
| 36 |
+
# input is native bfloat16 tensor
|
| 37 |
+
not is_traceable_wrapper_subclass(input_tensor)
|
| 38 |
+
and input_tensor.dtype == torch.bfloat16
|
| 39 |
+
and
|
| 40 |
+
# weight is uint4, group quantized tensor_core_tiled tensor impl affine quantized tensor
|
| 41 |
+
isinstance(weight_tensor, AffineQuantizedTensor)
|
| 42 |
+
and _aqt_is_xpu_layout_uint4(weight_tensor)
|
| 43 |
+
and weight_tensor.dtype == torch.bfloat16
|
| 44 |
+
and len(weight_tensor.shape) == 2
|
| 45 |
+
and weight_tensor.zero_point_domain == ZeroPointDomain.FLOAT
|
| 46 |
+
and weight_tensor.tensor_impl.scale_and_zero is not None
|
| 47 |
+
and weight_tensor.tensor_impl.scale_and_zero.dtype == torch.bfloat16
|
| 48 |
+
and isinstance(weight_tensor._layout, Int4XPULayout)
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _linear_bf16_act_uint4_weight_float_zero_impl(input_tensor, weight_tensor, bias):
|
| 53 |
+
assert weight_tensor.block_size[0] == 1, (
|
| 54 |
+
f"Requires groupwise quantization, got block_size: {weight_tensor.block_size}"
|
| 55 |
+
)
|
| 56 |
+
assert input_tensor.shape[-1] == weight_tensor.shape[1], (
|
| 57 |
+
f"need input_tensor shape: {input_tensor.shape} final"
|
| 58 |
+
f"dim to match weight_tensor shape: {weight_tensor.shape} second dim "
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
# TODO: check groupsize quantization
|
| 62 |
+
# avoid circular dep, TODO: move this to a common util.py
|
| 63 |
+
act_mat = input_tensor
|
| 64 |
+
if act_mat.is_contiguous() == False:
|
| 65 |
+
act_mat = act_mat.contiguous()
|
| 66 |
+
# weight is packed from padded (out_features, in_features) weight tensor
|
| 67 |
+
# (same dimension requirement as F.linear weight)
|
| 68 |
+
packed_weight = weight_tensor.tensor_impl.packed_weight
|
| 69 |
+
scales_and_zeros = weight_tensor.tensor_impl.scale_and_zero
|
| 70 |
+
|
| 71 |
+
orig_act_size = act_mat.size()
|
| 72 |
+
orig_dtype = act_mat.dtype
|
| 73 |
+
|
| 74 |
+
act_mat = act_mat.reshape(-1, act_mat.shape[-1]).to(torch.bfloat16)
|
| 75 |
+
|
| 76 |
+
# groupwise int4 quantization
|
| 77 |
+
groupsize = weight_tensor.block_size[1]
|
| 78 |
+
y = torch.ops.aten._weight_int4pack_mm(
|
| 79 |
+
act_mat, packed_weight, groupsize, scales_and_zeros
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
# remove out_feature padding
|
| 83 |
+
orig_out_features = weight_tensor.shape[-2]
|
| 84 |
+
y = y[:, :orig_out_features]
|
| 85 |
+
y = y.reshape(*orig_act_size[:-1], orig_out_features)
|
| 86 |
+
|
| 87 |
+
if bias is not None:
|
| 88 |
+
y += bias
|
| 89 |
+
return y.to(orig_dtype)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def _linear_bf16_act_uint4_weight_int8_zero_check(input_tensor, weight_tensor, bias):
|
| 93 |
+
return (
|
| 94 |
+
# input is native bfloat16 tensor
|
| 95 |
+
not is_traceable_wrapper_subclass(input_tensor)
|
| 96 |
+
and input_tensor.dtype == torch.bfloat16
|
| 97 |
+
and
|
| 98 |
+
# weight is uint4, group quantized tensor_core_tiled tensor impl affine quantized tensor
|
| 99 |
+
isinstance(weight_tensor, AffineQuantizedTensor)
|
| 100 |
+
and _aqt_is_xpu_layout_uint4(weight_tensor)
|
| 101 |
+
and weight_tensor.dtype == torch.bfloat16
|
| 102 |
+
and len(weight_tensor.shape) == 2
|
| 103 |
+
and weight_tensor.zero_point_domain == ZeroPointDomain.INT
|
| 104 |
+
and weight_tensor.tensor_impl.scale_and_zero is None
|
| 105 |
+
and weight_tensor.tensor_impl.scale.dtype == torch.bfloat16
|
| 106 |
+
and weight_tensor.tensor_impl.zero.dtype == torch.int8
|
| 107 |
+
and isinstance(weight_tensor._layout, Int4XPULayout)
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def _linear_bf16_act_uint4_weight_int8_zero_impl(input_tensor, weight_tensor, bias):
|
| 112 |
+
assert weight_tensor.block_size[0] == 1, (
|
| 113 |
+
f"Requires groupwise quantization, got block_size: {weight_tensor.block_size}"
|
| 114 |
+
)
|
| 115 |
+
assert input_tensor.shape[-1] == weight_tensor.shape[1], (
|
| 116 |
+
f"need input_tensor shape: {input_tensor.shape} final"
|
| 117 |
+
f"dim to match weight_tensor shape: {weight_tensor.shape} second dim "
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
# TODO: check groupsize quantization
|
| 121 |
+
# avoid circular dep, TODO: move this to a common util.py
|
| 122 |
+
act_mat = input_tensor
|
| 123 |
+
# weight is packed from padded (out_features, in_features) weight tensor
|
| 124 |
+
# (same dimension requirement as F.linear weight)
|
| 125 |
+
packed_weight = weight_tensor.tensor_impl.packed_weight
|
| 126 |
+
scale = weight_tensor.tensor_impl.scale
|
| 127 |
+
zero = weight_tensor.tensor_impl.zero
|
| 128 |
+
|
| 129 |
+
orig_act_size = act_mat.size()
|
| 130 |
+
orig_dtype = act_mat.dtype
|
| 131 |
+
|
| 132 |
+
act_mat = act_mat.reshape(-1, act_mat.shape[-1]).to(torch.bfloat16)
|
| 133 |
+
|
| 134 |
+
# groupwise int4 quantization
|
| 135 |
+
groupsize = weight_tensor.block_size[1]
|
| 136 |
+
|
| 137 |
+
y = torch.ops.aten._weight_int4pack_mm_with_scales_and_zeros(
|
| 138 |
+
act_mat, packed_weight, groupsize, scale, zero
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
# remove out_feature padding
|
| 142 |
+
orig_out_features = weight_tensor.shape[-2]
|
| 143 |
+
y = y[:, :orig_out_features]
|
| 144 |
+
y = y.reshape(*orig_act_size[:-1], orig_out_features)
|
| 145 |
+
|
| 146 |
+
if bias is not None:
|
| 147 |
+
y += bias
|
| 148 |
+
return y.to(orig_dtype)
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
@dataclass(frozen=True)
|
| 152 |
+
class Int4XPULayout(Layout):
|
| 153 |
+
"""Only for PyTorch version at least 2.7"""
|
| 154 |
+
|
| 155 |
+
pass
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
@register_layout(Int4XPULayout)
|
| 159 |
+
class Int4XPUAQTTensorImpl(AQTTensorImpl):
|
| 160 |
+
"""
|
| 161 |
+
TensorImpl for int4 XPU layout for affine quantized tensor, this is for int4 only,
|
| 162 |
+
used by tinygemm kernels `_weight_int4pack_mm_xpu` and `_weight_int4pack_mm_with_zeros_and_scales` (TBD)
|
| 163 |
+
It stores the original tensor of dimension [n][k] (int32 dtype) as packed weight of 2-d tensor of
|
| 164 |
+
dimension: [n][k / 8] (int32 dtype)
|
| 165 |
+
(unpacked Tensor shape is n * k)
|
| 166 |
+
Note: we also pack scale and zero point together here for tinygemm kernel
|
| 167 |
+
Note: technically Int4 XPU layout should be the layout for the underlying packed weight
|
| 168 |
+
(int Tensor) but since the scale and zero_point are also packed into the same tensor here which is not used
|
| 169 |
+
in plain layout, we just created a layout for AQT right now, this could be improved if we split out
|
| 170 |
+
int4 aqt into a separate tensor subclass
|
| 171 |
+
fields:
|
| 172 |
+
packed_weight (torch.Tensor): the 2-d packed tensor in a Int4 XPU layout
|
| 173 |
+
[Optional] scale_and_zero (torch.Tensor): the combined scale Tensor used to map between floating point tensor to quantized tensor and zero_point Tensor
|
| 174 |
+
[Optional] scale (torch.Tensor): scale tensors, should be the same dtype of packed weight
|
| 175 |
+
[Optional] zeros (torch.Tensor): can be of the same dtype of packed weight or different dtype
|
| 176 |
+
"""
|
| 177 |
+
|
| 178 |
+
def __new__(
|
| 179 |
+
cls,
|
| 180 |
+
packed_weight: torch.Tensor,
|
| 181 |
+
scale_and_zero: torch.Tensor,
|
| 182 |
+
transposed: bool,
|
| 183 |
+
_layout: Layout,
|
| 184 |
+
scale: torch.Tensor = None,
|
| 185 |
+
zero: torch.Tensor = None,
|
| 186 |
+
):
|
| 187 |
+
kwargs = {}
|
| 188 |
+
kwargs["device"] = packed_weight.device
|
| 189 |
+
kwargs["layout"] = (
|
| 190 |
+
kwargs.get("layout")
|
| 191 |
+
if kwargs.get("layout", False)
|
| 192 |
+
else packed_weight.layout
|
| 193 |
+
)
|
| 194 |
+
kwargs["dtype"] = packed_weight.dtype
|
| 195 |
+
kwargs["requires_grad"] = False
|
| 196 |
+
shape = packed_weight.shape
|
| 197 |
+
return torch.Tensor._make_wrapper_subclass(cls, shape, **kwargs) # type: ignore[attr-defined]
|
| 198 |
+
|
| 199 |
+
def __init__(
|
| 200 |
+
self,
|
| 201 |
+
packed_weight: torch.Tensor,
|
| 202 |
+
scale_and_zero: torch.Tensor,
|
| 203 |
+
transposed: bool,
|
| 204 |
+
_layout: Layout,
|
| 205 |
+
scale: torch.Tensor = None,
|
| 206 |
+
zero: torch.Tensor = None,
|
| 207 |
+
):
|
| 208 |
+
self.packed_weight = packed_weight
|
| 209 |
+
self.scale_and_zero = scale_and_zero
|
| 210 |
+
self.transposed = False
|
| 211 |
+
self._layout = _layout
|
| 212 |
+
self.scale = scale
|
| 213 |
+
self.zero = zero
|
| 214 |
+
|
| 215 |
+
def __tensor_flatten__(self):
|
| 216 |
+
if self.scale_and_zero is not None:
|
| 217 |
+
return ["packed_weight", "scale_and_zero"], [self.transposed, self._layout]
|
| 218 |
+
else:
|
| 219 |
+
return ["packed_weight", "scale", "zero"], [self.transposed, self._layout]
|
| 220 |
+
|
| 221 |
+
@classmethod
|
| 222 |
+
def __tensor_unflatten__(
|
| 223 |
+
cls, tensor_data_dict, tensor_attributes, outer_size, outer_stride
|
| 224 |
+
):
|
| 225 |
+
packed_weight = tensor_data_dict["packed_weight"]
|
| 226 |
+
scale_and_zero = (
|
| 227 |
+
tensor_data_dict.get("scale_and_zero")
|
| 228 |
+
if "scale_and_zero" in tensor_data_dict
|
| 229 |
+
else None
|
| 230 |
+
)
|
| 231 |
+
scale = tensor_data_dict.get("scale") if "scale" in tensor_data_dict else None
|
| 232 |
+
zero = tensor_data_dict.get("zero") if "zero" in tensor_data_dict else None
|
| 233 |
+
(
|
| 234 |
+
transposed,
|
| 235 |
+
_layout,
|
| 236 |
+
) = tensor_attributes
|
| 237 |
+
return cls(packed_weight, scale_and_zero, transposed, _layout, scale, zero)
|
| 238 |
+
|
| 239 |
+
@classmethod
|
| 240 |
+
def from_plain(
|
| 241 |
+
cls,
|
| 242 |
+
int_data: torch.Tensor,
|
| 243 |
+
scale: torch.Tensor,
|
| 244 |
+
zero_point: Optional[torch.Tensor],
|
| 245 |
+
_layout: Layout,
|
| 246 |
+
):
|
| 247 |
+
assert isinstance(_layout, Int4XPULayout)
|
| 248 |
+
|
| 249 |
+
from torchao.quantization.utils import convert_weight_to_int4pack_xpu
|
| 250 |
+
|
| 251 |
+
if TORCH_VERSION_AT_LEAST_2_8:
|
| 252 |
+
assert int_data.dtype == torch.int32, (
|
| 253 |
+
"torch.ops.aten._convert_weight_to_int4pack_for_cpu expects `int32` dtype"
|
| 254 |
+
)
|
| 255 |
+
packed_weight = convert_weight_to_int4pack_xpu(
|
| 256 |
+
int_data, zero_point.dtype != scale.dtype
|
| 257 |
+
)
|
| 258 |
+
else:
|
| 259 |
+
assert False, "INT4 not supported on XPU until 2.8"
|
| 260 |
+
|
| 261 |
+
scale = scale.reshape(int_data.shape[0], -1)
|
| 262 |
+
zero_point = zero_point.reshape(int_data.shape[0], -1)
|
| 263 |
+
if zero_point.dtype == scale.dtype:
|
| 264 |
+
from torchao.quantization.utils import pack_tinygemm_scales_and_zeros
|
| 265 |
+
|
| 266 |
+
scale_and_zero = pack_tinygemm_scales_and_zeros(scale, zero_point)
|
| 267 |
+
return cls(packed_weight, scale_and_zero, False, _layout, None, None)
|
| 268 |
+
else:
|
| 269 |
+
return cls(
|
| 270 |
+
packed_weight,
|
| 271 |
+
None,
|
| 272 |
+
False,
|
| 273 |
+
_layout,
|
| 274 |
+
scale.transpose(0, 1).contiguous(),
|
| 275 |
+
zero_point.transpose(0, 1).contiguous().to(torch.int8),
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
def to(self, *args, **kwargs):
|
| 279 |
+
kwargs = self._get_to_kwargs(*args, **kwargs)
|
| 280 |
+
device = kwargs["device"]
|
| 281 |
+
if not is_device(torch.device(self.device).type, device):
|
| 282 |
+
raise ValueError(
|
| 283 |
+
f"Int4XPUAQTTensorImpl does not support conversion from {self.device} to {device}"
|
| 284 |
+
)
|
| 285 |
+
return self.__class__(
|
| 286 |
+
self.packed_weight.to(device),
|
| 287 |
+
self.scale_and_zero.to(device) if self.scale_and_zero is not None else None,
|
| 288 |
+
self.transposed,
|
| 289 |
+
self._layout,
|
| 290 |
+
self.scale.to(device) if self.scale is not None else None,
|
| 291 |
+
self.zero.to(device) if self.zero is not None else None,
|
| 292 |
+
)
|
| 293 |
+
|
| 294 |
+
def _apply_fn_to_data(self, fn):
|
| 295 |
+
return self.__class__(
|
| 296 |
+
fn(self.packed_weight),
|
| 297 |
+
fn(self.scale_and_zero) if self.scale_and_zero is not None else None,
|
| 298 |
+
self.transposed,
|
| 299 |
+
self._layout,
|
| 300 |
+
fn(self.scale) if self.scale is not None else None,
|
| 301 |
+
fn(self.zero) if self.zero is not None else None,
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
@classmethod
|
| 305 |
+
def __torch_dispatch__(cls, func, types, args, kwargs):
|
| 306 |
+
kwargs = {} if kwargs is None else kwargs
|
| 307 |
+
|
| 308 |
+
if func is aten.detach.default:
|
| 309 |
+
return return_and_correct_aliasing(
|
| 310 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.detach)
|
| 311 |
+
)
|
| 312 |
+
|
| 313 |
+
if func is aten.clone.default:
|
| 314 |
+
return return_and_correct_aliasing(
|
| 315 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.clone)
|
| 316 |
+
)
|
| 317 |
+
|
| 318 |
+
if func is aten.t.default:
|
| 319 |
+
"""we don't need to repack the weight and just rely on external
|
| 320 |
+
shape being changed and record the status of transpose/no-transpose
|
| 321 |
+
"""
|
| 322 |
+
transposed = Int4XPUAQTTensorImpl(
|
| 323 |
+
args[0].packed_weight,
|
| 324 |
+
args[0].scale_and_zero,
|
| 325 |
+
not args[0].transposed,
|
| 326 |
+
args[0]._layout,
|
| 327 |
+
args[0].scale,
|
| 328 |
+
args[0].zero,
|
| 329 |
+
)
|
| 330 |
+
return return_and_correct_aliasing(func, args, kwargs, transposed)
|
| 331 |
+
|
| 332 |
+
if func is torch.ops.aten.copy_.default:
|
| 333 |
+
return return_and_correct_aliasing(
|
| 334 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.clone)
|
| 335 |
+
)
|
| 336 |
+
|
| 337 |
+
if func is aten.slice.Tensor:
|
| 338 |
+
self, dim, start, end, step = fill_defaults(args, 5, [0, None, None, 1])
|
| 339 |
+
if dim == 0:
|
| 340 |
+
int_data, scale, zero_point = self.get_plain()
|
| 341 |
+
int_data = aten.slice.Tensor(int_data, dim, start, end, step)
|
| 342 |
+
# this is to handle padding
|
| 343 |
+
int_data = self._layout.post_process(int_data)
|
| 344 |
+
sliced = self.from_plain(int_data, scale, zero_point, self._layout)
|
| 345 |
+
return return_and_correct_aliasing(func, args, kwargs, sliced)
|
| 346 |
+
elif dim == 1:
|
| 347 |
+
int_data, scale, zero_point = self.get_plain()
|
| 348 |
+
assert step == 1, "Only step == 1 is supported in slicing right now"
|
| 349 |
+
data_len = int_data.shape[dim]
|
| 350 |
+
scale_len = scale.shape[dim]
|
| 351 |
+
ratio = data_len / scale_len
|
| 352 |
+
start_scale = int(start / ratio)
|
| 353 |
+
end_scale = int(end / ratio)
|
| 354 |
+
|
| 355 |
+
int_data = aten.slice.Tensor(int_data, dim, start, end, step)
|
| 356 |
+
# this is to handle padding
|
| 357 |
+
int_data = self._layout.post_process(int_data)
|
| 358 |
+
scale = aten.slice.Tensor(scale, dim, start_scale, end_scale, step)
|
| 359 |
+
zero_point = aten.slice.Tensor(
|
| 360 |
+
zero_point, dim, start_scale, end_scale, step
|
| 361 |
+
)
|
| 362 |
+
sliced = self.from_plain(int_data, scale, zero_point, self._layout)
|
| 363 |
+
return sliced
|
| 364 |
+
else:
|
| 365 |
+
raise NotImplementedError(
|
| 366 |
+
f"Int4XPUAQTTensorImpl dispatch: attempting to run {func}, with dim={dim}, that is not supported"
|
| 367 |
+
)
|
| 368 |
+
|
| 369 |
+
raise NotImplementedError(
|
| 370 |
+
f"Int4XPUAQTTensorImpl dispatch: attempting to run {func}, this is not supported"
|
| 371 |
+
)
|
| 372 |
+
|
| 373 |
+
__torch_function__ = torch._C._disabled_torch_function_impl
|
| 374 |
+
|
| 375 |
+
def get_plain(self) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 376 |
+
from torchao.quantization.quant_primitives import (
|
| 377 |
+
ZeroPointDomain,
|
| 378 |
+
quantize_affine,
|
| 379 |
+
)
|
| 380 |
+
from torchao.quantization.utils import unpack_tinygemm_scales_and_zeros
|
| 381 |
+
|
| 382 |
+
if self.scale_and_zero is not None:
|
| 383 |
+
scale, zero = unpack_tinygemm_scales_and_zeros(self.scale_and_zero)
|
| 384 |
+
else:
|
| 385 |
+
scale = self.scale.transpose(0, 1).contiguous()
|
| 386 |
+
zero = self.zero.transpose(0, 1).contiguous()
|
| 387 |
+
|
| 388 |
+
cur_shape = self.shape
|
| 389 |
+
assert len(cur_shape) == 2
|
| 390 |
+
original_shape = (cur_shape[0], cur_shape[1] * 8)
|
| 391 |
+
eye_shape = original_shape[1]
|
| 392 |
+
groupsize = int(original_shape[1] / scale.shape[1])
|
| 393 |
+
block_size = (1, groupsize)
|
| 394 |
+
device = self.device
|
| 395 |
+
original_dtype = torch.bfloat16
|
| 396 |
+
target_dtype = torch.int32
|
| 397 |
+
quant_min = 0
|
| 398 |
+
quant_max = 15
|
| 399 |
+
assert len(block_size) == 2 and block_size[0] == 1
|
| 400 |
+
if self.scale_and_zero is None:
|
| 401 |
+
zero_point_domain = ZeroPointDomain.INT
|
| 402 |
+
dequantized = torch.ops.aten._weight_int4pack_mm_with_scales_and_zeros(
|
| 403 |
+
torch.eye(eye_shape, device=device, dtype=original_dtype),
|
| 404 |
+
self.packed_weight,
|
| 405 |
+
groupsize,
|
| 406 |
+
self.scale,
|
| 407 |
+
self.zero,
|
| 408 |
+
)
|
| 409 |
+
dequantized = dequantized.t().contiguous()
|
| 410 |
+
int_data = quantize_affine(
|
| 411 |
+
dequantized,
|
| 412 |
+
block_size,
|
| 413 |
+
scale,
|
| 414 |
+
zero,
|
| 415 |
+
target_dtype,
|
| 416 |
+
quant_min,
|
| 417 |
+
quant_max,
|
| 418 |
+
zero_point_domain,
|
| 419 |
+
)
|
| 420 |
+
else:
|
| 421 |
+
zero_point_domain = ZeroPointDomain.FLOAT
|
| 422 |
+
dequantized = torch.ops.aten._weight_int4pack_mm(
|
| 423 |
+
torch.eye(eye_shape, device=device, dtype=original_dtype),
|
| 424 |
+
self.packed_weight,
|
| 425 |
+
groupsize,
|
| 426 |
+
self.scale_and_zero,
|
| 427 |
+
)
|
| 428 |
+
dequantized = dequantized.t().contiguous()
|
| 429 |
+
# TODO: move this to `unpack_tinygemm_scales_and_zeros`?
|
| 430 |
+
scale = scale.reshape(scale.shape[:-1]).contiguous()
|
| 431 |
+
zero = zero.reshape(zero.shape[:-1]).contiguous()
|
| 432 |
+
int_data = quantize_affine(
|
| 433 |
+
dequantized,
|
| 434 |
+
block_size,
|
| 435 |
+
scale,
|
| 436 |
+
zero,
|
| 437 |
+
target_dtype,
|
| 438 |
+
quant_min,
|
| 439 |
+
quant_max,
|
| 440 |
+
zero_point_domain,
|
| 441 |
+
)
|
| 442 |
+
return int_data, scale, zero
|
| 443 |
+
|
| 444 |
+
def get_layout(self) -> Layout:
|
| 445 |
+
return self._layout
|
lib/python3.12/site-packages/torchao/dtypes/uintx/marlin_qqq_tensor.py
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD 3-Clause license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
import logging
|
| 7 |
+
import math
|
| 8 |
+
from dataclasses import dataclass
|
| 9 |
+
from typing import Optional, Tuple
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
from torch.utils._python_dispatch import (
|
| 13 |
+
return_and_correct_aliasing,
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
from torchao.dtypes.affine_quantized_tensor import (
|
| 17 |
+
AffineQuantizedTensor,
|
| 18 |
+
get_tensor_impl_constructor,
|
| 19 |
+
register_layout,
|
| 20 |
+
)
|
| 21 |
+
from torchao.dtypes.uintx.plain_layout import (
|
| 22 |
+
_aqt_is_int8_reduced_range,
|
| 23 |
+
)
|
| 24 |
+
from torchao.dtypes.utils import AQTTensorImpl, Layout
|
| 25 |
+
from torchao.quantization.quant_primitives import (
|
| 26 |
+
ZeroPointDomain,
|
| 27 |
+
choose_qparams_and_quantize_affine_qqq,
|
| 28 |
+
dequantize_affine_qqq,
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
logger = logging.getLogger(__name__)
|
| 32 |
+
|
| 33 |
+
aten = torch.ops.aten
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class MarlinQQQTensor(AffineQuantizedTensor):
|
| 37 |
+
"""MarlinQQQ quantized tensor subclass which inherits AffineQuantizedTensor class.
|
| 38 |
+
|
| 39 |
+
To see what happens during choose_qparams_and_quantize_affine_qqq, quantization and dequantization for marlin qqq quantization,
|
| 40 |
+
please checkout https://github.com/pytorch/ao/blob/main/torchao/quantization/quant_primitives.py
|
| 41 |
+
and check the two quant primitive ops: choose_qparams_and_quantize_affine_qqq and dequantize_affine_qqq
|
| 42 |
+
"""
|
| 43 |
+
|
| 44 |
+
def dequantize(self, output_dtype: Optional[torch.dtype] = None) -> torch.Tensor:
|
| 45 |
+
if output_dtype is None:
|
| 46 |
+
output_dtype = self.dtype
|
| 47 |
+
|
| 48 |
+
int_data, s_group, s_channel = self.tensor_impl.get_plain()
|
| 49 |
+
nbits = int(math.log2(self.quant_max - self.quant_min + 1))
|
| 50 |
+
group_size = max(self.block_size)
|
| 51 |
+
return dequantize_affine_qqq(
|
| 52 |
+
int_data, s_group, s_channel, nbits, group_size, output_dtype
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
@classmethod
|
| 56 |
+
def from_hp_to_intx(
|
| 57 |
+
cls,
|
| 58 |
+
input_float: torch.Tensor,
|
| 59 |
+
block_size: Tuple[int, ...],
|
| 60 |
+
quant_min: Optional[int] = None,
|
| 61 |
+
quant_max: Optional[int] = None,
|
| 62 |
+
zero_point_domain: ZeroPointDomain = ZeroPointDomain.INT,
|
| 63 |
+
_layout: Optional[Layout] = None,
|
| 64 |
+
):
|
| 65 |
+
"""Converts a floating point tensor to a Marlin QQQ quantized tensor."""
|
| 66 |
+
if zero_point_domain is None:
|
| 67 |
+
raise ValueError("Please use ZeroPointDomain.NONE instead of None")
|
| 68 |
+
original_shape = input_float.shape
|
| 69 |
+
input_float = _layout.pre_process(input_float)
|
| 70 |
+
nbits = int(math.log2(quant_max - quant_min + 1))
|
| 71 |
+
group_size = max(block_size)
|
| 72 |
+
data, s_group, s_channel, _ = choose_qparams_and_quantize_affine_qqq(
|
| 73 |
+
input_float, nbits, group_size
|
| 74 |
+
)
|
| 75 |
+
tensor_impl_ctr = get_tensor_impl_constructor(type(_layout))
|
| 76 |
+
tensor_impl = tensor_impl_ctr(data, s_group, s_channel, _layout)
|
| 77 |
+
return cls(
|
| 78 |
+
tensor_impl,
|
| 79 |
+
block_size,
|
| 80 |
+
original_shape,
|
| 81 |
+
quant_min,
|
| 82 |
+
quant_max,
|
| 83 |
+
zero_point_domain,
|
| 84 |
+
dtype=input_float.dtype,
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
@dataclass(frozen=True)
|
| 89 |
+
class MarlinQQQLayout(Layout):
|
| 90 |
+
"""MarlinQQQLayout is a layout class for Marlin QQQ quantization."""
|
| 91 |
+
|
| 92 |
+
pass
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
@register_layout(MarlinQQQLayout)
|
| 96 |
+
class MarlinQQQAQTTensorImpl(AQTTensorImpl):
|
| 97 |
+
"""
|
| 98 |
+
TensorImpl storage class for sparse_qqq layout for affine quantized tensor.
|
| 99 |
+
|
| 100 |
+
Can only be used with 4 bits quantization for now.
|
| 101 |
+
|
| 102 |
+
Original marlin documentation and information:
|
| 103 |
+
https://github.com/IST-DASLab/marlin/tree/master
|
| 104 |
+
|
| 105 |
+
Marlin qqq information:
|
| 106 |
+
https://github.com/HandH1998/QQQ/tree/main
|
| 107 |
+
https://arxiv.org/pdf/2406.09904
|
| 108 |
+
|
| 109 |
+
fields:
|
| 110 |
+
original_shape (torch.Size): the original shape of the tensor. used to unpack the tensor to the original shape
|
| 111 |
+
group_size (int): the group size used to pack the tensor
|
| 112 |
+
num_bits (int): the number of bits used to quantize the tensor
|
| 113 |
+
"""
|
| 114 |
+
|
| 115 |
+
@staticmethod
|
| 116 |
+
def __new__(
|
| 117 |
+
cls,
|
| 118 |
+
int_data: torch.Tensor,
|
| 119 |
+
s_group: torch.Tensor,
|
| 120 |
+
s_channel: torch.Tensor,
|
| 121 |
+
_layout: Layout,
|
| 122 |
+
original_shape: torch.Size,
|
| 123 |
+
group_size: int,
|
| 124 |
+
num_bits: int,
|
| 125 |
+
):
|
| 126 |
+
kwargs = {}
|
| 127 |
+
kwargs["device"] = int_data.device
|
| 128 |
+
kwargs["layout"] = (
|
| 129 |
+
kwargs.get("layout") if kwargs.get("layout", False) else int_data.layout
|
| 130 |
+
)
|
| 131 |
+
kwargs["dtype"] = int_data.dtype
|
| 132 |
+
kwargs["requires_grad"] = False
|
| 133 |
+
shape = int_data.shape
|
| 134 |
+
return torch.Tensor._make_wrapper_subclass(cls, shape, **kwargs) # type: ignore[attr-defined]
|
| 135 |
+
|
| 136 |
+
def __init__(
|
| 137 |
+
self,
|
| 138 |
+
int_data: torch.Tensor,
|
| 139 |
+
s_group: torch.Tensor,
|
| 140 |
+
s_channel: torch.Tensor,
|
| 141 |
+
_layout: Layout,
|
| 142 |
+
original_shape: torch.Size,
|
| 143 |
+
group_size: int,
|
| 144 |
+
num_bits: int,
|
| 145 |
+
):
|
| 146 |
+
self.int_data = int_data
|
| 147 |
+
self.s_group = s_group
|
| 148 |
+
self.s_channel = s_channel
|
| 149 |
+
self._layout = _layout
|
| 150 |
+
self.original_shape = original_shape
|
| 151 |
+
self.group_size = group_size
|
| 152 |
+
self.num_bits = num_bits
|
| 153 |
+
|
| 154 |
+
@classmethod
|
| 155 |
+
def __torch_dispatch__(cls, func, types, args, kwargs):
|
| 156 |
+
kwargs = {} if kwargs is None else kwargs
|
| 157 |
+
|
| 158 |
+
if func is aten.detach.default:
|
| 159 |
+
return return_and_correct_aliasing(
|
| 160 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.detach)
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
raise NotImplementedError(
|
| 164 |
+
f"MarlinQQQAQTTensorImpl dispatch: attempting to run {func}, this is not supported"
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
def __tensor_flatten__(self):
|
| 168 |
+
return ["int_data", "s_group", "s_channel"], [
|
| 169 |
+
self._layout,
|
| 170 |
+
self.original_shape,
|
| 171 |
+
self.group_size,
|
| 172 |
+
self.num_bits,
|
| 173 |
+
]
|
| 174 |
+
|
| 175 |
+
@classmethod
|
| 176 |
+
def __tensor_unflatten__(
|
| 177 |
+
cls, tensor_data_dict, tensor_attributes, outer_size, outer_stride
|
| 178 |
+
):
|
| 179 |
+
int_data = tensor_data_dict["int_data"]
|
| 180 |
+
s_group = tensor_data_dict["s_group"]
|
| 181 |
+
s_channel = tensor_data_dict["s_channel"]
|
| 182 |
+
_layout, original_shape, group_size, num_bits = tensor_attributes
|
| 183 |
+
return cls(
|
| 184 |
+
int_data, s_group, s_channel, _layout, original_shape, group_size, num_bits
|
| 185 |
+
)
|
| 186 |
+
|
| 187 |
+
def get_plain(self):
|
| 188 |
+
from torchao.quantization.marlin_qqq import (
|
| 189 |
+
unpack_from_marlin_qqq,
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
int_data_expanded, s_group_expanded, s_channel_expanded = (
|
| 193 |
+
unpack_from_marlin_qqq(
|
| 194 |
+
self.int_data,
|
| 195 |
+
self.s_group,
|
| 196 |
+
self.s_channel,
|
| 197 |
+
self.original_shape,
|
| 198 |
+
self.num_bits,
|
| 199 |
+
self.group_size,
|
| 200 |
+
)
|
| 201 |
+
)
|
| 202 |
+
int_data_expanded_t = int_data_expanded.t()
|
| 203 |
+
s_group_expanded_t = s_group_expanded.t()
|
| 204 |
+
s_channel_expanded_t = s_channel_expanded.t()
|
| 205 |
+
return int_data_expanded_t, s_group_expanded_t, s_channel_expanded_t
|
| 206 |
+
|
| 207 |
+
@classmethod
|
| 208 |
+
def from_plain(
|
| 209 |
+
cls,
|
| 210 |
+
int_data: torch.Tensor,
|
| 211 |
+
s_group: torch.Tensor,
|
| 212 |
+
s_channel: torch.Tensor,
|
| 213 |
+
_layout: Layout,
|
| 214 |
+
):
|
| 215 |
+
from torchao.quantization.marlin_qqq import (
|
| 216 |
+
const,
|
| 217 |
+
pack_to_marlin_qqq,
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
assert isinstance(_layout, MarlinQQQLayout)
|
| 221 |
+
|
| 222 |
+
# Linear layers are (in_features, out_features) but the int_data that is reaching this point
|
| 223 |
+
# is (out_features, in_features). We need to transpose it to match the expected shape in the marlin code.
|
| 224 |
+
q_w = int_data.t()
|
| 225 |
+
s_group_t = s_group.t()
|
| 226 |
+
s_channel_t = s_channel.t()
|
| 227 |
+
|
| 228 |
+
if not torch.cuda.get_device_capability()[0] >= 8:
|
| 229 |
+
raise ValueError(
|
| 230 |
+
f"Can not use Marlin QQQ int4*int8 kernel with a device of compute capability {torch.cuda.get_device_capability()}, the minimum compute capability is 8.0 for Marlin kernel."
|
| 231 |
+
)
|
| 232 |
+
|
| 233 |
+
if q_w.dtype != torch.int32:
|
| 234 |
+
raise ValueError("Only `torch.int32` weights are supported.")
|
| 235 |
+
|
| 236 |
+
in_features, out_features = q_w.shape
|
| 237 |
+
# (thread_k, thread_n)
|
| 238 |
+
thread_config = [(64, 256), (128, 128), (128, 64), (64, 128)]
|
| 239 |
+
if not any(
|
| 240 |
+
[
|
| 241 |
+
in_features % thread_k == 0 and out_features % thread_n == 0
|
| 242 |
+
for thread_k, thread_n in thread_config
|
| 243 |
+
]
|
| 244 |
+
):
|
| 245 |
+
raise ValueError(
|
| 246 |
+
"Not supported `in_features`: {} and `out_features`: {}.".format(
|
| 247 |
+
in_features, out_features
|
| 248 |
+
)
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
num_bits = 4 if torch.max(q_w) - torch.min(q_w) < 16 else -1
|
| 252 |
+
if num_bits not in [4]:
|
| 253 |
+
raise ValueError(f"Only {[4]} bits are supported, got {num_bits}.")
|
| 254 |
+
|
| 255 |
+
if s_group.numel() == 0:
|
| 256 |
+
group_size = -1
|
| 257 |
+
else:
|
| 258 |
+
group_size = in_features // s_group_t.shape[0]
|
| 259 |
+
assert group_size <= in_features, (
|
| 260 |
+
"Group size must be less than or equal to in_features."
|
| 261 |
+
)
|
| 262 |
+
|
| 263 |
+
if group_size not in const.SUPPORTED_GROUP_SIZES:
|
| 264 |
+
raise ValueError(
|
| 265 |
+
f"Only {const.SUPPORTED_GROUP_SIZES} group sizes are supported, got {group_size}."
|
| 266 |
+
)
|
| 267 |
+
|
| 268 |
+
# Compress quantized weight to marlin format
|
| 269 |
+
marlin_qqq_q_w, marlin_qqq_s_group, marlin_qqq_s_channel = pack_to_marlin_qqq(
|
| 270 |
+
q_w, s_group_t, s_channel_t, num_bits, group_size
|
| 271 |
+
)
|
| 272 |
+
|
| 273 |
+
return cls(
|
| 274 |
+
marlin_qqq_q_w,
|
| 275 |
+
marlin_qqq_s_group,
|
| 276 |
+
marlin_qqq_s_channel,
|
| 277 |
+
_layout,
|
| 278 |
+
q_w.shape,
|
| 279 |
+
group_size,
|
| 280 |
+
num_bits,
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
def get_layout(self) -> Layout:
|
| 284 |
+
return self._layout
|
| 285 |
+
|
| 286 |
+
def _apply_fn_to_data(self, fn):
|
| 287 |
+
self.int_data = fn(self.int_data)
|
| 288 |
+
self.s_group = fn(self.s_group)
|
| 289 |
+
self.s_channel = fn(self.s_channel)
|
| 290 |
+
return self
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
def _linear_int8_act_int4_weight_marlin_qqq_check(input_tensor, weight_tensor, bias):
|
| 294 |
+
return (
|
| 295 |
+
isinstance(input_tensor, AffineQuantizedTensor)
|
| 296 |
+
and _aqt_is_int8_reduced_range(input_tensor)
|
| 297 |
+
and input_tensor.dtype == torch.float16
|
| 298 |
+
and input_tensor.tensor_impl.scale.dtype == torch.float32
|
| 299 |
+
and len(input_tensor.tensor_impl.scale.shape) == len(input_tensor.shape) - 1
|
| 300 |
+
and isinstance(weight_tensor, AffineQuantizedTensor)
|
| 301 |
+
and weight_tensor.tensor_impl.dtype == torch.int32
|
| 302 |
+
and len(weight_tensor.shape) == 2
|
| 303 |
+
and isinstance(weight_tensor._layout, MarlinQQQLayout)
|
| 304 |
+
)
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
def _linear_int8_act_int4_weight_marlin_qqq_impl(input_tensor, weight_tensor, bias):
|
| 308 |
+
from torchao.ops import marlin_qqq_gemm
|
| 309 |
+
from torchao.quantization.marlin_qqq import marlin_qqq_workspace
|
| 310 |
+
|
| 311 |
+
assert isinstance(input_tensor, AffineQuantizedTensor)
|
| 312 |
+
assert isinstance(weight_tensor, AffineQuantizedTensor)
|
| 313 |
+
|
| 314 |
+
input = input_tensor.tensor_impl.int_data
|
| 315 |
+
input_scale = input_tensor.tensor_impl.scale
|
| 316 |
+
|
| 317 |
+
w_int4 = weight_tensor.tensor_impl.int_data
|
| 318 |
+
s_group = weight_tensor.tensor_impl.s_group
|
| 319 |
+
s_channel = weight_tensor.tensor_impl.s_channel
|
| 320 |
+
original_shape = weight_tensor.tensor_impl.original_shape
|
| 321 |
+
|
| 322 |
+
# Folds batch dimension into the first dimension
|
| 323 |
+
input_2d = input.view(-1, input.shape[-1])
|
| 324 |
+
input_scale = input_scale.view(1, -1)
|
| 325 |
+
|
| 326 |
+
size_m = input_2d.shape[0]
|
| 327 |
+
size_n = s_channel.shape[1]
|
| 328 |
+
size_k = input_2d.shape[1]
|
| 329 |
+
workspace_qqq = marlin_qqq_workspace(original_shape[1])
|
| 330 |
+
|
| 331 |
+
out = marlin_qqq_gemm(
|
| 332 |
+
input_2d,
|
| 333 |
+
w_int4,
|
| 334 |
+
input_scale,
|
| 335 |
+
s_channel,
|
| 336 |
+
s_group,
|
| 337 |
+
workspace_qqq,
|
| 338 |
+
size_m,
|
| 339 |
+
size_n,
|
| 340 |
+
size_k,
|
| 341 |
+
)
|
| 342 |
+
|
| 343 |
+
# Unfold the batch dimension
|
| 344 |
+
out = out.reshape(input.shape[:-1] + (s_channel.shape[1],))
|
| 345 |
+
|
| 346 |
+
if bias is not None:
|
| 347 |
+
out += bias.to(out.dtype)
|
| 348 |
+
return out
|
| 349 |
+
|
| 350 |
+
|
| 351 |
+
to_marlinqqq_quantized_intx = MarlinQQQTensor.from_hp_to_intx
|
lib/python3.12/site-packages/torchao/dtypes/uintx/marlin_sparse_layout.py
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD 3-Clause license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
from torch.utils._python_dispatch import (
|
| 10 |
+
return_and_correct_aliasing,
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
from torchao.dtypes.affine_quantized_tensor import (
|
| 14 |
+
AffineQuantizedTensor,
|
| 15 |
+
register_layout,
|
| 16 |
+
)
|
| 17 |
+
from torchao.dtypes.uintx.tensor_core_tiled_layout import _aqt_is_tensor_core_tile_uint4
|
| 18 |
+
from torchao.dtypes.utils import AQTTensorImpl, Layout
|
| 19 |
+
from torchao.quantization.quant_primitives import (
|
| 20 |
+
ZeroPointDomain,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
aten = torch.ops.aten
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def _linear_fp_act_int4_weight_sparse_marlin_check(input_tensor, weight_tensor, bias):
|
| 27 |
+
return (
|
| 28 |
+
isinstance(weight_tensor, AffineQuantizedTensor)
|
| 29 |
+
and _aqt_is_tensor_core_tile_uint4(weight_tensor)
|
| 30 |
+
and input_tensor.dtype == torch.float16
|
| 31 |
+
and len(weight_tensor.shape) == 2
|
| 32 |
+
and weight_tensor.zero_point_domain == ZeroPointDomain.INT
|
| 33 |
+
and isinstance(weight_tensor._layout, MarlinSparseLayout)
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _linear_fp_act_int4_weight_sparse_marlin_impl(input_tensor, weight_tensor, bias):
|
| 38 |
+
from torchao.ops import marlin_24_gemm
|
| 39 |
+
from torchao.sparsity.marlin import marlin_24_workspace
|
| 40 |
+
|
| 41 |
+
assert isinstance(weight_tensor, AffineQuantizedTensor)
|
| 42 |
+
|
| 43 |
+
sparse_w_int4 = weight_tensor.tensor_impl.int_data
|
| 44 |
+
scale = weight_tensor.tensor_impl.scale
|
| 45 |
+
meta = weight_tensor.tensor_impl.meta
|
| 46 |
+
original_shape = weight_tensor.tensor_impl.original_shape
|
| 47 |
+
num_bits = weight_tensor.tensor_impl.num_bits
|
| 48 |
+
|
| 49 |
+
# Folds batch dimension into the first dimension
|
| 50 |
+
input_2d = input_tensor.view(-1, input_tensor.shape[-1])
|
| 51 |
+
|
| 52 |
+
size_m = input_2d.shape[0]
|
| 53 |
+
size_n = scale.shape[1]
|
| 54 |
+
size_k = input_2d.shape[1]
|
| 55 |
+
workspace_24 = marlin_24_workspace(original_shape[1])
|
| 56 |
+
|
| 57 |
+
out = marlin_24_gemm(
|
| 58 |
+
input_2d,
|
| 59 |
+
sparse_w_int4,
|
| 60 |
+
meta,
|
| 61 |
+
scale,
|
| 62 |
+
workspace_24,
|
| 63 |
+
num_bits,
|
| 64 |
+
size_m,
|
| 65 |
+
size_n,
|
| 66 |
+
size_k,
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
# Unfold the batch dimension
|
| 70 |
+
out = out.reshape(input_tensor.shape[:-1] + (scale.shape[1],))
|
| 71 |
+
|
| 72 |
+
if bias is not None:
|
| 73 |
+
out += bias.to(out.dtype)
|
| 74 |
+
return out
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
@dataclass(frozen=True)
|
| 78 |
+
class MarlinSparseLayout(Layout):
|
| 79 |
+
"""MarlinSparseLayout is a layout class for handling sparse tensor formats
|
| 80 |
+
specifically designed for the Marlin sparse kernel. This layout is used
|
| 81 |
+
to optimize the storage and computation of affine quantized tensors with
|
| 82 |
+
2:4 sparsity patterns.
|
| 83 |
+
|
| 84 |
+
The layout ensures that the tensor data is pre-processed and stored in a
|
| 85 |
+
format that is compatible with the Marlin sparse kernel operations. It
|
| 86 |
+
provides methods for preprocessing input tensors and managing the layout
|
| 87 |
+
of quantized tensors.
|
| 88 |
+
"""
|
| 89 |
+
|
| 90 |
+
def pre_process(self, input: torch.Tensor) -> torch.Tensor:
|
| 91 |
+
"""Preprocess the input tensor to be in the correct format for the Marlin sparse kernel.
|
| 92 |
+
- 1º: the input tensor is transposed since the linear layer keeps the weights in a transposed format
|
| 93 |
+
- 2º: tensor is injected with 2:4 sparsity
|
| 94 |
+
- 3º: transposes it again because the quantization process will compute the scales for dim=-1
|
| 95 |
+
|
| 96 |
+
Args:
|
| 97 |
+
input (torch.Tensor): the input tensor to preprocess
|
| 98 |
+
|
| 99 |
+
Returns:
|
| 100 |
+
torch.Tensor: the preprocessed tensor
|
| 101 |
+
"""
|
| 102 |
+
from torchao.sparsity.marlin import inject_24 # avoid circular import
|
| 103 |
+
|
| 104 |
+
input_t = input.t()
|
| 105 |
+
w_24, _ = inject_24(input_t, *input_t.shape)
|
| 106 |
+
return w_24.t()
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
@register_layout(MarlinSparseLayout)
|
| 110 |
+
class MarlinSparseAQTTensorImpl(AQTTensorImpl):
|
| 111 |
+
"""
|
| 112 |
+
TensorImpl for sparse_marlin_24 layout for affine quantized tensor.
|
| 113 |
+
|
| 114 |
+
Can be used with 4 bits and 8 bits quantization.
|
| 115 |
+
|
| 116 |
+
Original marlin documentation and information:
|
| 117 |
+
https://github.com/IST-DASLab/marlin/tree/master
|
| 118 |
+
|
| 119 |
+
Sparse marlin documentation and information:
|
| 120 |
+
https://github.com/IST-DASLab/Sparse-Marlin?tab=readme-ov-file
|
| 121 |
+
|
| 122 |
+
fields:
|
| 123 |
+
original_shape (torch.Size): the original shape of the tensor. used to unpack the tensor to the original shape
|
| 124 |
+
group_size (int): the group size used to pack the tensor
|
| 125 |
+
num_bits (int): the number of bits used to quantize the tensor
|
| 126 |
+
"""
|
| 127 |
+
|
| 128 |
+
@staticmethod
|
| 129 |
+
def __new__(
|
| 130 |
+
cls,
|
| 131 |
+
int_data: torch.Tensor,
|
| 132 |
+
scale: torch.Tensor,
|
| 133 |
+
zero_point: torch.Tensor,
|
| 134 |
+
meta: torch.Tensor,
|
| 135 |
+
_layout: Layout,
|
| 136 |
+
original_shape: torch.Size,
|
| 137 |
+
group_size: int,
|
| 138 |
+
num_bits: int,
|
| 139 |
+
):
|
| 140 |
+
kwargs = {}
|
| 141 |
+
kwargs["device"] = int_data.device
|
| 142 |
+
kwargs["layout"] = (
|
| 143 |
+
kwargs.get("layout") if kwargs.get("layout", False) else int_data.layout
|
| 144 |
+
)
|
| 145 |
+
kwargs["dtype"] = int_data.dtype
|
| 146 |
+
kwargs["requires_grad"] = False
|
| 147 |
+
shape = int_data.shape
|
| 148 |
+
return torch.Tensor._make_wrapper_subclass(cls, shape, **kwargs) # type: ignore[attr-defined]
|
| 149 |
+
|
| 150 |
+
def __init__(
|
| 151 |
+
self,
|
| 152 |
+
int_data: torch.Tensor,
|
| 153 |
+
scale: torch.Tensor,
|
| 154 |
+
zero_point: torch.Tensor,
|
| 155 |
+
meta: torch.Tensor,
|
| 156 |
+
_layout: Layout,
|
| 157 |
+
original_shape: torch.Size,
|
| 158 |
+
group_size: int,
|
| 159 |
+
num_bits: int,
|
| 160 |
+
):
|
| 161 |
+
self.int_data = int_data
|
| 162 |
+
self.scale = scale
|
| 163 |
+
self.zero_point = zero_point
|
| 164 |
+
self.meta = meta
|
| 165 |
+
self._layout = _layout
|
| 166 |
+
self.original_shape = original_shape
|
| 167 |
+
self.group_size = group_size
|
| 168 |
+
self.num_bits = num_bits
|
| 169 |
+
|
| 170 |
+
@classmethod
|
| 171 |
+
def __torch_dispatch__(cls, func, types, args, kwargs):
|
| 172 |
+
kwargs = {} if kwargs is None else kwargs
|
| 173 |
+
|
| 174 |
+
if func is aten.detach.default:
|
| 175 |
+
return return_and_correct_aliasing(
|
| 176 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.detach)
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
raise NotImplementedError(
|
| 180 |
+
f"MarlinSparseAQTTensorImpl dispatch: attempting to run {func}, this is not supported"
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
def __tensor_flatten__(self):
|
| 184 |
+
return ["int_data", "scale", "zero_point", "meta"], [
|
| 185 |
+
self._layout,
|
| 186 |
+
self.original_shape,
|
| 187 |
+
self.group_size,
|
| 188 |
+
self.num_bits,
|
| 189 |
+
]
|
| 190 |
+
|
| 191 |
+
@classmethod
|
| 192 |
+
def __tensor_unflatten__(
|
| 193 |
+
cls, tensor_data_dict, tensor_attributes, outer_size, outer_stride
|
| 194 |
+
):
|
| 195 |
+
int_data = tensor_data_dict["int_data"]
|
| 196 |
+
scale = tensor_data_dict["scale"]
|
| 197 |
+
zero_point = tensor_data_dict["zero_point"]
|
| 198 |
+
meta = tensor_data_dict["meta"]
|
| 199 |
+
_layout, original_shape, group_size, num_bits = tensor_attributes
|
| 200 |
+
return cls(
|
| 201 |
+
int_data,
|
| 202 |
+
scale,
|
| 203 |
+
zero_point,
|
| 204 |
+
meta,
|
| 205 |
+
_layout,
|
| 206 |
+
original_shape,
|
| 207 |
+
group_size,
|
| 208 |
+
num_bits,
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
def get_plain(self):
|
| 212 |
+
from torchao.sparsity.marlin import (
|
| 213 |
+
unpack_from_marlin_24,
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
int_data_expanded, scales_expanded = unpack_from_marlin_24(
|
| 217 |
+
self.int_data,
|
| 218 |
+
self.scale,
|
| 219 |
+
self.meta,
|
| 220 |
+
self.original_shape,
|
| 221 |
+
self.group_size,
|
| 222 |
+
self.num_bits,
|
| 223 |
+
)
|
| 224 |
+
int_data_expanded_t = int_data_expanded.t()
|
| 225 |
+
scales_expanded_t = scales_expanded.t()
|
| 226 |
+
return int_data_expanded_t, scales_expanded_t, self.zero_point
|
| 227 |
+
|
| 228 |
+
@classmethod
|
| 229 |
+
def from_plain(
|
| 230 |
+
cls,
|
| 231 |
+
int_data: torch.Tensor,
|
| 232 |
+
scale: torch.Tensor,
|
| 233 |
+
zero_point: torch.Tensor,
|
| 234 |
+
_layout: Layout,
|
| 235 |
+
):
|
| 236 |
+
from torchao.sparsity.marlin import (
|
| 237 |
+
const,
|
| 238 |
+
pack_to_marlin_24,
|
| 239 |
+
)
|
| 240 |
+
|
| 241 |
+
assert isinstance(_layout, MarlinSparseLayout)
|
| 242 |
+
|
| 243 |
+
# Linear layers are (in_features, out_features) but the int_data that is reaching this point
|
| 244 |
+
# is (out_features, in_features). We need to transpose it to match the expected shape in the marlin code.
|
| 245 |
+
q_w_24 = int_data.t()
|
| 246 |
+
# addressing the case when scale has dimension 1, happens when
|
| 247 |
+
# weight_shape[-1] == group_size == 128
|
| 248 |
+
if scale.ndim == 1:
|
| 249 |
+
scale = scale.reshape(scale.shape[0], -1)
|
| 250 |
+
|
| 251 |
+
scale_t = scale.t()
|
| 252 |
+
|
| 253 |
+
if not torch.cuda.get_device_capability()[0] >= 8:
|
| 254 |
+
raise ValueError(
|
| 255 |
+
f"Can not use Sparse Marlin 2:4 int4*fp16 kernel with a device of compute capability {torch.cuda.get_device_capability()}, the minimum compute capability is 8.0 for Marlin kernel."
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
if q_w_24.dtype != torch.int32:
|
| 259 |
+
raise ValueError("Only `torch.int32` weights are supported.")
|
| 260 |
+
|
| 261 |
+
in_features, out_features = q_w_24.shape
|
| 262 |
+
if in_features % 128 != 0 or out_features != 256 == 0:
|
| 263 |
+
raise ValueError(
|
| 264 |
+
"`in_features` must be divisible by 64 and `out_features` by 256."
|
| 265 |
+
)
|
| 266 |
+
|
| 267 |
+
# NOTE: The current marlin 2:4 kernel supports both 4 and 8 bits quantization but fp8
|
| 268 |
+
# will require a bit more work to get our current quantization flow to work with it.
|
| 269 |
+
# Check the link for a reference: https://github.com/neuralmagic/nm-vllm/tree/main
|
| 270 |
+
num_bits = 4 if torch.max(q_w_24) < 16 else -1
|
| 271 |
+
if num_bits not in [4]:
|
| 272 |
+
raise ValueError(f"Only {[4]} bits are supported, got {num_bits}.")
|
| 273 |
+
|
| 274 |
+
group_size = in_features // scale_t.shape[0]
|
| 275 |
+
if group_size == 0:
|
| 276 |
+
group_size = in_features
|
| 277 |
+
assert group_size <= in_features, (
|
| 278 |
+
"Group size must be less than or equal to in_features."
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
if group_size not in const.SUPPORTED_GROUP_SIZES:
|
| 282 |
+
raise ValueError(
|
| 283 |
+
f"Only {const.SUPPORTED_GROUP_SIZES} group sizes are supported, got {group_size}."
|
| 284 |
+
)
|
| 285 |
+
|
| 286 |
+
# Compress quantized weight to marlin 2:4 format
|
| 287 |
+
marlin_24_q_w_comp, marlin_24_s, meta = pack_to_marlin_24(
|
| 288 |
+
q_w_24, scale_t, num_bits, group_size
|
| 289 |
+
)
|
| 290 |
+
|
| 291 |
+
return cls(
|
| 292 |
+
marlin_24_q_w_comp,
|
| 293 |
+
marlin_24_s,
|
| 294 |
+
zero_point,
|
| 295 |
+
meta,
|
| 296 |
+
_layout,
|
| 297 |
+
q_w_24.shape,
|
| 298 |
+
group_size,
|
| 299 |
+
num_bits,
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
def get_layout(self) -> Layout:
|
| 303 |
+
return self._layout
|
| 304 |
+
|
| 305 |
+
def _apply_fn_to_data(self, fn):
|
| 306 |
+
self.int_data = fn(self.int_data)
|
| 307 |
+
self.scale = fn(self.scale)
|
| 308 |
+
self.zero_point = fn(self.zero_point)
|
| 309 |
+
self.meta = fn(self.meta)
|
| 310 |
+
return self
|
lib/python3.12/site-packages/torchao/dtypes/uintx/packed_linear_int8_dynamic_activation_intx_weight_layout.py
ADDED
|
@@ -0,0 +1,467 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
import logging
|
| 8 |
+
from enum import Enum, auto
|
| 9 |
+
from typing import Optional, Tuple, Union
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
from torch.utils._python_dispatch import return_and_correct_aliasing
|
| 13 |
+
|
| 14 |
+
from torchao.dtypes.affine_quantized_tensor import register_layout
|
| 15 |
+
from torchao.dtypes.utils import AQTTensorImpl, Layout
|
| 16 |
+
from torchao.experimental.op_lib_utils import _check_torchao_ops_loaded
|
| 17 |
+
from torchao.quantization.quant_primitives import (
|
| 18 |
+
_DTYPE_TO_BIT_WIDTH,
|
| 19 |
+
_DTYPE_TO_QVALUE_BOUNDS,
|
| 20 |
+
ZeroPointDomain,
|
| 21 |
+
)
|
| 22 |
+
from torchao.utils import TORCH_VERSION_AT_LEAST_2_6
|
| 23 |
+
|
| 24 |
+
logger = logging.getLogger(__name__)
|
| 25 |
+
logger.setLevel(logging.WARNING)
|
| 26 |
+
|
| 27 |
+
import sys
|
| 28 |
+
|
| 29 |
+
handler = logging.StreamHandler(sys.stdout)
|
| 30 |
+
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
| 31 |
+
handler.setFormatter(formatter)
|
| 32 |
+
logger.addHandler(handler)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class Target(Enum):
|
| 36 |
+
"""Enum that indicates the backend target"""
|
| 37 |
+
|
| 38 |
+
# AUTO target will automatically select a packing format
|
| 39 |
+
# based on the available hardware.
|
| 40 |
+
AUTO = auto()
|
| 41 |
+
UNIVERSAL = auto()
|
| 42 |
+
KLEIDIAI = auto()
|
| 43 |
+
|
| 44 |
+
# ATEN target will use the ATen operator
|
| 45 |
+
ATEN = auto()
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
_TARGET_AND_STR = [
|
| 49 |
+
(Target.AUTO, "auto"),
|
| 50 |
+
(Target.ATEN, "aten"),
|
| 51 |
+
(Target.UNIVERSAL, "universal"),
|
| 52 |
+
(Target.KLEIDIAI, "kleidiai"),
|
| 53 |
+
]
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def target_to_str(target: Target) -> str:
|
| 57 |
+
target_to_str = {t: s for t, s in _TARGET_AND_STR}
|
| 58 |
+
return target_to_str[target]
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def target_from_str(target: str) -> Target:
|
| 62 |
+
str_to_target = {s: t for t, s in _TARGET_AND_STR}
|
| 63 |
+
if target.lower() in str_to_target:
|
| 64 |
+
return str_to_target[target.lower()]
|
| 65 |
+
raise ValueError(f"Invalid target: {target}")
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class PackedLinearInt8DynamicActivationIntxWeightLayout(Layout):
|
| 69 |
+
bit_width: Optional[int]
|
| 70 |
+
group_size: Optional[int]
|
| 71 |
+
has_weight_zeros: Optional[bool]
|
| 72 |
+
has_bias: Optional[bool]
|
| 73 |
+
target: Optional[Target]
|
| 74 |
+
|
| 75 |
+
def __init__(
|
| 76 |
+
self,
|
| 77 |
+
target: Union[str, Target] = "auto",
|
| 78 |
+
):
|
| 79 |
+
if isinstance(target, str):
|
| 80 |
+
target = target_from_str(target)
|
| 81 |
+
self.target = target
|
| 82 |
+
|
| 83 |
+
self.bit_width: Optional[int] = None
|
| 84 |
+
self.group_size: Optional[int] = None
|
| 85 |
+
self.has_weight_zeros: Optional[bool] = None
|
| 86 |
+
# has_bias is whether the packed weights
|
| 87 |
+
# have bias packed with them, not whether the
|
| 88 |
+
# linear operator has bias
|
| 89 |
+
self.has_bias: Optional[bool] = None
|
| 90 |
+
|
| 91 |
+
def extra_repr(self):
|
| 92 |
+
return f"group_size={self.group_size}, bit_width={self.bit_width}, has_weight_zeros={self.has_weight_zeros}, has_bias={self.has_bias}, target={self.target}"
|
| 93 |
+
|
| 94 |
+
def has_params_set(self) -> bool:
|
| 95 |
+
return (
|
| 96 |
+
(self.bit_width is not None)
|
| 97 |
+
and (self.group_size is not None)
|
| 98 |
+
and (self.has_weight_zeros is not None)
|
| 99 |
+
and (self.has_bias is not None)
|
| 100 |
+
and (self.target is not None)
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
def set_params(
|
| 104 |
+
self, bit_width: int, group_size: int, has_weight_zeros: bool, has_bias: bool
|
| 105 |
+
):
|
| 106 |
+
assert bit_width >= 1 and bit_width <= 8, "bit_width must be 1 to 8"
|
| 107 |
+
assert group_size >= 1, f"group_size must be positive, got {group_size}"
|
| 108 |
+
|
| 109 |
+
self.bit_width = bit_width
|
| 110 |
+
self.group_size = group_size
|
| 111 |
+
self.has_weight_zeros = has_weight_zeros
|
| 112 |
+
self.has_bias = has_bias
|
| 113 |
+
assert self.has_params_set()
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
@register_layout(PackedLinearInt8DynamicActivationIntxWeightLayout)
|
| 117 |
+
class PackedLinearInt8DynamicActivationIntxWeightAQTTensorImpl(AQTTensorImpl):
|
| 118 |
+
def __new__(
|
| 119 |
+
cls,
|
| 120 |
+
packed_weight: torch.Tensor,
|
| 121 |
+
_layout: Layout,
|
| 122 |
+
):
|
| 123 |
+
kwargs = {}
|
| 124 |
+
kwargs["device"] = packed_weight.device
|
| 125 |
+
kwargs["dtype"] = packed_weight.dtype
|
| 126 |
+
assert not packed_weight.requires_grad
|
| 127 |
+
kwargs["requires_grad"] = False
|
| 128 |
+
shape = packed_weight.shape
|
| 129 |
+
return torch.Tensor._make_wrapper_subclass(cls, shape, **kwargs) # type: ignore[attr-defined]
|
| 130 |
+
|
| 131 |
+
def __init__(
|
| 132 |
+
self,
|
| 133 |
+
packed_weight: torch.Tensor,
|
| 134 |
+
_layout: Layout,
|
| 135 |
+
):
|
| 136 |
+
assert isinstance(_layout, PackedLinearInt8DynamicActivationIntxWeightLayout)
|
| 137 |
+
self.packed_weight = packed_weight
|
| 138 |
+
self._layout = _layout
|
| 139 |
+
|
| 140 |
+
def __repr__(self):
|
| 141 |
+
return f"{self.__class__.__name__}(packed_weight={str(self.packed_weight)}, layout={self.get_layout()})"
|
| 142 |
+
|
| 143 |
+
def get_layout(self) -> Layout:
|
| 144 |
+
return self._layout
|
| 145 |
+
|
| 146 |
+
def get_plain(self) -> Tuple[torch.Tensor, torch.Tensor, Optional[torch.Tensor]]:
|
| 147 |
+
raise NotImplementedError(
|
| 148 |
+
"get_plain is not implemented for PackedLinearInt8DynamicActivationIntxWeightAQTTensorImpl"
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
@classmethod
|
| 152 |
+
def from_plain(
|
| 153 |
+
cls,
|
| 154 |
+
int_data: torch.Tensor,
|
| 155 |
+
scale: torch.Tensor,
|
| 156 |
+
zero_point: Optional[torch.Tensor],
|
| 157 |
+
layout: Layout,
|
| 158 |
+
bias: Optional[torch.Tensor] = None,
|
| 159 |
+
*,
|
| 160 |
+
validate_inputs: bool = True,
|
| 161 |
+
):
|
| 162 |
+
assert isinstance(layout, PackedLinearInt8DynamicActivationIntxWeightLayout)
|
| 163 |
+
assert layout.target in [t for t, _ in _TARGET_AND_STR], (
|
| 164 |
+
f"Unexpected target: {layout.target}"
|
| 165 |
+
)
|
| 166 |
+
assert layout.has_params_set(), (
|
| 167 |
+
"PackedLinearInt8DynamicActivationIntxWeightLayout params must be set before calling from_plain"
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
if layout.target != Target.ATEN:
|
| 171 |
+
_check_torchao_ops_loaded()
|
| 172 |
+
else:
|
| 173 |
+
assert TORCH_VERSION_AT_LEAST_2_6, (
|
| 174 |
+
"aten target is requires torch version > 2.6.0"
|
| 175 |
+
)
|
| 176 |
+
assert torch.backends.kleidiai.is_available(), (
|
| 177 |
+
"ATEN target requires torch.backends.kleidiai.is_available()"
|
| 178 |
+
)
|
| 179 |
+
layout.bit_width == 4, "ATEN target only supports torch.int4"
|
| 180 |
+
assert not layout.has_weight_zeros, "ATEN target does not support zeros"
|
| 181 |
+
|
| 182 |
+
data_dtype = getattr(torch, f"int{layout.bit_width}")
|
| 183 |
+
qmin, qmax = _DTYPE_TO_QVALUE_BOUNDS[data_dtype]
|
| 184 |
+
|
| 185 |
+
int_types = [torch.int8, torch.int16, torch.int32, torch.int64]
|
| 186 |
+
|
| 187 |
+
# Check int_data
|
| 188 |
+
assert int_data.device == torch.device("cpu")
|
| 189 |
+
assert int_data.dtype in int_types
|
| 190 |
+
n, k = int_data.shape
|
| 191 |
+
assert k % layout.group_size == 0, "k must be divisible by group_size"
|
| 192 |
+
if validate_inputs:
|
| 193 |
+
assert int_data.min().item() >= qmin
|
| 194 |
+
assert int_data.max().item() <= qmax
|
| 195 |
+
int_data = int_data.to(torch.int8)
|
| 196 |
+
|
| 197 |
+
# Check scale
|
| 198 |
+
assert scale.device == torch.device("cpu")
|
| 199 |
+
if scale.dtype != torch.float32:
|
| 200 |
+
logging.info(f"scale has dtype {scale.dtype}, converting to torch.float32")
|
| 201 |
+
scale = scale.to(torch.float32)
|
| 202 |
+
n_, _ = scale.shape
|
| 203 |
+
assert n_ == n
|
| 204 |
+
assert scale.numel() * layout.group_size == int_data.numel(), (
|
| 205 |
+
"must have 1 scale per group"
|
| 206 |
+
)
|
| 207 |
+
if validate_inputs:
|
| 208 |
+
assert scale.min().item() > 0
|
| 209 |
+
# Some targets round scales to bfloat16, give warning if scales are at higher precision
|
| 210 |
+
scale_is_rounded_to_bf16 = torch.allclose(
|
| 211 |
+
scale, scale.to(torch.bfloat16).to(torch.float32)
|
| 212 |
+
)
|
| 213 |
+
if not scale_is_rounded_to_bf16:
|
| 214 |
+
if layout.target == Target.ATEN and (layout.group_size < k):
|
| 215 |
+
logging.warning(
|
| 216 |
+
"When using Target.ATEN with group_size < k, scales will be rounded to bfloat16"
|
| 217 |
+
)
|
| 218 |
+
if layout.target in [Target.AUTO, Target.KLEIDIAI]:
|
| 219 |
+
logging.warning(
|
| 220 |
+
"When using [Target.AUTO, Target.KLEIDIAI], scales will be rounded to bfloat16"
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
# Check zero_point
|
| 224 |
+
if zero_point is None:
|
| 225 |
+
assert not layout.has_weight_zeros, (
|
| 226 |
+
"zero_point must be provided if has_weight_zeros=True"
|
| 227 |
+
)
|
| 228 |
+
else:
|
| 229 |
+
assert zero_point.device == torch.device("cpu")
|
| 230 |
+
assert zero_point.shape == scale.shape
|
| 231 |
+
assert zero_point.dtype in int_types
|
| 232 |
+
assert zero_point.numel() * layout.group_size == int_data.numel(), (
|
| 233 |
+
"must have 1 zero_point per group"
|
| 234 |
+
)
|
| 235 |
+
if validate_inputs:
|
| 236 |
+
zero_point_min = zero_point.min().item()
|
| 237 |
+
zero_point_max = zero_point.max().item()
|
| 238 |
+
assert zero_point.min().item() >= qmin
|
| 239 |
+
assert zero_point.max().item() <= qmax
|
| 240 |
+
has_weight_zeros = True
|
| 241 |
+
if zero_point_min == 0 and zero_point_max == 0:
|
| 242 |
+
has_weight_zeros = False
|
| 243 |
+
assert has_weight_zeros == layout.has_weight_zeros, (
|
| 244 |
+
"zero_point being all zeros must be consistent with layout.has_weight_zeros"
|
| 245 |
+
)
|
| 246 |
+
zero_point = zero_point.to(torch.int8)
|
| 247 |
+
|
| 248 |
+
# Check bias
|
| 249 |
+
has_bias = bias is not None
|
| 250 |
+
assert has_bias == layout.has_bias, (
|
| 251 |
+
"bias being None must be consistent with layout.has_bias"
|
| 252 |
+
)
|
| 253 |
+
if has_bias:
|
| 254 |
+
assert bias.device == torch.device("cpu")
|
| 255 |
+
if bias.dtype != torch.float32:
|
| 256 |
+
logging.info(
|
| 257 |
+
f"bias has dtype {bias.dtype}, converting to torch.float32"
|
| 258 |
+
)
|
| 259 |
+
bias = bias.to(torch.float32)
|
| 260 |
+
assert bias.shape == (n,)
|
| 261 |
+
|
| 262 |
+
# Construct packed_weight
|
| 263 |
+
if layout.target == Target.ATEN:
|
| 264 |
+
int_data = int_data.add(8)
|
| 265 |
+
int_data = (int_data[::, 1::2] << 4 | int_data[::, ::2]).to(torch.uint8)
|
| 266 |
+
|
| 267 |
+
# If group_size < k, convert scales to bfloat16
|
| 268 |
+
# to call optimized kernel
|
| 269 |
+
if layout.group_size < k:
|
| 270 |
+
scale = scale.to(torch.bfloat16)
|
| 271 |
+
packed_weight = torch.ops.aten._dyn_quant_pack_4bit_weight(
|
| 272 |
+
int_data, scale, bias, layout.group_size, k, n
|
| 273 |
+
)
|
| 274 |
+
return cls(packed_weight, layout)
|
| 275 |
+
|
| 276 |
+
args = [
|
| 277 |
+
int_data,
|
| 278 |
+
scale.reshape(-1),
|
| 279 |
+
zero_point.reshape(-1) if layout.has_weight_zeros else None,
|
| 280 |
+
layout.group_size,
|
| 281 |
+
bias,
|
| 282 |
+
target_to_str(layout.target) if layout.target != Target.AUTO else None,
|
| 283 |
+
]
|
| 284 |
+
packed_weight = getattr(
|
| 285 |
+
torch.ops.torchao,
|
| 286 |
+
f"_pack_8bit_act_{layout.bit_width}bit_weight",
|
| 287 |
+
)(*args)
|
| 288 |
+
|
| 289 |
+
return cls(packed_weight, layout)
|
| 290 |
+
|
| 291 |
+
def _apply_fn_to_data(self, fn):
|
| 292 |
+
self.packed_weight = fn(self.packed_weight)
|
| 293 |
+
return self
|
| 294 |
+
|
| 295 |
+
@classmethod
|
| 296 |
+
def __torch_dispatch__(cls, func, types, args, kwargs):
|
| 297 |
+
kwargs = {} if kwargs is None else kwargs
|
| 298 |
+
|
| 299 |
+
if func is torch.ops.aten.detach.default:
|
| 300 |
+
return return_and_correct_aliasing(
|
| 301 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.detach)
|
| 302 |
+
)
|
| 303 |
+
if func is torch.ops.aten.clone.default:
|
| 304 |
+
return return_and_correct_aliasing(
|
| 305 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.clone)
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
raise NotImplementedError(
|
| 309 |
+
f"PackedLinearInt8DynamicActivationIntxWeightAQTTensorImpl dispatch: attempting to run {func}, this is not supported"
|
| 310 |
+
)
|
| 311 |
+
|
| 312 |
+
def __tensor_flatten__(self):
|
| 313 |
+
return ["packed_weight"], [self.get_layout()]
|
| 314 |
+
|
| 315 |
+
@classmethod
|
| 316 |
+
def __tensor_unflatten__(
|
| 317 |
+
cls, tensor_data_dict, tensor_attributes, outer_size, outer_stride
|
| 318 |
+
):
|
| 319 |
+
packed_weight = tensor_data_dict["packed_weight"]
|
| 320 |
+
(layout,) = tensor_attributes
|
| 321 |
+
return cls(packed_weight, layout)
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
def _linear_check(input_tensor, weight_tensor, bias):
|
| 325 |
+
layout = weight_tensor.tensor_impl.get_layout()
|
| 326 |
+
return isinstance(layout, PackedLinearInt8DynamicActivationIntxWeightLayout)
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
def _linear_impl(input_tensor, weight_tensor, bias):
|
| 330 |
+
def _impl_2d_non_aten(input_tensor, weight_tensor):
|
| 331 |
+
assert input_tensor.dim() == 2
|
| 332 |
+
assert weight_tensor.dim() == 2
|
| 333 |
+
|
| 334 |
+
m, k = input_tensor.shape
|
| 335 |
+
n, k_ = weight_tensor.shape
|
| 336 |
+
assert k_ == k
|
| 337 |
+
group_size = weight_tensor.tensor_impl.get_layout().group_size
|
| 338 |
+
|
| 339 |
+
args = (
|
| 340 |
+
input_tensor,
|
| 341 |
+
weight_tensor.tensor_impl.packed_weight,
|
| 342 |
+
group_size,
|
| 343 |
+
n,
|
| 344 |
+
k,
|
| 345 |
+
)
|
| 346 |
+
|
| 347 |
+
assert len(weight_tensor.block_size) == 2
|
| 348 |
+
assert weight_tensor.block_size[0] == 1
|
| 349 |
+
assert group_size == weight_tensor.block_size[1]
|
| 350 |
+
bit_width = weight_tensor.tensor_impl.get_layout().bit_width
|
| 351 |
+
|
| 352 |
+
return getattr(torch.ops.torchao, f"_linear_8bit_act_{bit_width}bit_weight")(
|
| 353 |
+
*args
|
| 354 |
+
)
|
| 355 |
+
|
| 356 |
+
def _impl_2d_aten(input_tensor, weight_tensor):
|
| 357 |
+
assert input_tensor.dim() == 2
|
| 358 |
+
assert weight_tensor.dim() == 2
|
| 359 |
+
|
| 360 |
+
m, k = input_tensor.shape
|
| 361 |
+
n, k_ = weight_tensor.shape
|
| 362 |
+
|
| 363 |
+
assert k_ == k
|
| 364 |
+
group_size = weight_tensor.tensor_impl.get_layout().group_size
|
| 365 |
+
packed_weight = weight_tensor.tensor_impl.packed_weight
|
| 366 |
+
return torch.ops.aten._dyn_quant_matmul_4bit(
|
| 367 |
+
input_tensor, packed_weight, group_size, k, n
|
| 368 |
+
)
|
| 369 |
+
|
| 370 |
+
if input_tensor.numel() == 0:
|
| 371 |
+
return input_tensor
|
| 372 |
+
|
| 373 |
+
target = weight_tensor.tensor_impl.get_layout().target
|
| 374 |
+
|
| 375 |
+
if weight_tensor.tensor_impl.get_layout().has_bias:
|
| 376 |
+
assert bias is None, (
|
| 377 |
+
"bias should be None because it is already packed with the weights (has_bias=True)"
|
| 378 |
+
)
|
| 379 |
+
|
| 380 |
+
if target == Target.ATEN:
|
| 381 |
+
assert TORCH_VERSION_AT_LEAST_2_6 == 1, "Target.ATEN requires torch >= 2.6.0"
|
| 382 |
+
_impl_2d = _impl_2d_aten
|
| 383 |
+
else:
|
| 384 |
+
_impl_2d = _impl_2d_non_aten
|
| 385 |
+
|
| 386 |
+
if input_tensor.dim() == 2:
|
| 387 |
+
res = _impl_2d(input_tensor, weight_tensor)
|
| 388 |
+
else:
|
| 389 |
+
assert input_tensor.dim() >= 3
|
| 390 |
+
lead_shape = input_tensor.shape[0:-2]
|
| 391 |
+
m, k = input_tensor.shape[-2], input_tensor.shape[-1]
|
| 392 |
+
n, k_ = weight_tensor.shape
|
| 393 |
+
assert k_ == k
|
| 394 |
+
|
| 395 |
+
res = _impl_2d(input_tensor.reshape(-1, k), weight_tensor)
|
| 396 |
+
res = res.reshape(*lead_shape, m, n)
|
| 397 |
+
|
| 398 |
+
if bias is not None:
|
| 399 |
+
res = res + bias
|
| 400 |
+
return res
|
| 401 |
+
|
| 402 |
+
|
| 403 |
+
from torchao.dtypes.affine_quantized_tensor import (
|
| 404 |
+
AffineQuantizedTensor,
|
| 405 |
+
)
|
| 406 |
+
from torchao.dtypes.utils import AQTTensorImpl, Layout
|
| 407 |
+
|
| 408 |
+
|
| 409 |
+
def make_packed_linear_int8_dynamic_activation_intx_weight_tensor(
|
| 410 |
+
int_data: torch.Tensor,
|
| 411 |
+
scale: torch.Tensor,
|
| 412 |
+
zero_point: Optional[torch.Tensor],
|
| 413 |
+
bias: Optional[torch.Tensor],
|
| 414 |
+
data_dtype: torch.dtype,
|
| 415 |
+
target: Union[str, Target] = "auto",
|
| 416 |
+
*,
|
| 417 |
+
validate_inputs: bool = True,
|
| 418 |
+
) -> AffineQuantizedTensor:
|
| 419 |
+
"""
|
| 420 |
+
Constructs an AffineQuantizedTensor with PackedLinearInt8DynamicActivationIntxWeightLayout
|
| 421 |
+
from plain data.
|
| 422 |
+
"""
|
| 423 |
+
# TORCH_VERSION_AT_LEAST_2_6 is needed for torch.intx with x < 8
|
| 424 |
+
assert TORCH_VERSION_AT_LEAST_2_6, (
|
| 425 |
+
"Using PackedLinearInt8DynamicActivationIntxWeightLayout requires torch version > 2.6.0"
|
| 426 |
+
)
|
| 427 |
+
|
| 428 |
+
layout = PackedLinearInt8DynamicActivationIntxWeightLayout(target=target)
|
| 429 |
+
|
| 430 |
+
bit_width = _DTYPE_TO_BIT_WIDTH[data_dtype]
|
| 431 |
+
qmin, qmax = _DTYPE_TO_QVALUE_BOUNDS[data_dtype]
|
| 432 |
+
|
| 433 |
+
n, k = int_data.shape
|
| 434 |
+
n_, groups_per_k = scale.shape
|
| 435 |
+
assert k % groups_per_k == 0
|
| 436 |
+
group_size = k // groups_per_k
|
| 437 |
+
|
| 438 |
+
has_weight_zeros = True
|
| 439 |
+
if zero_point is None:
|
| 440 |
+
has_weight_zeros = False
|
| 441 |
+
else:
|
| 442 |
+
zero_point_min = zero_point.min().item()
|
| 443 |
+
zero_point_max = zero_point.max().item()
|
| 444 |
+
if zero_point_min == 0 and zero_point_max == 0:
|
| 445 |
+
has_weight_zeros = False
|
| 446 |
+
|
| 447 |
+
has_bias = bias is not None
|
| 448 |
+
|
| 449 |
+
layout.set_params(bit_width, group_size, has_weight_zeros, has_bias)
|
| 450 |
+
assert layout.has_params_set()
|
| 451 |
+
tensor_impl = PackedLinearInt8DynamicActivationIntxWeightAQTTensorImpl.from_plain(
|
| 452 |
+
int_data,
|
| 453 |
+
scale,
|
| 454 |
+
zero_point,
|
| 455 |
+
layout,
|
| 456 |
+
bias,
|
| 457 |
+
validate_inputs=validate_inputs,
|
| 458 |
+
)
|
| 459 |
+
|
| 460 |
+
return AffineQuantizedTensor(
|
| 461 |
+
tensor_impl,
|
| 462 |
+
block_size=(1, group_size),
|
| 463 |
+
shape=int_data.shape,
|
| 464 |
+
quant_min=qmin,
|
| 465 |
+
quant_max=qmax,
|
| 466 |
+
zero_point_domain=ZeroPointDomain.INT,
|
| 467 |
+
)
|
lib/python3.12/site-packages/torchao/dtypes/uintx/plain_layout.py
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD 3-Clause license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
from typing import Optional, Tuple
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
from torch.utils._python_dispatch import (
|
| 10 |
+
is_traceable_wrapper_subclass,
|
| 11 |
+
return_and_correct_aliasing,
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
from torchao.dtypes.affine_quantized_tensor import (
|
| 15 |
+
AffineQuantizedTensor,
|
| 16 |
+
register_layout,
|
| 17 |
+
)
|
| 18 |
+
from torchao.dtypes.utils import AQTTensorImpl, Layout, PlainLayout
|
| 19 |
+
from torchao.kernel import (
|
| 20 |
+
int_scaled_matmul,
|
| 21 |
+
)
|
| 22 |
+
from torchao.quantization.quant_primitives import (
|
| 23 |
+
ZeroPointDomain,
|
| 24 |
+
)
|
| 25 |
+
from torchao.utils import fill_defaults
|
| 26 |
+
|
| 27 |
+
aten = torch.ops.aten
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _same_metadata(self: "PlainAQTTensorImpl", src: "PlainAQTTensorImpl") -> bool:
|
| 31 |
+
return (
|
| 32 |
+
isinstance(self, PlainAQTTensorImpl)
|
| 33 |
+
and isinstance(src, PlainAQTTensorImpl)
|
| 34 |
+
and self.shape == src.shape
|
| 35 |
+
and self.int_data.shape == src.int_data.shape
|
| 36 |
+
and self.scale.shape == src.scale.shape
|
| 37 |
+
and (self.zero_point is None and src.zero_point is None)
|
| 38 |
+
or (
|
| 39 |
+
self.zero_point is not None
|
| 40 |
+
and src.zero_point is not None
|
| 41 |
+
and self.zero_point.shape == src.zero_point.shape
|
| 42 |
+
)
|
| 43 |
+
and type(self._layout) == type(src._layout)
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
@register_layout(PlainLayout)
|
| 48 |
+
class PlainAQTTensorImpl(AQTTensorImpl):
|
| 49 |
+
"""
|
| 50 |
+
TensorImpl for plain layout for affine quantized tensor, it stores int_data, scale, zero_point
|
| 51 |
+
tensors directly as plain tensors.
|
| 52 |
+
|
| 53 |
+
fields:
|
| 54 |
+
int_data (torch.Tensor): the quantized integer data Tensor
|
| 55 |
+
scale (torch.Tensor): the scale Tensor used to map between floating point tensor to quantized tensor
|
| 56 |
+
zero_point (torch.Tensor): the zero_point Tensor used to map between floating point tensor to quantized tensor
|
| 57 |
+
"""
|
| 58 |
+
|
| 59 |
+
def __new__(
|
| 60 |
+
cls,
|
| 61 |
+
int_data: torch.Tensor,
|
| 62 |
+
scale: torch.Tensor,
|
| 63 |
+
zero_point: Optional[torch.Tensor],
|
| 64 |
+
_layout: Layout,
|
| 65 |
+
):
|
| 66 |
+
kwargs = {}
|
| 67 |
+
kwargs["device"] = int_data.device
|
| 68 |
+
kwargs["layout"] = (
|
| 69 |
+
kwargs.get("layout") if kwargs.get("layout", False) else int_data.layout
|
| 70 |
+
)
|
| 71 |
+
kwargs["dtype"] = int_data.dtype
|
| 72 |
+
kwargs["requires_grad"] = False
|
| 73 |
+
shape = int_data.shape
|
| 74 |
+
return torch.Tensor._make_wrapper_subclass(cls, shape, **kwargs) # type: ignore[attr-defined]
|
| 75 |
+
|
| 76 |
+
def __init__(
|
| 77 |
+
self,
|
| 78 |
+
int_data: torch.Tensor,
|
| 79 |
+
scale: torch.Tensor,
|
| 80 |
+
zero_point: Optional[torch.Tensor],
|
| 81 |
+
_layout: Layout,
|
| 82 |
+
):
|
| 83 |
+
self.int_data = int_data
|
| 84 |
+
self.scale = scale
|
| 85 |
+
self.zero_point = zero_point
|
| 86 |
+
self._layout = _layout
|
| 87 |
+
|
| 88 |
+
def __tensor_flatten__(self):
|
| 89 |
+
if self.zero_point is None:
|
| 90 |
+
return ["int_data", "scale"], [self._layout]
|
| 91 |
+
return ["int_data", "scale", "zero_point"], [self._layout]
|
| 92 |
+
|
| 93 |
+
@classmethod
|
| 94 |
+
def __tensor_unflatten__(
|
| 95 |
+
cls, tensor_data_dict, tensor_attributes, outer_size, outer_stride
|
| 96 |
+
):
|
| 97 |
+
int_data, scale, zero_point = (
|
| 98 |
+
tensor_data_dict["int_data"],
|
| 99 |
+
tensor_data_dict["scale"],
|
| 100 |
+
tensor_data_dict.get("zero_point", None),
|
| 101 |
+
)
|
| 102 |
+
(_layout,) = tensor_attributes
|
| 103 |
+
return cls(int_data, scale, zero_point, _layout)
|
| 104 |
+
|
| 105 |
+
def to(self, *args, **kwargs):
|
| 106 |
+
kwargs = self._get_to_kwargs(*args, **kwargs)
|
| 107 |
+
return self.__class__(
|
| 108 |
+
self.int_data.to(kwargs["device"]),
|
| 109 |
+
self.scale.to(kwargs["device"]),
|
| 110 |
+
self.zero_point.to(kwargs["device"])
|
| 111 |
+
if self.zero_point is not None
|
| 112 |
+
else None,
|
| 113 |
+
self._layout,
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
def _apply_fn_to_data(self, fn):
|
| 117 |
+
return self.__class__(
|
| 118 |
+
fn(self.int_data),
|
| 119 |
+
fn(self.scale),
|
| 120 |
+
fn(self.zero_point) if self.zero_point is not None else None,
|
| 121 |
+
self._layout,
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
@classmethod
|
| 125 |
+
def __torch_dispatch__(cls, func, types, args, kwargs):
|
| 126 |
+
kwargs = {} if kwargs is None else kwargs
|
| 127 |
+
|
| 128 |
+
if func is aten.detach.default:
|
| 129 |
+
return return_and_correct_aliasing(
|
| 130 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.detach)
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
elif func is aten.clone.default:
|
| 134 |
+
return return_and_correct_aliasing(
|
| 135 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.clone)
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
elif func is aten.copy_.default:
|
| 139 |
+
self = args[0]
|
| 140 |
+
src = args[1]
|
| 141 |
+
if _same_metadata(self, src):
|
| 142 |
+
self_tensors = self.__tensor_flatten__()[0]
|
| 143 |
+
for tensor_name in self_tensors:
|
| 144 |
+
getattr(self, tensor_name).copy_(getattr(src, tensor_name))
|
| 145 |
+
return
|
| 146 |
+
raise ValueError(
|
| 147 |
+
f"Not supported args for copy_ due to metadata mistach: {args[0], args[1]}"
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
elif func is aten.t.default:
|
| 151 |
+
tensor = args[0]
|
| 152 |
+
new = tensor.__class__(
|
| 153 |
+
tensor.int_data.t(), tensor.scale, tensor.zero_point, tensor._layout
|
| 154 |
+
)
|
| 155 |
+
return return_and_correct_aliasing(func, args, kwargs, new)
|
| 156 |
+
|
| 157 |
+
elif func in [aten.select.int, aten.index.Tensor]:
|
| 158 |
+
return return_and_correct_aliasing(
|
| 159 |
+
func,
|
| 160 |
+
args,
|
| 161 |
+
kwargs,
|
| 162 |
+
args[0]._apply_fn_to_data(lambda x: func(x, *args[1:], **kwargs)),
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
elif func is aten.slice.Tensor:
|
| 166 |
+
self, dim, start, end, step = fill_defaults(args, 5, [0, None, None, 1])
|
| 167 |
+
if dim == 0:
|
| 168 |
+
return return_and_correct_aliasing(
|
| 169 |
+
func,
|
| 170 |
+
args,
|
| 171 |
+
kwargs,
|
| 172 |
+
args[0]._apply_fn_to_data(
|
| 173 |
+
lambda x: aten.slice.Tensor(x, dim, start, end, step)
|
| 174 |
+
),
|
| 175 |
+
)
|
| 176 |
+
elif dim == 1:
|
| 177 |
+
assert len(self.scale.shape) == 1, (
|
| 178 |
+
f"slice dim==1 only works when len(scale.shape) == 1 currently, got: {self.scale.shape}"
|
| 179 |
+
)
|
| 180 |
+
return PlainAQTTensorImpl(
|
| 181 |
+
aten.slice.Tensor(self.int_data, dim, start, end, step),
|
| 182 |
+
self.scale.view(-1),
|
| 183 |
+
self.zero_point.view(-1) if self.zero_point is not None else None,
|
| 184 |
+
self._layout,
|
| 185 |
+
)
|
| 186 |
+
else:
|
| 187 |
+
raise NotImplementedError(
|
| 188 |
+
f"PlainAQTTensorImpl dispatch: attempting to run {func}, with dim={dim}, that is not supported"
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
raise NotImplementedError(
|
| 192 |
+
f"PlainAQTTensorImpl dispatch: attempting to run {func}, this is not supported"
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
__torch_function__ = torch._C._disabled_torch_function_impl
|
| 196 |
+
|
| 197 |
+
def get_plain(self) -> Tuple[torch.Tensor, torch.Tensor, Optional[torch.Tensor]]:
|
| 198 |
+
return self.int_data, self.scale, self.zero_point
|
| 199 |
+
|
| 200 |
+
def get_layout(self) -> Layout:
|
| 201 |
+
return self._layout
|
| 202 |
+
|
| 203 |
+
@classmethod
|
| 204 |
+
def from_plain(
|
| 205 |
+
cls,
|
| 206 |
+
int_data: torch.Tensor,
|
| 207 |
+
scale: torch.Tensor,
|
| 208 |
+
zero_point: Optional[torch.Tensor],
|
| 209 |
+
_layout: Layout,
|
| 210 |
+
):
|
| 211 |
+
assert isinstance(_layout, PlainLayout)
|
| 212 |
+
return cls(int_data, scale, zero_point, _layout)
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
def _aqt_is_int8(aqt):
|
| 216 |
+
"""Check if an AffineQuantizedTensor is int8 quantized Tensor"""
|
| 217 |
+
return (
|
| 218 |
+
aqt.tensor_impl.dtype == torch.int8
|
| 219 |
+
and (aqt.quant_min is None or aqt.quant_min == -128)
|
| 220 |
+
and (aqt.quant_max is None or aqt.quant_max == 127)
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def _aqt_is_int8_reduced_range(aqt):
|
| 225 |
+
return (
|
| 226 |
+
aqt.tensor_impl.dtype == torch.int8
|
| 227 |
+
and aqt.quant_min == -127
|
| 228 |
+
and (aqt.quant_max is None or aqt.quant_max == 127)
|
| 229 |
+
)
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def _linear_fp_act_int8_weight_check(input_tensor, weight_tensor, bias):
|
| 233 |
+
return (
|
| 234 |
+
# input is native float tensor
|
| 235 |
+
not is_traceable_wrapper_subclass(input_tensor)
|
| 236 |
+
and input_tensor.is_floating_point()
|
| 237 |
+
and
|
| 238 |
+
# weight is int8 per channel quantized affine quantized tensor
|
| 239 |
+
isinstance(weight_tensor, AffineQuantizedTensor)
|
| 240 |
+
and _aqt_is_int8(weight_tensor)
|
| 241 |
+
and len(weight_tensor.shape) == 2
|
| 242 |
+
and len(weight_tensor.block_size) == 2
|
| 243 |
+
and weight_tensor.block_size[0] == 1
|
| 244 |
+
and weight_tensor.block_size[1] == weight_tensor.shape[1]
|
| 245 |
+
and weight_tensor.zero_point_domain == ZeroPointDomain.INT
|
| 246 |
+
and isinstance(weight_tensor._layout, PlainLayout)
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
def _linear_fp_act_int8_weight_impl(input_tensor, weight_tensor, bias):
|
| 251 |
+
# TODO: enable cpu and mps efficient path
|
| 252 |
+
# is_cpu and is_mps only, some issue with is_contiguous() currently
|
| 253 |
+
# return torch.ops.aten._weight_int8pack_mm(input_tensor.contiguous(), w_vals_int8_t, weight_tensor.tensor_impl.scale)
|
| 254 |
+
|
| 255 |
+
# per channel int8 weight only quantizated mm
|
| 256 |
+
w_vals_int8_t = weight_tensor.tensor_impl.int_data.t()
|
| 257 |
+
scale = weight_tensor.tensor_impl.scale
|
| 258 |
+
m = torch.mm(
|
| 259 |
+
input_tensor.reshape(-1, input_tensor.shape[-1]),
|
| 260 |
+
w_vals_int8_t.to(input_tensor.dtype),
|
| 261 |
+
)
|
| 262 |
+
y = m * scale.to(m.dtype)
|
| 263 |
+
y = y.reshape(*input_tensor.shape[:-1], y.shape[-1])
|
| 264 |
+
if bias is not None:
|
| 265 |
+
y += bias.to(m.dtype)
|
| 266 |
+
return y
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
def _linear_int8_act_int8_weight_check(input_tensor, weight_tensor, bias):
|
| 270 |
+
return (
|
| 271 |
+
isinstance(input_tensor, AffineQuantizedTensor)
|
| 272 |
+
and _aqt_is_int8_reduced_range(input_tensor)
|
| 273 |
+
and isinstance(weight_tensor, AffineQuantizedTensor)
|
| 274 |
+
and _aqt_is_int8(weight_tensor)
|
| 275 |
+
and input_tensor.dtype == weight_tensor.dtype
|
| 276 |
+
and isinstance(input_tensor._layout, PlainLayout)
|
| 277 |
+
and isinstance(weight_tensor._layout, PlainLayout)
|
| 278 |
+
)
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
def _linear_int8_act_int8_weight_impl(input_tensor, weight_tensor, bias):
|
| 282 |
+
#
|
| 283 |
+
# 1. do the matrix form of dot(X_i, W_j)
|
| 284 |
+
#
|
| 285 |
+
#
|
| 286 |
+
# 2. rescale the output
|
| 287 |
+
#
|
| 288 |
+
# in cases with large matrices, y_dot_int32 can grow sufficiently
|
| 289 |
+
# large that y_dot_int32 * a float16 scale is greater than the maximum
|
| 290 |
+
# value of a float 16, (which results in a value of inf even if multiplying
|
| 291 |
+
# by the other scale would bring it within the expected range)
|
| 292 |
+
|
| 293 |
+
x_vals_int8 = input_tensor.tensor_impl.int_data
|
| 294 |
+
x_scales = input_tensor.tensor_impl.scale
|
| 295 |
+
w_vals_int8_t = weight_tensor.tensor_impl.int_data.contiguous().t()
|
| 296 |
+
w_scales = weight_tensor.tensor_impl.scale
|
| 297 |
+
tmp = x_vals_int8.reshape(-1, x_vals_int8.shape[-1])
|
| 298 |
+
x_scales_dtype = x_scales.dtype
|
| 299 |
+
# Cast fp16 scale to float to avoid overflow in int_scaled_matmul
|
| 300 |
+
intermediate_dtype = torch.float if x_scales_dtype == torch.half else x_scales_dtype
|
| 301 |
+
y_dot_scaled = int_scaled_matmul(
|
| 302 |
+
tmp, w_vals_int8_t, x_scales.reshape(-1, 1).to(intermediate_dtype)
|
| 303 |
+
)
|
| 304 |
+
y_dot_scaled = y_dot_scaled.to(x_scales_dtype)
|
| 305 |
+
|
| 306 |
+
y = (y_dot_scaled * w_scales).reshape(
|
| 307 |
+
*x_vals_int8.shape[:-1], y_dot_scaled.shape[-1]
|
| 308 |
+
)
|
| 309 |
+
|
| 310 |
+
# can downcast only at the very end
|
| 311 |
+
output_dtype = input_tensor.dtype
|
| 312 |
+
y = y.to(output_dtype)
|
| 313 |
+
if bias is not None:
|
| 314 |
+
y += bias
|
| 315 |
+
return y
|
lib/python3.12/site-packages/torchao/dtypes/uintx/q_dq_layout.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
import logging
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
from torchao.dtypes.affine_quantized_tensor import (
|
| 12 |
+
AffineQuantizedTensor,
|
| 13 |
+
register_layout,
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
logger = logging.getLogger(__name__)
|
| 17 |
+
logger.setLevel(logging.WARNING)
|
| 18 |
+
|
| 19 |
+
import sys
|
| 20 |
+
|
| 21 |
+
handler = logging.StreamHandler(sys.stdout)
|
| 22 |
+
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
| 23 |
+
handler.setFormatter(formatter)
|
| 24 |
+
logger.addHandler(handler)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
from dataclasses import dataclass
|
| 28 |
+
from typing import Optional, Tuple
|
| 29 |
+
|
| 30 |
+
from torch.utils._python_dispatch import (
|
| 31 |
+
return_and_correct_aliasing,
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
from torchao.dtypes.utils import AQTTensorImpl, Layout
|
| 35 |
+
from torchao.utils import fill_defaults
|
| 36 |
+
|
| 37 |
+
aten = torch.ops.aten
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@dataclass(frozen=True)
|
| 41 |
+
class QDQLayout(Layout):
|
| 42 |
+
pass
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def _same_metadata(self: "QDQTensorImpl", src: "QDQTensorImpl") -> bool:
|
| 46 |
+
return (
|
| 47 |
+
isinstance(self, QDQTensorImpl)
|
| 48 |
+
and isinstance(src, QDQTensorImpl)
|
| 49 |
+
and self.shape == src.shape
|
| 50 |
+
and self.int_data.shape == src.int_data.shape
|
| 51 |
+
and self.scale.shape == src.scale.shape
|
| 52 |
+
and (self.zero_point is None and src.zero_point is None)
|
| 53 |
+
or (
|
| 54 |
+
self.zero_point is not None
|
| 55 |
+
and src.zero_point is not None
|
| 56 |
+
and self.zero_point.shape == src.zero_point.shape
|
| 57 |
+
)
|
| 58 |
+
and type(self._layout) == type(src._layout)
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
@register_layout(QDQLayout)
|
| 63 |
+
class QDQTensorImpl(AQTTensorImpl):
|
| 64 |
+
"""
|
| 65 |
+
TensorImpl for QDQLayout layout for affine quantized tensor, it stores int_data, scale, zero_point
|
| 66 |
+
tensors directly as plain tensors.
|
| 67 |
+
|
| 68 |
+
fields:
|
| 69 |
+
int_data (torch.Tensor): the quantized integer data Tensor
|
| 70 |
+
scale (torch.Tensor): the scale Tensor used to map between floating point tensor to quantized tensor
|
| 71 |
+
zero_point (torch.Tensor): the zero_point Tensor used to map between floating point tensor to quantized tensor
|
| 72 |
+
"""
|
| 73 |
+
|
| 74 |
+
def __new__(
|
| 75 |
+
cls,
|
| 76 |
+
int_data: torch.Tensor,
|
| 77 |
+
scale: torch.Tensor,
|
| 78 |
+
zero_point: Optional[torch.Tensor],
|
| 79 |
+
_layout: Layout,
|
| 80 |
+
):
|
| 81 |
+
kwargs = {}
|
| 82 |
+
kwargs["device"] = int_data.device
|
| 83 |
+
kwargs["layout"] = (
|
| 84 |
+
kwargs.get("layout") if kwargs.get("layout", False) else int_data.layout
|
| 85 |
+
)
|
| 86 |
+
kwargs["dtype"] = int_data.dtype
|
| 87 |
+
kwargs["requires_grad"] = False
|
| 88 |
+
shape = int_data.shape
|
| 89 |
+
return torch.Tensor._make_wrapper_subclass(cls, shape, **kwargs) # type: ignore[attr-defined]
|
| 90 |
+
|
| 91 |
+
def __init__(
|
| 92 |
+
self,
|
| 93 |
+
int_data: torch.Tensor,
|
| 94 |
+
scale: torch.Tensor,
|
| 95 |
+
zero_point: Optional[torch.Tensor],
|
| 96 |
+
_layout: Layout,
|
| 97 |
+
):
|
| 98 |
+
self.int_data = int_data
|
| 99 |
+
self.scale = scale
|
| 100 |
+
self.zero_point = zero_point
|
| 101 |
+
self._layout = _layout
|
| 102 |
+
|
| 103 |
+
def __tensor_flatten__(self):
|
| 104 |
+
if self.zero_point is None:
|
| 105 |
+
return ["int_data", "scale"], [self._layout]
|
| 106 |
+
return ["int_data", "scale", "zero_point"], [self._layout]
|
| 107 |
+
|
| 108 |
+
@classmethod
|
| 109 |
+
def __tensor_unflatten__(
|
| 110 |
+
cls, tensor_data_dict, tensor_attributes, outer_size, outer_stride
|
| 111 |
+
):
|
| 112 |
+
int_data, scale, zero_point = (
|
| 113 |
+
tensor_data_dict["int_data"],
|
| 114 |
+
tensor_data_dict["scale"],
|
| 115 |
+
tensor_data_dict.get("zero_point", None),
|
| 116 |
+
)
|
| 117 |
+
(_layout,) = tensor_attributes
|
| 118 |
+
return cls(int_data, scale, zero_point, _layout)
|
| 119 |
+
|
| 120 |
+
def to(self, *args, **kwargs):
|
| 121 |
+
kwargs = self._get_to_kwargs(*args, **kwargs)
|
| 122 |
+
return self.__class__(
|
| 123 |
+
self.int_data.to(kwargs["device"]),
|
| 124 |
+
self.scale.to(kwargs["device"]),
|
| 125 |
+
self.zero_point.to(kwargs["device"])
|
| 126 |
+
if self.zero_point is not None
|
| 127 |
+
else None,
|
| 128 |
+
self._layout,
|
| 129 |
+
)
|
| 130 |
+
|
| 131 |
+
def _apply_fn_to_data(self, fn):
|
| 132 |
+
return self.__class__(
|
| 133 |
+
fn(self.int_data),
|
| 134 |
+
fn(self.scale),
|
| 135 |
+
fn(self.zero_point) if self.zero_point is not None else None,
|
| 136 |
+
self._layout,
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
@classmethod
|
| 140 |
+
def __torch_dispatch__(cls, func, types, args, kwargs):
|
| 141 |
+
kwargs = {} if kwargs is None else kwargs
|
| 142 |
+
|
| 143 |
+
if func is aten.detach.default:
|
| 144 |
+
return return_and_correct_aliasing(
|
| 145 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.detach)
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
elif func is aten.clone.default:
|
| 149 |
+
return return_and_correct_aliasing(
|
| 150 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.clone)
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
elif func is aten.copy_.default:
|
| 154 |
+
self = args[0]
|
| 155 |
+
src = args[1]
|
| 156 |
+
if _same_metadata(self, src):
|
| 157 |
+
self_tensors = self.__tensor_flatten__()[0]
|
| 158 |
+
for tensor_name in self_tensors:
|
| 159 |
+
getattr(self, tensor_name).copy_(getattr(src, tensor_name))
|
| 160 |
+
return
|
| 161 |
+
raise ValueError(
|
| 162 |
+
f"Not supported args for copy_ due to metadata mistach: {args[0], args[1]}"
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
elif func is aten.t.default:
|
| 166 |
+
tensor = args[0]
|
| 167 |
+
new = tensor.__class__(
|
| 168 |
+
tensor.int_data.t(), tensor.scale, tensor.zero_point, tensor._layout
|
| 169 |
+
)
|
| 170 |
+
return return_and_correct_aliasing(func, args, kwargs, new)
|
| 171 |
+
|
| 172 |
+
elif func is aten.slice.Tensor:
|
| 173 |
+
self, dim, start, end, step = fill_defaults(args, 5, [0, None, None, 1])
|
| 174 |
+
if dim in [0, 1]:
|
| 175 |
+
int_data, scale, zero_point = self.get_plain()
|
| 176 |
+
data_len = int_data.shape[dim]
|
| 177 |
+
scale_len = scale.shape[dim]
|
| 178 |
+
ratio = data_len / scale_len
|
| 179 |
+
start_scale = int(start / ratio)
|
| 180 |
+
end_scale = int(end / ratio)
|
| 181 |
+
|
| 182 |
+
int_data = aten.slice.Tensor(int_data, dim, start, end, step)
|
| 183 |
+
scale = aten.slice.Tensor(scale, dim, start_scale, end_scale, step)
|
| 184 |
+
if zero_point is not None:
|
| 185 |
+
zero_point = aten.slice.Tensor(
|
| 186 |
+
zero_point, dim, start_scale, end_scale, step
|
| 187 |
+
)
|
| 188 |
+
sliced = self.from_plain(int_data, scale, zero_point, self._layout)
|
| 189 |
+
return return_and_correct_aliasing(func, args, kwargs, sliced)
|
| 190 |
+
else:
|
| 191 |
+
raise NotImplementedError(
|
| 192 |
+
f"QDQTensorImpl dispatch: attempting to run {func}, with dim={dim}, that is not supported"
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
raise NotImplementedError(
|
| 196 |
+
f"QDQTensorImpl dispatch: attempting to run {func}, this is not supported"
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
__torch_function__ = torch._C._disabled_torch_function_impl
|
| 200 |
+
|
| 201 |
+
def get_plain(self) -> Tuple[torch.Tensor, torch.Tensor, Optional[torch.Tensor]]:
|
| 202 |
+
return self.int_data, self.scale, self.zero_point
|
| 203 |
+
|
| 204 |
+
def get_layout(self) -> Layout:
|
| 205 |
+
return self._layout
|
| 206 |
+
|
| 207 |
+
@classmethod
|
| 208 |
+
def from_plain(
|
| 209 |
+
cls,
|
| 210 |
+
int_data: torch.Tensor,
|
| 211 |
+
scale: torch.Tensor,
|
| 212 |
+
zero_point: Optional[torch.Tensor],
|
| 213 |
+
_layout: Layout,
|
| 214 |
+
):
|
| 215 |
+
assert isinstance(_layout, QDQLayout)
|
| 216 |
+
return cls(int_data, scale, zero_point, _layout)
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def _linear_check(input_tensor, weight_tensor, bias):
|
| 220 |
+
layout = weight_tensor.tensor_impl.get_layout()
|
| 221 |
+
return isinstance(layout, QDQLayout)
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def _linear_impl(input_tensor, weight_tensor, bias):
|
| 225 |
+
if isinstance(input_tensor, AffineQuantizedTensor):
|
| 226 |
+
input_tensor = input_tensor.dequantize()
|
| 227 |
+
if isinstance(weight_tensor, AffineQuantizedTensor):
|
| 228 |
+
weight_tensor = weight_tensor.dequantize()
|
| 229 |
+
return torch.nn.functional.linear(input_tensor, weight_tensor, bias)
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def _embedding_check(args, kwargs):
|
| 233 |
+
_, weight_tensor = args
|
| 234 |
+
layout = weight_tensor.tensor_impl.get_layout()
|
| 235 |
+
return isinstance(layout, QDQLayout)
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def _embedding_impl(args, kwargs):
|
| 239 |
+
input_tensor, weight_tensor = args
|
| 240 |
+
if isinstance(weight_tensor, AffineQuantizedTensor):
|
| 241 |
+
weight_tensor = weight_tensor.dequantize()
|
| 242 |
+
return torch.nn.functional.embedding(input_tensor, weight_tensor, **kwargs)
|
lib/python3.12/site-packages/torchao/dtypes/uintx/semi_sparse_layout.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD 3-Clause license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
from typing import Optional
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
from torch.utils._python_dispatch import (
|
| 11 |
+
return_and_correct_aliasing,
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
from torchao.dtypes.affine_quantized_tensor import (
|
| 15 |
+
AffineQuantizedTensor,
|
| 16 |
+
register_layout,
|
| 17 |
+
)
|
| 18 |
+
from torchao.dtypes.uintx.plain_layout import (
|
| 19 |
+
PlainAQTTensorImpl,
|
| 20 |
+
_aqt_is_int8_reduced_range,
|
| 21 |
+
)
|
| 22 |
+
from torchao.dtypes.utils import Layout, PlainLayout
|
| 23 |
+
|
| 24 |
+
aten = torch.ops.aten
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def _linear_int8_act_int8_weight_semi_structured_sparse_check(
|
| 28 |
+
input_tensor, weight_tensor, bias
|
| 29 |
+
):
|
| 30 |
+
return (
|
| 31 |
+
isinstance(input_tensor, AffineQuantizedTensor)
|
| 32 |
+
and _aqt_is_int8_reduced_range(input_tensor)
|
| 33 |
+
and isinstance(weight_tensor, AffineQuantizedTensor)
|
| 34 |
+
and weight_tensor.is_cuda
|
| 35 |
+
and input_tensor.dtype == weight_tensor.dtype
|
| 36 |
+
and isinstance(input_tensor._layout, PlainLayout)
|
| 37 |
+
and isinstance(weight_tensor._layout, SemiSparseLayout)
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def _linear_int8_act_int8_weight_semi_structured_sparse_impl(
|
| 42 |
+
input_tensor, weight_tensor, bias
|
| 43 |
+
):
|
| 44 |
+
x_vals_int8 = input_tensor.tensor_impl.int_data
|
| 45 |
+
x_scales = input_tensor.tensor_impl.scale
|
| 46 |
+
w_vals_int8 = weight_tensor.tensor_impl.int_data
|
| 47 |
+
w_scales = weight_tensor.tensor_impl.scale
|
| 48 |
+
tmp = x_vals_int8.reshape(-1, x_vals_int8.shape[-1])
|
| 49 |
+
# must pad
|
| 50 |
+
row, col = tmp.shape
|
| 51 |
+
from torch.sparse import SparseSemiStructuredTensorCUSPARSELT
|
| 52 |
+
|
| 53 |
+
tmp_padded = SparseSemiStructuredTensorCUSPARSELT._pad_dense_input(tmp)
|
| 54 |
+
# we fuse one of the scalar matrix multiplications (w_scales) into the sparse mm
|
| 55 |
+
y_dot_bf16_w_scales_fused = torch._cslt_sparse_mm(
|
| 56 |
+
w_vals_int8,
|
| 57 |
+
tmp_padded.t(),
|
| 58 |
+
alpha=w_scales.to(torch.float32),
|
| 59 |
+
out_dtype=torch.bfloat16,
|
| 60 |
+
).t()[:row, :]
|
| 61 |
+
y = (y_dot_bf16_w_scales_fused * x_scales.reshape(-1, 1)).reshape(
|
| 62 |
+
*x_vals_int8.shape[:-1], y_dot_bf16_w_scales_fused.shape[-1]
|
| 63 |
+
)
|
| 64 |
+
output_dtype = input_tensor.dtype
|
| 65 |
+
# TODO: waiting for jesse's test/fix
|
| 66 |
+
y = y.to(output_dtype).contiguous()
|
| 67 |
+
if bias is not None:
|
| 68 |
+
y += bias
|
| 69 |
+
return y
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
@dataclass(frozen=True)
|
| 73 |
+
class SemiSparseLayout(Layout):
|
| 74 |
+
"""SemiSparseLayout is a layout class for handling semi-structured sparse
|
| 75 |
+
matrices in affine quantized tensors. This layout is specifically designed
|
| 76 |
+
to work with the 2:4 sparsity pattern, where two out of every four elements
|
| 77 |
+
are pruned to zero. This class provides methods for preprocessing input
|
| 78 |
+
tensors to conform to this sparsity pattern.
|
| 79 |
+
"""
|
| 80 |
+
|
| 81 |
+
def pre_process(self, input: torch.Tensor) -> torch.Tensor:
|
| 82 |
+
# prune to 2:4 if not already
|
| 83 |
+
temp = input.detach()
|
| 84 |
+
pruning_inds = temp.abs().view(-1, 4).argsort(dim=1)[:, :2]
|
| 85 |
+
temp.view(-1, 4).scatter_(1, pruning_inds, value=0)
|
| 86 |
+
return temp
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
@register_layout(SemiSparseLayout)
|
| 90 |
+
class SemiSparseAQTTensorImpl(PlainAQTTensorImpl):
|
| 91 |
+
"""
|
| 92 |
+
TensorImpl for semi_sparse_cusparselt layout for affine quantized tensor
|
| 93 |
+
"""
|
| 94 |
+
|
| 95 |
+
@classmethod
|
| 96 |
+
def __torch_dispatch__(cls, func, types, args, kwargs):
|
| 97 |
+
kwargs = {} if kwargs is None else kwargs
|
| 98 |
+
|
| 99 |
+
if func is aten.detach.default:
|
| 100 |
+
return return_and_correct_aliasing(
|
| 101 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.detach)
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
raise NotImplementedError(
|
| 105 |
+
f"SparseAQTTensorImpl dispatch: attempting to run {func}, this is not supported"
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
def get_plain(self):
|
| 109 |
+
# Currently we don't have cuSPARSELt expansion routines, so we matmul by
|
| 110 |
+
# the identity matrix to get the original dense matrix. This is slow though.
|
| 111 |
+
cols = self.int_data.numel() * 16 // (10 * self.scale.shape[0])
|
| 112 |
+
int_data_expanded = torch._cslt_sparse_mm(
|
| 113 |
+
self.int_data,
|
| 114 |
+
torch.eye(cols, dtype=self.int_data.dtype, device=self.int_data.device).t(),
|
| 115 |
+
)
|
| 116 |
+
return int_data_expanded, self.scale, self.zero_point
|
| 117 |
+
|
| 118 |
+
@classmethod
|
| 119 |
+
def from_plain(
|
| 120 |
+
cls,
|
| 121 |
+
int_data: torch.Tensor,
|
| 122 |
+
scale: torch.Tensor,
|
| 123 |
+
zero_point: Optional[torch.Tensor],
|
| 124 |
+
_layout: Layout,
|
| 125 |
+
):
|
| 126 |
+
assert isinstance(_layout, SemiSparseLayout)
|
| 127 |
+
int_data_compressed = torch._cslt_compress(int_data)
|
| 128 |
+
return cls(int_data_compressed, scale, zero_point, _layout)
|
lib/python3.12/site-packages/torchao/dtypes/uintx/tensor_core_tiled_layout.py
ADDED
|
@@ -0,0 +1,527 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD 3-Clause license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
from typing import Optional, Tuple
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
from torch.utils._python_dispatch import (
|
| 11 |
+
is_traceable_wrapper_subclass,
|
| 12 |
+
return_and_correct_aliasing,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
from torchao.dtypes.affine_quantized_tensor import (
|
| 16 |
+
AffineQuantizedTensor,
|
| 17 |
+
register_layout,
|
| 18 |
+
)
|
| 19 |
+
from torchao.dtypes.utils import AQTTensorImpl, Layout, is_device
|
| 20 |
+
from torchao.quantization.quant_primitives import ZeroPointDomain, _get_reduction_params
|
| 21 |
+
from torchao.utils import (
|
| 22 |
+
TORCH_VERSION_AT_LEAST_2_5,
|
| 23 |
+
fill_defaults,
|
| 24 |
+
find_multiple,
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
aten = torch.ops.aten
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _aqt_is_tensor_core_tile_uint4(aqt):
|
| 31 |
+
"""Check if an AffineQuantizedTensor is uint4 quantized Tensor"""
|
| 32 |
+
# TODO: use torch.uint4
|
| 33 |
+
return (
|
| 34 |
+
aqt.tensor_impl.dtype == torch.int32
|
| 35 |
+
and aqt.quant_min == 0
|
| 36 |
+
and aqt.quant_max == 15
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _same_metadata(
|
| 41 |
+
self: "TensorCoreTiledAQTTensorImpl", src: "TensorCoreTiledAQTTensorImpl"
|
| 42 |
+
) -> bool:
|
| 43 |
+
return (
|
| 44 |
+
isinstance(self, TensorCoreTiledAQTTensorImpl)
|
| 45 |
+
and isinstance(src, TensorCoreTiledAQTTensorImpl)
|
| 46 |
+
and self.shape == src.shape
|
| 47 |
+
and self.packed_weight.shape == src.packed_weight.shape
|
| 48 |
+
and self.scale_and_zero.shape == src.scale_and_zero.shape
|
| 49 |
+
and self.transposed == src.transposed
|
| 50 |
+
and type(self._layout) == type(src._layout)
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def _linear_bf16_act_uint4_weight_check(input_tensor, weight_tensor, bias):
|
| 55 |
+
return (
|
| 56 |
+
# input is native bfloat16 tensor
|
| 57 |
+
not is_traceable_wrapper_subclass(input_tensor)
|
| 58 |
+
and input_tensor.dtype == torch.bfloat16
|
| 59 |
+
and
|
| 60 |
+
# weight is uint4, group quantized tensor_core_tiled tensor impl affine quantized tensor
|
| 61 |
+
isinstance(weight_tensor, AffineQuantizedTensor)
|
| 62 |
+
and _aqt_is_tensor_core_tile_uint4(weight_tensor)
|
| 63 |
+
and weight_tensor.dtype == torch.bfloat16
|
| 64 |
+
and len(weight_tensor.shape) == 2
|
| 65 |
+
and weight_tensor.zero_point_domain == ZeroPointDomain.FLOAT
|
| 66 |
+
and isinstance(weight_tensor._layout, TensorCoreTiledLayout)
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def _linear_bf16_act_uint4_weight_impl(input_tensor, weight_tensor, bias):
|
| 71 |
+
assert weight_tensor.block_size[0] == 1, (
|
| 72 |
+
f"Requires groupwise quantization, got block_size: {weight_tensor.block_size}"
|
| 73 |
+
)
|
| 74 |
+
assert input_tensor.shape[-1] == weight_tensor.shape[1], (
|
| 75 |
+
f"need input_tensor shape: {input_tensor.shape} final"
|
| 76 |
+
f"dim to match weight_tensor shape: {weight_tensor.shape} second dim "
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
# TODO: check groupsize quantization
|
| 80 |
+
# avoid circular dep, TODO: move this to a common util.py
|
| 81 |
+
act_mat = input_tensor
|
| 82 |
+
# weight is packed from padded (out_features, in_features) weight tensor
|
| 83 |
+
# (same dimension requirement as F.linear weight)
|
| 84 |
+
packed_weight = weight_tensor.tensor_impl.packed_weight
|
| 85 |
+
scale_and_zero = weight_tensor.tensor_impl.scale_and_zero
|
| 86 |
+
|
| 87 |
+
orig_act_size = act_mat.size()
|
| 88 |
+
orig_dtype = act_mat.dtype
|
| 89 |
+
|
| 90 |
+
# reshape and pad activation
|
| 91 |
+
act_mat = act_mat.reshape(-1, act_mat.shape[-1]).to(torch.bfloat16)
|
| 92 |
+
pad_size = find_multiple(act_mat.shape[-1], 1024)
|
| 93 |
+
act_mat = torch.nn.functional.pad(act_mat, (0, pad_size - act_mat.shape[-1]))
|
| 94 |
+
|
| 95 |
+
# groupwise int4 quantization
|
| 96 |
+
groupsize = weight_tensor.block_size[-1]
|
| 97 |
+
if act_mat.numel() == 0: # handling for empty input
|
| 98 |
+
y = act_mat
|
| 99 |
+
else:
|
| 100 |
+
y = torch.ops.aten._weight_int4pack_mm(
|
| 101 |
+
act_mat.contiguous(), packed_weight, groupsize, scale_and_zero
|
| 102 |
+
)
|
| 103 |
+
# remove out_feature padding
|
| 104 |
+
orig_out_features = weight_tensor.shape[-2]
|
| 105 |
+
y = y[:, :orig_out_features]
|
| 106 |
+
y = y.reshape(*orig_act_size[:-1], orig_out_features)
|
| 107 |
+
|
| 108 |
+
if bias is not None:
|
| 109 |
+
y += bias
|
| 110 |
+
return y.to(orig_dtype)
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
@dataclass(frozen=True)
|
| 114 |
+
class TensorCoreTiledLayout(Layout):
|
| 115 |
+
"""TensorCoreTiledLayout is a layout class for handling tensor core tiled layouts in affine quantized tensors. It provides methods for pre-processing and post-processing tensors to fit the required layout for efficient computation on tensor cores.
|
| 116 |
+
|
| 117 |
+
Attributes:
|
| 118 |
+
inner_k_tiles (int): An internal argument for the packing function of tensor core tiled layout that can affect the performance of the matmul kernel. Defaults to 8.
|
| 119 |
+
"""
|
| 120 |
+
|
| 121 |
+
inner_k_tiles: int = 8
|
| 122 |
+
|
| 123 |
+
def pre_process(self, input: torch.Tensor) -> torch.Tensor:
|
| 124 |
+
orig_out_features, orig_in_features = input.shape[-2:]
|
| 125 |
+
in_features = find_multiple(orig_in_features, 1024)
|
| 126 |
+
out_features = find_multiple(orig_out_features, 8)
|
| 127 |
+
input = torch.nn.functional.pad(
|
| 128 |
+
input,
|
| 129 |
+
(0, in_features - orig_in_features, 0, out_features - orig_out_features),
|
| 130 |
+
)
|
| 131 |
+
return input
|
| 132 |
+
|
| 133 |
+
def pre_process_static(
|
| 134 |
+
self,
|
| 135 |
+
input: torch.Tensor,
|
| 136 |
+
scale: torch.Tensor,
|
| 137 |
+
zero_point: torch.Tensor,
|
| 138 |
+
block_size: Tuple[int, ...],
|
| 139 |
+
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 140 |
+
input = self.pre_process(input)
|
| 141 |
+
orig_qparam_shape = scale.shape
|
| 142 |
+
new_qparam_shape, reduction_dims = _get_reduction_params(
|
| 143 |
+
block_size, input.size()
|
| 144 |
+
)
|
| 145 |
+
for dim in reduction_dims:
|
| 146 |
+
new_qparam_shape.pop(dim)
|
| 147 |
+
change_in_qparam_shape = [
|
| 148 |
+
new_dim_size - orig_dim_size
|
| 149 |
+
for new_dim_size, orig_dim_size in zip(new_qparam_shape, orig_qparam_shape)
|
| 150 |
+
]
|
| 151 |
+
padding_changes = []
|
| 152 |
+
for dim_change in change_in_qparam_shape:
|
| 153 |
+
padding_changes = [0, dim_change] + padding_changes
|
| 154 |
+
scale = torch.nn.functional.pad(scale, padding_changes)
|
| 155 |
+
zero_point = torch.nn.functional.pad(zero_point, padding_changes)
|
| 156 |
+
return input, scale, zero_point
|
| 157 |
+
|
| 158 |
+
def post_process(
|
| 159 |
+
self,
|
| 160 |
+
input: torch.Tensor,
|
| 161 |
+
scale: torch.Tensor,
|
| 162 |
+
zero_point: torch.Tensor,
|
| 163 |
+
block_size: Tuple[int, ...],
|
| 164 |
+
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 165 |
+
orig_out_features, orig_in_features = input.shape[-2:]
|
| 166 |
+
in_features = find_multiple(orig_in_features, 1024)
|
| 167 |
+
out_features = find_multiple(orig_out_features, 8)
|
| 168 |
+
input = torch.nn.functional.pad(
|
| 169 |
+
input,
|
| 170 |
+
(0, in_features - orig_in_features, 0, out_features - orig_out_features),
|
| 171 |
+
)
|
| 172 |
+
assert len(block_size) == 2 or len(block_size) == 3, (
|
| 173 |
+
f"TensorCoreTiledLayout only supports len(block_size) == 2 or 3, got: {block_size}"
|
| 174 |
+
)
|
| 175 |
+
scale_pad_dim_0 = (out_features - orig_out_features) // block_size[-2]
|
| 176 |
+
scale_pad_dim_1 = (in_features - orig_in_features) // block_size[-1]
|
| 177 |
+
scale = torch.nn.functional.pad(scale, (0, scale_pad_dim_1, 0, scale_pad_dim_0))
|
| 178 |
+
zero_point = torch.nn.functional.pad(
|
| 179 |
+
zero_point, (0, scale_pad_dim_1, 0, scale_pad_dim_0)
|
| 180 |
+
)
|
| 181 |
+
return input, scale, zero_point
|
| 182 |
+
|
| 183 |
+
def extra_repr(self):
|
| 184 |
+
return f"inner_k_tiles={self.inner_k_tiles}"
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
@register_layout(TensorCoreTiledLayout)
|
| 188 |
+
class TensorCoreTiledAQTTensorImpl(AQTTensorImpl):
|
| 189 |
+
"""TensorImpl for tensor_core_tiled layout for affine quantized tensor, this is for int4 only,
|
| 190 |
+
used by tinygemm kernels `_weight_int4pack_mm`
|
| 191 |
+
|
| 192 |
+
It stores the original tensor of dimension [n][k] (int32 dtype) as packed weight of 4-d tensor of
|
| 193 |
+
dimension: [n / 8][k / (inner_k_tiles * 16)][32][inner_k_tiles / 2]
|
| 194 |
+
(unpacked Tensor shape is n * k)
|
| 195 |
+
where inner_k_tiles is an internal argument for packing function of tensor core tiled layout
|
| 196 |
+
that can affect the performance of the matmul kernel (defaults to 8)
|
| 197 |
+
|
| 198 |
+
Note: we also pack scale and zero point together here for tinygemm kernel
|
| 199 |
+
|
| 200 |
+
Note: technically tensor core tiled layout should be the layout for the underlying packed weight
|
| 201 |
+
(int Tensor) but since the scale and zero_point are also packed into the same tensor here which is not used
|
| 202 |
+
in plain layout, we just created a layout for AQT right now, this could be improved if we split out
|
| 203 |
+
int4 aqt into a separate tensor subclass
|
| 204 |
+
|
| 205 |
+
fields:
|
| 206 |
+
packed_weight (torch.Tensor): the 4-d packed tensor in a tensor_core_tiled layout
|
| 207 |
+
scale_and_zero (torch.Tensor): the combined scale Tensor used to map between floating point tensor to quantized tensor and zero_point Tensor
|
| 208 |
+
"""
|
| 209 |
+
|
| 210 |
+
def __new__(
|
| 211 |
+
cls,
|
| 212 |
+
packed_weight: torch.Tensor,
|
| 213 |
+
scale_and_zero: torch.Tensor,
|
| 214 |
+
transposed: bool,
|
| 215 |
+
_layout: Layout,
|
| 216 |
+
):
|
| 217 |
+
kwargs = {}
|
| 218 |
+
kwargs["device"] = packed_weight.device
|
| 219 |
+
kwargs["layout"] = (
|
| 220 |
+
kwargs.get("layout")
|
| 221 |
+
if kwargs.get("layout", False)
|
| 222 |
+
else packed_weight.layout
|
| 223 |
+
)
|
| 224 |
+
kwargs["dtype"] = packed_weight.dtype
|
| 225 |
+
kwargs["requires_grad"] = False
|
| 226 |
+
shape = packed_weight.shape
|
| 227 |
+
return torch.Tensor._make_wrapper_subclass(cls, shape, **kwargs) # type: ignore[attr-defined]
|
| 228 |
+
|
| 229 |
+
def __init__(
|
| 230 |
+
self,
|
| 231 |
+
packed_weight: torch.Tensor,
|
| 232 |
+
scale_and_zero: torch.Tensor,
|
| 233 |
+
transposed: bool,
|
| 234 |
+
_layout: Layout,
|
| 235 |
+
):
|
| 236 |
+
self.packed_weight = packed_weight
|
| 237 |
+
self.scale_and_zero = scale_and_zero
|
| 238 |
+
self.transposed = False
|
| 239 |
+
self._layout = _layout
|
| 240 |
+
|
| 241 |
+
def __tensor_flatten__(self):
|
| 242 |
+
return ["packed_weight", "scale_and_zero"], [self.transposed, self._layout]
|
| 243 |
+
|
| 244 |
+
@classmethod
|
| 245 |
+
def __tensor_unflatten__(
|
| 246 |
+
cls, tensor_data_dict, tensor_attributes, outer_size, outer_stride
|
| 247 |
+
):
|
| 248 |
+
packed_weight, scale_and_zero = (
|
| 249 |
+
tensor_data_dict["packed_weight"],
|
| 250 |
+
tensor_data_dict["scale_and_zero"],
|
| 251 |
+
)
|
| 252 |
+
(
|
| 253 |
+
transposed,
|
| 254 |
+
_layout,
|
| 255 |
+
) = tensor_attributes
|
| 256 |
+
return cls(packed_weight, scale_and_zero, transposed, _layout)
|
| 257 |
+
|
| 258 |
+
@classmethod
|
| 259 |
+
def from_plain(
|
| 260 |
+
cls,
|
| 261 |
+
int_data: torch.Tensor,
|
| 262 |
+
scale: torch.Tensor,
|
| 263 |
+
zero_point: Optional[torch.Tensor],
|
| 264 |
+
_layout: Layout,
|
| 265 |
+
):
|
| 266 |
+
assert isinstance(_layout, TensorCoreTiledLayout)
|
| 267 |
+
assert int_data.dtype == torch.int32, (
|
| 268 |
+
"torch.ops.aten._convert_weight_to_int4pack in torch 2.4 expects `int32` dtype"
|
| 269 |
+
)
|
| 270 |
+
|
| 271 |
+
def quant_2d(int_data_2d):
|
| 272 |
+
if TORCH_VERSION_AT_LEAST_2_5:
|
| 273 |
+
int_data_2d = (int_data_2d[::, ::2] << 4 | int_data_2d[::, 1::2]).to(
|
| 274 |
+
torch.uint8
|
| 275 |
+
)
|
| 276 |
+
else:
|
| 277 |
+
assert int_data_2d.dtype == torch.int32, (
|
| 278 |
+
"torch.ops.aten._convert_weight_to_int4pack in torch 2.4 expects `int32` dtype"
|
| 279 |
+
)
|
| 280 |
+
return torch.ops.aten._convert_weight_to_int4pack(
|
| 281 |
+
int_data_2d.contiguous(), _layout.inner_k_tiles
|
| 282 |
+
)
|
| 283 |
+
|
| 284 |
+
if int_data.dim() == 3: # for moe quant
|
| 285 |
+
num_experts = int_data.shape[0]
|
| 286 |
+
packed_weight_list = []
|
| 287 |
+
for expert in range(num_experts):
|
| 288 |
+
packed_weight_list.append(quant_2d(int_data[expert]).unsqueeze(0))
|
| 289 |
+
packed_weight = torch.cat(packed_weight_list, dim=0)
|
| 290 |
+
scale = scale.reshape(int_data.shape[0], int_data.shape[-2], -1)
|
| 291 |
+
zero_point = (
|
| 292 |
+
zero_point.reshape(int_data.shape[0], int_data.shape[-2], -1)
|
| 293 |
+
if zero_point is not None
|
| 294 |
+
else None
|
| 295 |
+
)
|
| 296 |
+
else:
|
| 297 |
+
assert int_data.dim() == 2
|
| 298 |
+
packed_weight = quant_2d(int_data)
|
| 299 |
+
scale = scale.reshape(int_data.shape[0], -1)
|
| 300 |
+
zero_point = (
|
| 301 |
+
zero_point.reshape(int_data.shape[0], -1)
|
| 302 |
+
if zero_point is not None
|
| 303 |
+
else None
|
| 304 |
+
)
|
| 305 |
+
from torchao.quantization.utils import pack_tinygemm_scales_and_zeros
|
| 306 |
+
|
| 307 |
+
scale_and_zero = pack_tinygemm_scales_and_zeros(scale, zero_point, scale.dtype)
|
| 308 |
+
return cls(packed_weight, scale_and_zero, False, _layout)
|
| 309 |
+
|
| 310 |
+
def to(self, *args, **kwargs):
|
| 311 |
+
kwargs = self._get_to_kwargs(*args, **kwargs)
|
| 312 |
+
device = kwargs["device"]
|
| 313 |
+
# tensor core tiled layout supports both cpu and cuda but does not support the conversion
|
| 314 |
+
# between these two devices, in the future we should not use the same layout for
|
| 315 |
+
# cpu and cuda device: https://github.com/pytorch/ao/issues/1117
|
| 316 |
+
if not is_device(torch.device(self.device).type, device):
|
| 317 |
+
raise ValueError(
|
| 318 |
+
f"TensorCoreTiledAQTTensorImpl does not support conversion from {self.device} to {device}"
|
| 319 |
+
)
|
| 320 |
+
return self.__class__(
|
| 321 |
+
self.packed_weight.to(device),
|
| 322 |
+
self.scale_and_zero.to(device),
|
| 323 |
+
self.transposed,
|
| 324 |
+
self._layout,
|
| 325 |
+
)
|
| 326 |
+
|
| 327 |
+
def _apply_fn_to_data(self, fn):
|
| 328 |
+
# self.packed_weight = fn(self.packed_weight)
|
| 329 |
+
# self.scale_and_zero = fn(self.scale_and_zero)
|
| 330 |
+
# return self
|
| 331 |
+
return self.__class__(
|
| 332 |
+
fn(self.packed_weight),
|
| 333 |
+
fn(self.scale_and_zero),
|
| 334 |
+
self.transposed,
|
| 335 |
+
self._layout,
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
@classmethod
|
| 339 |
+
def __torch_dispatch__(cls, func, types, args, kwargs):
|
| 340 |
+
kwargs = {} if kwargs is None else kwargs
|
| 341 |
+
|
| 342 |
+
if func is aten.detach.default:
|
| 343 |
+
return return_and_correct_aliasing(
|
| 344 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.detach)
|
| 345 |
+
)
|
| 346 |
+
|
| 347 |
+
if func is aten.clone.default:
|
| 348 |
+
return return_and_correct_aliasing(
|
| 349 |
+
func, args, kwargs, args[0]._apply_fn_to_data(torch.clone)
|
| 350 |
+
)
|
| 351 |
+
|
| 352 |
+
if func is aten.copy_.default:
|
| 353 |
+
self = args[0]
|
| 354 |
+
src = args[1]
|
| 355 |
+
if _same_metadata(self, src):
|
| 356 |
+
self_tensors = self.__tensor_flatten__()[0]
|
| 357 |
+
for tensor_name in self_tensors:
|
| 358 |
+
getattr(self, tensor_name).copy_(getattr(src, tensor_name))
|
| 359 |
+
return
|
| 360 |
+
raise ValueError(
|
| 361 |
+
f"Not supported args for copy_ due to metadata mistach: {args[0], args[1]}"
|
| 362 |
+
)
|
| 363 |
+
|
| 364 |
+
if func in [aten.select.int, aten.index.Tensor]:
|
| 365 |
+
assert not (func is aten.select.int and args[1] != 0), (
|
| 366 |
+
"aten.select.int currently only has support for dim=0"
|
| 367 |
+
)
|
| 368 |
+
return return_and_correct_aliasing(
|
| 369 |
+
func,
|
| 370 |
+
args,
|
| 371 |
+
kwargs,
|
| 372 |
+
args[0]._apply_fn_to_data(lambda x: func(x, *args[1:], **kwargs)),
|
| 373 |
+
)
|
| 374 |
+
|
| 375 |
+
if func is aten.t.default:
|
| 376 |
+
"""we don't need to repack the weight and just rely on external
|
| 377 |
+
shape being changed and record the status of transpose/no-transpose
|
| 378 |
+
"""
|
| 379 |
+
transposed = TensorCoreTiledAQTTensorImpl(
|
| 380 |
+
args[0].packed_weight,
|
| 381 |
+
args[0].scale_and_zero,
|
| 382 |
+
not args[0].transposed,
|
| 383 |
+
args[0]._layout,
|
| 384 |
+
)
|
| 385 |
+
return return_and_correct_aliasing(func, args, kwargs, transposed)
|
| 386 |
+
|
| 387 |
+
if func is aten.slice.Tensor:
|
| 388 |
+
self, dim, start, end, step = fill_defaults(args, 5, [0, None, None, 1])
|
| 389 |
+
cur_shape = self.shape
|
| 390 |
+
assert len(cur_shape) == 4
|
| 391 |
+
inner_k_tiles = cur_shape[-1] * 2
|
| 392 |
+
original_shape = (cur_shape[0] * 8, cur_shape[1] * (inner_k_tiles * 16))
|
| 393 |
+
|
| 394 |
+
n_by_8, k_by_inner_tiles, _, _ = self.packed_weight.shape
|
| 395 |
+
sz_dim1, sz_dim0, _ = self.scale_and_zero.shape
|
| 396 |
+
|
| 397 |
+
data_len = original_shape[dim]
|
| 398 |
+
assert dim in [0, 1], (
|
| 399 |
+
f"TensorCoreTiledAQTTensorImpl dispatch: attempting to run {func}, with dim={dim}, that is not supported"
|
| 400 |
+
)
|
| 401 |
+
|
| 402 |
+
if dim == 0:
|
| 403 |
+
pw_len = n_by_8
|
| 404 |
+
sz_len = sz_dim0
|
| 405 |
+
else:
|
| 406 |
+
pw_len = k_by_inner_tiles
|
| 407 |
+
sz_len = sz_dim1
|
| 408 |
+
|
| 409 |
+
if pw_len == 0 or sz_len == 0:
|
| 410 |
+
return return_and_correct_aliasing(
|
| 411 |
+
func,
|
| 412 |
+
args,
|
| 413 |
+
kwargs,
|
| 414 |
+
TensorCoreTiledAQTTensorImpl(
|
| 415 |
+
self.packed_weight,
|
| 416 |
+
self.scale_and_zero,
|
| 417 |
+
self.transposed,
|
| 418 |
+
self._layout,
|
| 419 |
+
),
|
| 420 |
+
)
|
| 421 |
+
|
| 422 |
+
pw_ratio = data_len / pw_len
|
| 423 |
+
start_pw = int(start / pw_ratio)
|
| 424 |
+
end_pw = int(end / pw_ratio)
|
| 425 |
+
|
| 426 |
+
sz_ratio = data_len / sz_len
|
| 427 |
+
start_sz = int(start / sz_ratio)
|
| 428 |
+
end_sz = int(end / sz_ratio)
|
| 429 |
+
|
| 430 |
+
packed_weight = aten.slice(self.packed_weight, dim, start_pw, end_pw, step)
|
| 431 |
+
scale_and_zero = aten.slice(
|
| 432 |
+
self.scale_and_zero, 1 - dim, start_sz, end_sz, step
|
| 433 |
+
)
|
| 434 |
+
return return_and_correct_aliasing(
|
| 435 |
+
func,
|
| 436 |
+
args,
|
| 437 |
+
kwargs,
|
| 438 |
+
TensorCoreTiledAQTTensorImpl(
|
| 439 |
+
packed_weight, scale_and_zero, self.transposed, self._layout
|
| 440 |
+
),
|
| 441 |
+
)
|
| 442 |
+
|
| 443 |
+
raise NotImplementedError(
|
| 444 |
+
f"TensorCoreTiledAQTTensorImpl dispatch: attempting to run {func}, this is not supported"
|
| 445 |
+
)
|
| 446 |
+
|
| 447 |
+
__torch_function__ = torch._C._disabled_torch_function_impl
|
| 448 |
+
|
| 449 |
+
@property
|
| 450 |
+
def block_size(self):
|
| 451 |
+
from torchao.quantization.utils import unpack_tinygemm_scales_and_zeros
|
| 452 |
+
|
| 453 |
+
scale, zero = unpack_tinygemm_scales_and_zeros(self.scale_and_zero)
|
| 454 |
+
cur_shape = self.shape
|
| 455 |
+
if len(cur_shape) == 5:
|
| 456 |
+
ones = [1, 1]
|
| 457 |
+
cur_shape = cur_shape[1:]
|
| 458 |
+
else:
|
| 459 |
+
assert len(cur_shape) == 4
|
| 460 |
+
ones = [1]
|
| 461 |
+
inner_k_tiles = cur_shape[-1] * 2
|
| 462 |
+
original_shape = (cur_shape[0] * 8, cur_shape[1] * (inner_k_tiles * 16))
|
| 463 |
+
groupsize = int(original_shape[1] / scale.shape[-2])
|
| 464 |
+
return tuple([*ones, groupsize])
|
| 465 |
+
|
| 466 |
+
def get_plain(self) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 467 |
+
from torchao.quantization.quant_primitives import (
|
| 468 |
+
ZeroPointDomain,
|
| 469 |
+
quantize_affine,
|
| 470 |
+
)
|
| 471 |
+
from torchao.quantization.utils import unpack_tinygemm_scales_and_zeros
|
| 472 |
+
|
| 473 |
+
def dequant_4d(self):
|
| 474 |
+
cur_shape = self.shape
|
| 475 |
+
scale, zero = unpack_tinygemm_scales_and_zeros(self.scale_and_zero)
|
| 476 |
+
assert len(cur_shape) == 4
|
| 477 |
+
inner_k_tiles = cur_shape[-1] * 2
|
| 478 |
+
original_shape = (cur_shape[0] * 8, cur_shape[1] * (inner_k_tiles * 16))
|
| 479 |
+
eye_shape = original_shape[1]
|
| 480 |
+
groupsize = int(original_shape[1] / scale.shape[-2])
|
| 481 |
+
block_size = (1, groupsize)
|
| 482 |
+
original_dtype = torch.bfloat16
|
| 483 |
+
assert len(block_size) == 2 and block_size[0] == 1
|
| 484 |
+
dequantized = torch.ops.aten._weight_int4pack_mm(
|
| 485 |
+
torch.eye(eye_shape, device=self.device, dtype=original_dtype),
|
| 486 |
+
self.packed_weight,
|
| 487 |
+
groupsize,
|
| 488 |
+
self.scale_and_zero,
|
| 489 |
+
)
|
| 490 |
+
dequantized = dequantized.t().contiguous()
|
| 491 |
+
return dequantized
|
| 492 |
+
|
| 493 |
+
cur_shape = self.shape
|
| 494 |
+
|
| 495 |
+
if len(cur_shape) == 4:
|
| 496 |
+
dequantized = dequant_4d(self)
|
| 497 |
+
else:
|
| 498 |
+
assert len(cur_shape) == 5
|
| 499 |
+
num_experts = cur_shape[0]
|
| 500 |
+
dequantized_list = []
|
| 501 |
+
for expert in range(num_experts):
|
| 502 |
+
dequantized_list.append(dequant_4d(self[expert]).unsqueeze(0))
|
| 503 |
+
dequantized = torch.cat(dequantized_list, dim=0)
|
| 504 |
+
|
| 505 |
+
scale, zero = unpack_tinygemm_scales_and_zeros(self.scale_and_zero)
|
| 506 |
+
# TODO: move this to `unpack_tinygemm_scales_and_zeros`?
|
| 507 |
+
scale = scale.reshape(scale.shape[:-1]).contiguous()
|
| 508 |
+
zero = zero.reshape(zero.shape[:-1]).contiguous()
|
| 509 |
+
|
| 510 |
+
target_dtype = torch.int32
|
| 511 |
+
quant_min = 0
|
| 512 |
+
quant_max = 15
|
| 513 |
+
zero_point_domain = ZeroPointDomain.FLOAT
|
| 514 |
+
int_data = quantize_affine(
|
| 515 |
+
dequantized,
|
| 516 |
+
self.block_size,
|
| 517 |
+
scale,
|
| 518 |
+
zero,
|
| 519 |
+
target_dtype,
|
| 520 |
+
quant_min,
|
| 521 |
+
quant_max,
|
| 522 |
+
zero_point_domain,
|
| 523 |
+
)
|
| 524 |
+
return int_data, scale, zero
|
| 525 |
+
|
| 526 |
+
def get_layout(self) -> Layout:
|
| 527 |
+
return self._layout
|
lib/python3.12/site-packages/torchao/dtypes/uintx/uint4_layout.py
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD 3-Clause license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
import torch
|
| 7 |
+
import torch._prims_common as utils
|
| 8 |
+
import torch.utils._pytree as pytree
|
| 9 |
+
from torch.library import Library, impl
|
| 10 |
+
|
| 11 |
+
from torchao.utils import fill_defaults
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def down_size(size):
|
| 15 |
+
assert size[-1] % 2 == 0, f"{size} last dim not divisible by two"
|
| 16 |
+
return (*size[:-1], size[-1] // 2)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def up_size(size):
|
| 20 |
+
return (*size[:-1], size[-1] * 2)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
# from
|
| 24 |
+
# https://github.com/drisspg/transformer_nuggets/blob/9ad3a7fc552a954eb702ade0e276b8d8e09c3db6/transformer_nuggets/quant/qlora.py#L233
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def unpack_uint4(uint8_data) -> torch.Tensor:
|
| 28 |
+
"""Get the original weight from the normalized float weight format"""
|
| 29 |
+
# since we are using uint8 we will decode 2 entries per byte
|
| 30 |
+
# Shift elements down 4 and select out the bottom 4 bits
|
| 31 |
+
shape = uint8_data.shape
|
| 32 |
+
first_elements = (uint8_data >> 4).to(torch.uint8)
|
| 33 |
+
second_elements = (uint8_data & 0b1111).to(torch.uint8)
|
| 34 |
+
return torch.stack([first_elements, second_elements], dim=-1).view(up_size(shape))
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def pack_uint4(uint8_data) -> torch.Tensor:
|
| 38 |
+
# converting to uint8 for operations
|
| 39 |
+
shape = uint8_data.shape
|
| 40 |
+
assert shape[-1] % 2 == 0
|
| 41 |
+
uint8_data = uint8_data.contiguous().view(-1)
|
| 42 |
+
return (uint8_data[::2] << 4 | uint8_data[1::2]).view(down_size(shape))
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
qtensor_lib = Library("qtensors", "DEF")
|
| 46 |
+
qtensor_lib.define(
|
| 47 |
+
"quantize_per_tensor_uint4(Tensor input, float scale, int zero_point) -> Tensor"
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
@impl(qtensor_lib, "quantize_per_tensor_uint4", "CompositeExplicitAutograd")
|
| 52 |
+
def quantize_per_tensor_uint4(
|
| 53 |
+
input: torch.Tensor,
|
| 54 |
+
scale: float,
|
| 55 |
+
zero_point: int,
|
| 56 |
+
) -> torch.Tensor:
|
| 57 |
+
inv_scale = 1.0 / scale
|
| 58 |
+
return pack_uint4(
|
| 59 |
+
torch.clamp(torch.round(input * inv_scale) + zero_point, 0, 15).to(torch.uint8)
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
qtensor_lib.define(
|
| 64 |
+
"dequantize_per_tensor_uint4(Tensor input, float scale, int zero_point) -> Tensor"
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
@impl(qtensor_lib, "dequantize_per_tensor_uint4", "CompositeExplicitAutograd")
|
| 69 |
+
def dequantize_per_tensor_uint4(
|
| 70 |
+
input: torch.Tensor,
|
| 71 |
+
scale: float,
|
| 72 |
+
zero_point: int,
|
| 73 |
+
) -> torch.Tensor:
|
| 74 |
+
input = unpack_uint4(input)
|
| 75 |
+
return (input.view(torch.uint8).to(torch.float32) - zero_point) * scale
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class UInt4Tensor(torch.Tensor):
|
| 79 |
+
@staticmethod
|
| 80 |
+
def __new__(cls, elem, **kwargs):
|
| 81 |
+
assert elem.dtype is torch.uint8
|
| 82 |
+
assert not kwargs.get("requires_grad", False)
|
| 83 |
+
kwargs["requires_grad"] = False
|
| 84 |
+
|
| 85 |
+
return torch.Tensor._make_wrapper_subclass(
|
| 86 |
+
cls, up_size(elem.shape), dtype=torch.uint4, **kwargs
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
def __init__(self, elem, **kwargs):
|
| 90 |
+
self.elem = elem
|
| 91 |
+
|
| 92 |
+
@classmethod
|
| 93 |
+
def from_unpacked(cls, unpacked):
|
| 94 |
+
return UInt4Tensor(pack_uint4(unpacked))
|
| 95 |
+
|
| 96 |
+
def tolist(self):
|
| 97 |
+
return self.to(torch.uint8).tolist()
|
| 98 |
+
|
| 99 |
+
def __tensor_flatten__(self):
|
| 100 |
+
return ["elem"], None
|
| 101 |
+
|
| 102 |
+
@staticmethod
|
| 103 |
+
def __tensor_unflatten__(flattened, meta, outer_size, outer_stride):
|
| 104 |
+
assert meta is None
|
| 105 |
+
elem = flattened["elem"]
|
| 106 |
+
return UInt4Tensor(elem)
|
| 107 |
+
|
| 108 |
+
def __hash__(self):
|
| 109 |
+
return hash(self.elem)
|
| 110 |
+
|
| 111 |
+
def __eq__(self, other):
|
| 112 |
+
return torch.equal(self.elem, other.elem)
|
| 113 |
+
|
| 114 |
+
@classmethod
|
| 115 |
+
def __torch_dispatch__(cls, func, types, args, kwargs=None):
|
| 116 |
+
if func is torch.ops.aten.view.default:
|
| 117 |
+
self, size = args
|
| 118 |
+
size = utils.infer_size(size, self.numel())
|
| 119 |
+
assert not kwargs
|
| 120 |
+
# WARNING: views not preserved
|
| 121 |
+
return UInt4Tensor(self.elem.reshape(down_size(size)))
|
| 122 |
+
elif func is torch.ops.aten.view.dtype:
|
| 123 |
+
self, dtype = args
|
| 124 |
+
if dtype == torch.uint8:
|
| 125 |
+
return unpack_uint4(self.elem).view(torch.uint8)
|
| 126 |
+
return NotImplementedError(f"view {args}")
|
| 127 |
+
elif func is torch.ops.aten.to.dtype:
|
| 128 |
+
self, dtype = args
|
| 129 |
+
if dtype == torch.uint8:
|
| 130 |
+
return unpack_uint4(self.elem).view(torch.uint8)
|
| 131 |
+
return NotImplementedError(f"to {args}")
|
| 132 |
+
elif func is torch.ops.aten.eq.Tensor:
|
| 133 |
+
args = pytree.tree_map_only(
|
| 134 |
+
UInt4Tensor, lambda x: x.elem.view(torch.uint8), args
|
| 135 |
+
)
|
| 136 |
+
kwargs = pytree.tree_map_only(
|
| 137 |
+
UInt4Tensor, lambda x: x.elem.view(torch.uint8), kwargs
|
| 138 |
+
)
|
| 139 |
+
return torch.ops.aten.eq.Tensor(*args, **kwargs)
|
| 140 |
+
elif func is torch.ops.aten._to_copy.default:
|
| 141 |
+
(self,) = args
|
| 142 |
+
if kwargs == {"dtype": torch.uint8}:
|
| 143 |
+
return unpack_uint4(self.elem).view(self.shape) # no wrap
|
| 144 |
+
else:
|
| 145 |
+
raise NotImplementedError(f"_to_copy {kwargs}")
|
| 146 |
+
elif func is torch.ops.aten.unbind.int:
|
| 147 |
+
# This is tricky. Given torch.tensor([0, 1, 2, 3]) we want to
|
| 148 |
+
# create four tensors containing one element each. But we can't
|
| 149 |
+
# do this with uint4 because such a tensor's size is not divisible
|
| 150 |
+
# by bytes. What I am going to do instead is promote to uint8
|
| 151 |
+
# when this happens
|
| 152 |
+
self, dim = fill_defaults(args, 2, [0])
|
| 153 |
+
if dim != self.dim() - 1:
|
| 154 |
+
raise NotImplementedError(f"unbind dim={dim}")
|
| 155 |
+
else:
|
| 156 |
+
# We're unbinding the last dimension, need to promote
|
| 157 |
+
return torch.ops.aten._to_copy.default(self, dtype=torch.uint8).unbind(
|
| 158 |
+
dim
|
| 159 |
+
)
|
| 160 |
+
elif func is torch.ops.aten.select.int:
|
| 161 |
+
self, dim, index = args
|
| 162 |
+
if dim != self.dim() - 1:
|
| 163 |
+
return UInt4Tensor(torch.ops.aten.select.int(self.elem, dim, index))
|
| 164 |
+
else:
|
| 165 |
+
raise NotImplementedError(f"select dim={dim}")
|
| 166 |
+
elif func is torch.ops.aten.slice.Tensor:
|
| 167 |
+
self, dim, start, end, step = fill_defaults(args, 5, [0, None, None, 1])
|
| 168 |
+
if dim == self.dim() - 1:
|
| 169 |
+
# hard case
|
| 170 |
+
if step != 1:
|
| 171 |
+
raise NotImplementedError(f"slice step={step}")
|
| 172 |
+
assert start % 2 == 0, start
|
| 173 |
+
assert end >= self.shape[dim] or end % 2 == 0, end
|
| 174 |
+
return UInt4Tensor(
|
| 175 |
+
torch.ops.aten.slice.Tensor(self.elem, dim, start // 2, end // 2, 1)
|
| 176 |
+
)
|
| 177 |
+
else:
|
| 178 |
+
# easy case
|
| 179 |
+
return UInt4Tensor(
|
| 180 |
+
torch.ops.aten.slice.Tensor(self.elem, dim, start, end, step)
|
| 181 |
+
)
|
| 182 |
+
elif func is torch.ops.aten.t.default:
|
| 183 |
+
# assert False, "transpose is not properly implemented currently"
|
| 184 |
+
(self,) = args
|
| 185 |
+
unpacked = unpack_uint4(self.elem)
|
| 186 |
+
transposed = torch.ops.aten.t.default(unpacked)
|
| 187 |
+
transposed_and_packed = pack_uint4(transposed)
|
| 188 |
+
return UInt4Tensor(transposed_and_packed)
|
| 189 |
+
elif func is torch.ops.aten.transpose_copy.int:
|
| 190 |
+
self, dim0, dim1 = args
|
| 191 |
+
unpacked = unpack_uint4(self.elem).view(self.shape)
|
| 192 |
+
transposed = torch.ops.aten.transpose_copy.int(unpacked, dim0, dim1)
|
| 193 |
+
transposed_and_packed = pack_uint4(transposed)
|
| 194 |
+
return UInt4Tensor(transposed_and_packed)
|
| 195 |
+
elif func is torch.ops.aten.as_strided.default:
|
| 196 |
+
# size, stride, storage_offset are referring to tensor elements, not physical bytes
|
| 197 |
+
self, size, stride, storage_offset = args
|
| 198 |
+
size = down_size(size)
|
| 199 |
+
|
| 200 |
+
new_stride = []
|
| 201 |
+
for s in stride:
|
| 202 |
+
if s != 1:
|
| 203 |
+
# since two int4 equals to 1 uint8
|
| 204 |
+
new_stride.append(s // 2)
|
| 205 |
+
else:
|
| 206 |
+
new_stride.append(s)
|
| 207 |
+
stride = new_stride
|
| 208 |
+
|
| 209 |
+
storage_offset //= 2
|
| 210 |
+
return UInt4Tensor(
|
| 211 |
+
torch.ops.aten.as_strided.default(
|
| 212 |
+
self.elem, size, stride, storage_offset
|
| 213 |
+
)
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
raise NotImplementedError(f"{func}")
|
| 217 |
+
|
| 218 |
+
__torch_function__ = torch._C._disabled_torch_function_impl
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def _dynamically_quantize_per_channel_int4(x, quant_min, quant_max, target_dtype):
|
| 222 |
+
# assumes symmetric quantization
|
| 223 |
+
# assumes axis == 0
|
| 224 |
+
# assumes dense memory format
|
| 225 |
+
# TODO(future): relax ^ as needed
|
| 226 |
+
|
| 227 |
+
# default setup for affine quantization of activations
|
| 228 |
+
eps = torch.finfo(torch.float32).eps
|
| 229 |
+
|
| 230 |
+
# get min and max
|
| 231 |
+
min_val, max_val = torch.aminmax(x, dim=1)
|
| 232 |
+
|
| 233 |
+
# calculate scale and zero point based on min and max
|
| 234 |
+
# reference: https://fburl.com/code/srbiybme
|
| 235 |
+
min_val_neg = torch.min(min_val, torch.zeros_like(min_val))
|
| 236 |
+
max_val_pos = torch.max(max_val, torch.zeros_like(max_val))
|
| 237 |
+
device = min_val_neg.device
|
| 238 |
+
|
| 239 |
+
# reference: https://fburl.com/code/4wll53rk
|
| 240 |
+
max_val_pos = torch.max(-min_val_neg, max_val_pos)
|
| 241 |
+
scale = max_val_pos / (float(quant_max - quant_min) / 2)
|
| 242 |
+
# ensure scale is the same dtype as the original tensor
|
| 243 |
+
scale = torch.clamp(scale, min=eps).to(x.dtype)
|
| 244 |
+
zero_point = torch.zeros(min_val_neg.size(), dtype=torch.int64, device=device)
|
| 245 |
+
|
| 246 |
+
# quantize based on qmin/qmax/scale/zp
|
| 247 |
+
# reference: torch/ao/quantization/fx/_decomposed.py?lines=63
|
| 248 |
+
x_div = x.transpose(0, 1) / scale
|
| 249 |
+
x_round = torch.round(x_div)
|
| 250 |
+
x_zp = x_round + zero_point
|
| 251 |
+
x_zp = x_zp.transpose(0, 1)
|
| 252 |
+
quant = torch.clamp(x_zp, quant_min, quant_max)
|
| 253 |
+
|
| 254 |
+
if target_dtype == torch.uint4:
|
| 255 |
+
# TODO: simplify (maybe implement to)
|
| 256 |
+
quant = PerChannelSymmetricWeightUInt4Tensor.from_unpacked(
|
| 257 |
+
quant.to(torch.uint8), scale
|
| 258 |
+
)
|
| 259 |
+
else:
|
| 260 |
+
quant = quant.to(target_dtype)
|
| 261 |
+
|
| 262 |
+
return quant, scale, zero_point
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
class PerChannelSymmetricWeightUInt4Tensor(UInt4Tensor):
|
| 266 |
+
@staticmethod
|
| 267 |
+
def __new__(cls, elem, scales, **kwargs):
|
| 268 |
+
return super().__new__(cls, elem, **kwargs)
|
| 269 |
+
|
| 270 |
+
def __init__(self, elem, scales, **kwargs):
|
| 271 |
+
super().__init__(elem, **kwargs)
|
| 272 |
+
|
| 273 |
+
self.scales = scales
|
| 274 |
+
|
| 275 |
+
def __tensor_flatten__(self):
|
| 276 |
+
return ["elem", "scales"], None
|
| 277 |
+
|
| 278 |
+
@staticmethod
|
| 279 |
+
def __tensor_unflatten__(flattened, meta, outer_size, outer_stride):
|
| 280 |
+
assert meta is None
|
| 281 |
+
elem = flattened["elem"]
|
| 282 |
+
scales = flattened["scales"]
|
| 283 |
+
return PerChannelSymmetricWeightUInt4Tensor(elem, scales)
|
| 284 |
+
|
| 285 |
+
@classmethod
|
| 286 |
+
|
| 287 |
+
# inconsistently.
|
| 288 |
+
|
| 289 |
+
def from_unpacked(cls, unpacked, scales):
|
| 290 |
+
return cls(pack_uint4(unpacked), scales)
|
| 291 |
+
|
| 292 |
+
@classmethod
|
| 293 |
+
def __torch_dispatch__(cls, func, types, args, kwargs=None):
|
| 294 |
+
if func is torch.ops.aten.addmm.default:
|
| 295 |
+
bias, x, weight = args
|
| 296 |
+
x_view = x.view(-1, x.shape[-1])
|
| 297 |
+
y = torch.mm(x_view, weight.to(torch.uint8).to(x.dtype)) * weight.scales
|
| 298 |
+
y = y.reshape(*x.shape[:-1], -1)
|
| 299 |
+
if bias is not None:
|
| 300 |
+
y += bias
|
| 301 |
+
return y
|
| 302 |
+
elif func is torch.ops.aten.t.default:
|
| 303 |
+
# TODO: add proper support for transpose
|
| 304 |
+
(self,) = args
|
| 305 |
+
unpacked = unpack_uint4(self.elem)
|
| 306 |
+
transposed = torch.ops.aten.t.default(unpacked)
|
| 307 |
+
return PerChannelSymmetricWeightUInt4Tensor.from_unpacked(
|
| 308 |
+
transposed, self.scales
|
| 309 |
+
)
|
| 310 |
+
elif func is torch.ops.aten.detach.default:
|
| 311 |
+
(self,) = args
|
| 312 |
+
return self
|
| 313 |
+
return super().__torch_dispatch__(func, types, args, kwargs)
|
| 314 |
+
|
| 315 |
+
@classmethod
|
| 316 |
+
def from_float(cls, w_fp32):
|
| 317 |
+
w_int4, scales, _zp = _dynamically_quantize_per_channel_int4(
|
| 318 |
+
w_fp32, 0, 15, torch.uint4
|
| 319 |
+
)
|
| 320 |
+
w_int4 = w_int4.to(device=w_fp32.device)
|
| 321 |
+
return w_int4
|