ZhengyangZhang commited on
Commit
bd71d55
·
verified ·
1 Parent(s): d2a6b9f

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. lib/python3.12/site-packages/torchao/dtypes/__init__.py +64 -0
  2. lib/python3.12/site-packages/torchao/dtypes/__pycache__/__init__.cpython-312.pyc +0 -0
  3. lib/python3.12/site-packages/torchao/dtypes/__pycache__/_nf4tensor_api.cpython-312.pyc +0 -0
  4. lib/python3.12/site-packages/torchao/dtypes/__pycache__/affine_quantized_tensor.cpython-312.pyc +0 -0
  5. lib/python3.12/site-packages/torchao/dtypes/__pycache__/affine_quantized_tensor_ops.cpython-312.pyc +0 -0
  6. lib/python3.12/site-packages/torchao/dtypes/__pycache__/nf4tensor.cpython-312.pyc +0 -0
  7. lib/python3.12/site-packages/torchao/dtypes/__pycache__/utils.cpython-312.pyc +0 -0
  8. lib/python3.12/site-packages/torchao/dtypes/_nf4tensor_api.py +39 -0
  9. lib/python3.12/site-packages/torchao/dtypes/affine_quantized_tensor.py +507 -0
  10. lib/python3.12/site-packages/torchao/dtypes/affine_quantized_tensor_ops.py +580 -0
  11. lib/python3.12/site-packages/torchao/dtypes/floatx/__init__.py +17 -0
  12. lib/python3.12/site-packages/torchao/dtypes/floatx/__pycache__/__init__.cpython-312.pyc +0 -0
  13. lib/python3.12/site-packages/torchao/dtypes/floatx/__pycache__/cutlass_semi_sparse_layout.cpython-312.pyc +0 -0
  14. lib/python3.12/site-packages/torchao/dtypes/floatx/__pycache__/float8_layout.cpython-312.pyc +0 -0
  15. lib/python3.12/site-packages/torchao/dtypes/floatx/__pycache__/floatx_tensor_core_layout.cpython-312.pyc +0 -0
  16. lib/python3.12/site-packages/torchao/dtypes/floatx/cutlass_semi_sparse_layout.py +212 -0
  17. lib/python3.12/site-packages/torchao/dtypes/floatx/float8_layout.py +386 -0
  18. lib/python3.12/site-packages/torchao/dtypes/floatx/floatx_tensor_core_layout.py +666 -0
  19. lib/python3.12/site-packages/torchao/dtypes/nf4tensor.py +1086 -0
  20. lib/python3.12/site-packages/torchao/dtypes/uintx/__init__.py +51 -0
  21. lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/__init__.cpython-312.pyc +0 -0
  22. lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/bitpacking.cpython-312.pyc +0 -0
  23. lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/block_sparse_layout.cpython-312.pyc +0 -0
  24. lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/cutlass_int4_packed_layout.cpython-312.pyc +0 -0
  25. lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/gemlite_layout.cpython-312.pyc +0 -0
  26. lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/int4_cpu_layout.cpython-312.pyc +0 -0
  27. lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/int4_xpu_layout.cpython-312.pyc +0 -0
  28. lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/marlin_qqq_tensor.cpython-312.pyc +0 -0
  29. lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/marlin_sparse_layout.cpython-312.pyc +0 -0
  30. lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/packed_linear_int8_dynamic_activation_intx_weight_layout.cpython-312.pyc +0 -0
  31. lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/plain_layout.cpython-312.pyc +0 -0
  32. lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/q_dq_layout.cpython-312.pyc +0 -0
  33. lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/semi_sparse_layout.cpython-312.pyc +0 -0
  34. lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/tensor_core_tiled_layout.cpython-312.pyc +0 -0
  35. lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/uint4_layout.cpython-312.pyc +0 -0
  36. lib/python3.12/site-packages/torchao/dtypes/uintx/__pycache__/uintx_layout.cpython-312.pyc +0 -0
  37. lib/python3.12/site-packages/torchao/dtypes/uintx/bitpacking.py +265 -0
  38. lib/python3.12/site-packages/torchao/dtypes/uintx/block_sparse_layout.py +233 -0
  39. lib/python3.12/site-packages/torchao/dtypes/uintx/cutlass_int4_packed_layout.py +224 -0
  40. lib/python3.12/site-packages/torchao/dtypes/uintx/gemlite_layout.py +403 -0
  41. lib/python3.12/site-packages/torchao/dtypes/uintx/int4_cpu_layout.py +354 -0
  42. lib/python3.12/site-packages/torchao/dtypes/uintx/int4_xpu_layout.py +445 -0
  43. lib/python3.12/site-packages/torchao/dtypes/uintx/marlin_qqq_tensor.py +351 -0
  44. lib/python3.12/site-packages/torchao/dtypes/uintx/marlin_sparse_layout.py +310 -0
  45. lib/python3.12/site-packages/torchao/dtypes/uintx/packed_linear_int8_dynamic_activation_intx_weight_layout.py +467 -0
  46. lib/python3.12/site-packages/torchao/dtypes/uintx/plain_layout.py +315 -0
  47. lib/python3.12/site-packages/torchao/dtypes/uintx/q_dq_layout.py +242 -0
  48. lib/python3.12/site-packages/torchao/dtypes/uintx/semi_sparse_layout.py +128 -0
  49. lib/python3.12/site-packages/torchao/dtypes/uintx/tensor_core_tiled_layout.py +527 -0
  50. 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