Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- _distutils_hack/__init__.py +239 -0
- _distutils_hack/override.py +1 -0
- aiohappyeyeballs-2.6.1.dist-info/INSTALLER +1 -0
- aiohappyeyeballs-2.6.1.dist-info/LICENSE +279 -0
- aiohappyeyeballs-2.6.1.dist-info/METADATA +123 -0
- aiohappyeyeballs-2.6.1.dist-info/RECORD +12 -0
- aiohappyeyeballs-2.6.1.dist-info/REQUESTED +0 -0
- aiohappyeyeballs-2.6.1.dist-info/WHEEL +4 -0
- aiohappyeyeballs/__init__.py +14 -0
- aiohappyeyeballs/_staggered.py +207 -0
- aiohappyeyeballs/impl.py +259 -0
- aiohappyeyeballs/py.typed +0 -0
- aiohappyeyeballs/types.py +17 -0
- aiohappyeyeballs/utils.py +97 -0
- aiosignal/__init__.py +59 -0
- aiosignal/py.typed +0 -0
- annotated_doc-0.0.4.dist-info/INSTALLER +1 -0
- annotated_doc-0.0.4.dist-info/METADATA +145 -0
- annotated_doc-0.0.4.dist-info/RECORD +10 -0
- annotated_doc-0.0.4.dist-info/REQUESTED +0 -0
- annotated_doc-0.0.4.dist-info/WHEEL +4 -0
- annotated_doc-0.0.4.dist-info/entry_points.txt +4 -0
- annotated_doc-0.0.4.dist-info/licenses/LICENSE +21 -0
- attrs-26.1.0.dist-info/INSTALLER +1 -0
- attrs-26.1.0.dist-info/METADATA +199 -0
- attrs-26.1.0.dist-info/RECORD +37 -0
- attrs-26.1.0.dist-info/REQUESTED +0 -0
- attrs-26.1.0.dist-info/WHEEL +4 -0
- bitsandbytes/__init__.py +75 -0
- bitsandbytes/__main__.py +4 -0
- bitsandbytes/_ops.py +433 -0
- bitsandbytes/autograd/__init__.py +0 -0
- bitsandbytes/autograd/_functions.py +401 -0
- bitsandbytes/backends/__init__.py +0 -0
- bitsandbytes/backends/cpu/__init__.py +0 -0
- bitsandbytes/backends/cpu/ops.py +301 -0
- bitsandbytes/backends/cuda/__init__.py +0 -0
- bitsandbytes/backends/cuda/ops.py +770 -0
- bitsandbytes/backends/default/__init__.py +0 -0
- bitsandbytes/backends/default/ops.py +616 -0
- bitsandbytes/backends/hpu/__init__.py +0 -0
- bitsandbytes/backends/hpu/ops.py +55 -0
- bitsandbytes/backends/triton/__init__.py +0 -0
- bitsandbytes/backends/triton/kernels_4bit.py +577 -0
- bitsandbytes/backends/triton/kernels_8bit_quant.py +195 -0
- bitsandbytes/backends/triton/kernels_optim.py +1154 -0
- bitsandbytes/backends/triton/ops.py +298 -0
- bitsandbytes/backends/utils.py +84 -0
- bitsandbytes/backends/xpu/__init__.py +0 -0
- bitsandbytes/backends/xpu/ops.py +242 -0
_distutils_hack/__init__.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# don't import any costly modules
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
|
| 5 |
+
report_url = (
|
| 6 |
+
"https://github.com/pypa/setuptools/issues/new?template=distutils-deprecation.yml"
|
| 7 |
+
)
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def warn_distutils_present():
|
| 11 |
+
if 'distutils' not in sys.modules:
|
| 12 |
+
return
|
| 13 |
+
import warnings
|
| 14 |
+
|
| 15 |
+
warnings.warn(
|
| 16 |
+
"Distutils was imported before Setuptools, but importing Setuptools "
|
| 17 |
+
"also replaces the `distutils` module in `sys.modules`. This may lead "
|
| 18 |
+
"to undesirable behaviors or errors. To avoid these issues, avoid "
|
| 19 |
+
"using distutils directly, ensure that setuptools is installed in the "
|
| 20 |
+
"traditional way (e.g. not an editable install), and/or make sure "
|
| 21 |
+
"that setuptools is always imported before distutils."
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def clear_distutils():
|
| 26 |
+
if 'distutils' not in sys.modules:
|
| 27 |
+
return
|
| 28 |
+
import warnings
|
| 29 |
+
|
| 30 |
+
warnings.warn(
|
| 31 |
+
"Setuptools is replacing distutils. Support for replacing "
|
| 32 |
+
"an already imported distutils is deprecated. In the future, "
|
| 33 |
+
"this condition will fail. "
|
| 34 |
+
f"Register concerns at {report_url}"
|
| 35 |
+
)
|
| 36 |
+
mods = [
|
| 37 |
+
name
|
| 38 |
+
for name in sys.modules
|
| 39 |
+
if name == "distutils" or name.startswith("distutils.")
|
| 40 |
+
]
|
| 41 |
+
for name in mods:
|
| 42 |
+
del sys.modules[name]
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def enabled():
|
| 46 |
+
"""
|
| 47 |
+
Allow selection of distutils by environment variable.
|
| 48 |
+
"""
|
| 49 |
+
which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local')
|
| 50 |
+
if which == 'stdlib':
|
| 51 |
+
import warnings
|
| 52 |
+
|
| 53 |
+
warnings.warn(
|
| 54 |
+
"Reliance on distutils from stdlib is deprecated. Users "
|
| 55 |
+
"must rely on setuptools to provide the distutils module. "
|
| 56 |
+
"Avoid importing distutils or import setuptools first, "
|
| 57 |
+
"and avoid setting SETUPTOOLS_USE_DISTUTILS=stdlib. "
|
| 58 |
+
f"Register concerns at {report_url}"
|
| 59 |
+
)
|
| 60 |
+
return which == 'local'
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def ensure_local_distutils():
|
| 64 |
+
import importlib
|
| 65 |
+
|
| 66 |
+
clear_distutils()
|
| 67 |
+
|
| 68 |
+
# With the DistutilsMetaFinder in place,
|
| 69 |
+
# perform an import to cause distutils to be
|
| 70 |
+
# loaded from setuptools._distutils. Ref #2906.
|
| 71 |
+
with shim():
|
| 72 |
+
importlib.import_module('distutils')
|
| 73 |
+
|
| 74 |
+
# check that submodules load as expected
|
| 75 |
+
core = importlib.import_module('distutils.core')
|
| 76 |
+
assert '_distutils' in core.__file__, core.__file__
|
| 77 |
+
assert 'setuptools._distutils.log' not in sys.modules
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def do_override():
|
| 81 |
+
"""
|
| 82 |
+
Ensure that the local copy of distutils is preferred over stdlib.
|
| 83 |
+
|
| 84 |
+
See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
|
| 85 |
+
for more motivation.
|
| 86 |
+
"""
|
| 87 |
+
if enabled():
|
| 88 |
+
warn_distutils_present()
|
| 89 |
+
ensure_local_distutils()
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class _TrivialRe:
|
| 93 |
+
def __init__(self, *patterns) -> None:
|
| 94 |
+
self._patterns = patterns
|
| 95 |
+
|
| 96 |
+
def match(self, string):
|
| 97 |
+
return all(pat in string for pat in self._patterns)
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
class DistutilsMetaFinder:
|
| 101 |
+
def find_spec(self, fullname, path, target=None):
|
| 102 |
+
# optimization: only consider top level modules and those
|
| 103 |
+
# found in the CPython test suite.
|
| 104 |
+
if path is not None and not fullname.startswith('test.'):
|
| 105 |
+
return None
|
| 106 |
+
|
| 107 |
+
method_name = 'spec_for_{fullname}'.format(**locals())
|
| 108 |
+
method = getattr(self, method_name, lambda: None)
|
| 109 |
+
return method()
|
| 110 |
+
|
| 111 |
+
def spec_for_distutils(self):
|
| 112 |
+
if self.is_cpython():
|
| 113 |
+
return None
|
| 114 |
+
|
| 115 |
+
import importlib
|
| 116 |
+
import importlib.abc
|
| 117 |
+
import importlib.util
|
| 118 |
+
|
| 119 |
+
try:
|
| 120 |
+
mod = importlib.import_module('setuptools._distutils')
|
| 121 |
+
except Exception:
|
| 122 |
+
# There are a couple of cases where setuptools._distutils
|
| 123 |
+
# may not be present:
|
| 124 |
+
# - An older Setuptools without a local distutils is
|
| 125 |
+
# taking precedence. Ref #2957.
|
| 126 |
+
# - Path manipulation during sitecustomize removes
|
| 127 |
+
# setuptools from the path but only after the hook
|
| 128 |
+
# has been loaded. Ref #2980.
|
| 129 |
+
# In either case, fall back to stdlib behavior.
|
| 130 |
+
return None
|
| 131 |
+
|
| 132 |
+
class DistutilsLoader(importlib.abc.Loader):
|
| 133 |
+
def create_module(self, spec):
|
| 134 |
+
mod.__name__ = 'distutils'
|
| 135 |
+
return mod
|
| 136 |
+
|
| 137 |
+
def exec_module(self, module):
|
| 138 |
+
pass
|
| 139 |
+
|
| 140 |
+
return importlib.util.spec_from_loader(
|
| 141 |
+
'distutils', DistutilsLoader(), origin=mod.__file__
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
@staticmethod
|
| 145 |
+
def is_cpython():
|
| 146 |
+
"""
|
| 147 |
+
Suppress supplying distutils for CPython (build and tests).
|
| 148 |
+
Ref #2965 and #3007.
|
| 149 |
+
"""
|
| 150 |
+
return os.path.isfile('pybuilddir.txt')
|
| 151 |
+
|
| 152 |
+
def spec_for_pip(self):
|
| 153 |
+
"""
|
| 154 |
+
Ensure stdlib distutils when running under pip.
|
| 155 |
+
See pypa/pip#8761 for rationale.
|
| 156 |
+
"""
|
| 157 |
+
if sys.version_info >= (3, 12) or self.pip_imported_during_build():
|
| 158 |
+
return
|
| 159 |
+
clear_distutils()
|
| 160 |
+
self.spec_for_distutils = lambda: None
|
| 161 |
+
|
| 162 |
+
@classmethod
|
| 163 |
+
def pip_imported_during_build(cls):
|
| 164 |
+
"""
|
| 165 |
+
Detect if pip is being imported in a build script. Ref #2355.
|
| 166 |
+
"""
|
| 167 |
+
import traceback
|
| 168 |
+
|
| 169 |
+
return any(
|
| 170 |
+
cls.frame_file_is_setup(frame) for frame, line in traceback.walk_stack(None)
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
@staticmethod
|
| 174 |
+
def frame_file_is_setup(frame):
|
| 175 |
+
"""
|
| 176 |
+
Return True if the indicated frame suggests a setup.py file.
|
| 177 |
+
"""
|
| 178 |
+
# some frames may not have __file__ (#2940)
|
| 179 |
+
return frame.f_globals.get('__file__', '').endswith('setup.py')
|
| 180 |
+
|
| 181 |
+
def spec_for_sensitive_tests(self):
|
| 182 |
+
"""
|
| 183 |
+
Ensure stdlib distutils when running select tests under CPython.
|
| 184 |
+
|
| 185 |
+
python/cpython#91169
|
| 186 |
+
"""
|
| 187 |
+
clear_distutils()
|
| 188 |
+
self.spec_for_distutils = lambda: None
|
| 189 |
+
|
| 190 |
+
sensitive_tests = (
|
| 191 |
+
[
|
| 192 |
+
'test.test_distutils',
|
| 193 |
+
'test.test_peg_generator',
|
| 194 |
+
'test.test_importlib',
|
| 195 |
+
]
|
| 196 |
+
if sys.version_info < (3, 10)
|
| 197 |
+
else [
|
| 198 |
+
'test.test_distutils',
|
| 199 |
+
]
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
for name in DistutilsMetaFinder.sensitive_tests:
|
| 204 |
+
setattr(
|
| 205 |
+
DistutilsMetaFinder,
|
| 206 |
+
f'spec_for_{name}',
|
| 207 |
+
DistutilsMetaFinder.spec_for_sensitive_tests,
|
| 208 |
+
)
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
DISTUTILS_FINDER = DistutilsMetaFinder()
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
def add_shim():
|
| 215 |
+
DISTUTILS_FINDER in sys.meta_path or insert_shim()
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
class shim:
|
| 219 |
+
def __enter__(self) -> None:
|
| 220 |
+
insert_shim()
|
| 221 |
+
|
| 222 |
+
def __exit__(self, exc: object, value: object, tb: object) -> None:
|
| 223 |
+
_remove_shim()
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def insert_shim():
|
| 227 |
+
sys.meta_path.insert(0, DISTUTILS_FINDER)
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
def _remove_shim():
|
| 231 |
+
try:
|
| 232 |
+
sys.meta_path.remove(DISTUTILS_FINDER)
|
| 233 |
+
except ValueError:
|
| 234 |
+
pass
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
if sys.version_info < (3, 12):
|
| 238 |
+
# DistutilsMetaFinder can only be disabled in Python < 3.12 (PEP 632)
|
| 239 |
+
remove_shim = _remove_shim
|
_distutils_hack/override.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
__import__('_distutils_hack').do_override()
|
aiohappyeyeballs-2.6.1.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
uv
|
aiohappyeyeballs-2.6.1.dist-info/LICENSE
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
A. HISTORY OF THE SOFTWARE
|
| 2 |
+
==========================
|
| 3 |
+
|
| 4 |
+
Python was created in the early 1990s by Guido van Rossum at Stichting
|
| 5 |
+
Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands
|
| 6 |
+
as a successor of a language called ABC. Guido remains Python's
|
| 7 |
+
principal author, although it includes many contributions from others.
|
| 8 |
+
|
| 9 |
+
In 1995, Guido continued his work on Python at the Corporation for
|
| 10 |
+
National Research Initiatives (CNRI, see https://www.cnri.reston.va.us)
|
| 11 |
+
in Reston, Virginia where he released several versions of the
|
| 12 |
+
software.
|
| 13 |
+
|
| 14 |
+
In May 2000, Guido and the Python core development team moved to
|
| 15 |
+
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
|
| 16 |
+
year, the PythonLabs team moved to Digital Creations, which became
|
| 17 |
+
Zope Corporation. In 2001, the Python Software Foundation (PSF, see
|
| 18 |
+
https://www.python.org/psf/) was formed, a non-profit organization
|
| 19 |
+
created specifically to own Python-related Intellectual Property.
|
| 20 |
+
Zope Corporation was a sponsoring member of the PSF.
|
| 21 |
+
|
| 22 |
+
All Python releases are Open Source (see https://opensource.org for
|
| 23 |
+
the Open Source Definition). Historically, most, but not all, Python
|
| 24 |
+
releases have also been GPL-compatible; the table below summarizes
|
| 25 |
+
the various releases.
|
| 26 |
+
|
| 27 |
+
Release Derived Year Owner GPL-
|
| 28 |
+
from compatible? (1)
|
| 29 |
+
|
| 30 |
+
0.9.0 thru 1.2 1991-1995 CWI yes
|
| 31 |
+
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
|
| 32 |
+
1.6 1.5.2 2000 CNRI no
|
| 33 |
+
2.0 1.6 2000 BeOpen.com no
|
| 34 |
+
1.6.1 1.6 2001 CNRI yes (2)
|
| 35 |
+
2.1 2.0+1.6.1 2001 PSF no
|
| 36 |
+
2.0.1 2.0+1.6.1 2001 PSF yes
|
| 37 |
+
2.1.1 2.1+2.0.1 2001 PSF yes
|
| 38 |
+
2.1.2 2.1.1 2002 PSF yes
|
| 39 |
+
2.1.3 2.1.2 2002 PSF yes
|
| 40 |
+
2.2 and above 2.1.1 2001-now PSF yes
|
| 41 |
+
|
| 42 |
+
Footnotes:
|
| 43 |
+
|
| 44 |
+
(1) GPL-compatible doesn't mean that we're distributing Python under
|
| 45 |
+
the GPL. All Python licenses, unlike the GPL, let you distribute
|
| 46 |
+
a modified version without making your changes open source. The
|
| 47 |
+
GPL-compatible licenses make it possible to combine Python with
|
| 48 |
+
other software that is released under the GPL; the others don't.
|
| 49 |
+
|
| 50 |
+
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
|
| 51 |
+
because its license has a choice of law clause. According to
|
| 52 |
+
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
|
| 53 |
+
is "not incompatible" with the GPL.
|
| 54 |
+
|
| 55 |
+
Thanks to the many outside volunteers who have worked under Guido's
|
| 56 |
+
direction to make these releases possible.
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
|
| 60 |
+
===============================================================
|
| 61 |
+
|
| 62 |
+
Python software and documentation are licensed under the
|
| 63 |
+
Python Software Foundation License Version 2.
|
| 64 |
+
|
| 65 |
+
Starting with Python 3.8.6, examples, recipes, and other code in
|
| 66 |
+
the documentation are dual licensed under the PSF License Version 2
|
| 67 |
+
and the Zero-Clause BSD license.
|
| 68 |
+
|
| 69 |
+
Some software incorporated into Python is under different licenses.
|
| 70 |
+
The licenses are listed with code falling under that license.
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
| 74 |
+
--------------------------------------------
|
| 75 |
+
|
| 76 |
+
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
| 77 |
+
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
| 78 |
+
otherwise using this software ("Python") in source or binary form and
|
| 79 |
+
its associated documentation.
|
| 80 |
+
|
| 81 |
+
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
| 82 |
+
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
| 83 |
+
analyze, test, perform and/or display publicly, prepare derivative works,
|
| 84 |
+
distribute, and otherwise use Python alone or in any derivative version,
|
| 85 |
+
provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
| 86 |
+
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
|
| 87 |
+
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation;
|
| 88 |
+
All Rights Reserved" are retained in Python alone or in any derivative version
|
| 89 |
+
prepared by Licensee.
|
| 90 |
+
|
| 91 |
+
3. In the event Licensee prepares a derivative work that is based on
|
| 92 |
+
or incorporates Python or any part thereof, and wants to make
|
| 93 |
+
the derivative work available to others as provided herein, then
|
| 94 |
+
Licensee hereby agrees to include in any such work a brief summary of
|
| 95 |
+
the changes made to Python.
|
| 96 |
+
|
| 97 |
+
4. PSF is making Python available to Licensee on an "AS IS"
|
| 98 |
+
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
| 99 |
+
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
| 100 |
+
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
| 101 |
+
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
| 102 |
+
INFRINGE ANY THIRD PARTY RIGHTS.
|
| 103 |
+
|
| 104 |
+
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
| 105 |
+
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
| 106 |
+
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
| 107 |
+
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
| 108 |
+
|
| 109 |
+
6. This License Agreement will automatically terminate upon a material
|
| 110 |
+
breach of its terms and conditions.
|
| 111 |
+
|
| 112 |
+
7. Nothing in this License Agreement shall be deemed to create any
|
| 113 |
+
relationship of agency, partnership, or joint venture between PSF and
|
| 114 |
+
Licensee. This License Agreement does not grant permission to use PSF
|
| 115 |
+
trademarks or trade name in a trademark sense to endorse or promote
|
| 116 |
+
products or services of Licensee, or any third party.
|
| 117 |
+
|
| 118 |
+
8. By copying, installing or otherwise using Python, Licensee
|
| 119 |
+
agrees to be bound by the terms and conditions of this License
|
| 120 |
+
Agreement.
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
|
| 124 |
+
-------------------------------------------
|
| 125 |
+
|
| 126 |
+
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
|
| 127 |
+
|
| 128 |
+
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
|
| 129 |
+
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
|
| 130 |
+
Individual or Organization ("Licensee") accessing and otherwise using
|
| 131 |
+
this software in source or binary form and its associated
|
| 132 |
+
documentation ("the Software").
|
| 133 |
+
|
| 134 |
+
2. Subject to the terms and conditions of this BeOpen Python License
|
| 135 |
+
Agreement, BeOpen hereby grants Licensee a non-exclusive,
|
| 136 |
+
royalty-free, world-wide license to reproduce, analyze, test, perform
|
| 137 |
+
and/or display publicly, prepare derivative works, distribute, and
|
| 138 |
+
otherwise use the Software alone or in any derivative version,
|
| 139 |
+
provided, however, that the BeOpen Python License is retained in the
|
| 140 |
+
Software, alone or in any derivative version prepared by Licensee.
|
| 141 |
+
|
| 142 |
+
3. BeOpen is making the Software available to Licensee on an "AS IS"
|
| 143 |
+
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
| 144 |
+
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
|
| 145 |
+
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
| 146 |
+
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
|
| 147 |
+
INFRINGE ANY THIRD PARTY RIGHTS.
|
| 148 |
+
|
| 149 |
+
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
|
| 150 |
+
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
|
| 151 |
+
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
|
| 152 |
+
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
| 153 |
+
|
| 154 |
+
5. This License Agreement will automatically terminate upon a material
|
| 155 |
+
breach of its terms and conditions.
|
| 156 |
+
|
| 157 |
+
6. This License Agreement shall be governed by and interpreted in all
|
| 158 |
+
respects by the law of the State of California, excluding conflict of
|
| 159 |
+
law provisions. Nothing in this License Agreement shall be deemed to
|
| 160 |
+
create any relationship of agency, partnership, or joint venture
|
| 161 |
+
between BeOpen and Licensee. This License Agreement does not grant
|
| 162 |
+
permission to use BeOpen trademarks or trade names in a trademark
|
| 163 |
+
sense to endorse or promote products or services of Licensee, or any
|
| 164 |
+
third party. As an exception, the "BeOpen Python" logos available at
|
| 165 |
+
http://www.pythonlabs.com/logos.html may be used according to the
|
| 166 |
+
permissions granted on that web page.
|
| 167 |
+
|
| 168 |
+
7. By copying, installing or otherwise using the software, Licensee
|
| 169 |
+
agrees to be bound by the terms and conditions of this License
|
| 170 |
+
Agreement.
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
|
| 174 |
+
---------------------------------------
|
| 175 |
+
|
| 176 |
+
1. This LICENSE AGREEMENT is between the Corporation for National
|
| 177 |
+
Research Initiatives, having an office at 1895 Preston White Drive,
|
| 178 |
+
Reston, VA 20191 ("CNRI"), and the Individual or Organization
|
| 179 |
+
("Licensee") accessing and otherwise using Python 1.6.1 software in
|
| 180 |
+
source or binary form and its associated documentation.
|
| 181 |
+
|
| 182 |
+
2. Subject to the terms and conditions of this License Agreement, CNRI
|
| 183 |
+
hereby grants Licensee a nonexclusive, royalty-free, world-wide
|
| 184 |
+
license to reproduce, analyze, test, perform and/or display publicly,
|
| 185 |
+
prepare derivative works, distribute, and otherwise use Python 1.6.1
|
| 186 |
+
alone or in any derivative version, provided, however, that CNRI's
|
| 187 |
+
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
|
| 188 |
+
1995-2001 Corporation for National Research Initiatives; All Rights
|
| 189 |
+
Reserved" are retained in Python 1.6.1 alone or in any derivative
|
| 190 |
+
version prepared by Licensee. Alternately, in lieu of CNRI's License
|
| 191 |
+
Agreement, Licensee may substitute the following text (omitting the
|
| 192 |
+
quotes): "Python 1.6.1 is made available subject to the terms and
|
| 193 |
+
conditions in CNRI's License Agreement. This Agreement together with
|
| 194 |
+
Python 1.6.1 may be located on the internet using the following
|
| 195 |
+
unique, persistent identifier (known as a handle): 1895.22/1013. This
|
| 196 |
+
Agreement may also be obtained from a proxy server on the internet
|
| 197 |
+
using the following URL: http://hdl.handle.net/1895.22/1013".
|
| 198 |
+
|
| 199 |
+
3. In the event Licensee prepares a derivative work that is based on
|
| 200 |
+
or incorporates Python 1.6.1 or any part thereof, and wants to make
|
| 201 |
+
the derivative work available to others as provided herein, then
|
| 202 |
+
Licensee hereby agrees to include in any such work a brief summary of
|
| 203 |
+
the changes made to Python 1.6.1.
|
| 204 |
+
|
| 205 |
+
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
|
| 206 |
+
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
| 207 |
+
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
|
| 208 |
+
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
| 209 |
+
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
|
| 210 |
+
INFRINGE ANY THIRD PARTY RIGHTS.
|
| 211 |
+
|
| 212 |
+
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
| 213 |
+
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
| 214 |
+
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
|
| 215 |
+
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
| 216 |
+
|
| 217 |
+
6. This License Agreement will automatically terminate upon a material
|
| 218 |
+
breach of its terms and conditions.
|
| 219 |
+
|
| 220 |
+
7. This License Agreement shall be governed by the federal
|
| 221 |
+
intellectual property law of the United States, including without
|
| 222 |
+
limitation the federal copyright law, and, to the extent such
|
| 223 |
+
U.S. federal law does not apply, by the law of the Commonwealth of
|
| 224 |
+
Virginia, excluding Virginia's conflict of law provisions.
|
| 225 |
+
Notwithstanding the foregoing, with regard to derivative works based
|
| 226 |
+
on Python 1.6.1 that incorporate non-separable material that was
|
| 227 |
+
previously distributed under the GNU General Public License (GPL), the
|
| 228 |
+
law of the Commonwealth of Virginia shall govern this License
|
| 229 |
+
Agreement only as to issues arising under or with respect to
|
| 230 |
+
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
|
| 231 |
+
License Agreement shall be deemed to create any relationship of
|
| 232 |
+
agency, partnership, or joint venture between CNRI and Licensee. This
|
| 233 |
+
License Agreement does not grant permission to use CNRI trademarks or
|
| 234 |
+
trade name in a trademark sense to endorse or promote products or
|
| 235 |
+
services of Licensee, or any third party.
|
| 236 |
+
|
| 237 |
+
8. By clicking on the "ACCEPT" button where indicated, or by copying,
|
| 238 |
+
installing or otherwise using Python 1.6.1, Licensee agrees to be
|
| 239 |
+
bound by the terms and conditions of this License Agreement.
|
| 240 |
+
|
| 241 |
+
ACCEPT
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
|
| 245 |
+
--------------------------------------------------
|
| 246 |
+
|
| 247 |
+
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
|
| 248 |
+
The Netherlands. All rights reserved.
|
| 249 |
+
|
| 250 |
+
Permission to use, copy, modify, and distribute this software and its
|
| 251 |
+
documentation for any purpose and without fee is hereby granted,
|
| 252 |
+
provided that the above copyright notice appear in all copies and that
|
| 253 |
+
both that copyright notice and this permission notice appear in
|
| 254 |
+
supporting documentation, and that the name of Stichting Mathematisch
|
| 255 |
+
Centrum or CWI not be used in advertising or publicity pertaining to
|
| 256 |
+
distribution of the software without specific, written prior
|
| 257 |
+
permission.
|
| 258 |
+
|
| 259 |
+
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
| 260 |
+
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
| 261 |
+
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
|
| 262 |
+
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
| 263 |
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
| 264 |
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
| 265 |
+
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
| 266 |
+
|
| 267 |
+
ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION
|
| 268 |
+
----------------------------------------------------------------------
|
| 269 |
+
|
| 270 |
+
Permission to use, copy, modify, and/or distribute this software for any
|
| 271 |
+
purpose with or without fee is hereby granted.
|
| 272 |
+
|
| 273 |
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
| 274 |
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
| 275 |
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
| 276 |
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
| 277 |
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
| 278 |
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
| 279 |
+
PERFORMANCE OF THIS SOFTWARE.
|
aiohappyeyeballs-2.6.1.dist-info/METADATA
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.3
|
| 2 |
+
Name: aiohappyeyeballs
|
| 3 |
+
Version: 2.6.1
|
| 4 |
+
Summary: Happy Eyeballs for asyncio
|
| 5 |
+
License: PSF-2.0
|
| 6 |
+
Author: J. Nick Koston
|
| 7 |
+
Author-email: nick@koston.org
|
| 8 |
+
Requires-Python: >=3.9
|
| 9 |
+
Classifier: Development Status :: 5 - Production/Stable
|
| 10 |
+
Classifier: Intended Audience :: Developers
|
| 11 |
+
Classifier: Natural Language :: English
|
| 12 |
+
Classifier: Operating System :: OS Independent
|
| 13 |
+
Classifier: Topic :: Software Development :: Libraries
|
| 14 |
+
Classifier: Programming Language :: Python :: 3
|
| 15 |
+
Classifier: Programming Language :: Python :: 3.9
|
| 16 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 17 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 18 |
+
Classifier: Programming Language :: Python :: 3.12
|
| 19 |
+
Classifier: Programming Language :: Python :: 3.13
|
| 20 |
+
Classifier: License :: OSI Approved :: Python Software Foundation License
|
| 21 |
+
Project-URL: Bug Tracker, https://github.com/aio-libs/aiohappyeyeballs/issues
|
| 22 |
+
Project-URL: Changelog, https://github.com/aio-libs/aiohappyeyeballs/blob/main/CHANGELOG.md
|
| 23 |
+
Project-URL: Documentation, https://aiohappyeyeballs.readthedocs.io
|
| 24 |
+
Project-URL: Repository, https://github.com/aio-libs/aiohappyeyeballs
|
| 25 |
+
Description-Content-Type: text/markdown
|
| 26 |
+
|
| 27 |
+
# aiohappyeyeballs
|
| 28 |
+
|
| 29 |
+
<p align="center">
|
| 30 |
+
<a href="https://github.com/aio-libs/aiohappyeyeballs/actions/workflows/ci.yml?query=branch%3Amain">
|
| 31 |
+
<img src="https://img.shields.io/github/actions/workflow/status/aio-libs/aiohappyeyeballs/ci-cd.yml?branch=main&label=CI&logo=github&style=flat-square" alt="CI Status" >
|
| 32 |
+
</a>
|
| 33 |
+
<a href="https://aiohappyeyeballs.readthedocs.io">
|
| 34 |
+
<img src="https://img.shields.io/readthedocs/aiohappyeyeballs.svg?logo=read-the-docs&logoColor=fff&style=flat-square" alt="Documentation Status">
|
| 35 |
+
</a>
|
| 36 |
+
<a href="https://codecov.io/gh/aio-libs/aiohappyeyeballs">
|
| 37 |
+
<img src="https://img.shields.io/codecov/c/github/aio-libs/aiohappyeyeballs.svg?logo=codecov&logoColor=fff&style=flat-square" alt="Test coverage percentage">
|
| 38 |
+
</a>
|
| 39 |
+
</p>
|
| 40 |
+
<p align="center">
|
| 41 |
+
<a href="https://python-poetry.org/">
|
| 42 |
+
<img src="https://img.shields.io/badge/packaging-poetry-299bd7?style=flat-square&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAASCAYAAABrXO8xAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAJJSURBVHgBfZLPa1NBEMe/s7tNXoxW1KJQKaUHkXhQvHgW6UHQQ09CBS/6V3hKc/AP8CqCrUcpmop3Cx48eDB4yEECjVQrlZb80CRN8t6OM/teagVxYZi38+Yz853dJbzoMV3MM8cJUcLMSUKIE8AzQ2PieZzFxEJOHMOgMQQ+dUgSAckNXhapU/NMhDSWLs1B24A8sO1xrN4NECkcAC9ASkiIJc6k5TRiUDPhnyMMdhKc+Zx19l6SgyeW76BEONY9exVQMzKExGKwwPsCzza7KGSSWRWEQhyEaDXp6ZHEr416ygbiKYOd7TEWvvcQIeusHYMJGhTwF9y7sGnSwaWyFAiyoxzqW0PM/RjghPxF2pWReAowTEXnDh0xgcLs8l2YQmOrj3N7ByiqEoH0cARs4u78WgAVkoEDIDoOi3AkcLOHU60RIg5wC4ZuTC7FaHKQm8Hq1fQuSOBvX/sodmNJSB5geaF5CPIkUeecdMxieoRO5jz9bheL6/tXjrwCyX/UYBUcjCaWHljx1xiX6z9xEjkYAzbGVnB8pvLmyXm9ep+W8CmsSHQQY77Zx1zboxAV0w7ybMhQmfqdmmw3nEp1I0Z+FGO6M8LZdoyZnuzzBdjISicKRnpxzI9fPb+0oYXsNdyi+d3h9bm9MWYHFtPeIZfLwzmFDKy1ai3p+PDls1Llz4yyFpferxjnyjJDSEy9CaCx5m2cJPerq6Xm34eTrZt3PqxYO1XOwDYZrFlH1fWnpU38Y9HRze3lj0vOujZcXKuuXm3jP+s3KbZVra7y2EAAAAAASUVORK5CYII=" alt="Poetry">
|
| 43 |
+
</a>
|
| 44 |
+
<a href="https://github.com/astral-sh/ruff">
|
| 45 |
+
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff">
|
| 46 |
+
</a>
|
| 47 |
+
<a href="https://github.com/pre-commit/pre-commit">
|
| 48 |
+
<img src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square" alt="pre-commit">
|
| 49 |
+
</a>
|
| 50 |
+
</p>
|
| 51 |
+
<p align="center">
|
| 52 |
+
<a href="https://pypi.org/project/aiohappyeyeballs/">
|
| 53 |
+
<img src="https://img.shields.io/pypi/v/aiohappyeyeballs.svg?logo=python&logoColor=fff&style=flat-square" alt="PyPI Version">
|
| 54 |
+
</a>
|
| 55 |
+
<img src="https://img.shields.io/pypi/pyversions/aiohappyeyeballs.svg?style=flat-square&logo=python&logoColor=fff" alt="Supported Python versions">
|
| 56 |
+
<img src="https://img.shields.io/pypi/l/aiohappyeyeballs.svg?style=flat-square" alt="License">
|
| 57 |
+
</p>
|
| 58 |
+
|
| 59 |
+
---
|
| 60 |
+
|
| 61 |
+
**Documentation**: <a href="https://aiohappyeyeballs.readthedocs.io" target="_blank">https://aiohappyeyeballs.readthedocs.io </a>
|
| 62 |
+
|
| 63 |
+
**Source Code**: <a href="https://github.com/aio-libs/aiohappyeyeballs" target="_blank">https://github.com/aio-libs/aiohappyeyeballs </a>
|
| 64 |
+
|
| 65 |
+
---
|
| 66 |
+
|
| 67 |
+
[Happy Eyeballs](https://en.wikipedia.org/wiki/Happy_Eyeballs)
|
| 68 |
+
([RFC 8305](https://www.rfc-editor.org/rfc/rfc8305.html))
|
| 69 |
+
|
| 70 |
+
## Use case
|
| 71 |
+
|
| 72 |
+
This library exists to allow connecting with
|
| 73 |
+
[Happy Eyeballs](https://en.wikipedia.org/wiki/Happy_Eyeballs)
|
| 74 |
+
([RFC 8305](https://www.rfc-editor.org/rfc/rfc8305.html))
|
| 75 |
+
when you
|
| 76 |
+
already have a list of addrinfo and not a DNS name.
|
| 77 |
+
|
| 78 |
+
The stdlib version of `loop.create_connection()`
|
| 79 |
+
will only work when you pass in an unresolved name which
|
| 80 |
+
is not a good fit when using DNS caching or resolving
|
| 81 |
+
names via another method such as `zeroconf`.
|
| 82 |
+
|
| 83 |
+
## Installation
|
| 84 |
+
|
| 85 |
+
Install this via pip (or your favourite package manager):
|
| 86 |
+
|
| 87 |
+
`pip install aiohappyeyeballs`
|
| 88 |
+
|
| 89 |
+
## License
|
| 90 |
+
|
| 91 |
+
[aiohappyeyeballs is licensed under the same terms as cpython itself.](https://github.com/python/cpython/blob/main/LICENSE)
|
| 92 |
+
|
| 93 |
+
## Example usage
|
| 94 |
+
|
| 95 |
+
```python
|
| 96 |
+
|
| 97 |
+
addr_infos = await loop.getaddrinfo("example.org", 80)
|
| 98 |
+
|
| 99 |
+
socket = await start_connection(addr_infos)
|
| 100 |
+
socket = await start_connection(addr_infos, local_addr_infos=local_addr_infos, happy_eyeballs_delay=0.2)
|
| 101 |
+
|
| 102 |
+
transport, protocol = await loop.create_connection(
|
| 103 |
+
MyProtocol, sock=socket, ...)
|
| 104 |
+
|
| 105 |
+
# Remove the first address for each family from addr_info
|
| 106 |
+
pop_addr_infos_interleave(addr_info, 1)
|
| 107 |
+
|
| 108 |
+
# Remove all matching address from addr_info
|
| 109 |
+
remove_addr_infos(addr_info, "dead::beef::")
|
| 110 |
+
|
| 111 |
+
# Convert a local_addr to local_addr_infos
|
| 112 |
+
local_addr_infos = addr_to_addr_infos(("127.0.0.1",0))
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
## Credits
|
| 116 |
+
|
| 117 |
+
This package contains code from cpython and is licensed under the same terms as cpython itself.
|
| 118 |
+
|
| 119 |
+
This package was created with
|
| 120 |
+
[Copier](https://copier.readthedocs.io/) and the
|
| 121 |
+
[browniebroke/pypackage-template](https://github.com/browniebroke/pypackage-template)
|
| 122 |
+
project template.
|
| 123 |
+
|
aiohappyeyeballs-2.6.1.dist-info/RECORD
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
aiohappyeyeballs-2.6.1.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
| 2 |
+
aiohappyeyeballs-2.6.1.dist-info/LICENSE,sha256=Oy-B_iHRgcSZxZolbI4ZaEVdZonSaaqFNzv7avQdo78,13936
|
| 3 |
+
aiohappyeyeballs-2.6.1.dist-info/METADATA,sha256=NSXlhJwAfi380eEjAo7BQ4P_TVal9xi0qkyZWibMsVM,5915
|
| 4 |
+
aiohappyeyeballs-2.6.1.dist-info/RECORD,,
|
| 5 |
+
aiohappyeyeballs-2.6.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 6 |
+
aiohappyeyeballs-2.6.1.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
| 7 |
+
aiohappyeyeballs/__init__.py,sha256=x7kktHEtaD9quBcWDJPuLeKyjuVAI-Jj14S9B_5hcTs,361
|
| 8 |
+
aiohappyeyeballs/_staggered.py,sha256=edfVowFx-P-ywJjIEF3MdPtEMVODujV6CeMYr65otac,6900
|
| 9 |
+
aiohappyeyeballs/impl.py,sha256=Dlcm2mTJ28ucrGnxkb_fo9CZzLAkOOBizOt7dreBbXE,9681
|
| 10 |
+
aiohappyeyeballs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 11 |
+
aiohappyeyeballs/types.py,sha256=YZJIAnyoV4Dz0WFtlaf_OyE4EW7Xus1z7aIfNI6tDDQ,425
|
| 12 |
+
aiohappyeyeballs/utils.py,sha256=on9GxIR0LhEfZu8P6Twi9hepX9zDanuZM20MWsb3xlQ,3028
|
aiohappyeyeballs-2.6.1.dist-info/REQUESTED
ADDED
|
File without changes
|
aiohappyeyeballs-2.6.1.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: poetry-core 2.1.1
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
aiohappyeyeballs/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__version__ = "2.6.1"
|
| 2 |
+
|
| 3 |
+
from .impl import start_connection
|
| 4 |
+
from .types import AddrInfoType, SocketFactoryType
|
| 5 |
+
from .utils import addr_to_addr_infos, pop_addr_infos_interleave, remove_addr_infos
|
| 6 |
+
|
| 7 |
+
__all__ = (
|
| 8 |
+
"AddrInfoType",
|
| 9 |
+
"SocketFactoryType",
|
| 10 |
+
"addr_to_addr_infos",
|
| 11 |
+
"pop_addr_infos_interleave",
|
| 12 |
+
"remove_addr_infos",
|
| 13 |
+
"start_connection",
|
| 14 |
+
)
|
aiohappyeyeballs/_staggered.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
import contextlib
|
| 3 |
+
|
| 4 |
+
# PY3.9: Import Callable from typing until we drop Python 3.9 support
|
| 5 |
+
# https://github.com/python/cpython/issues/87131
|
| 6 |
+
from typing import (
|
| 7 |
+
TYPE_CHECKING,
|
| 8 |
+
Any,
|
| 9 |
+
Awaitable,
|
| 10 |
+
Callable,
|
| 11 |
+
Iterable,
|
| 12 |
+
List,
|
| 13 |
+
Optional,
|
| 14 |
+
Set,
|
| 15 |
+
Tuple,
|
| 16 |
+
TypeVar,
|
| 17 |
+
Union,
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
_T = TypeVar("_T")
|
| 21 |
+
|
| 22 |
+
RE_RAISE_EXCEPTIONS = (SystemExit, KeyboardInterrupt)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _set_result(wait_next: "asyncio.Future[None]") -> None:
|
| 26 |
+
"""Set the result of a future if it is not already done."""
|
| 27 |
+
if not wait_next.done():
|
| 28 |
+
wait_next.set_result(None)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
async def _wait_one(
|
| 32 |
+
futures: "Iterable[asyncio.Future[Any]]",
|
| 33 |
+
loop: asyncio.AbstractEventLoop,
|
| 34 |
+
) -> _T:
|
| 35 |
+
"""Wait for the first future to complete."""
|
| 36 |
+
wait_next = loop.create_future()
|
| 37 |
+
|
| 38 |
+
def _on_completion(fut: "asyncio.Future[Any]") -> None:
|
| 39 |
+
if not wait_next.done():
|
| 40 |
+
wait_next.set_result(fut)
|
| 41 |
+
|
| 42 |
+
for f in futures:
|
| 43 |
+
f.add_done_callback(_on_completion)
|
| 44 |
+
|
| 45 |
+
try:
|
| 46 |
+
return await wait_next
|
| 47 |
+
finally:
|
| 48 |
+
for f in futures:
|
| 49 |
+
f.remove_done_callback(_on_completion)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
async def staggered_race(
|
| 53 |
+
coro_fns: Iterable[Callable[[], Awaitable[_T]]],
|
| 54 |
+
delay: Optional[float],
|
| 55 |
+
*,
|
| 56 |
+
loop: Optional[asyncio.AbstractEventLoop] = None,
|
| 57 |
+
) -> Tuple[Optional[_T], Optional[int], List[Optional[BaseException]]]:
|
| 58 |
+
"""
|
| 59 |
+
Run coroutines with staggered start times and take the first to finish.
|
| 60 |
+
|
| 61 |
+
This method takes an iterable of coroutine functions. The first one is
|
| 62 |
+
started immediately. From then on, whenever the immediately preceding one
|
| 63 |
+
fails (raises an exception), or when *delay* seconds has passed, the next
|
| 64 |
+
coroutine is started. This continues until one of the coroutines complete
|
| 65 |
+
successfully, in which case all others are cancelled, or until all
|
| 66 |
+
coroutines fail.
|
| 67 |
+
|
| 68 |
+
The coroutines provided should be well-behaved in the following way:
|
| 69 |
+
|
| 70 |
+
* They should only ``return`` if completed successfully.
|
| 71 |
+
|
| 72 |
+
* They should always raise an exception if they did not complete
|
| 73 |
+
successfully. In particular, if they handle cancellation, they should
|
| 74 |
+
probably reraise, like this::
|
| 75 |
+
|
| 76 |
+
try:
|
| 77 |
+
# do work
|
| 78 |
+
except asyncio.CancelledError:
|
| 79 |
+
# undo partially completed work
|
| 80 |
+
raise
|
| 81 |
+
|
| 82 |
+
Args:
|
| 83 |
+
----
|
| 84 |
+
coro_fns: an iterable of coroutine functions, i.e. callables that
|
| 85 |
+
return a coroutine object when called. Use ``functools.partial`` or
|
| 86 |
+
lambdas to pass arguments.
|
| 87 |
+
|
| 88 |
+
delay: amount of time, in seconds, between starting coroutines. If
|
| 89 |
+
``None``, the coroutines will run sequentially.
|
| 90 |
+
|
| 91 |
+
loop: the event loop to use. If ``None``, the running loop is used.
|
| 92 |
+
|
| 93 |
+
Returns:
|
| 94 |
+
-------
|
| 95 |
+
tuple *(winner_result, winner_index, exceptions)* where
|
| 96 |
+
|
| 97 |
+
- *winner_result*: the result of the winning coroutine, or ``None``
|
| 98 |
+
if no coroutines won.
|
| 99 |
+
|
| 100 |
+
- *winner_index*: the index of the winning coroutine in
|
| 101 |
+
``coro_fns``, or ``None`` if no coroutines won. If the winning
|
| 102 |
+
coroutine may return None on success, *winner_index* can be used
|
| 103 |
+
to definitively determine whether any coroutine won.
|
| 104 |
+
|
| 105 |
+
- *exceptions*: list of exceptions returned by the coroutines.
|
| 106 |
+
``len(exceptions)`` is equal to the number of coroutines actually
|
| 107 |
+
started, and the order is the same as in ``coro_fns``. The winning
|
| 108 |
+
coroutine's entry is ``None``.
|
| 109 |
+
|
| 110 |
+
"""
|
| 111 |
+
loop = loop or asyncio.get_running_loop()
|
| 112 |
+
exceptions: List[Optional[BaseException]] = []
|
| 113 |
+
tasks: Set[asyncio.Task[Optional[Tuple[_T, int]]]] = set()
|
| 114 |
+
|
| 115 |
+
async def run_one_coro(
|
| 116 |
+
coro_fn: Callable[[], Awaitable[_T]],
|
| 117 |
+
this_index: int,
|
| 118 |
+
start_next: "asyncio.Future[None]",
|
| 119 |
+
) -> Optional[Tuple[_T, int]]:
|
| 120 |
+
"""
|
| 121 |
+
Run a single coroutine.
|
| 122 |
+
|
| 123 |
+
If the coroutine fails, set the exception in the exceptions list and
|
| 124 |
+
start the next coroutine by setting the result of the start_next.
|
| 125 |
+
|
| 126 |
+
If the coroutine succeeds, return the result and the index of the
|
| 127 |
+
coroutine in the coro_fns list.
|
| 128 |
+
|
| 129 |
+
If SystemExit or KeyboardInterrupt is raised, re-raise it.
|
| 130 |
+
"""
|
| 131 |
+
try:
|
| 132 |
+
result = await coro_fn()
|
| 133 |
+
except RE_RAISE_EXCEPTIONS:
|
| 134 |
+
raise
|
| 135 |
+
except BaseException as e:
|
| 136 |
+
exceptions[this_index] = e
|
| 137 |
+
_set_result(start_next) # Kickstart the next coroutine
|
| 138 |
+
return None
|
| 139 |
+
|
| 140 |
+
return result, this_index
|
| 141 |
+
|
| 142 |
+
start_next_timer: Optional[asyncio.TimerHandle] = None
|
| 143 |
+
start_next: Optional[asyncio.Future[None]]
|
| 144 |
+
task: asyncio.Task[Optional[Tuple[_T, int]]]
|
| 145 |
+
done: Union[asyncio.Future[None], asyncio.Task[Optional[Tuple[_T, int]]]]
|
| 146 |
+
coro_iter = iter(coro_fns)
|
| 147 |
+
this_index = -1
|
| 148 |
+
try:
|
| 149 |
+
while True:
|
| 150 |
+
if coro_fn := next(coro_iter, None):
|
| 151 |
+
this_index += 1
|
| 152 |
+
exceptions.append(None)
|
| 153 |
+
start_next = loop.create_future()
|
| 154 |
+
task = loop.create_task(run_one_coro(coro_fn, this_index, start_next))
|
| 155 |
+
tasks.add(task)
|
| 156 |
+
start_next_timer = (
|
| 157 |
+
loop.call_later(delay, _set_result, start_next) if delay else None
|
| 158 |
+
)
|
| 159 |
+
elif not tasks:
|
| 160 |
+
# We exhausted the coro_fns list and no tasks are running
|
| 161 |
+
# so we have no winner and all coroutines failed.
|
| 162 |
+
break
|
| 163 |
+
|
| 164 |
+
while tasks or start_next:
|
| 165 |
+
done = await _wait_one(
|
| 166 |
+
(*tasks, start_next) if start_next else tasks, loop
|
| 167 |
+
)
|
| 168 |
+
if done is start_next:
|
| 169 |
+
# The current task has failed or the timer has expired
|
| 170 |
+
# so we need to start the next task.
|
| 171 |
+
start_next = None
|
| 172 |
+
if start_next_timer:
|
| 173 |
+
start_next_timer.cancel()
|
| 174 |
+
start_next_timer = None
|
| 175 |
+
|
| 176 |
+
# Break out of the task waiting loop to start the next
|
| 177 |
+
# task.
|
| 178 |
+
break
|
| 179 |
+
|
| 180 |
+
if TYPE_CHECKING:
|
| 181 |
+
assert isinstance(done, asyncio.Task)
|
| 182 |
+
|
| 183 |
+
tasks.remove(done)
|
| 184 |
+
if winner := done.result():
|
| 185 |
+
return *winner, exceptions
|
| 186 |
+
finally:
|
| 187 |
+
# We either have:
|
| 188 |
+
# - a winner
|
| 189 |
+
# - all tasks failed
|
| 190 |
+
# - a KeyboardInterrupt or SystemExit.
|
| 191 |
+
|
| 192 |
+
#
|
| 193 |
+
# If the timer is still running, cancel it.
|
| 194 |
+
#
|
| 195 |
+
if start_next_timer:
|
| 196 |
+
start_next_timer.cancel()
|
| 197 |
+
|
| 198 |
+
#
|
| 199 |
+
# If there are any tasks left, cancel them and than
|
| 200 |
+
# wait them so they fill the exceptions list.
|
| 201 |
+
#
|
| 202 |
+
for task in tasks:
|
| 203 |
+
task.cancel()
|
| 204 |
+
with contextlib.suppress(asyncio.CancelledError):
|
| 205 |
+
await task
|
| 206 |
+
|
| 207 |
+
return None, None, exceptions
|
aiohappyeyeballs/impl.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Base implementation."""
|
| 2 |
+
|
| 3 |
+
import asyncio
|
| 4 |
+
import collections
|
| 5 |
+
import contextlib
|
| 6 |
+
import functools
|
| 7 |
+
import itertools
|
| 8 |
+
import socket
|
| 9 |
+
from typing import List, Optional, Sequence, Set, Union
|
| 10 |
+
|
| 11 |
+
from . import _staggered
|
| 12 |
+
from .types import AddrInfoType, SocketFactoryType
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
async def start_connection(
|
| 16 |
+
addr_infos: Sequence[AddrInfoType],
|
| 17 |
+
*,
|
| 18 |
+
local_addr_infos: Optional[Sequence[AddrInfoType]] = None,
|
| 19 |
+
happy_eyeballs_delay: Optional[float] = None,
|
| 20 |
+
interleave: Optional[int] = None,
|
| 21 |
+
loop: Optional[asyncio.AbstractEventLoop] = None,
|
| 22 |
+
socket_factory: Optional[SocketFactoryType] = None,
|
| 23 |
+
) -> socket.socket:
|
| 24 |
+
"""
|
| 25 |
+
Connect to a TCP server.
|
| 26 |
+
|
| 27 |
+
Create a socket connection to a specified destination. The
|
| 28 |
+
destination is specified as a list of AddrInfoType tuples as
|
| 29 |
+
returned from getaddrinfo().
|
| 30 |
+
|
| 31 |
+
The arguments are, in order:
|
| 32 |
+
|
| 33 |
+
* ``family``: the address family, e.g. ``socket.AF_INET`` or
|
| 34 |
+
``socket.AF_INET6``.
|
| 35 |
+
* ``type``: the socket type, e.g. ``socket.SOCK_STREAM`` or
|
| 36 |
+
``socket.SOCK_DGRAM``.
|
| 37 |
+
* ``proto``: the protocol, e.g. ``socket.IPPROTO_TCP`` or
|
| 38 |
+
``socket.IPPROTO_UDP``.
|
| 39 |
+
* ``canonname``: the canonical name of the address, e.g.
|
| 40 |
+
``"www.python.org"``.
|
| 41 |
+
* ``sockaddr``: the socket address
|
| 42 |
+
|
| 43 |
+
This method is a coroutine which will try to establish the connection
|
| 44 |
+
in the background. When successful, the coroutine returns a
|
| 45 |
+
socket.
|
| 46 |
+
|
| 47 |
+
The expected use case is to use this method in conjunction with
|
| 48 |
+
loop.create_connection() to establish a connection to a server::
|
| 49 |
+
|
| 50 |
+
socket = await start_connection(addr_infos)
|
| 51 |
+
transport, protocol = await loop.create_connection(
|
| 52 |
+
MyProtocol, sock=socket, ...)
|
| 53 |
+
"""
|
| 54 |
+
if not (current_loop := loop):
|
| 55 |
+
current_loop = asyncio.get_running_loop()
|
| 56 |
+
|
| 57 |
+
single_addr_info = len(addr_infos) == 1
|
| 58 |
+
|
| 59 |
+
if happy_eyeballs_delay is not None and interleave is None:
|
| 60 |
+
# If using happy eyeballs, default to interleave addresses by family
|
| 61 |
+
interleave = 1
|
| 62 |
+
|
| 63 |
+
if interleave and not single_addr_info:
|
| 64 |
+
addr_infos = _interleave_addrinfos(addr_infos, interleave)
|
| 65 |
+
|
| 66 |
+
sock: Optional[socket.socket] = None
|
| 67 |
+
# uvloop can raise RuntimeError instead of OSError
|
| 68 |
+
exceptions: List[List[Union[OSError, RuntimeError]]] = []
|
| 69 |
+
if happy_eyeballs_delay is None or single_addr_info:
|
| 70 |
+
# not using happy eyeballs
|
| 71 |
+
for addrinfo in addr_infos:
|
| 72 |
+
try:
|
| 73 |
+
sock = await _connect_sock(
|
| 74 |
+
current_loop,
|
| 75 |
+
exceptions,
|
| 76 |
+
addrinfo,
|
| 77 |
+
local_addr_infos,
|
| 78 |
+
None,
|
| 79 |
+
socket_factory,
|
| 80 |
+
)
|
| 81 |
+
break
|
| 82 |
+
except (RuntimeError, OSError):
|
| 83 |
+
continue
|
| 84 |
+
else: # using happy eyeballs
|
| 85 |
+
open_sockets: Set[socket.socket] = set()
|
| 86 |
+
try:
|
| 87 |
+
sock, _, _ = await _staggered.staggered_race(
|
| 88 |
+
(
|
| 89 |
+
functools.partial(
|
| 90 |
+
_connect_sock,
|
| 91 |
+
current_loop,
|
| 92 |
+
exceptions,
|
| 93 |
+
addrinfo,
|
| 94 |
+
local_addr_infos,
|
| 95 |
+
open_sockets,
|
| 96 |
+
socket_factory,
|
| 97 |
+
)
|
| 98 |
+
for addrinfo in addr_infos
|
| 99 |
+
),
|
| 100 |
+
happy_eyeballs_delay,
|
| 101 |
+
)
|
| 102 |
+
finally:
|
| 103 |
+
# If we have a winner, staggered_race will
|
| 104 |
+
# cancel the other tasks, however there is a
|
| 105 |
+
# small race window where any of the other tasks
|
| 106 |
+
# can be done before they are cancelled which
|
| 107 |
+
# will leave the socket open. To avoid this problem
|
| 108 |
+
# we pass a set to _connect_sock to keep track of
|
| 109 |
+
# the open sockets and close them here if there
|
| 110 |
+
# are any "runner up" sockets.
|
| 111 |
+
for s in open_sockets:
|
| 112 |
+
if s is not sock:
|
| 113 |
+
with contextlib.suppress(OSError):
|
| 114 |
+
s.close()
|
| 115 |
+
open_sockets = None # type: ignore[assignment]
|
| 116 |
+
|
| 117 |
+
if sock is None:
|
| 118 |
+
all_exceptions = [exc for sub in exceptions for exc in sub]
|
| 119 |
+
try:
|
| 120 |
+
first_exception = all_exceptions[0]
|
| 121 |
+
if len(all_exceptions) == 1:
|
| 122 |
+
raise first_exception
|
| 123 |
+
else:
|
| 124 |
+
# If they all have the same str(), raise one.
|
| 125 |
+
model = str(first_exception)
|
| 126 |
+
if all(str(exc) == model for exc in all_exceptions):
|
| 127 |
+
raise first_exception
|
| 128 |
+
# Raise a combined exception so the user can see all
|
| 129 |
+
# the various error messages.
|
| 130 |
+
msg = "Multiple exceptions: {}".format(
|
| 131 |
+
", ".join(str(exc) for exc in all_exceptions)
|
| 132 |
+
)
|
| 133 |
+
# If the errno is the same for all exceptions, raise
|
| 134 |
+
# an OSError with that errno.
|
| 135 |
+
if isinstance(first_exception, OSError):
|
| 136 |
+
first_errno = first_exception.errno
|
| 137 |
+
if all(
|
| 138 |
+
isinstance(exc, OSError) and exc.errno == first_errno
|
| 139 |
+
for exc in all_exceptions
|
| 140 |
+
):
|
| 141 |
+
raise OSError(first_errno, msg)
|
| 142 |
+
elif isinstance(first_exception, RuntimeError) and all(
|
| 143 |
+
isinstance(exc, RuntimeError) for exc in all_exceptions
|
| 144 |
+
):
|
| 145 |
+
raise RuntimeError(msg)
|
| 146 |
+
# We have a mix of OSError and RuntimeError
|
| 147 |
+
# so we have to pick which one to raise.
|
| 148 |
+
# and we raise OSError for compatibility
|
| 149 |
+
raise OSError(msg)
|
| 150 |
+
finally:
|
| 151 |
+
all_exceptions = None # type: ignore[assignment]
|
| 152 |
+
exceptions = None # type: ignore[assignment]
|
| 153 |
+
|
| 154 |
+
return sock
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
async def _connect_sock(
|
| 158 |
+
loop: asyncio.AbstractEventLoop,
|
| 159 |
+
exceptions: List[List[Union[OSError, RuntimeError]]],
|
| 160 |
+
addr_info: AddrInfoType,
|
| 161 |
+
local_addr_infos: Optional[Sequence[AddrInfoType]] = None,
|
| 162 |
+
open_sockets: Optional[Set[socket.socket]] = None,
|
| 163 |
+
socket_factory: Optional[SocketFactoryType] = None,
|
| 164 |
+
) -> socket.socket:
|
| 165 |
+
"""
|
| 166 |
+
Create, bind and connect one socket.
|
| 167 |
+
|
| 168 |
+
If open_sockets is passed, add the socket to the set of open sockets.
|
| 169 |
+
Any failure caught here will remove the socket from the set and close it.
|
| 170 |
+
|
| 171 |
+
Callers can use this set to close any sockets that are not the winner
|
| 172 |
+
of all staggered tasks in the result there are runner up sockets aka
|
| 173 |
+
multiple winners.
|
| 174 |
+
"""
|
| 175 |
+
my_exceptions: List[Union[OSError, RuntimeError]] = []
|
| 176 |
+
exceptions.append(my_exceptions)
|
| 177 |
+
family, type_, proto, _, address = addr_info
|
| 178 |
+
sock = None
|
| 179 |
+
try:
|
| 180 |
+
if socket_factory is not None:
|
| 181 |
+
sock = socket_factory(addr_info)
|
| 182 |
+
else:
|
| 183 |
+
sock = socket.socket(family=family, type=type_, proto=proto)
|
| 184 |
+
if open_sockets is not None:
|
| 185 |
+
open_sockets.add(sock)
|
| 186 |
+
sock.setblocking(False)
|
| 187 |
+
if local_addr_infos is not None:
|
| 188 |
+
for lfamily, _, _, _, laddr in local_addr_infos:
|
| 189 |
+
# skip local addresses of different family
|
| 190 |
+
if lfamily != family:
|
| 191 |
+
continue
|
| 192 |
+
try:
|
| 193 |
+
sock.bind(laddr)
|
| 194 |
+
break
|
| 195 |
+
except OSError as exc:
|
| 196 |
+
msg = (
|
| 197 |
+
f"error while attempting to bind on "
|
| 198 |
+
f"address {laddr!r}: "
|
| 199 |
+
f"{(exc.strerror or '').lower()}"
|
| 200 |
+
)
|
| 201 |
+
exc = OSError(exc.errno, msg)
|
| 202 |
+
my_exceptions.append(exc)
|
| 203 |
+
else: # all bind attempts failed
|
| 204 |
+
if my_exceptions:
|
| 205 |
+
raise my_exceptions.pop()
|
| 206 |
+
else:
|
| 207 |
+
raise OSError(f"no matching local address with {family=} found")
|
| 208 |
+
await loop.sock_connect(sock, address)
|
| 209 |
+
return sock
|
| 210 |
+
except (RuntimeError, OSError) as exc:
|
| 211 |
+
my_exceptions.append(exc)
|
| 212 |
+
if sock is not None:
|
| 213 |
+
if open_sockets is not None:
|
| 214 |
+
open_sockets.remove(sock)
|
| 215 |
+
try:
|
| 216 |
+
sock.close()
|
| 217 |
+
except OSError as e:
|
| 218 |
+
my_exceptions.append(e)
|
| 219 |
+
raise
|
| 220 |
+
raise
|
| 221 |
+
except:
|
| 222 |
+
if sock is not None:
|
| 223 |
+
if open_sockets is not None:
|
| 224 |
+
open_sockets.remove(sock)
|
| 225 |
+
try:
|
| 226 |
+
sock.close()
|
| 227 |
+
except OSError as e:
|
| 228 |
+
my_exceptions.append(e)
|
| 229 |
+
raise
|
| 230 |
+
raise
|
| 231 |
+
finally:
|
| 232 |
+
exceptions = my_exceptions = None # type: ignore[assignment]
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
def _interleave_addrinfos(
|
| 236 |
+
addrinfos: Sequence[AddrInfoType], first_address_family_count: int = 1
|
| 237 |
+
) -> List[AddrInfoType]:
|
| 238 |
+
"""Interleave list of addrinfo tuples by family."""
|
| 239 |
+
# Group addresses by family
|
| 240 |
+
addrinfos_by_family: collections.OrderedDict[int, List[AddrInfoType]] = (
|
| 241 |
+
collections.OrderedDict()
|
| 242 |
+
)
|
| 243 |
+
for addr in addrinfos:
|
| 244 |
+
family = addr[0]
|
| 245 |
+
if family not in addrinfos_by_family:
|
| 246 |
+
addrinfos_by_family[family] = []
|
| 247 |
+
addrinfos_by_family[family].append(addr)
|
| 248 |
+
addrinfos_lists = list(addrinfos_by_family.values())
|
| 249 |
+
|
| 250 |
+
reordered: List[AddrInfoType] = []
|
| 251 |
+
if first_address_family_count > 1:
|
| 252 |
+
reordered.extend(addrinfos_lists[0][: first_address_family_count - 1])
|
| 253 |
+
del addrinfos_lists[0][: first_address_family_count - 1]
|
| 254 |
+
reordered.extend(
|
| 255 |
+
a
|
| 256 |
+
for a in itertools.chain.from_iterable(itertools.zip_longest(*addrinfos_lists))
|
| 257 |
+
if a is not None
|
| 258 |
+
)
|
| 259 |
+
return reordered
|
aiohappyeyeballs/py.typed
ADDED
|
File without changes
|
aiohappyeyeballs/types.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Types for aiohappyeyeballs."""
|
| 2 |
+
|
| 3 |
+
import socket
|
| 4 |
+
|
| 5 |
+
# PY3.9: Import Callable from typing until we drop Python 3.9 support
|
| 6 |
+
# https://github.com/python/cpython/issues/87131
|
| 7 |
+
from typing import Callable, Tuple, Union
|
| 8 |
+
|
| 9 |
+
AddrInfoType = Tuple[
|
| 10 |
+
Union[int, socket.AddressFamily],
|
| 11 |
+
Union[int, socket.SocketKind],
|
| 12 |
+
int,
|
| 13 |
+
str,
|
| 14 |
+
Tuple, # type: ignore[type-arg]
|
| 15 |
+
]
|
| 16 |
+
|
| 17 |
+
SocketFactoryType = Callable[[AddrInfoType], socket.socket]
|
aiohappyeyeballs/utils.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Utility functions for aiohappyeyeballs."""
|
| 2 |
+
|
| 3 |
+
import ipaddress
|
| 4 |
+
import socket
|
| 5 |
+
from typing import Dict, List, Optional, Tuple, Union
|
| 6 |
+
|
| 7 |
+
from .types import AddrInfoType
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def addr_to_addr_infos(
|
| 11 |
+
addr: Optional[
|
| 12 |
+
Union[Tuple[str, int, int, int], Tuple[str, int, int], Tuple[str, int]]
|
| 13 |
+
],
|
| 14 |
+
) -> Optional[List[AddrInfoType]]:
|
| 15 |
+
"""Convert an address tuple to a list of addr_info tuples."""
|
| 16 |
+
if addr is None:
|
| 17 |
+
return None
|
| 18 |
+
host = addr[0]
|
| 19 |
+
port = addr[1]
|
| 20 |
+
is_ipv6 = ":" in host
|
| 21 |
+
if is_ipv6:
|
| 22 |
+
flowinfo = 0
|
| 23 |
+
scopeid = 0
|
| 24 |
+
addr_len = len(addr)
|
| 25 |
+
if addr_len >= 4:
|
| 26 |
+
scopeid = addr[3] # type: ignore[misc]
|
| 27 |
+
if addr_len >= 3:
|
| 28 |
+
flowinfo = addr[2] # type: ignore[misc]
|
| 29 |
+
addr = (host, port, flowinfo, scopeid)
|
| 30 |
+
family = socket.AF_INET6
|
| 31 |
+
else:
|
| 32 |
+
addr = (host, port)
|
| 33 |
+
family = socket.AF_INET
|
| 34 |
+
return [(family, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", addr)]
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def pop_addr_infos_interleave(
|
| 38 |
+
addr_infos: List[AddrInfoType], interleave: Optional[int] = None
|
| 39 |
+
) -> None:
|
| 40 |
+
"""
|
| 41 |
+
Pop addr_info from the list of addr_infos by family up to interleave times.
|
| 42 |
+
|
| 43 |
+
The interleave parameter is used to know how many addr_infos for
|
| 44 |
+
each family should be popped of the top of the list.
|
| 45 |
+
"""
|
| 46 |
+
seen: Dict[int, int] = {}
|
| 47 |
+
if interleave is None:
|
| 48 |
+
interleave = 1
|
| 49 |
+
to_remove: List[AddrInfoType] = []
|
| 50 |
+
for addr_info in addr_infos:
|
| 51 |
+
family = addr_info[0]
|
| 52 |
+
if family not in seen:
|
| 53 |
+
seen[family] = 0
|
| 54 |
+
if seen[family] < interleave:
|
| 55 |
+
to_remove.append(addr_info)
|
| 56 |
+
seen[family] += 1
|
| 57 |
+
for addr_info in to_remove:
|
| 58 |
+
addr_infos.remove(addr_info)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def _addr_tuple_to_ip_address(
|
| 62 |
+
addr: Union[Tuple[str, int], Tuple[str, int, int, int]],
|
| 63 |
+
) -> Union[
|
| 64 |
+
Tuple[ipaddress.IPv4Address, int], Tuple[ipaddress.IPv6Address, int, int, int]
|
| 65 |
+
]:
|
| 66 |
+
"""Convert an address tuple to an IPv4Address."""
|
| 67 |
+
return (ipaddress.ip_address(addr[0]), *addr[1:])
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def remove_addr_infos(
|
| 71 |
+
addr_infos: List[AddrInfoType],
|
| 72 |
+
addr: Union[Tuple[str, int], Tuple[str, int, int, int]],
|
| 73 |
+
) -> None:
|
| 74 |
+
"""
|
| 75 |
+
Remove an address from the list of addr_infos.
|
| 76 |
+
|
| 77 |
+
The addr value is typically the return value of
|
| 78 |
+
sock.getpeername().
|
| 79 |
+
"""
|
| 80 |
+
bad_addrs_infos: List[AddrInfoType] = []
|
| 81 |
+
for addr_info in addr_infos:
|
| 82 |
+
if addr_info[-1] == addr:
|
| 83 |
+
bad_addrs_infos.append(addr_info)
|
| 84 |
+
if bad_addrs_infos:
|
| 85 |
+
for bad_addr_info in bad_addrs_infos:
|
| 86 |
+
addr_infos.remove(bad_addr_info)
|
| 87 |
+
return
|
| 88 |
+
# Slow path in case addr is formatted differently
|
| 89 |
+
match_addr = _addr_tuple_to_ip_address(addr)
|
| 90 |
+
for addr_info in addr_infos:
|
| 91 |
+
if match_addr == _addr_tuple_to_ip_address(addr_info[-1]):
|
| 92 |
+
bad_addrs_infos.append(addr_info)
|
| 93 |
+
if bad_addrs_infos:
|
| 94 |
+
for bad_addr_info in bad_addrs_infos:
|
| 95 |
+
addr_infos.remove(bad_addr_info)
|
| 96 |
+
return
|
| 97 |
+
raise ValueError(f"Address {addr} not found in addr_infos")
|
aiosignal/__init__.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
from typing import Any, Awaitable, Callable, TypeVar
|
| 3 |
+
|
| 4 |
+
from frozenlist import FrozenList
|
| 5 |
+
|
| 6 |
+
if sys.version_info >= (3, 11):
|
| 7 |
+
from typing import Unpack
|
| 8 |
+
else:
|
| 9 |
+
from typing_extensions import Unpack
|
| 10 |
+
|
| 11 |
+
if sys.version_info >= (3, 13):
|
| 12 |
+
from typing import TypeVarTuple
|
| 13 |
+
else:
|
| 14 |
+
from typing_extensions import TypeVarTuple
|
| 15 |
+
|
| 16 |
+
_T = TypeVar("_T")
|
| 17 |
+
_Ts = TypeVarTuple("_Ts", default=Unpack[tuple[()]])
|
| 18 |
+
|
| 19 |
+
__version__ = "1.4.0"
|
| 20 |
+
|
| 21 |
+
__all__ = ("Signal",)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class Signal(FrozenList[Callable[[Unpack[_Ts]], Awaitable[object]]]):
|
| 25 |
+
"""Coroutine-based signal implementation.
|
| 26 |
+
|
| 27 |
+
To connect a callback to a signal, use any list method.
|
| 28 |
+
|
| 29 |
+
Signals are fired using the send() coroutine, which takes named
|
| 30 |
+
arguments.
|
| 31 |
+
"""
|
| 32 |
+
|
| 33 |
+
__slots__ = ("_owner",)
|
| 34 |
+
|
| 35 |
+
def __init__(self, owner: object):
|
| 36 |
+
super().__init__()
|
| 37 |
+
self._owner = owner
|
| 38 |
+
|
| 39 |
+
def __repr__(self) -> str:
|
| 40 |
+
return "<Signal owner={}, frozen={}, {!r}>".format(
|
| 41 |
+
self._owner, self.frozen, list(self)
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
async def send(self, *args: Unpack[_Ts], **kwargs: Any) -> None:
|
| 45 |
+
"""
|
| 46 |
+
Sends data to all registered receivers.
|
| 47 |
+
"""
|
| 48 |
+
if not self.frozen:
|
| 49 |
+
raise RuntimeError("Cannot send non-frozen signal.")
|
| 50 |
+
|
| 51 |
+
for receiver in self:
|
| 52 |
+
await receiver(*args, **kwargs)
|
| 53 |
+
|
| 54 |
+
def __call__(
|
| 55 |
+
self, func: Callable[[Unpack[_Ts]], Awaitable[_T]]
|
| 56 |
+
) -> Callable[[Unpack[_Ts]], Awaitable[_T]]:
|
| 57 |
+
"""Decorator to add a function to this Signal."""
|
| 58 |
+
self.append(func)
|
| 59 |
+
return func
|
aiosignal/py.typed
ADDED
|
File without changes
|
annotated_doc-0.0.4.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
uv
|
annotated_doc-0.0.4.dist-info/METADATA
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: annotated-doc
|
| 3 |
+
Version: 0.0.4
|
| 4 |
+
Summary: Document parameters, class attributes, return types, and variables inline, with Annotated.
|
| 5 |
+
Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?= <tiangolo@gmail.com>
|
| 6 |
+
License-Expression: MIT
|
| 7 |
+
License-File: LICENSE
|
| 8 |
+
Classifier: Intended Audience :: Information Technology
|
| 9 |
+
Classifier: Intended Audience :: System Administrators
|
| 10 |
+
Classifier: Operating System :: OS Independent
|
| 11 |
+
Classifier: Programming Language :: Python :: 3
|
| 12 |
+
Classifier: Programming Language :: Python
|
| 13 |
+
Classifier: Topic :: Internet
|
| 14 |
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
| 15 |
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
| 16 |
+
Classifier: Topic :: Software Development :: Libraries
|
| 17 |
+
Classifier: Topic :: Software Development
|
| 18 |
+
Classifier: Typing :: Typed
|
| 19 |
+
Classifier: Development Status :: 4 - Beta
|
| 20 |
+
Classifier: Intended Audience :: Developers
|
| 21 |
+
Classifier: Programming Language :: Python :: 3 :: Only
|
| 22 |
+
Classifier: Programming Language :: Python :: 3.8
|
| 23 |
+
Classifier: Programming Language :: Python :: 3.9
|
| 24 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 25 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 26 |
+
Classifier: Programming Language :: Python :: 3.12
|
| 27 |
+
Classifier: Programming Language :: Python :: 3.13
|
| 28 |
+
Classifier: Programming Language :: Python :: 3.14
|
| 29 |
+
Project-URL: Homepage, https://github.com/fastapi/annotated-doc
|
| 30 |
+
Project-URL: Documentation, https://github.com/fastapi/annotated-doc
|
| 31 |
+
Project-URL: Repository, https://github.com/fastapi/annotated-doc
|
| 32 |
+
Project-URL: Issues, https://github.com/fastapi/annotated-doc/issues
|
| 33 |
+
Project-URL: Changelog, https://github.com/fastapi/annotated-doc/release-notes.md
|
| 34 |
+
Requires-Python: >=3.8
|
| 35 |
+
Description-Content-Type: text/markdown
|
| 36 |
+
|
| 37 |
+
# Annotated Doc
|
| 38 |
+
|
| 39 |
+
Document parameters, class attributes, return types, and variables inline, with `Annotated`.
|
| 40 |
+
|
| 41 |
+
<a href="https://github.com/fastapi/annotated-doc/actions?query=workflow%3ATest+event%3Apush+branch%3Amain" target="_blank">
|
| 42 |
+
<img src="https://github.com/fastapi/annotated-doc/actions/workflows/test.yml/badge.svg?event=push&branch=main" alt="Test">
|
| 43 |
+
</a>
|
| 44 |
+
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/fastapi/annotated-doc" target="_blank">
|
| 45 |
+
<img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/annotated-doc.svg" alt="Coverage">
|
| 46 |
+
</a>
|
| 47 |
+
<a href="https://pypi.org/project/annotated-doc" target="_blank">
|
| 48 |
+
<img src="https://img.shields.io/pypi/v/annotated-doc?color=%2334D058&label=pypi%20package" alt="Package version">
|
| 49 |
+
</a>
|
| 50 |
+
<a href="https://pypi.org/project/annotated-doc" target="_blank">
|
| 51 |
+
<img src="https://img.shields.io/pypi/pyversions/annotated-doc.svg?color=%2334D058" alt="Supported Python versions">
|
| 52 |
+
</a>
|
| 53 |
+
|
| 54 |
+
## Installation
|
| 55 |
+
|
| 56 |
+
```bash
|
| 57 |
+
pip install annotated-doc
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
Or with `uv`:
|
| 61 |
+
|
| 62 |
+
```Python
|
| 63 |
+
uv add annotated-doc
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
## Usage
|
| 67 |
+
|
| 68 |
+
Import `Doc` and pass a single literal string with the documentation for the specific parameter, class attribute, return type, or variable.
|
| 69 |
+
|
| 70 |
+
For example, to document a parameter `name` in a function `hi` you could do:
|
| 71 |
+
|
| 72 |
+
```Python
|
| 73 |
+
from typing import Annotated
|
| 74 |
+
|
| 75 |
+
from annotated_doc import Doc
|
| 76 |
+
|
| 77 |
+
def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None:
|
| 78 |
+
print(f"Hi, {name}!")
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
You can also use it to document class attributes:
|
| 82 |
+
|
| 83 |
+
```Python
|
| 84 |
+
from typing import Annotated
|
| 85 |
+
|
| 86 |
+
from annotated_doc import Doc
|
| 87 |
+
|
| 88 |
+
class User:
|
| 89 |
+
name: Annotated[str, Doc("The user's name")]
|
| 90 |
+
age: Annotated[int, Doc("The user's age")]
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
The same way, you could document return types and variables, or anything that could have a type annotation with `Annotated`.
|
| 94 |
+
|
| 95 |
+
## Who Uses This
|
| 96 |
+
|
| 97 |
+
`annotated-doc` was made for:
|
| 98 |
+
|
| 99 |
+
* [FastAPI](https://fastapi.tiangolo.com/)
|
| 100 |
+
* [Typer](https://typer.tiangolo.com/)
|
| 101 |
+
* [SQLModel](https://sqlmodel.tiangolo.com/)
|
| 102 |
+
* [Asyncer](https://asyncer.tiangolo.com/)
|
| 103 |
+
|
| 104 |
+
`annotated-doc` is supported by [griffe-typingdoc](https://github.com/mkdocstrings/griffe-typingdoc), which powers reference documentation like the one in the [FastAPI Reference](https://fastapi.tiangolo.com/reference/).
|
| 105 |
+
|
| 106 |
+
## Reasons not to use `annotated-doc`
|
| 107 |
+
|
| 108 |
+
You are already comfortable with one of the existing docstring formats, like:
|
| 109 |
+
|
| 110 |
+
* Sphinx
|
| 111 |
+
* numpydoc
|
| 112 |
+
* Google
|
| 113 |
+
* Keras
|
| 114 |
+
|
| 115 |
+
Your team is already comfortable using them.
|
| 116 |
+
|
| 117 |
+
You prefer having the documentation about parameters all together in a docstring, separated from the code defining them.
|
| 118 |
+
|
| 119 |
+
You care about a specific set of users, using one specific editor, and that editor already has support for the specific docstring format you use.
|
| 120 |
+
|
| 121 |
+
## Reasons to use `annotated-doc`
|
| 122 |
+
|
| 123 |
+
* No micro-syntax to learn for newcomers, it’s **just Python** syntax.
|
| 124 |
+
* **Editing** would be already fully supported by default by any editor (current or future) supporting Python syntax, including syntax errors, syntax highlighting, etc.
|
| 125 |
+
* **Rendering** would be relatively straightforward to implement by static tools (tools that don't need runtime execution), as the information can be extracted from the AST they normally already create.
|
| 126 |
+
* **Deduplication of information**: the name of a parameter would be defined in a single place, not duplicated inside of a docstring.
|
| 127 |
+
* **Elimination** of the possibility of having **inconsistencies** when removing a parameter or class variable and **forgetting to remove** its documentation.
|
| 128 |
+
* **Minimization** of the probability of adding a new parameter or class variable and **forgetting to add its documentation**.
|
| 129 |
+
* **Elimination** of the possibility of having **inconsistencies** between the **name** of a parameter in the **signature** and the name in the docstring when it is renamed.
|
| 130 |
+
* **Access** to the documentation string for each symbol at **runtime**, including existing (older) Python versions.
|
| 131 |
+
* A more formalized way to document other symbols, like type aliases, that could use Annotated.
|
| 132 |
+
* **Support** for apps using FastAPI, Typer and others.
|
| 133 |
+
* **AI Accessibility**: AI tools will have an easier way understanding each parameter as the distance from documentation to parameter is much closer.
|
| 134 |
+
|
| 135 |
+
## History
|
| 136 |
+
|
| 137 |
+
I ([@tiangolo](https://github.com/tiangolo)) originally wanted for this to be part of the Python standard library (in [PEP 727](https://peps.python.org/pep-0727/)), but the proposal was withdrawn as there was a fair amount of negative feedback and opposition.
|
| 138 |
+
|
| 139 |
+
The conclusion was that this was better done as an external effort, in a third-party library.
|
| 140 |
+
|
| 141 |
+
So, here it is, with a simpler approach, as a third-party library, in a way that can be used by others, starting with FastAPI and friends.
|
| 142 |
+
|
| 143 |
+
## License
|
| 144 |
+
|
| 145 |
+
This project is licensed under the terms of the MIT license.
|
annotated_doc-0.0.4.dist-info/RECORD
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
annotated_doc-0.0.4.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
| 2 |
+
annotated_doc-0.0.4.dist-info/METADATA,sha256=Irm5KJua33dY2qKKAjJ-OhKaVBVIfwFGej_dSe3Z1TU,6566
|
| 3 |
+
annotated_doc-0.0.4.dist-info/RECORD,,
|
| 4 |
+
annotated_doc-0.0.4.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 5 |
+
annotated_doc-0.0.4.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
|
| 6 |
+
annotated_doc-0.0.4.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
| 7 |
+
annotated_doc-0.0.4.dist-info/licenses/LICENSE,sha256=__Fwd5pqy_ZavbQFwIfxzuF4ZpHkqWpANFF-SlBKDN8,1086
|
| 8 |
+
annotated_doc/__init__.py,sha256=VuyxxUe80kfEyWnOrCx_Bk8hybo3aKo6RYBlkBBYW8k,52
|
| 9 |
+
annotated_doc/main.py,sha256=5Zfvxv80SwwLqpRW73AZyZyiM4bWma9QWRbp_cgD20s,1075
|
| 10 |
+
annotated_doc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
annotated_doc-0.0.4.dist-info/REQUESTED
ADDED
|
File without changes
|
annotated_doc-0.0.4.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: pdm-backend (2.4.5)
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
annotated_doc-0.0.4.dist-info/entry_points.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[console_scripts]
|
| 2 |
+
|
| 3 |
+
[gui_scripts]
|
| 4 |
+
|
annotated_doc-0.0.4.dist-info/licenses/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
The MIT License (MIT)
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 Sebastián Ramírez
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in
|
| 13 |
+
all copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
| 21 |
+
THE SOFTWARE.
|
attrs-26.1.0.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
uv
|
attrs-26.1.0.dist-info/METADATA
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: attrs
|
| 3 |
+
Version: 26.1.0
|
| 4 |
+
Summary: Classes Without Boilerplate
|
| 5 |
+
Project-URL: Documentation, https://www.attrs.org/
|
| 6 |
+
Project-URL: Changelog, https://www.attrs.org/en/stable/changelog.html
|
| 7 |
+
Project-URL: GitHub, https://github.com/python-attrs/attrs
|
| 8 |
+
Project-URL: Funding, https://github.com/sponsors/hynek
|
| 9 |
+
Project-URL: Tidelift, https://tidelift.com/subscription/pkg/pypi-attrs?utm_source=pypi-attrs&utm_medium=pypi
|
| 10 |
+
Author-email: Hynek Schlawack <hs@ox.cx>
|
| 11 |
+
License-Expression: MIT
|
| 12 |
+
License-File: LICENSE
|
| 13 |
+
Keywords: attribute,boilerplate,class
|
| 14 |
+
Classifier: Development Status :: 5 - Production/Stable
|
| 15 |
+
Classifier: Programming Language :: Python :: 3.9
|
| 16 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 17 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 18 |
+
Classifier: Programming Language :: Python :: 3.12
|
| 19 |
+
Classifier: Programming Language :: Python :: 3.13
|
| 20 |
+
Classifier: Programming Language :: Python :: 3.14
|
| 21 |
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
| 22 |
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
| 23 |
+
Classifier: Typing :: Typed
|
| 24 |
+
Requires-Python: >=3.9
|
| 25 |
+
Description-Content-Type: text/markdown
|
| 26 |
+
|
| 27 |
+
<p align="center">
|
| 28 |
+
<a href="https://www.attrs.org/">
|
| 29 |
+
<img src="https://raw.githubusercontent.com/python-attrs/attrs/main/docs/_static/attrs_logo.svg" width="35%" alt="attrs" />
|
| 30 |
+
</a>
|
| 31 |
+
</p>
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
*attrs* is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka [dunder methods](https://www.attrs.org/en/latest/glossary.html#term-dunder-methods)).
|
| 35 |
+
Trusted by NASA for [Mars missions since 2020](https://github.com/readme/featured/nasa-ingenuity-helicopter)!
|
| 36 |
+
|
| 37 |
+
Its main goal is to help you to write **concise** and **correct** software without slowing down your code.
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
## Sponsors
|
| 41 |
+
|
| 42 |
+
*attrs* would not be possible without our [amazing sponsors](https://github.com/sponsors/hynek).
|
| 43 |
+
Especially those generously supporting us at the *The Organization* tier and higher:
|
| 44 |
+
|
| 45 |
+
<!-- sponsor-break-begin -->
|
| 46 |
+
|
| 47 |
+
<p align="center">
|
| 48 |
+
|
| 49 |
+
<!-- [[[cog
|
| 50 |
+
import pathlib, tomllib
|
| 51 |
+
|
| 52 |
+
for sponsor in tomllib.loads(pathlib.Path("pyproject.toml").read_text())["tool"]["sponcon"]["sponsors"]:
|
| 53 |
+
print(f'<a href="{sponsor["url"]}"><img title="{sponsor["title"]}" src="https://www.attrs.org/en/26.1.0/_static/sponsors/{sponsor["img"]}" width="190" /></a>')
|
| 54 |
+
]]] -->
|
| 55 |
+
<a href="https://www.variomedia.de/"><img title="Variomedia AG" src="https://www.attrs.org/en/26.1.0/_static/sponsors/Variomedia.svg" width="190" /></a>
|
| 56 |
+
<a href="https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek"><img title="Tidelift" src="https://www.attrs.org/en/26.1.0/_static/sponsors/Tidelift.svg" width="190" /></a>
|
| 57 |
+
<a href="https://kraken.tech/"><img title="Kraken Tech" src="https://www.attrs.org/en/26.1.0/_static/sponsors/Kraken.svg" width="190" /></a>
|
| 58 |
+
<a href="https://privacy-solutions.org/"><img title="Privacy Solutions" src="https://www.attrs.org/en/26.1.0/_static/sponsors/Privacy-Solutions.svg" width="190" /></a>
|
| 59 |
+
<a href="https://filepreviews.io/"><img title="FilePreviews" src="https://www.attrs.org/en/26.1.0/_static/sponsors/FilePreviews.svg" width="190" /></a>
|
| 60 |
+
<a href="https://www.testmuai.com/?utm_medium=sponsor&utm_source=structlog"><img title="TestMu AI" src="https://www.attrs.org/en/26.1.0/_static/sponsors/TestMu-AI.svg" width="190" /></a>
|
| 61 |
+
<a href="https://polar.sh/"><img title="Polar" src="https://www.attrs.org/en/26.1.0/_static/sponsors/Polar.svg" width="190" /></a>
|
| 62 |
+
<!-- [[[end]]] -->
|
| 63 |
+
|
| 64 |
+
</p>
|
| 65 |
+
|
| 66 |
+
<!-- sponsor-break-end -->
|
| 67 |
+
|
| 68 |
+
<p align="center">
|
| 69 |
+
<strong>Please consider <a href="https://github.com/sponsors/hynek">joining them</a> to help make <em>attrs</em>’s maintenance more sustainable!</strong>
|
| 70 |
+
</p>
|
| 71 |
+
|
| 72 |
+
<!-- teaser-end -->
|
| 73 |
+
|
| 74 |
+
## Example
|
| 75 |
+
|
| 76 |
+
*attrs* gives you a class decorator and a way to declaratively define the attributes on that class:
|
| 77 |
+
|
| 78 |
+
<!-- code-begin -->
|
| 79 |
+
|
| 80 |
+
```pycon
|
| 81 |
+
>>> from attrs import asdict, define, make_class, Factory
|
| 82 |
+
|
| 83 |
+
>>> @define
|
| 84 |
+
... class SomeClass:
|
| 85 |
+
... a_number: int = 42
|
| 86 |
+
... list_of_numbers: list[int] = Factory(list)
|
| 87 |
+
...
|
| 88 |
+
... def hard_math(self, another_number):
|
| 89 |
+
... return self.a_number + sum(self.list_of_numbers) * another_number
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
>>> sc = SomeClass(1, [1, 2, 3])
|
| 93 |
+
>>> sc
|
| 94 |
+
SomeClass(a_number=1, list_of_numbers=[1, 2, 3])
|
| 95 |
+
|
| 96 |
+
>>> sc.hard_math(3)
|
| 97 |
+
19
|
| 98 |
+
>>> sc == SomeClass(1, [1, 2, 3])
|
| 99 |
+
True
|
| 100 |
+
>>> sc != SomeClass(2, [3, 2, 1])
|
| 101 |
+
True
|
| 102 |
+
|
| 103 |
+
>>> asdict(sc)
|
| 104 |
+
{'a_number': 1, 'list_of_numbers': [1, 2, 3]}
|
| 105 |
+
|
| 106 |
+
>>> SomeClass()
|
| 107 |
+
SomeClass(a_number=42, list_of_numbers=[])
|
| 108 |
+
|
| 109 |
+
>>> C = make_class("C", ["a", "b"])
|
| 110 |
+
>>> C("foo", "bar")
|
| 111 |
+
C(a='foo', b='bar')
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
After *declaring* your attributes, *attrs* gives you:
|
| 115 |
+
|
| 116 |
+
- a concise and explicit overview of the class's attributes,
|
| 117 |
+
- a nice human-readable `__repr__`,
|
| 118 |
+
- equality-checking methods,
|
| 119 |
+
- an initializer,
|
| 120 |
+
- and much more,
|
| 121 |
+
|
| 122 |
+
*without* writing dull boilerplate code again and again and *without* runtime performance penalties.
|
| 123 |
+
|
| 124 |
+
---
|
| 125 |
+
|
| 126 |
+
This example uses *attrs*'s modern APIs that have been introduced in version 20.1.0, and the *attrs* package import name that has been added in version 21.3.0.
|
| 127 |
+
The classic APIs (`@attr.s`, `attr.ib`, plus their serious-business aliases) and the `attr` package import name will remain **indefinitely**.
|
| 128 |
+
|
| 129 |
+
Check out [*On The Core API Names*](https://www.attrs.org/en/latest/names.html) for an in-depth explanation!
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
### Hate Type Annotations!?
|
| 133 |
+
|
| 134 |
+
No problem!
|
| 135 |
+
Types are entirely **optional** with *attrs*.
|
| 136 |
+
Simply assign `attrs.field()` to the attributes instead of annotating them with types:
|
| 137 |
+
|
| 138 |
+
```python
|
| 139 |
+
from attrs import define, field
|
| 140 |
+
|
| 141 |
+
@define
|
| 142 |
+
class SomeClass:
|
| 143 |
+
a_number = field(default=42)
|
| 144 |
+
list_of_numbers = field(factory=list)
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
## Data Classes
|
| 149 |
+
|
| 150 |
+
On the tin, *attrs* might remind you of `dataclasses` (and indeed, `dataclasses` [are a descendant](https://hynek.me/articles/import-attrs/) of *attrs*).
|
| 151 |
+
In practice it does a lot more and is more flexible.
|
| 152 |
+
For instance, it allows you to define [special handling of NumPy arrays for equality checks](https://www.attrs.org/en/stable/comparison.html#customization), allows more ways to [plug into the initialization process](https://www.attrs.org/en/stable/init.html#hooking-yourself-into-initialization), has a replacement for `__init_subclass__`, and allows for stepping through the generated methods using a debugger.
|
| 153 |
+
|
| 154 |
+
For more details, please refer to our [comparison page](https://www.attrs.org/en/stable/why.html#data-classes), but generally speaking, we are more likely to commit crimes against nature to make things work that one would expect to work, but that are quite complicated in practice.
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
## Project Information
|
| 158 |
+
|
| 159 |
+
- [**Changelog**](https://www.attrs.org/en/stable/changelog.html)
|
| 160 |
+
- [**Documentation**](https://www.attrs.org/)
|
| 161 |
+
- [**PyPI**](https://pypi.org/project/attrs/)
|
| 162 |
+
- [**Source Code**](https://github.com/python-attrs/attrs)
|
| 163 |
+
- [**Contributing**](https://github.com/python-attrs/attrs/blob/main/.github/CONTRIBUTING.md)
|
| 164 |
+
- [**Third-party Extensions**](https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs)
|
| 165 |
+
- **Get Help**: use the `python-attrs` tag on [Stack Overflow](https://stackoverflow.com/questions/tagged/python-attrs)
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
### *attrs* for Enterprise
|
| 169 |
+
|
| 170 |
+
Available as part of the [Tidelift Subscription](https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek).
|
| 171 |
+
|
| 172 |
+
The maintainers of *attrs* and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications.
|
| 173 |
+
Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use.
|
| 174 |
+
|
| 175 |
+
## Release Information
|
| 176 |
+
|
| 177 |
+
### Backwards-incompatible Changes
|
| 178 |
+
|
| 179 |
+
- Field aliases are now resolved *before* calling `field_transformer`, so transformers receive fully populated `Attribute` objects with usable `alias` values instead of `None`.
|
| 180 |
+
The new `Attribute.alias_is_default` flag indicates whether the alias was auto-generated (`True`) or explicitly set by the user (`False`).
|
| 181 |
+
[#1509](https://github.com/python-attrs/attrs/issues/1509)
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
### Changes
|
| 185 |
+
|
| 186 |
+
- Fix type annotations for `attrs.validators.optional()`, so it no longer rejects tuples with more than one validator.
|
| 187 |
+
[#1496](https://github.com/python-attrs/attrs/issues/1496)
|
| 188 |
+
- The `attrs.validators.disabled()` contextmanager can now be nested.
|
| 189 |
+
[#1513](https://github.com/python-attrs/attrs/issues/1513)
|
| 190 |
+
- Frozen classes can set `on_setattr=attrs.setters.NO_OP` in addition to `None`.
|
| 191 |
+
[#1515](https://github.com/python-attrs/attrs/issues/1515)
|
| 192 |
+
- It's now possible to pass *attrs* **instances** in addition to *attrs* **classes** to `attrs.fields()`.
|
| 193 |
+
[#1529](https://github.com/python-attrs/attrs/issues/1529)
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
---
|
| 198 |
+
|
| 199 |
+
[Full changelog →](https://www.attrs.org/en/stable/changelog.html)
|
attrs-26.1.0.dist-info/RECORD
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
attr/__init__.py,sha256=fOYIvt1eGSqQre4uCS3sJWKZ0mwAuC8UD6qba5OS9_U,2057
|
| 2 |
+
attr/__init__.pyi,sha256=pVGImAUVovq2_TYl_r_HIYnGlyOaoCuEhxo-EvsnnSc,11325
|
| 3 |
+
attr/_cmp.py,sha256=3Nn1TjxllUYiX_nJoVnEkXoDk0hM1DYKj5DE7GZe4i0,4117
|
| 4 |
+
attr/_cmp.pyi,sha256=U-_RU_UZOyPUEQzXE6RMYQQcjkZRY25wTH99sN0s7MM,368
|
| 5 |
+
attr/_compat.py,sha256=x0g7iEUOnBVJC72zyFCgb1eKqyxS-7f2LGnNyZ_r95s,2829
|
| 6 |
+
attr/_config.py,sha256=dGq3xR6fgZEF6UBt_L0T-eUHIB4i43kRmH0P28sJVw8,843
|
| 7 |
+
attr/_funcs.py,sha256=Ix5IETTfz5F01F-12MF_CSFomIn2h8b67EVVz2gCtBE,16479
|
| 8 |
+
attr/_make.py,sha256=H7OH2eWS5CnBzLUjNFE1WymfPrmF1r8fv2RPdt9MuYA,106129
|
| 9 |
+
attr/_next_gen.py,sha256=BQtCUlzwg2gWHTYXBQvrEYBnzBUrDvO57u0Py6UCPhc,26274
|
| 10 |
+
attr/_typing_compat.pyi,sha256=XDP54TUn-ZKhD62TOQebmzrwFyomhUCoGRpclb6alRA,469
|
| 11 |
+
attr/_version_info.py,sha256=w4R-FYC3NK_kMkGUWJlYP4cVAlH9HRaC-um3fcjYkHM,2222
|
| 12 |
+
attr/_version_info.pyi,sha256=x_M3L3WuB7r_ULXAWjx959udKQ4HLB8l-hsc1FDGNvk,209
|
| 13 |
+
attr/converters.py,sha256=GlDeOzPeTFgeBBLbj9G57Ez5lAk68uhSALRYJ_exe84,3861
|
| 14 |
+
attr/converters.pyi,sha256=orU2bff-VjQa2kMDyvnMQV73oJT2WRyQuw4ZR1ym1bE,643
|
| 15 |
+
attr/exceptions.py,sha256=b4vMbnoQ3VpwWZhqrYi_ssXVCK8o2c4HQSS09cSUM9o,1990
|
| 16 |
+
attr/exceptions.pyi,sha256=zZq8bCUnKAy9mDtBEw42ZhPhAUIHoTKedDQInJD883M,539
|
| 17 |
+
attr/filters.py,sha256=ZBiKWLp3R0LfCZsq7X11pn9WX8NslS2wXM4jsnLOGc8,1795
|
| 18 |
+
attr/filters.pyi,sha256=3J5BG-dTxltBk1_-RuNRUHrv2qu1v8v4aDNAQ7_mifA,208
|
| 19 |
+
attr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 20 |
+
attr/setters.py,sha256=5-dcT63GQK35ONEzSgfXCkbB7pPkaR-qv15mm4PVSzQ,1617
|
| 21 |
+
attr/setters.pyi,sha256=NnVkaFU1BB4JB8E4JuXyrzTUgvtMpj8p3wBdJY7uix4,584
|
| 22 |
+
attr/validators.py,sha256=m3QRzZTANr4f2C4eVdUoFg11NgXWak8Wat4qQTGhvcs,21553
|
| 23 |
+
attr/validators.pyi,sha256=gM1ZmHaBckyYWI2EirpRNzqm3B19cw5Iq6B4Kno9YCM,4087
|
| 24 |
+
attrs-26.1.0.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
| 25 |
+
attrs-26.1.0.dist-info/METADATA,sha256=TNQOaQ8jvzfLytNO_WdY4GLfHfB8hoM_fjzpW_H6OMw,8754
|
| 26 |
+
attrs-26.1.0.dist-info/RECORD,,
|
| 27 |
+
attrs-26.1.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 28 |
+
attrs-26.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
| 29 |
+
attrs-26.1.0.dist-info/licenses/LICENSE,sha256=iCEVyV38KvHutnFPjsbVy8q_Znyv-HKfQkINpj9xTp8,1109
|
| 30 |
+
attrs/__init__.py,sha256=RxaAZNwYiEh-fcvHLZNpQ_DWKni73M_jxEPEftiq1Zc,1183
|
| 31 |
+
attrs/__init__.pyi,sha256=2gV79g9UxJppGSM48hAZJ6h_MHb70dZoJL31ZNJeZYI,9416
|
| 32 |
+
attrs/converters.py,sha256=8kQljrVwfSTRu8INwEk8SI0eGrzmWftsT7rM0EqyohM,76
|
| 33 |
+
attrs/exceptions.py,sha256=ACCCmg19-vDFaDPY9vFl199SPXCQMN_bENs4DALjzms,76
|
| 34 |
+
attrs/filters.py,sha256=VOUMZug9uEU6dUuA0dF1jInUK0PL3fLgP0VBS5d-CDE,73
|
| 35 |
+
attrs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 36 |
+
attrs/setters.py,sha256=eL1YidYQV3T2h9_SYIZSZR1FAcHGb1TuCTy0E0Lv2SU,73
|
| 37 |
+
attrs/validators.py,sha256=xcy6wD5TtTkdCG1f4XWbocPSO0faBjk5IfVJfP6SUj0,76
|
attrs-26.1.0.dist-info/REQUESTED
ADDED
|
File without changes
|
attrs-26.1.0.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: hatchling 1.29.0
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
bitsandbytes/__init__.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
import importlib
|
| 8 |
+
import sys
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
|
| 12 |
+
from . import _ops, research, utils
|
| 13 |
+
from .autograd._functions import (
|
| 14 |
+
MatmulLtState,
|
| 15 |
+
matmul,
|
| 16 |
+
matmul_4bit,
|
| 17 |
+
)
|
| 18 |
+
from .backends.cpu import ops as cpu_ops
|
| 19 |
+
from .backends.default import ops as default_ops
|
| 20 |
+
from .nn import modules
|
| 21 |
+
from .optim import adam
|
| 22 |
+
|
| 23 |
+
# This is a signal for integrations with transformers/diffusers.
|
| 24 |
+
# Eventually we may remove this but it is currently required for compatibility.
|
| 25 |
+
features = {"multi_backend"}
|
| 26 |
+
supported_torch_devices = {
|
| 27 |
+
"cpu",
|
| 28 |
+
"cuda", # NVIDIA/AMD GPU
|
| 29 |
+
"xpu", # Intel GPU
|
| 30 |
+
"hpu", # Intel Gaudi
|
| 31 |
+
"npu", # Ascend NPU
|
| 32 |
+
"mps", # Apple Silicon
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
if torch.cuda.is_available():
|
| 36 |
+
from .backends.cuda import ops as cuda_ops
|
| 37 |
+
|
| 38 |
+
if hasattr(torch, "xpu") and torch.xpu.is_available():
|
| 39 |
+
from .backends.xpu import ops as xpu_ops
|
| 40 |
+
|
| 41 |
+
if importlib.util.find_spec("habana_frameworks") and importlib.util.find_spec("habana_frameworks.torch"):
|
| 42 |
+
# In case not automatically imported
|
| 43 |
+
import habana_frameworks.torch
|
| 44 |
+
|
| 45 |
+
if hasattr(torch, "hpu") and torch.hpu.is_available():
|
| 46 |
+
from .backends.hpu import ops as hpu_ops
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def _import_backends():
|
| 50 |
+
"""
|
| 51 |
+
Discover and autoload all available backends installed as separate packages.
|
| 52 |
+
Packages with an entrypoint for "bitsandbytes.backends" will be loaded.
|
| 53 |
+
Inspired by PyTorch implementation: https://pytorch.org/tutorials/prototype/python_extension_autoload.html
|
| 54 |
+
"""
|
| 55 |
+
from importlib.metadata import entry_points
|
| 56 |
+
|
| 57 |
+
extensions = entry_points(group="bitsandbytes.backends")
|
| 58 |
+
|
| 59 |
+
for ext in extensions:
|
| 60 |
+
try:
|
| 61 |
+
entry = ext.load()
|
| 62 |
+
entry()
|
| 63 |
+
except Exception as e:
|
| 64 |
+
raise RuntimeError(f"bitsandbytes: failed to load backend {ext.name}: {e}") from e
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
_import_backends()
|
| 68 |
+
|
| 69 |
+
__pdoc__ = {
|
| 70 |
+
"libbitsandbytes": False,
|
| 71 |
+
"optim.optimizer.Optimizer8bit": False,
|
| 72 |
+
"optim.optimizer.MockArgs": False,
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
__version__ = "0.49.2"
|
bitsandbytes/__main__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
if __name__ == "__main__":
|
| 2 |
+
from bitsandbytes.diagnostics.main import main
|
| 3 |
+
|
| 4 |
+
main()
|
bitsandbytes/_ops.py
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import Sequence
|
| 2 |
+
from math import prod
|
| 3 |
+
from typing import Optional
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
_IS_TORCH_GTE_24 = False
|
| 8 |
+
|
| 9 |
+
if hasattr(torch.library, "register_fake"):
|
| 10 |
+
_IS_TORCH_GTE_24 = True
|
| 11 |
+
register_fake = torch.library.register_fake
|
| 12 |
+
register_kernel = torch.library.register_kernel
|
| 13 |
+
else:
|
| 14 |
+
# PyTorch <= 2.3
|
| 15 |
+
register_fake = torch.library.impl_abstract
|
| 16 |
+
register_kernel = torch.library.impl
|
| 17 |
+
|
| 18 |
+
# Int8 mixed precision matmul + dequant + bias
|
| 19 |
+
torch.library.define(
|
| 20 |
+
"bitsandbytes::int8_mixed_scaled_mm",
|
| 21 |
+
"(Tensor A, Tensor CA, Tensor CB, Tensor SCA, Tensor SCB, Tensor? outlier_cols=None, Tensor? bias=None) -> (Tensor, Tensor?)",
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@register_fake("bitsandbytes::int8_mixed_scaled_mm")
|
| 26 |
+
def _(
|
| 27 |
+
A: torch.Tensor,
|
| 28 |
+
CA: torch.Tensor,
|
| 29 |
+
CB: torch.Tensor,
|
| 30 |
+
SCA: torch.Tensor,
|
| 31 |
+
SCB: torch.Tensor,
|
| 32 |
+
outlier_cols: Optional[torch.Tensor] = None,
|
| 33 |
+
bias: Optional[torch.Tensor] = None,
|
| 34 |
+
) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
|
| 35 |
+
shapeC = (*CA.shape[:-1], CB.shape[0])
|
| 36 |
+
|
| 37 |
+
out = torch.empty(shapeC, device=A.device, dtype=A.dtype)
|
| 38 |
+
|
| 39 |
+
outlier_cols = torch.library.get_ctx().new_dynamic_size()
|
| 40 |
+
subA = A.new_empty(outlier_cols, dtype=torch.int64)
|
| 41 |
+
|
| 42 |
+
return out, subA
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
# Higher level op: int8 matmul + dequant + bias
|
| 46 |
+
torch.library.define(
|
| 47 |
+
"bitsandbytes::int8_scaled_mm",
|
| 48 |
+
"(Tensor A, Tensor B, Tensor row_stats, Tensor col_stats, Tensor? bias=None, ScalarType? dtype=None) -> Tensor",
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
@register_fake("bitsandbytes::int8_scaled_mm")
|
| 53 |
+
def _(
|
| 54 |
+
A: torch.Tensor,
|
| 55 |
+
B: torch.Tensor,
|
| 56 |
+
row_stats: torch.Tensor,
|
| 57 |
+
col_stats: torch.Tensor,
|
| 58 |
+
bias: Optional[torch.Tensor] = None,
|
| 59 |
+
dtype: Optional[torch.dtype] = None,
|
| 60 |
+
) -> torch.Tensor:
|
| 61 |
+
shapeC = (*A.shape[:-1], B.shape[0])
|
| 62 |
+
return torch.empty(shapeC, device=A.device, dtype=dtype or torch.float16)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
torch.library.define(
|
| 66 |
+
"bitsandbytes::int8_linear_matmul",
|
| 67 |
+
"(Tensor A, Tensor B) -> Tensor",
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
@register_fake("bitsandbytes::int8_linear_matmul")
|
| 72 |
+
def _(A: torch.Tensor, B: torch.Tensor):
|
| 73 |
+
torch._check(A.dtype == torch.int8, lambda: "A must be int8")
|
| 74 |
+
torch._check(B.dtype == torch.int8, lambda: "B must be int8")
|
| 75 |
+
shapeC = (*A.shape[:-1], B.shape[0])
|
| 76 |
+
return torch.empty(shapeC, device=A.device, dtype=torch.int32)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
# More info on `out` overloads:
|
| 80 |
+
# https://github.com/pytorch/pytorch/issues/125044
|
| 81 |
+
torch.library.define(
|
| 82 |
+
"bitsandbytes::int8_linear_matmul.out",
|
| 83 |
+
"(Tensor A, Tensor B, Tensor! out) -> ()",
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
@register_fake("bitsandbytes::int8_linear_matmul.out")
|
| 88 |
+
def _(A: torch.Tensor, B: torch.Tensor, out: torch.Tensor):
|
| 89 |
+
shapeC = (*A.shape[:-1], B.shape[0])
|
| 90 |
+
|
| 91 |
+
torch._check(A.dtype == torch.int8, lambda: "A must be int8")
|
| 92 |
+
torch._check(B.dtype == torch.int8, lambda: "B must be int8")
|
| 93 |
+
torch._check(out.shape == shapeC, lambda: f"Expected out.shape == {shapeC}, got {out.shape}")
|
| 94 |
+
torch._check(out.device == A.device, lambda: f"Expected out.device == {A.device}, got {out.device}")
|
| 95 |
+
torch._check(out.dtype == torch.int32, lambda: f"Expected out.dtype == int32, got {out.dtype}")
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
torch.library.define(
|
| 99 |
+
"bitsandbytes::int8_vectorwise_quant",
|
| 100 |
+
"(Tensor A, float threshold=0.0) -> (Tensor, Tensor, Tensor?)",
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
@register_fake("bitsandbytes::int8_vectorwise_quant")
|
| 105 |
+
def _(A: torch.Tensor, threshold=0.0):
|
| 106 |
+
out_row = torch.empty(A.shape, device=A.device, dtype=torch.int8)
|
| 107 |
+
row_stats = torch.empty(prod(A.shape[:-1]), device=A.device, dtype=torch.float32)
|
| 108 |
+
|
| 109 |
+
if threshold == 0.0:
|
| 110 |
+
return out_row, row_stats, None
|
| 111 |
+
|
| 112 |
+
outlier_cols = torch.library.get_ctx().new_dynamic_size()
|
| 113 |
+
|
| 114 |
+
return out_row, row_stats, A.new_empty(outlier_cols, dtype=torch.int64)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
torch.library.define("bitsandbytes::int8_vectorwise_dequant", "(Tensor A, Tensor stats) -> Tensor")
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
@register_fake("bitsandbytes::int8_vectorwise_dequant")
|
| 121 |
+
def _(A: torch.Tensor, stats: torch.Tensor) -> torch.Tensor:
|
| 122 |
+
torch._check(A.dtype == torch.int8, lambda: "A must be int8")
|
| 123 |
+
return torch.empty_like(A, dtype=torch.float32)
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
# Default PyTorch-native implementation
|
| 127 |
+
@register_kernel("bitsandbytes::int8_vectorwise_dequant", "default")
|
| 128 |
+
def _(A: torch.Tensor, stats: torch.Tensor):
|
| 129 |
+
# To dequantize we divide by 127, or multiply by the reciprocal.
|
| 130 |
+
return A * stats.view(-1, 1) * 7.874015718698502e-3
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
torch.library.define(
|
| 134 |
+
"bitsandbytes::int8_mm_dequant",
|
| 135 |
+
"(Tensor A, Tensor row_stats, Tensor col_stats, ScalarType? dtype=None, Tensor? bias=None) -> Tensor",
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
@register_fake("bitsandbytes::int8_mm_dequant")
|
| 140 |
+
def _(
|
| 141 |
+
A: torch.Tensor,
|
| 142 |
+
row_stats: torch.Tensor,
|
| 143 |
+
col_stats: torch.Tensor,
|
| 144 |
+
dtype: Optional[torch.dtype] = None,
|
| 145 |
+
bias: Optional[torch.Tensor] = None,
|
| 146 |
+
) -> torch.Tensor:
|
| 147 |
+
torch._check(A.dtype == torch.int32, lambda: "A must be int32")
|
| 148 |
+
return torch.empty_like(A, dtype=dtype or torch.float16)
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
torch.library.define(
|
| 152 |
+
"bitsandbytes::int8_double_quant",
|
| 153 |
+
"(Tensor A, float threshold=0.0) -> (Tensor, Tensor, Tensor, Tensor, Tensor?)",
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
@register_fake("bitsandbytes::int8_double_quant")
|
| 158 |
+
def _(
|
| 159 |
+
A: torch.Tensor,
|
| 160 |
+
threshold=0.0,
|
| 161 |
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, Optional[torch.Tensor]]:
|
| 162 |
+
out_row = torch.empty_like(A, dtype=torch.int8)
|
| 163 |
+
out_col = torch.empty_like(A, dtype=torch.int8)
|
| 164 |
+
row_stats = torch.empty(prod(A.shape[:-1]), device=A.device, dtype=torch.float32)
|
| 165 |
+
col_stats = torch.empty(A.shape[-1], device=A.device, dtype=torch.float32)
|
| 166 |
+
outlier_n = torch.library.get_ctx().new_dynamic_size()
|
| 167 |
+
outlier_cols = A.new_empty(outlier_n, dtype=torch.int64)
|
| 168 |
+
return out_row, out_col, row_stats, col_stats, outlier_cols
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
torch.library.define(
|
| 172 |
+
"bitsandbytes::dequantize_4bit",
|
| 173 |
+
"(Tensor A, Tensor absmax, int blocksize, str quant_type, int[] shape, ScalarType dtype) -> Tensor",
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
@register_fake("bitsandbytes::dequantize_4bit")
|
| 178 |
+
def _(
|
| 179 |
+
A: torch.Tensor,
|
| 180 |
+
absmax: torch.Tensor,
|
| 181 |
+
blocksize: int,
|
| 182 |
+
quant_type: str,
|
| 183 |
+
shape: Sequence[int],
|
| 184 |
+
dtype: torch.dtype,
|
| 185 |
+
) -> torch.Tensor:
|
| 186 |
+
torch._check_is_size(blocksize)
|
| 187 |
+
return torch.empty(shape, dtype=dtype, device=A.device)
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
torch.library.define(
|
| 191 |
+
"bitsandbytes::dequantize_4bit.out",
|
| 192 |
+
"(Tensor A, Tensor absmax, int blocksize, str quant_type, int[] shape, ScalarType dtype, Tensor! out) -> ()",
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
@register_fake("bitsandbytes::dequantize_4bit.out")
|
| 197 |
+
def _(
|
| 198 |
+
A: torch.Tensor,
|
| 199 |
+
absmax: torch.Tensor,
|
| 200 |
+
blocksize: int,
|
| 201 |
+
quant_type: str,
|
| 202 |
+
shape: Sequence[int],
|
| 203 |
+
dtype: torch.dtype,
|
| 204 |
+
out: torch.Tensor,
|
| 205 |
+
) -> None:
|
| 206 |
+
torch._check_is_size(blocksize)
|
| 207 |
+
torch._check(out.shape == shape, lambda: f"Expected out.shape == {shape}, got {out.shape}")
|
| 208 |
+
torch._check(out.device == A.device, lambda: f"Expected out.device == {A.device}, got {out.device}")
|
| 209 |
+
torch._check(out.dtype == dtype, lambda: f"Expected out.dtype == {dtype}, got {out.dtype}")
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
torch.library.define(
|
| 213 |
+
"bitsandbytes::quantize_4bit",
|
| 214 |
+
"(Tensor A, int blocksize, str quant_type, ScalarType quant_storage) -> (Tensor, Tensor)",
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
@register_fake("bitsandbytes::quantize_4bit")
|
| 219 |
+
def _(
|
| 220 |
+
A: torch.Tensor, blocksize: int, quant_type: str, quant_storage: torch.dtype
|
| 221 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 222 |
+
torch._check_is_size(blocksize)
|
| 223 |
+
|
| 224 |
+
n = A.numel()
|
| 225 |
+
blocks = -(n // -blocksize)
|
| 226 |
+
absmax = torch.empty((blocks,), device=A.device, dtype=torch.float32)
|
| 227 |
+
out = torch.empty(((n + 1) // (quant_storage.itemsize * 2), 1), device=A.device, dtype=quant_storage)
|
| 228 |
+
return out, absmax
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
torch.library.define(
|
| 232 |
+
"bitsandbytes::dequantize_blockwise",
|
| 233 |
+
"(Tensor A, Tensor absmax, Tensor code, int blocksize, ScalarType dtype) -> Tensor",
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
@register_fake("bitsandbytes::dequantize_blockwise")
|
| 238 |
+
def _(A: torch.Tensor, absmax: torch.Tensor, code: torch.Tensor, blocksize: int, dtype: torch.dtype) -> torch.Tensor:
|
| 239 |
+
torch._check_is_size(blocksize)
|
| 240 |
+
torch._check(A.dtype == torch.uint8, lambda: f"A must be uint8, got {A.dtype}")
|
| 241 |
+
return torch.empty_like(A, dtype=dtype)
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
torch.library.define(
|
| 245 |
+
"bitsandbytes::dequantize_blockwise.out",
|
| 246 |
+
"(Tensor A, Tensor absmax, Tensor code, int blocksize, ScalarType dtype, Tensor! out) -> ()",
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
@register_fake("bitsandbytes::dequantize_blockwise.out")
|
| 251 |
+
def _(
|
| 252 |
+
A: torch.Tensor, absmax: torch.Tensor, code: torch.Tensor, blocksize: int, dtype: torch.dtype, out: torch.Tensor
|
| 253 |
+
):
|
| 254 |
+
torch._check_is_size(blocksize)
|
| 255 |
+
torch._check(A.dtype == torch.uint8, lambda: f"A must be uint8, got {A.dtype}")
|
| 256 |
+
torch._check(out.shape == A.shape, lambda: f"Expected out.shape == {A.shape}, got {out.shape}")
|
| 257 |
+
torch._check(out.device == A.device, lambda: f"Expected out.device == {A.device}, got {out.device}")
|
| 258 |
+
torch._check(out.dtype == dtype, lambda: f"Expected out.dtype == {dtype}, got {out.dtype}")
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
torch.library.define("bitsandbytes::quantize_blockwise", "(Tensor A, Tensor code, int blocksize) -> (Tensor, Tensor)")
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
@register_fake("bitsandbytes::quantize_blockwise")
|
| 265 |
+
def _(A: torch.Tensor, code: torch.Tensor, blocksize: int) -> tuple[torch.Tensor, torch.Tensor]:
|
| 266 |
+
torch._check_is_size(blocksize)
|
| 267 |
+
n = A.numel()
|
| 268 |
+
blocks = -(n // -blocksize)
|
| 269 |
+
absmax = torch.empty((blocks,), device=A.device, dtype=torch.float32)
|
| 270 |
+
out = torch.empty_like(A, dtype=torch.uint8)
|
| 271 |
+
return out, absmax
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
torch.library.define(
|
| 275 |
+
"bitsandbytes::gemv_4bit",
|
| 276 |
+
"(Tensor A, Tensor B, int[] shapeB, Tensor absmax, Tensor code, int blocksize) -> Tensor",
|
| 277 |
+
)
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
@register_fake("bitsandbytes::gemv_4bit")
|
| 281 |
+
def _(
|
| 282 |
+
A: torch.Tensor, B: torch.Tensor, shapeB: Sequence[int], absmax: torch.Tensor, code: torch.Tensor, blocksize: int
|
| 283 |
+
) -> torch.Tensor:
|
| 284 |
+
torch._check_is_size(blocksize)
|
| 285 |
+
torch._check(A.numel() == A.size(-1), lambda: f"A must be a vector with leading dimensions of 1, got {A.shape}")
|
| 286 |
+
torch._check(
|
| 287 |
+
A.dtype in [torch.float16, torch.bfloat16, torch.float32],
|
| 288 |
+
lambda: f"A must be float16, bfloat16, or float32, got {A.dtype}",
|
| 289 |
+
)
|
| 290 |
+
torch._check(
|
| 291 |
+
B.dtype in [torch.uint8, torch.bfloat16, torch.float16, torch.float32],
|
| 292 |
+
lambda: f"B must be backed by storage of type uint8, bfloat16, float16, or float32, got {B.dtype}",
|
| 293 |
+
)
|
| 294 |
+
shape = (*A.shape[:-1], shapeB[0])
|
| 295 |
+
return torch.empty(shape, device=A.device, dtype=A.dtype)
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
torch.library.define(
|
| 299 |
+
"bitsandbytes::gemv_4bit.out",
|
| 300 |
+
"(Tensor A, Tensor B, int[] shapeB, Tensor absmax, Tensor code, int blocksize, Tensor! out) -> ()",
|
| 301 |
+
)
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
@register_fake("bitsandbytes::gemv_4bit.out")
|
| 305 |
+
def _(
|
| 306 |
+
A: torch.Tensor,
|
| 307 |
+
B: torch.Tensor,
|
| 308 |
+
shapeB: Sequence[int],
|
| 309 |
+
absmax: torch.Tensor,
|
| 310 |
+
code: torch.Tensor,
|
| 311 |
+
blocksize: int,
|
| 312 |
+
out: torch.Tensor,
|
| 313 |
+
) -> None:
|
| 314 |
+
torch._check_is_size(blocksize)
|
| 315 |
+
torch._check(A.numel() == A.size(-1), lambda: f"A must be a vector with leading dimensions of 1, got {A.shape}")
|
| 316 |
+
torch._check(
|
| 317 |
+
A.dtype in [torch.float16, torch.bfloat16, torch.float32],
|
| 318 |
+
lambda: f"A must be float16, bfloat16, or float32, got {A.dtype}",
|
| 319 |
+
)
|
| 320 |
+
torch._check(
|
| 321 |
+
B.dtype in [torch.uint8, torch.bfloat16, torch.float16, torch.float32],
|
| 322 |
+
lambda: f"B must be backed by storage of type uint8, bfloat16, float16, or float32, got {B.dtype}",
|
| 323 |
+
)
|
| 324 |
+
torch._check(
|
| 325 |
+
out.shape == (*A.shape[:-1], shapeB[0]),
|
| 326 |
+
lambda: f"Expected out.shape == {(*A.shape[:-1], shapeB[0])}, got {out.shape}",
|
| 327 |
+
)
|
| 328 |
+
torch._check(out.device == A.device, lambda: f"Expected out.device == {A.device}, got {out.device}")
|
| 329 |
+
torch._check(out.dtype == A.dtype, lambda: f"Expected out.dtype == {A.dtype}, got {out.dtype}")
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
torch.library.define(
|
| 333 |
+
"bitsandbytes::optimizer_update_32bit",
|
| 334 |
+
"(str optimizer_name, Tensor(a0!) g, Tensor(a1!) p, Tensor(a2!) state1, Tensor(a3!)? state2, Tensor(a4!)? unorm_vec, float max_unorm, float param_norm, float beta1, float beta2, float beta3, float alpha, float eps, float weight_decay, int step, float lr, float gnorm_scale, bool skip_zeros=False) -> ()",
|
| 335 |
+
)
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
@register_fake("bitsandbytes::optimizer_update_32bit")
|
| 339 |
+
def _(
|
| 340 |
+
optimizer_name: str,
|
| 341 |
+
g: torch.Tensor,
|
| 342 |
+
p: torch.Tensor,
|
| 343 |
+
state1: torch.Tensor,
|
| 344 |
+
state2: Optional[torch.Tensor],
|
| 345 |
+
unorm_vec: Optional[torch.Tensor],
|
| 346 |
+
max_unorm: float,
|
| 347 |
+
param_norm: float,
|
| 348 |
+
beta1: float,
|
| 349 |
+
beta2: float,
|
| 350 |
+
beta3: float,
|
| 351 |
+
alpha: float,
|
| 352 |
+
eps: float,
|
| 353 |
+
weight_decay: float,
|
| 354 |
+
step: int,
|
| 355 |
+
lr: float,
|
| 356 |
+
gnorm_scale: float,
|
| 357 |
+
skip_zeros=False,
|
| 358 |
+
) -> None:
|
| 359 |
+
torch._check(
|
| 360 |
+
g.numel() == p.numel(),
|
| 361 |
+
lambda: f"g and p must have the same number of elements, got {g.numel()} and {p.numel()}",
|
| 362 |
+
)
|
| 363 |
+
compute_dtypes = [torch.float16, torch.bfloat16, torch.float32]
|
| 364 |
+
|
| 365 |
+
torch._check(
|
| 366 |
+
g.dtype in compute_dtypes,
|
| 367 |
+
lambda: f"g must be bfloat16, float16, or float32, got {g.dtype}",
|
| 368 |
+
)
|
| 369 |
+
torch._check(
|
| 370 |
+
g.dtype == p.dtype,
|
| 371 |
+
lambda: f"Expected all tensors to have the same dtype, got g.dtype={g.dtype}, p.dtype={p.dtype}",
|
| 372 |
+
)
|
| 373 |
+
|
| 374 |
+
|
| 375 |
+
torch.library.define(
|
| 376 |
+
"bitsandbytes::optimizer_update_8bit_blockwise",
|
| 377 |
+
"(str optimizer_name, Tensor(a0!) g, Tensor(a1!) p, Tensor(a2!) state1, Tensor(a3!)? state2, float beta1, float beta2, float beta3, float alpha, float eps, int step, float lr, Tensor(a4!) qmap1, Tensor(a5!)? qmap2, Tensor(a6!) absmax1, Tensor(a7!)? absmax2, float weight_decay, float gnorm_scale, bool skip_zeros=False) -> ()",
|
| 378 |
+
)
|
| 379 |
+
|
| 380 |
+
|
| 381 |
+
@register_fake("bitsandbytes::optimizer_update_8bit_blockwise")
|
| 382 |
+
def _(
|
| 383 |
+
optimizer_name: str,
|
| 384 |
+
g: torch.Tensor,
|
| 385 |
+
p: torch.Tensor,
|
| 386 |
+
state1: torch.Tensor,
|
| 387 |
+
state2: Optional[torch.Tensor],
|
| 388 |
+
beta1: float,
|
| 389 |
+
beta2: float,
|
| 390 |
+
beta3: float,
|
| 391 |
+
alpha: float,
|
| 392 |
+
eps: float,
|
| 393 |
+
step: int,
|
| 394 |
+
lr: float,
|
| 395 |
+
qmap1: torch.Tensor,
|
| 396 |
+
qmap2: Optional[torch.Tensor],
|
| 397 |
+
absmax1: torch.Tensor,
|
| 398 |
+
absmax2: Optional[torch.Tensor],
|
| 399 |
+
weight_decay: float,
|
| 400 |
+
gnorm_scale: float,
|
| 401 |
+
skip_zeros=False,
|
| 402 |
+
) -> None:
|
| 403 |
+
torch._check(
|
| 404 |
+
g.numel() == p.numel(),
|
| 405 |
+
lambda: f"g and p must have the same number of elements, got {g.numel()} and {p.numel()}",
|
| 406 |
+
)
|
| 407 |
+
compute_dtypes = [torch.float16, torch.bfloat16, torch.float32]
|
| 408 |
+
|
| 409 |
+
torch._check(
|
| 410 |
+
g.dtype in compute_dtypes,
|
| 411 |
+
lambda: f"g must be bfloat16, float16, or float32, got {g.dtype}",
|
| 412 |
+
)
|
| 413 |
+
torch._check(
|
| 414 |
+
g.dtype == p.dtype,
|
| 415 |
+
lambda: f"Expected all tensors to have the same dtype, got g.dtype={g.dtype}, p.dtype={p.dtype}",
|
| 416 |
+
)
|
| 417 |
+
torch._check(
|
| 418 |
+
state1.dtype == torch.uint8,
|
| 419 |
+
lambda: f"state1 must be uint8, got {state1.dtype}",
|
| 420 |
+
)
|
| 421 |
+
torch._check(
|
| 422 |
+
qmap1.dtype == absmax1.dtype == torch.float32,
|
| 423 |
+
lambda: f"Expected qmap1 and absmax1 to be float32, got qmap1.dtype={qmap1.dtype}, absmax1.dtype={absmax1.dtype}",
|
| 424 |
+
)
|
| 425 |
+
if state2 is not None:
|
| 426 |
+
torch._check(
|
| 427 |
+
state2.dtype == torch.uint8,
|
| 428 |
+
lambda: f"state2 must be uint8, got {state2.dtype}",
|
| 429 |
+
)
|
| 430 |
+
torch._check(
|
| 431 |
+
qmap2.dtype == absmax2.dtype == torch.float32,
|
| 432 |
+
lambda: f"Expected qmap2 and absmax2 to be float32, got qmap2.dtype={qmap2.dtype}, absmax2.dtype={absmax2.dtype}",
|
| 433 |
+
)
|
bitsandbytes/autograd/__init__.py
ADDED
|
File without changes
|
bitsandbytes/autograd/_functions.py
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dataclasses import dataclass
|
| 2 |
+
from math import prod
|
| 3 |
+
from typing import Optional
|
| 4 |
+
import warnings
|
| 5 |
+
from warnings import warn
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
|
| 9 |
+
import bitsandbytes.functional as F
|
| 10 |
+
|
| 11 |
+
# The inverse transformation for the colTuring and colAmpere format were contributed by Alex Borzunov:
|
| 12 |
+
# https://github.com/bigscience-workshop/petals/blob/main/src/petals/utils/linear8bitlt_patch.py
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
"""
|
| 16 |
+
This class pools outlier dimensions across layers.
|
| 17 |
+
This is particularly important for small models where outlier features
|
| 18 |
+
are less systematic and occur with low frequency.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class GlobalOutlierPooler:
|
| 23 |
+
_instance = None
|
| 24 |
+
|
| 25 |
+
def __init__(self):
|
| 26 |
+
raise RuntimeError("Call get_instance() instead")
|
| 27 |
+
|
| 28 |
+
def initialize(self):
|
| 29 |
+
self.outliers = set()
|
| 30 |
+
self.model_dim = None
|
| 31 |
+
|
| 32 |
+
@classmethod
|
| 33 |
+
def get_instance(cls):
|
| 34 |
+
if cls._instance is None:
|
| 35 |
+
cls._instance = cls.__new__(cls)
|
| 36 |
+
cls._instance.initialize()
|
| 37 |
+
return cls._instance
|
| 38 |
+
|
| 39 |
+
def add_outliers(self, outlier_idx, feature_dim):
|
| 40 |
+
if self.model_dim is None:
|
| 41 |
+
self.model_dim = feature_dim
|
| 42 |
+
if feature_dim != self.model_dim:
|
| 43 |
+
return # we do not encode outliers for the 2nd FFN layer
|
| 44 |
+
|
| 45 |
+
self.outliers.update(outlier_idx.tolist())
|
| 46 |
+
|
| 47 |
+
def get_current_outlier_idx(self):
|
| 48 |
+
return torch.Tensor(list(self.outliers)).to(torch.int64)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
_is_compiling = torch.compiler.is_compiling
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
@dataclass
|
| 55 |
+
class MatmulLtState:
|
| 56 |
+
_tile_indices: Optional[torch.Tensor] = None # TODO: remove
|
| 57 |
+
|
| 58 |
+
force_no_igemmlt: bool = False
|
| 59 |
+
|
| 60 |
+
CB: Optional[torch.Tensor] = None
|
| 61 |
+
CxB: Optional[torch.Tensor] = None # TODO: Deprecate/remove
|
| 62 |
+
SB: Optional[torch.Tensor] = None
|
| 63 |
+
SCB: Optional[torch.Tensor] = None
|
| 64 |
+
|
| 65 |
+
CxBt: Optional[torch.Tensor] = None # TODO: Deprecate/remove
|
| 66 |
+
SBt: Optional[torch.Tensor] = None
|
| 67 |
+
CBt: Optional[torch.Tensor] = None
|
| 68 |
+
|
| 69 |
+
subB: Optional[torch.Tensor] = None
|
| 70 |
+
|
| 71 |
+
outlier_pool: Optional[GlobalOutlierPooler] = None
|
| 72 |
+
has_accumulated_gradients = False
|
| 73 |
+
threshold = 0.0
|
| 74 |
+
idx: Optional[torch.Tensor] = None
|
| 75 |
+
is_training = True
|
| 76 |
+
has_fp16_weights = True
|
| 77 |
+
use_pool = False
|
| 78 |
+
formatB = "row" # TODO: Deprecate/remove
|
| 79 |
+
|
| 80 |
+
def reset_grads(self):
|
| 81 |
+
self.CB = None
|
| 82 |
+
self.CxB = None
|
| 83 |
+
self.SB = None
|
| 84 |
+
self.SCB = None
|
| 85 |
+
|
| 86 |
+
self.CxBt = None
|
| 87 |
+
self.SBt = None
|
| 88 |
+
self.CBt = None
|
| 89 |
+
|
| 90 |
+
@property
|
| 91 |
+
def tile_indices(self):
|
| 92 |
+
raise ValueError("tile_indices is no longer supported.")
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
class MatMul8bitLt(torch.autograd.Function):
|
| 96 |
+
@staticmethod
|
| 97 |
+
def forward(
|
| 98 |
+
ctx: torch.autograd.function.FunctionCtx,
|
| 99 |
+
A: torch.Tensor,
|
| 100 |
+
B: torch.Tensor,
|
| 101 |
+
out: Optional[torch.Tensor] = None,
|
| 102 |
+
bias: Optional[torch.Tensor] = None,
|
| 103 |
+
state: Optional[MatmulLtState] = None,
|
| 104 |
+
):
|
| 105 |
+
state = state or MatmulLtState()
|
| 106 |
+
|
| 107 |
+
# default of pytorch behavior if inputs are empty
|
| 108 |
+
ctx.is_empty = False
|
| 109 |
+
if prod(A.shape) == 0:
|
| 110 |
+
ctx.is_empty = True
|
| 111 |
+
ctx.A = A
|
| 112 |
+
ctx.B = B
|
| 113 |
+
ctx.bias = bias
|
| 114 |
+
if A.shape[-1] == B.shape[0]:
|
| 115 |
+
return torch.empty(A.shape[:-1] + B.shape[1:], dtype=A.dtype, device=A.device)
|
| 116 |
+
else:
|
| 117 |
+
return torch.empty(A.shape[:-1] + B.shape[:1], dtype=A.dtype, device=A.device)
|
| 118 |
+
|
| 119 |
+
input_shape = A.shape
|
| 120 |
+
|
| 121 |
+
# Cast A to fp16
|
| 122 |
+
if A.dtype != torch.float16 and not _is_compiling():
|
| 123 |
+
warnings.warn(f"MatMul8bitLt: inputs will be cast from {A.dtype} to float16 during quantization")
|
| 124 |
+
|
| 125 |
+
if len(A.shape) == 3:
|
| 126 |
+
A = A.reshape(-1, A.shape[-1])
|
| 127 |
+
|
| 128 |
+
# 1. Quantize A. Note that as a side-effect, outliers are suppressed in CA/CAt.
|
| 129 |
+
if ctx.needs_input_grad[1]:
|
| 130 |
+
# Slower path
|
| 131 |
+
CA, CAt, SCA, SCAt, outlier_cols = F.int8_double_quant(A.to(torch.float16), threshold=state.threshold)
|
| 132 |
+
else:
|
| 133 |
+
# Fast path
|
| 134 |
+
CA, SCA, outlier_cols = F.int8_vectorwise_quant(A.to(torch.float16), threshold=state.threshold)
|
| 135 |
+
CAt = SCAt = None
|
| 136 |
+
|
| 137 |
+
has_grad = False
|
| 138 |
+
|
| 139 |
+
if state.has_fp16_weights or state.CB is None:
|
| 140 |
+
has_grad = getattr(B, "grad", None) is not None
|
| 141 |
+
is_transposed = not B.is_contiguous() and B.shape[0] == B.stride(1)
|
| 142 |
+
if is_transposed:
|
| 143 |
+
B = B.contiguous()
|
| 144 |
+
|
| 145 |
+
if (state.is_training and not has_grad) or state.CB is None or state.SCB is None:
|
| 146 |
+
state.reset_grads()
|
| 147 |
+
|
| 148 |
+
# 2. Quantize B
|
| 149 |
+
state.CB, state.SCB, _ = F.int8_vectorwise_quant(B.to(torch.float16))
|
| 150 |
+
|
| 151 |
+
# Handle sparse decomposition
|
| 152 |
+
if state.threshold > 0.0:
|
| 153 |
+
state.idx = outlier_cols
|
| 154 |
+
|
| 155 |
+
# Mixed Int8 Matmul + Dequant + Bias
|
| 156 |
+
output, subA = torch.ops.bitsandbytes.int8_mixed_scaled_mm(
|
| 157 |
+
A,
|
| 158 |
+
CA,
|
| 159 |
+
state.CB,
|
| 160 |
+
SCA,
|
| 161 |
+
state.SCB,
|
| 162 |
+
outlier_cols,
|
| 163 |
+
bias,
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
else:
|
| 167 |
+
# Int8 Matmul + Dequant + Bias
|
| 168 |
+
output = torch.ops.bitsandbytes.int8_scaled_mm.default(
|
| 169 |
+
CA, state.CB, SCA, state.SCB, bias=bias, dtype=A.dtype
|
| 170 |
+
)
|
| 171 |
+
subA = None
|
| 172 |
+
|
| 173 |
+
# 5. Save state
|
| 174 |
+
ctx.state = state
|
| 175 |
+
|
| 176 |
+
ctx.grad_shape = input_shape
|
| 177 |
+
ctx.dtype_A = A.dtype
|
| 178 |
+
ctx.dtype_bias = None if bias is None else bias.dtype
|
| 179 |
+
|
| 180 |
+
if any(ctx.needs_input_grad[:2]):
|
| 181 |
+
ctx.tensors = (CAt, subA, A)
|
| 182 |
+
ctx.tensor_states = (SCAt, state.idx)
|
| 183 |
+
else:
|
| 184 |
+
ctx.tensors = [None, None, None]
|
| 185 |
+
ctx.tensor_states = (None, None)
|
| 186 |
+
ctx.save_for_backward(None, None)
|
| 187 |
+
|
| 188 |
+
output_shape = (*input_shape[:-1], state.CB.shape[0])
|
| 189 |
+
|
| 190 |
+
if len(input_shape) == 3:
|
| 191 |
+
return output.reshape(output_shape)
|
| 192 |
+
|
| 193 |
+
return output
|
| 194 |
+
|
| 195 |
+
@staticmethod
|
| 196 |
+
def backward(ctx: torch.autograd.function.FunctionCtx, grad_output: torch.Tensor):
|
| 197 |
+
if ctx.is_empty:
|
| 198 |
+
bias_grad = None if ctx.bias is None else torch.zeros_like(ctx.bias)
|
| 199 |
+
return torch.zeros_like(ctx.A), torch.zeros_like(ctx.B), None, bias_grad, None
|
| 200 |
+
|
| 201 |
+
req_gradA, req_gradB, _, req_gradBias, _ = ctx.needs_input_grad
|
| 202 |
+
CAt, subA, _A = ctx.tensors
|
| 203 |
+
SCAt, idx = ctx.tensor_states
|
| 204 |
+
state: MatmulLtState = ctx.state
|
| 205 |
+
grad_A = grad_B = grad_bias = None
|
| 206 |
+
|
| 207 |
+
if req_gradBias:
|
| 208 |
+
# compute grad_bias first before changing grad_output dtype
|
| 209 |
+
grad_bias = grad_output.sum(0, dtype=ctx.dtype_bias)
|
| 210 |
+
|
| 211 |
+
# Cast grad_output to fp16
|
| 212 |
+
if len(grad_output.shape) == 3:
|
| 213 |
+
grad_output = grad_output.reshape(-1, grad_output.shape[-1]).contiguous()
|
| 214 |
+
|
| 215 |
+
if req_gradB:
|
| 216 |
+
Cgrad, _, _, SCgradt, _ = F.int8_double_quant(grad_output.to(torch.float16))
|
| 217 |
+
|
| 218 |
+
grad_B = torch.ops.bitsandbytes.int8_scaled_mm.default(
|
| 219 |
+
Cgrad.t().contiguous(),
|
| 220 |
+
CAt.t(),
|
| 221 |
+
SCgradt,
|
| 222 |
+
SCAt,
|
| 223 |
+
dtype=torch.float16,
|
| 224 |
+
)
|
| 225 |
+
|
| 226 |
+
if state.threshold > 0.0 and subA is not None and subA.numel() > 0:
|
| 227 |
+
grad_B[:, idx] += torch.matmul(grad_output.t(), subA)
|
| 228 |
+
|
| 229 |
+
if req_gradA:
|
| 230 |
+
if state.CB is not None:
|
| 231 |
+
CB = state.CB.to(ctx.dtype_A, copy=True).mul_(state.SCB.unsqueeze(1).mul(1.0 / 127.0))
|
| 232 |
+
grad_A = torch.matmul(grad_output.to(ctx.dtype_A), CB).view(ctx.grad_shape)
|
| 233 |
+
else:
|
| 234 |
+
raise Exception("State must contain CB matrix for backward")
|
| 235 |
+
|
| 236 |
+
return grad_A, grad_B, None, grad_bias, None
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
class MatMul8bitFp(torch.autograd.Function):
|
| 240 |
+
# For Intel CPU and XPU MatMul8bitFp is much faster (~3x) than MatMul8bitLt in finetune.
|
| 241 |
+
# Because the MatMul8bitLt has more mechanisms in computing grad.
|
| 242 |
+
# We don't have fast kernel for quant/dequant 8bit in CPU/XPU, so it's very slow.
|
| 243 |
+
# We'd like to use dequant + matmul to run finetune with good performance.
|
| 244 |
+
|
| 245 |
+
@staticmethod
|
| 246 |
+
def forward(ctx, A, B, out=None, bias=None, state=MatmulLtState):
|
| 247 |
+
if state.has_fp16_weights or state.CB is None:
|
| 248 |
+
has_grad = getattr(B, "grad", None) is not None
|
| 249 |
+
is_transposed = not B.is_contiguous() and B.shape[0] == B.stride(1)
|
| 250 |
+
if is_transposed:
|
| 251 |
+
B = B.contiguous()
|
| 252 |
+
|
| 253 |
+
if (state.is_training and not has_grad) or state.CB is None or state.SCB is None:
|
| 254 |
+
state.reset_grads()
|
| 255 |
+
state.CB, state.SCB, _ = F.int8_vectorwise_quant(B.to(torch.float16))
|
| 256 |
+
B = state.CB
|
| 257 |
+
|
| 258 |
+
CB = state.CB.data.to(A.dtype).mul_(state.SCB.unsqueeze(1).mul(1.0 / 127.0))
|
| 259 |
+
output = torch.nn.functional.linear(A, CB, bias)
|
| 260 |
+
ctx.state = state
|
| 261 |
+
ctx.dtype_A = A.dtype
|
| 262 |
+
ctx.grad_shape = A.shape
|
| 263 |
+
ctx.A = A
|
| 264 |
+
ctx.dtype_bias = None if bias is None else bias.dtype
|
| 265 |
+
return output
|
| 266 |
+
|
| 267 |
+
@staticmethod
|
| 268 |
+
def backward(ctx, grad_output):
|
| 269 |
+
req_gradA, req_gradB, _, req_gradBias, _ = ctx.needs_input_grad
|
| 270 |
+
A = ctx.A
|
| 271 |
+
state = ctx.state
|
| 272 |
+
grad_A = grad_B = grad_bias = None
|
| 273 |
+
if req_gradBias:
|
| 274 |
+
# compute grad_bias first before changing grad_output dtype
|
| 275 |
+
grad_bias = grad_output.sum(0, dtype=ctx.dtype_bias)
|
| 276 |
+
|
| 277 |
+
# Cast grad_output to fp16
|
| 278 |
+
if len(grad_output.shape) == 3:
|
| 279 |
+
grad_output = grad_output.reshape(-1, grad_output.shape[-1]).contiguous()
|
| 280 |
+
|
| 281 |
+
if req_gradB:
|
| 282 |
+
grad_B = torch.matmul(A.t(), grad_output).t()
|
| 283 |
+
|
| 284 |
+
if req_gradA:
|
| 285 |
+
if state.CB is not None:
|
| 286 |
+
CB = state.CB.to(ctx.dtype_A, copy=True).mul_(state.SCB.unsqueeze(1).mul(1.0 / 127.0))
|
| 287 |
+
grad_A = torch.matmul(grad_output.to(ctx.dtype_A), CB).view(ctx.grad_shape)
|
| 288 |
+
else:
|
| 289 |
+
raise Exception("State must contain CB matrix for backward")
|
| 290 |
+
|
| 291 |
+
return grad_A, grad_B, None, grad_bias, None
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
class MatMul4Bit(torch.autograd.Function):
|
| 295 |
+
# forward is the same, but we added the fallback for pre-turing GPUs
|
| 296 |
+
# backward is mostly the same, but adds one extra clause (see "elif state.CxB is not None")
|
| 297 |
+
|
| 298 |
+
@staticmethod
|
| 299 |
+
def forward(ctx, A, B, out=None, bias=None, quant_state: Optional[F.QuantState] = None):
|
| 300 |
+
# default of pytorch behavior if inputs are empty
|
| 301 |
+
ctx.is_empty = False
|
| 302 |
+
if prod(A.shape) == 0:
|
| 303 |
+
ctx.is_empty = True
|
| 304 |
+
ctx.A = A
|
| 305 |
+
ctx.B = B
|
| 306 |
+
ctx.bias = bias
|
| 307 |
+
B_shape = quant_state.shape
|
| 308 |
+
if A.shape[-1] == B_shape[0]:
|
| 309 |
+
return torch.empty(A.shape[:-1] + B_shape[1:], dtype=A.dtype, device=A.device)
|
| 310 |
+
else:
|
| 311 |
+
return torch.empty(A.shape[:-1] + B_shape[:1], dtype=A.dtype, device=A.device)
|
| 312 |
+
|
| 313 |
+
# 1. Dequantize
|
| 314 |
+
# 2. MatmulnN
|
| 315 |
+
output = torch.nn.functional.linear(A, F.dequantize_4bit(B, quant_state).to(A.dtype).t(), bias)
|
| 316 |
+
|
| 317 |
+
# 3. Save state
|
| 318 |
+
ctx.state = quant_state
|
| 319 |
+
ctx.dtype_A, ctx.dtype_B, ctx.dtype_bias = A.dtype, B.dtype, None if bias is None else bias.dtype
|
| 320 |
+
|
| 321 |
+
if any(ctx.needs_input_grad[:2]):
|
| 322 |
+
ctx.tensors = (None, B)
|
| 323 |
+
else:
|
| 324 |
+
ctx.tensors = (None, None)
|
| 325 |
+
|
| 326 |
+
return output
|
| 327 |
+
|
| 328 |
+
@staticmethod
|
| 329 |
+
def backward(ctx, grad_output):
|
| 330 |
+
if ctx.is_empty:
|
| 331 |
+
bias_grad = None if ctx.bias is None else torch.zeros_like(ctx.bias)
|
| 332 |
+
return torch.zeros_like(ctx.A), torch.zeros_like(ctx.B), None, bias_grad, None
|
| 333 |
+
|
| 334 |
+
req_gradA, _, _, req_gradBias, _ = ctx.needs_input_grad
|
| 335 |
+
_, B = ctx.tensors
|
| 336 |
+
|
| 337 |
+
grad_A, grad_B, grad_bias = None, None, None
|
| 338 |
+
|
| 339 |
+
if req_gradBias:
|
| 340 |
+
# compute grad_bias first before changing grad_output dtype
|
| 341 |
+
grad_bias = grad_output.sum(0, dtype=ctx.dtype_bias)
|
| 342 |
+
|
| 343 |
+
# not supported by PyTorch. TODO: create work-around
|
| 344 |
+
# if req_gradB: grad_B = torch.matmul(grad_output.t(), A)
|
| 345 |
+
if req_gradA:
|
| 346 |
+
grad_A = torch.matmul(grad_output, F.dequantize_4bit(B, ctx.state).to(grad_output.dtype).t())
|
| 347 |
+
|
| 348 |
+
return grad_A, grad_B, None, grad_bias, None
|
| 349 |
+
|
| 350 |
+
|
| 351 |
+
def matmul(
|
| 352 |
+
A: torch.Tensor,
|
| 353 |
+
B: torch.Tensor,
|
| 354 |
+
out: Optional[torch.Tensor] = None,
|
| 355 |
+
state: Optional[MatmulLtState] = None,
|
| 356 |
+
threshold=0.0,
|
| 357 |
+
bias: Optional[torch.Tensor] = None,
|
| 358 |
+
):
|
| 359 |
+
state = state or MatmulLtState()
|
| 360 |
+
if threshold > 0.0:
|
| 361 |
+
state.threshold = threshold
|
| 362 |
+
# MatMul8bitLt is slower because no fast kernel for quant/dequant 8bit in CPU/XPU
|
| 363 |
+
if state.is_training:
|
| 364 |
+
if A.device.type in ("cpu", "xpu"):
|
| 365 |
+
return MatMul8bitFp.apply(A, B, out, bias, state)
|
| 366 |
+
return MatMul8bitLt.apply(A, B, out, bias, state)
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
def matmul_4bit(
|
| 370 |
+
A: torch.Tensor,
|
| 371 |
+
B: torch.Tensor,
|
| 372 |
+
quant_state: F.QuantState,
|
| 373 |
+
out: Optional[torch.Tensor] = None,
|
| 374 |
+
bias: Optional[torch.Tensor] = None,
|
| 375 |
+
):
|
| 376 |
+
assert quant_state is not None
|
| 377 |
+
# Change dtype to input dtype on CPU
|
| 378 |
+
if A.device.type == "cpu":
|
| 379 |
+
quant_state.dtype = A.dtype
|
| 380 |
+
|
| 381 |
+
if getattr(quant_state, "packing_format_for_cpu", False):
|
| 382 |
+
out = F.gemv_4bit(A, B, out, state=quant_state)
|
| 383 |
+
if bias is not None:
|
| 384 |
+
out += bias
|
| 385 |
+
return out
|
| 386 |
+
else:
|
| 387 |
+
return MatMul4Bit.apply(A, B, out, bias, quant_state)
|
| 388 |
+
|
| 389 |
+
if A.numel() == A.shape[-1] and A.requires_grad == False and A.device.type != "hpu":
|
| 390 |
+
if A.shape[-1] % quant_state.blocksize != 0:
|
| 391 |
+
warn(
|
| 392 |
+
f"Some matrices hidden dimension is not a multiple of {quant_state.blocksize} and efficient inference kernels are not supported for these (slow). Matrix input size found: {A.shape}",
|
| 393 |
+
)
|
| 394 |
+
return MatMul4Bit.apply(A, B, out, bias, quant_state)
|
| 395 |
+
else:
|
| 396 |
+
out = F.gemv_4bit(A, B.t(), out, state=quant_state)
|
| 397 |
+
if bias is not None:
|
| 398 |
+
out += bias
|
| 399 |
+
return out
|
| 400 |
+
else:
|
| 401 |
+
return MatMul4Bit.apply(A, B, out, bias, quant_state)
|
bitsandbytes/backends/__init__.py
ADDED
|
File without changes
|
bitsandbytes/backends/cpu/__init__.py
ADDED
|
File without changes
|
bitsandbytes/backends/cpu/ops.py
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import Sequence
|
| 2 |
+
import ctypes as ct
|
| 3 |
+
import logging
|
| 4 |
+
from math import prod
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
from bitsandbytes.functional import get_ptr, has_avx512bf16
|
| 9 |
+
|
| 10 |
+
from ..._ops import register_kernel
|
| 11 |
+
from ...cextension import ErrorHandlerMockBNBNativeLibrary, lib
|
| 12 |
+
|
| 13 |
+
logger = logging.getLogger(__name__)
|
| 14 |
+
|
| 15 |
+
_has_avx512 = torch.backends.cpu.get_cpu_capability() == "AVX512"
|
| 16 |
+
|
| 17 |
+
# torch._int_mm for s8@s8->s32 is supported on CPU from torch 2.4+.
|
| 18 |
+
# However, we can overflow if we use this without AVX512_VNNI support.
|
| 19 |
+
# This is fixed in torch 2.6+, so we set this as the minimum to be safe.
|
| 20 |
+
# For more information: https://github.com/pytorch/pytorch/pull/136942
|
| 21 |
+
# TODO(matthewdouglas): aarch64?
|
| 22 |
+
if torch.__version__ >= (2, 6):
|
| 23 |
+
|
| 24 |
+
@register_kernel("bitsandbytes::int8_linear_matmul", "cpu")
|
| 25 |
+
def _(A: torch.Tensor, B: torch.Tensor):
|
| 26 |
+
return torch._int_mm(
|
| 27 |
+
A.reshape(-1, A.shape[-1]),
|
| 28 |
+
B.t(),
|
| 29 |
+
).reshape(*A.shape[:-1], B.shape[0])
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
if not isinstance(lib, ErrorHandlerMockBNBNativeLibrary):
|
| 33 |
+
|
| 34 |
+
@register_kernel("bitsandbytes::quantize_blockwise", "cpu")
|
| 35 |
+
def _(A: torch.Tensor, code: torch.Tensor, blocksize: int) -> tuple[torch.Tensor, torch.Tensor]:
|
| 36 |
+
torch._check_is_size(blocksize)
|
| 37 |
+
|
| 38 |
+
n = A.numel()
|
| 39 |
+
|
| 40 |
+
# Only FP32 has c++ kernrl
|
| 41 |
+
if A.dtype == torch.float32:
|
| 42 |
+
blocks = -(n // -blocksize)
|
| 43 |
+
|
| 44 |
+
absmax = torch.empty((blocks,), device=A.device, dtype=torch.float32)
|
| 45 |
+
out = torch.empty_like(A, dtype=torch.uint8)
|
| 46 |
+
|
| 47 |
+
lib.cquantize_blockwise_cpu_fp32(
|
| 48 |
+
get_ptr(code),
|
| 49 |
+
get_ptr(A),
|
| 50 |
+
get_ptr(absmax),
|
| 51 |
+
get_ptr(out),
|
| 52 |
+
ct.c_longlong(blocksize),
|
| 53 |
+
ct.c_longlong(n),
|
| 54 |
+
)
|
| 55 |
+
else:
|
| 56 |
+
rem = n % blocksize
|
| 57 |
+
has_rem = rem > 0
|
| 58 |
+
blocks = n // blocksize + has_rem
|
| 59 |
+
absmax = torch.zeros((blocks,), device=A.device, dtype=torch.float32)
|
| 60 |
+
A_reshaped = A.reshape(n)
|
| 61 |
+
A_com = A_reshaped[: n - rem]
|
| 62 |
+
A_com_reshaped = A_com.reshape(n // blocksize, blocksize)
|
| 63 |
+
absmax[: blocks - has_rem] = torch.abs(A_com_reshaped).max(dim=-1)[0]
|
| 64 |
+
scaled_A = torch.clamp(A_com_reshaped * (1 / absmax[: blocks - has_rem].view(-1, 1)), -1, 1)
|
| 65 |
+
scaled_A = scaled_A.reshape(-1)
|
| 66 |
+
if has_rem:
|
| 67 |
+
absmax[-1] = torch.abs(A_reshaped[n - rem :]).max()
|
| 68 |
+
scaled_A_rem = torch.clamp(A_reshaped[n - rem :] * (1 / absmax[-1]), -1, 1)
|
| 69 |
+
scaled_A = torch.cat([scaled_A, scaled_A_rem], dim=0)
|
| 70 |
+
|
| 71 |
+
diff = torch.abs(scaled_A.unsqueeze(-1) - code.to(scaled_A.device))
|
| 72 |
+
out = torch.argmin(diff, dim=-1).to(torch.uint8).to(scaled_A.device).reshape(A.shape)
|
| 73 |
+
|
| 74 |
+
return out, absmax
|
| 75 |
+
|
| 76 |
+
@register_kernel("bitsandbytes::dequantize_blockwise", "cpu")
|
| 77 |
+
def _(
|
| 78 |
+
A: torch.Tensor, absmax: torch.Tensor, code: torch.Tensor, blocksize: int, dtype: torch.dtype
|
| 79 |
+
) -> torch.Tensor:
|
| 80 |
+
torch._check_is_size(blocksize)
|
| 81 |
+
torch._check(A.dtype == torch.uint8, lambda: f"A must be uint8, got {A.dtype}")
|
| 82 |
+
|
| 83 |
+
out = torch.empty_like(A, dtype=dtype)
|
| 84 |
+
if dtype == torch.float32:
|
| 85 |
+
lib.cdequantize_blockwise_cpu_fp32(
|
| 86 |
+
get_ptr(code),
|
| 87 |
+
get_ptr(A),
|
| 88 |
+
get_ptr(absmax),
|
| 89 |
+
get_ptr(out),
|
| 90 |
+
ct.c_longlong(blocksize),
|
| 91 |
+
ct.c_longlong(A.numel()),
|
| 92 |
+
)
|
| 93 |
+
elif dtype == torch.bfloat16:
|
| 94 |
+
lib.cdequantize_blockwise_cpu_bf16(
|
| 95 |
+
get_ptr(code),
|
| 96 |
+
get_ptr(A),
|
| 97 |
+
get_ptr(absmax),
|
| 98 |
+
get_ptr(out),
|
| 99 |
+
ct.c_longlong(blocksize),
|
| 100 |
+
ct.c_longlong(A.numel()),
|
| 101 |
+
)
|
| 102 |
+
elif dtype == torch.float16:
|
| 103 |
+
lib.cdequantize_blockwise_cpu_fp16(
|
| 104 |
+
get_ptr(code),
|
| 105 |
+
get_ptr(A),
|
| 106 |
+
get_ptr(absmax),
|
| 107 |
+
get_ptr(out),
|
| 108 |
+
ct.c_longlong(blocksize),
|
| 109 |
+
ct.c_longlong(A.numel()),
|
| 110 |
+
)
|
| 111 |
+
else:
|
| 112 |
+
out = code[A.reshape(-1).int()]
|
| 113 |
+
blocks = out.shape[-1] // blocksize
|
| 114 |
+
res = out.shape[-1] % blocksize
|
| 115 |
+
if res != 0:
|
| 116 |
+
out = torch.nn.functional.pad(out, (0, blocksize - res), mode="constant", value=0)
|
| 117 |
+
out = (out.view(-1, blocksize) * absmax.view(-1, 1)).to(dtype).reshape(-1)
|
| 118 |
+
out = out[: blocks * blocksize + res]
|
| 119 |
+
out = out.reshape(A.shape)
|
| 120 |
+
|
| 121 |
+
return out
|
| 122 |
+
|
| 123 |
+
@register_kernel("bitsandbytes::dequantize_4bit", "cpu")
|
| 124 |
+
def _(
|
| 125 |
+
A: torch.Tensor,
|
| 126 |
+
absmax: torch.Tensor,
|
| 127 |
+
blocksize: int,
|
| 128 |
+
quant_type: str,
|
| 129 |
+
shape: Sequence[int],
|
| 130 |
+
dtype: torch.dtype,
|
| 131 |
+
) -> torch.Tensor:
|
| 132 |
+
torch._check_is_size(blocksize)
|
| 133 |
+
torch._check(quant_type in ("nf4", "fp4"), lambda: f"quant_type must be nf4 or fp4, got {quant_type}")
|
| 134 |
+
torch._check(
|
| 135 |
+
dtype in [torch.bfloat16, torch.float16, torch.float32],
|
| 136 |
+
lambda: f"Blockwise 4bit dequantization only supports 16/32-bit floats, but got {dtype}",
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
# Fallback as AVX512 implementation has accuracy issues with fp16/fp32 and blocksize >= 2048
|
| 140 |
+
# Note: this is not a common use case.
|
| 141 |
+
avx512_fallback = _has_avx512 and blocksize >= 2048 and dtype != torch.bfloat16
|
| 142 |
+
|
| 143 |
+
# Odd shape is not supported by this kernel; fallback to generic implementation
|
| 144 |
+
shape_fallback = shape[-1] % 2 != 0
|
| 145 |
+
|
| 146 |
+
if avx512_fallback or shape_fallback:
|
| 147 |
+
from ..default.ops import _dequantize_4bit_impl
|
| 148 |
+
|
| 149 |
+
return _dequantize_4bit_impl(A, absmax, blocksize, quant_type, shape, dtype)
|
| 150 |
+
|
| 151 |
+
# Enable non uint8 dtype
|
| 152 |
+
if A.dtype != torch.uint8:
|
| 153 |
+
A = A.view(torch.uint8)
|
| 154 |
+
|
| 155 |
+
# TODO: support half precision absmax
|
| 156 |
+
if absmax.dtype != torch.float32:
|
| 157 |
+
absmax = absmax.float()
|
| 158 |
+
|
| 159 |
+
if len(shape) == 1:
|
| 160 |
+
shape = (1, shape[0])
|
| 161 |
+
|
| 162 |
+
m = prod(shape[:-1])
|
| 163 |
+
n = shape[-1]
|
| 164 |
+
|
| 165 |
+
A = A.reshape(m, n // 2)
|
| 166 |
+
out = torch.empty(shape, dtype=dtype, device=A.device)
|
| 167 |
+
|
| 168 |
+
if quant_type == "fp4":
|
| 169 |
+
if dtype == torch.float32:
|
| 170 |
+
lib.cdequantize_blockwise_cpu_fp4_fp32(
|
| 171 |
+
get_ptr(A),
|
| 172 |
+
get_ptr(absmax),
|
| 173 |
+
get_ptr(out),
|
| 174 |
+
ct.c_longlong(blocksize),
|
| 175 |
+
ct.c_longlong(m),
|
| 176 |
+
ct.c_longlong(n),
|
| 177 |
+
)
|
| 178 |
+
elif dtype == torch.bfloat16:
|
| 179 |
+
lib.cdequantize_blockwise_cpu_fp4_bf16(
|
| 180 |
+
get_ptr(A),
|
| 181 |
+
get_ptr(absmax),
|
| 182 |
+
get_ptr(out),
|
| 183 |
+
ct.c_longlong(blocksize),
|
| 184 |
+
ct.c_longlong(m),
|
| 185 |
+
ct.c_longlong(n),
|
| 186 |
+
)
|
| 187 |
+
elif dtype == torch.float16:
|
| 188 |
+
lib.cdequantize_blockwise_cpu_fp4_fp16(
|
| 189 |
+
get_ptr(A),
|
| 190 |
+
get_ptr(absmax),
|
| 191 |
+
get_ptr(out),
|
| 192 |
+
ct.c_longlong(blocksize),
|
| 193 |
+
ct.c_longlong(m),
|
| 194 |
+
ct.c_longlong(n),
|
| 195 |
+
)
|
| 196 |
+
elif quant_type == "nf4":
|
| 197 |
+
if dtype == torch.float32:
|
| 198 |
+
lib.cdequantize_blockwise_cpu_nf4_fp32(
|
| 199 |
+
get_ptr(A),
|
| 200 |
+
get_ptr(absmax),
|
| 201 |
+
get_ptr(out),
|
| 202 |
+
ct.c_longlong(blocksize),
|
| 203 |
+
ct.c_longlong(m),
|
| 204 |
+
ct.c_longlong(n),
|
| 205 |
+
)
|
| 206 |
+
elif dtype == torch.bfloat16:
|
| 207 |
+
lib.cdequantize_blockwise_cpu_nf4_bf16(
|
| 208 |
+
get_ptr(A),
|
| 209 |
+
get_ptr(absmax),
|
| 210 |
+
get_ptr(out),
|
| 211 |
+
ct.c_longlong(blocksize),
|
| 212 |
+
ct.c_longlong(m),
|
| 213 |
+
ct.c_longlong(n),
|
| 214 |
+
)
|
| 215 |
+
elif dtype == torch.float16:
|
| 216 |
+
lib.cdequantize_blockwise_cpu_nf4_fp16(
|
| 217 |
+
get_ptr(A),
|
| 218 |
+
get_ptr(absmax),
|
| 219 |
+
get_ptr(out),
|
| 220 |
+
ct.c_longlong(blocksize),
|
| 221 |
+
ct.c_longlong(m),
|
| 222 |
+
ct.c_longlong(n),
|
| 223 |
+
)
|
| 224 |
+
else:
|
| 225 |
+
raise ValueError
|
| 226 |
+
|
| 227 |
+
return out
|
| 228 |
+
|
| 229 |
+
if has_avx512bf16():
|
| 230 |
+
gemm_4bit_forward_kernel = None
|
| 231 |
+
try:
|
| 232 |
+
from kernels import get_kernel
|
| 233 |
+
|
| 234 |
+
gemm_4bit_forward_kernel = get_kernel("kernels-community/quantization_bitsandbytes").gemm_4bit_forward
|
| 235 |
+
except Exception as exc: # pragma: no cover - best effort fallback
|
| 236 |
+
gemm_4bit_forward_kernel = None
|
| 237 |
+
logger.warning(
|
| 238 |
+
"Failed to load CPU gemm_4bit_forward from kernels-community: %s. Please make sure you already `pip install kernels` and the kernels >= 0.11.1",
|
| 239 |
+
exc,
|
| 240 |
+
)
|
| 241 |
+
|
| 242 |
+
@register_kernel("bitsandbytes::gemv_4bit", "cpu")
|
| 243 |
+
def _(
|
| 244 |
+
A: torch.Tensor,
|
| 245 |
+
B: torch.Tensor,
|
| 246 |
+
shapeB: Sequence[int],
|
| 247 |
+
absmax: torch.Tensor,
|
| 248 |
+
code: torch.Tensor,
|
| 249 |
+
blocksize: int,
|
| 250 |
+
) -> torch.Tensor:
|
| 251 |
+
assert B.dtype == torch.uint8, "Only support uint8 qweight"
|
| 252 |
+
dtype = A.dtype
|
| 253 |
+
quant_type = "fp4" if code[1] > 0 else "nf4"
|
| 254 |
+
# cpu fused op only support bf16 for now.
|
| 255 |
+
if dtype != torch.bfloat16:
|
| 256 |
+
A = A.to(torch.bfloat16)
|
| 257 |
+
|
| 258 |
+
final_out_shape = (*A.shape[:-1], shapeB[0])
|
| 259 |
+
A = A.reshape(-1, A.shape[-1])
|
| 260 |
+
out_shape = (*A.shape[:-1], shapeB[0])
|
| 261 |
+
if gemm_4bit_forward_kernel is not None:
|
| 262 |
+
quant_type_num = 1 if quant_type == "fp4" else 0
|
| 263 |
+
out = gemm_4bit_forward_kernel(A, B, absmax, blocksize, quant_type_num)
|
| 264 |
+
else:
|
| 265 |
+
out = torch.empty(out_shape, dtype=A.dtype, device=A.device)
|
| 266 |
+
M = A.shape[0]
|
| 267 |
+
N = shapeB[0]
|
| 268 |
+
K = A.shape[1]
|
| 269 |
+
x_strideM = A.stride(0)
|
| 270 |
+
out_strideM = out.stride(0)
|
| 271 |
+
if quant_type == "fp4":
|
| 272 |
+
lib.gemv_4bit_inference_cpu_fp4_bf16(
|
| 273 |
+
ct.c_int64(M),
|
| 274 |
+
ct.c_int64(N),
|
| 275 |
+
ct.c_int64(K),
|
| 276 |
+
get_ptr(A),
|
| 277 |
+
get_ptr(B),
|
| 278 |
+
get_ptr(absmax),
|
| 279 |
+
get_ptr(out),
|
| 280 |
+
ct.c_int64(blocksize),
|
| 281 |
+
ct.c_int64(x_strideM),
|
| 282 |
+
ct.c_int64(out_strideM),
|
| 283 |
+
)
|
| 284 |
+
elif quant_type == "nf4":
|
| 285 |
+
lib.gemv_4bit_inference_cpu_nf4_bf16(
|
| 286 |
+
ct.c_int64(M),
|
| 287 |
+
ct.c_int64(N),
|
| 288 |
+
ct.c_int64(K),
|
| 289 |
+
get_ptr(A),
|
| 290 |
+
get_ptr(B),
|
| 291 |
+
get_ptr(absmax),
|
| 292 |
+
get_ptr(out),
|
| 293 |
+
ct.c_int64(blocksize),
|
| 294 |
+
ct.c_int64(x_strideM),
|
| 295 |
+
ct.c_int64(out_strideM),
|
| 296 |
+
)
|
| 297 |
+
|
| 298 |
+
if dtype != torch.bfloat16:
|
| 299 |
+
out = out.to(dtype)
|
| 300 |
+
|
| 301 |
+
return out.reshape(final_out_shape)
|
bitsandbytes/backends/cuda/__init__.py
ADDED
|
File without changes
|
bitsandbytes/backends/cuda/ops.py
ADDED
|
@@ -0,0 +1,770 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import Sequence
|
| 2 |
+
import ctypes as ct
|
| 3 |
+
from math import prod
|
| 4 |
+
from typing import Optional
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
from bitsandbytes.functional import CUBLAS_Context, _cuda_device_of, _get_tensor_stream, get_ptr
|
| 9 |
+
|
| 10 |
+
from ..._ops import register_kernel
|
| 11 |
+
from ...cextension import ROCM_WARP_SIZE_64, lib
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@register_kernel("bitsandbytes::int8_linear_matmul", "cuda")
|
| 15 |
+
def _(A: torch.Tensor, B: torch.Tensor):
|
| 16 |
+
out = torch.empty((*A.shape[:-1], B.shape[0]), device=A.device, dtype=torch.int32)
|
| 17 |
+
return _int8_linear_matmul_impl(A, B, out)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@register_kernel("bitsandbytes::int8_linear_matmul.out", "cuda")
|
| 21 |
+
def _(A: torch.Tensor, B: torch.Tensor, out: torch.Tensor):
|
| 22 |
+
_int8_linear_matmul_impl(A, B, out)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _int8_linear_matmul_impl(A: torch.Tensor, B: torch.Tensor, out: torch.Tensor):
|
| 26 |
+
A, B = B, A
|
| 27 |
+
|
| 28 |
+
shapeA = A.shape
|
| 29 |
+
shapeB = B.shape
|
| 30 |
+
|
| 31 |
+
torch._check(A.dtype == torch.int8, lambda: "B must be int8")
|
| 32 |
+
torch._check(B.dtype == torch.int8, lambda: "A must be int8")
|
| 33 |
+
torch._check(A.ndim == 2, lambda: "Only two dimensional matrices are supported for argument B")
|
| 34 |
+
torch._check(B.ndim in [2, 3], lambda: "Only two or three dimensional matrices are supported for argument A")
|
| 35 |
+
torch._check(prod(shapeB) > 0, lambda: f"Input tensor dimensions need to be > 0: {shapeB}")
|
| 36 |
+
torch._check(out.dtype == torch.int32)
|
| 37 |
+
|
| 38 |
+
shapeC = (*shapeB[:-1], shapeA[0])
|
| 39 |
+
torch._check(out.shape == shapeC, lambda: f"Output shape {out.shape} does not match expected shape {shapeC}")
|
| 40 |
+
|
| 41 |
+
k, m = shapeA
|
| 42 |
+
n = prod(shapeB[:-1])
|
| 43 |
+
lda = shapeA[-1] # Weights (outputs, inputs)
|
| 44 |
+
ldb = shapeB[-1] # Activations (batch, tokens, inputs)
|
| 45 |
+
ldc = shapeC[-1] # Output (batch, tokens, outputs)
|
| 46 |
+
|
| 47 |
+
torch._check(
|
| 48 |
+
lda == ldb,
|
| 49 |
+
lambda: f"int8_linear_matmul only supports B^T @ A. Inner dimensions do not match: B @ A = {shapeB} @ {shapeA}",
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
# cuBLASLt does not support int8 matmul with inner dimensions that are not divisible by 4.
|
| 53 |
+
# We'll fall back to a slower fp32 calculation in this circumstance.
|
| 54 |
+
# Fortunately, this should not be very common.
|
| 55 |
+
if lda % 4 != 0:
|
| 56 |
+
result = torch.matmul(B.float(), A.float().t()).to(torch.int32)
|
| 57 |
+
return out.copy_(result)
|
| 58 |
+
|
| 59 |
+
with _cuda_device_of(A):
|
| 60 |
+
ctx = CUBLAS_Context.get_instance().get_context(A.device)
|
| 61 |
+
ptrA = get_ptr(A)
|
| 62 |
+
ptrB = get_ptr(B)
|
| 63 |
+
ptrC = get_ptr(out)
|
| 64 |
+
ptrRowScale = None
|
| 65 |
+
m = ct.c_int32(m)
|
| 66 |
+
n = ct.c_int32(n)
|
| 67 |
+
k = ct.c_int32(k)
|
| 68 |
+
lda = ct.c_int32(lda)
|
| 69 |
+
ldb = ct.c_int32(ldb)
|
| 70 |
+
ldc = ct.c_int32(ldc)
|
| 71 |
+
stream = _get_tensor_stream(A)
|
| 72 |
+
|
| 73 |
+
has_error = lib.cigemmlt_32(ctx, m, n, k, ptrA, ptrB, ptrC, ptrRowScale, lda, ldb, ldc, stream)
|
| 74 |
+
|
| 75 |
+
if has_error:
|
| 76 |
+
if has_error == 100:
|
| 77 |
+
# `ERR_NOT_IMPLEMENTED` is defined as 100 in `ops.cu`
|
| 78 |
+
# TODO: Warn and implement a fallback to fp32 compute?
|
| 79 |
+
raise NotImplementedError("int8_linear_matmul not implemented!")
|
| 80 |
+
else:
|
| 81 |
+
raise RuntimeError(
|
| 82 |
+
f"cublasLt ran into an error!\n\t{shapeA=}, {shapeB=}, {shapeC=}\n\t{(lda, ldb, ldc)=}\n\t{(m, n, k)=}"
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
return out
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
@register_kernel("bitsandbytes::int8_mm_dequant", "cuda")
|
| 89 |
+
def _(
|
| 90 |
+
A: torch.Tensor,
|
| 91 |
+
row_stats: torch.Tensor,
|
| 92 |
+
col_stats: torch.Tensor,
|
| 93 |
+
dtype: Optional[torch.dtype] = None,
|
| 94 |
+
bias: Optional[torch.Tensor] = None,
|
| 95 |
+
) -> torch.Tensor:
|
| 96 |
+
torch._check(A.dtype == torch.int32, lambda: f"A must be int32, got {A.dtype}")
|
| 97 |
+
torch._check(row_stats.dtype == torch.float32, lambda: f"row_stats must be float32, got {row_stats.dtype}")
|
| 98 |
+
torch._check(col_stats.dtype == torch.float32, lambda: f"col_stats must be float32, got {col_stats.dtype}")
|
| 99 |
+
|
| 100 |
+
# Note: cuda kernel only currently supports fp16 output.
|
| 101 |
+
# We'll later cast to desired dtype if needed.
|
| 102 |
+
out = torch.empty_like(A, dtype=torch.float16)
|
| 103 |
+
|
| 104 |
+
ptrA = get_ptr(A)
|
| 105 |
+
ptrOut = get_ptr(out)
|
| 106 |
+
ptrRowStats = get_ptr(row_stats)
|
| 107 |
+
ptrColStats = get_ptr(col_stats)
|
| 108 |
+
numRows = ct.c_int32(prod(A.shape[:-1]))
|
| 109 |
+
numCols = ct.c_int32(A.shape[-1])
|
| 110 |
+
|
| 111 |
+
# Note: fused bias in the kernel is only supported for fp16
|
| 112 |
+
# TODO(matthewdouglas): Consider supporting bf16 fused bias
|
| 113 |
+
ptrBias = get_ptr(bias) if bias is not None and bias.dtype == torch.float16 else None
|
| 114 |
+
|
| 115 |
+
with _cuda_device_of(A):
|
| 116 |
+
lib.cdequant_mm_int32_fp16(
|
| 117 |
+
ptrA, ptrRowStats, ptrColStats, ptrOut, ptrBias, numRows, numCols, _get_tensor_stream(A)
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
# Add bias separately if not fused in kernel
|
| 121 |
+
if bias is not None and bias.dtype != torch.float16:
|
| 122 |
+
out.add_(bias)
|
| 123 |
+
|
| 124 |
+
return out.to(dtype or torch.float16)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
@register_kernel("bitsandbytes::int8_vectorwise_quant", "cuda")
|
| 128 |
+
def _(A: torch.Tensor, threshold=0.0):
|
| 129 |
+
torch._check(A.dtype == torch.float16, lambda: f"A must be float16, got {A.dtype}")
|
| 130 |
+
torch._check(threshold >= 0.0, lambda: "threshold must be non-negative")
|
| 131 |
+
|
| 132 |
+
rows = prod(A.shape[:-1])
|
| 133 |
+
cols = A.shape[-1]
|
| 134 |
+
|
| 135 |
+
row_stats = torch.empty(rows, device=A.device, dtype=torch.float32)
|
| 136 |
+
out_row = torch.empty(A.shape, device=A.device, dtype=torch.int8)
|
| 137 |
+
|
| 138 |
+
outlier_cols = None
|
| 139 |
+
|
| 140 |
+
if threshold > 0.0:
|
| 141 |
+
# TODO we could improve perf of this
|
| 142 |
+
outliers = A.abs() >= threshold
|
| 143 |
+
|
| 144 |
+
if outliers.any():
|
| 145 |
+
outlier_cols = torch.argwhere(outliers.any(dim=0)).view(-1)
|
| 146 |
+
else:
|
| 147 |
+
# Needed for torch.compile support.
|
| 148 |
+
outlier_cols = torch.empty(0, device=A.device, dtype=torch.int64)
|
| 149 |
+
|
| 150 |
+
with _cuda_device_of(A):
|
| 151 |
+
lib.cint8_vector_quant(
|
| 152 |
+
get_ptr(A),
|
| 153 |
+
get_ptr(out_row),
|
| 154 |
+
get_ptr(row_stats),
|
| 155 |
+
ct.c_float(threshold),
|
| 156 |
+
ct.c_int32(rows),
|
| 157 |
+
ct.c_int32(cols),
|
| 158 |
+
_get_tensor_stream(A),
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
# Zero out values from outlier columns across all rows.
|
| 162 |
+
# The kernel will handle this for outliers themselves, so we can optimize for rows=1.
|
| 163 |
+
if rows > 1 and outlier_cols is not None:
|
| 164 |
+
out_row[:, outlier_cols] = 0
|
| 165 |
+
|
| 166 |
+
return out_row, row_stats, outlier_cols
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
@register_kernel("bitsandbytes::int8_double_quant", "cuda")
|
| 170 |
+
def _(
|
| 171 |
+
A: torch.Tensor,
|
| 172 |
+
threshold=0.0,
|
| 173 |
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, Optional[torch.Tensor]]:
|
| 174 |
+
# Use CUDA kernel for rowwise and COO tensor
|
| 175 |
+
quant_row, row_stats, outlier_cols = torch.ops.bitsandbytes.int8_vectorwise_quant.default(
|
| 176 |
+
A,
|
| 177 |
+
threshold=threshold,
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
# PyTorch impl for colwise
|
| 181 |
+
col_stats, outlier_mask = _get_col_absmax(A, threshold=threshold)
|
| 182 |
+
if threshold > 0.0 and outlier_mask is not None:
|
| 183 |
+
A = A.masked_fill(outlier_mask, 0.0)
|
| 184 |
+
quant_col = torch.round(A.mul(127.0) / col_stats.unsqueeze(0)).to(torch.int8)
|
| 185 |
+
|
| 186 |
+
return quant_row, quant_col, row_stats, col_stats.flatten().float(), outlier_cols
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def _get_col_absmax(
|
| 190 |
+
A: torch.Tensor,
|
| 191 |
+
threshold=0.0,
|
| 192 |
+
) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
|
| 193 |
+
torch._check(A.is_floating_point())
|
| 194 |
+
|
| 195 |
+
outlier_mask = None
|
| 196 |
+
|
| 197 |
+
absA = A.abs().view(-1, A.shape[-1])
|
| 198 |
+
|
| 199 |
+
if threshold > 0.0:
|
| 200 |
+
# Filter outliers from stats when enabled
|
| 201 |
+
outlier_mask = absA >= threshold
|
| 202 |
+
absA.masked_fill_(outlier_mask, 0.0)
|
| 203 |
+
|
| 204 |
+
# shape [cols]; unsqueeze(0) gives [1,cols]
|
| 205 |
+
col_stats = absA.amax(dim=0, keepdim=False).float()
|
| 206 |
+
|
| 207 |
+
return col_stats, outlier_mask
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
@register_kernel("bitsandbytes::quantize_blockwise", "cuda")
|
| 211 |
+
def _(A: torch.Tensor, code: torch.Tensor, blocksize: int) -> tuple[torch.Tensor, torch.Tensor]:
|
| 212 |
+
A = A.contiguous()
|
| 213 |
+
torch._check_is_size(blocksize)
|
| 214 |
+
|
| 215 |
+
if ROCM_WARP_SIZE_64:
|
| 216 |
+
torch._check(blocksize in [4096, 2048, 1024, 512, 256, 128, 64])
|
| 217 |
+
else:
|
| 218 |
+
torch._check(blocksize in [4096, 2048, 1024, 512, 256, 128, 64, 32])
|
| 219 |
+
|
| 220 |
+
torch._check(code.dtype == torch.float32, lambda: f"code must be float32, got {code.dtype}")
|
| 221 |
+
|
| 222 |
+
n = A.numel()
|
| 223 |
+
blocks = -(n // -blocksize)
|
| 224 |
+
absmax = torch.empty((blocks,), device=A.device, dtype=torch.float32)
|
| 225 |
+
out = torch.empty_like(A, dtype=torch.uint8)
|
| 226 |
+
|
| 227 |
+
with _cuda_device_of(A):
|
| 228 |
+
args = (
|
| 229 |
+
get_ptr(code),
|
| 230 |
+
get_ptr(A),
|
| 231 |
+
get_ptr(absmax),
|
| 232 |
+
get_ptr(out),
|
| 233 |
+
ct.c_int32(blocksize),
|
| 234 |
+
ct.c_int(A.numel()),
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
if A.dtype == torch.float16:
|
| 238 |
+
lib.cquantize_blockwise_fp16(*args)
|
| 239 |
+
elif A.dtype == torch.bfloat16:
|
| 240 |
+
lib.cquantize_blockwise_bf16(*args)
|
| 241 |
+
elif A.dtype == torch.float32:
|
| 242 |
+
lib.cquantize_blockwise_fp32(*args)
|
| 243 |
+
else:
|
| 244 |
+
raise ValueError(f"Blockwise quantization only supports 16/32-bit floats, but got {A.dtype}")
|
| 245 |
+
|
| 246 |
+
return out, absmax
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
@register_kernel("bitsandbytes::dequantize_blockwise", "cuda")
|
| 250 |
+
def _(A: torch.Tensor, absmax: torch.Tensor, code: torch.Tensor, blocksize: int, dtype: torch.dtype) -> torch.Tensor:
|
| 251 |
+
out = torch.empty_like(A, dtype=dtype)
|
| 252 |
+
_dequantize_blockwise_impl(A, absmax, code, blocksize, dtype, out=out)
|
| 253 |
+
return out
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
@register_kernel("bitsandbytes::dequantize_blockwise.out", "cuda")
|
| 257 |
+
def _(
|
| 258 |
+
A: torch.Tensor,
|
| 259 |
+
absmax: torch.Tensor,
|
| 260 |
+
code: torch.Tensor,
|
| 261 |
+
blocksize: int,
|
| 262 |
+
dtype: torch.dtype,
|
| 263 |
+
out: torch.Tensor,
|
| 264 |
+
) -> None:
|
| 265 |
+
torch._check(out.dtype == dtype, lambda: f"Expected out.dtype == {dtype}, got {out.dtype}")
|
| 266 |
+
torch._check(out.shape == A.shape, lambda: f"Expected out.shape == {A.shape}, got {out.shape}")
|
| 267 |
+
_dequantize_blockwise_impl(A, absmax, code, blocksize, dtype, out=out)
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
def _dequantize_blockwise_impl(
|
| 271 |
+
A: torch.Tensor, absmax: torch.Tensor, code: torch.Tensor, blocksize: int, dtype: torch.dtype, out: torch.Tensor
|
| 272 |
+
) -> None:
|
| 273 |
+
A = A.contiguous()
|
| 274 |
+
if ROCM_WARP_SIZE_64:
|
| 275 |
+
torch._check(blocksize in [4096, 2048, 1024, 512, 256, 128, 64])
|
| 276 |
+
else:
|
| 277 |
+
torch._check(blocksize in [4096, 2048, 1024, 512, 256, 128, 64, 32])
|
| 278 |
+
|
| 279 |
+
torch._check(A.dtype == torch.uint8, lambda: f"A must be uint8, got {A.dtype}")
|
| 280 |
+
torch._check(
|
| 281 |
+
dtype in [torch.float16, torch.bfloat16, torch.float32],
|
| 282 |
+
lambda: f"Blockwise dequantization only supports 16bit/32bit floating types, got {dtype}",
|
| 283 |
+
)
|
| 284 |
+
|
| 285 |
+
with _cuda_device_of(A):
|
| 286 |
+
args = (
|
| 287 |
+
get_ptr(code),
|
| 288 |
+
get_ptr(A),
|
| 289 |
+
get_ptr(absmax),
|
| 290 |
+
get_ptr(out),
|
| 291 |
+
ct.c_int(blocksize),
|
| 292 |
+
ct.c_int(A.numel()),
|
| 293 |
+
_get_tensor_stream(A),
|
| 294 |
+
)
|
| 295 |
+
|
| 296 |
+
if dtype == torch.float16:
|
| 297 |
+
lib.cdequantize_blockwise_fp16(*args)
|
| 298 |
+
elif dtype == torch.bfloat16:
|
| 299 |
+
lib.cdequantize_blockwise_bf16(*args)
|
| 300 |
+
elif dtype == torch.float32:
|
| 301 |
+
lib.cdequantize_blockwise_fp32(*args)
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
@register_kernel("bitsandbytes::quantize_4bit", "cuda")
|
| 305 |
+
def _(
|
| 306 |
+
A: torch.Tensor, blocksize: int, quant_type: str, quant_storage: torch.dtype
|
| 307 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 308 |
+
A = A.contiguous()
|
| 309 |
+
if ROCM_WARP_SIZE_64:
|
| 310 |
+
torch._check(blocksize in [4096, 2048, 1024, 512, 256, 128, 64])
|
| 311 |
+
else:
|
| 312 |
+
torch._check(blocksize in [4096, 2048, 1024, 512, 256, 128, 64, 32])
|
| 313 |
+
|
| 314 |
+
torch._check(quant_type in ["fp4", "nf4"])
|
| 315 |
+
torch._check(
|
| 316 |
+
A.dtype in [torch.bfloat16, torch.float16, torch.float32],
|
| 317 |
+
lambda: f"Blockwise 4bit quantization only supports 16/32-bit floats, but got {A.dtype}",
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
n = A.numel()
|
| 321 |
+
blocks = -(n // -blocksize)
|
| 322 |
+
absmax = torch.empty((blocks,), device=A.device, dtype=torch.float32)
|
| 323 |
+
out = torch.empty(((n + 1) // (quant_storage.itemsize * 2), 1), device=A.device, dtype=quant_storage)
|
| 324 |
+
|
| 325 |
+
with _cuda_device_of(A):
|
| 326 |
+
args = (
|
| 327 |
+
None,
|
| 328 |
+
get_ptr(A),
|
| 329 |
+
get_ptr(absmax),
|
| 330 |
+
get_ptr(out),
|
| 331 |
+
ct.c_int32(blocksize),
|
| 332 |
+
ct.c_int32(n),
|
| 333 |
+
)
|
| 334 |
+
|
| 335 |
+
if A.dtype == torch.bfloat16:
|
| 336 |
+
if quant_type == "fp4":
|
| 337 |
+
lib.cquantize_blockwise_bf16_fp4(*args)
|
| 338 |
+
else:
|
| 339 |
+
lib.cquantize_blockwise_bf16_nf4(*args)
|
| 340 |
+
elif A.dtype == torch.float16:
|
| 341 |
+
if quant_type == "fp4":
|
| 342 |
+
lib.cquantize_blockwise_fp16_fp4(*args)
|
| 343 |
+
else:
|
| 344 |
+
lib.cquantize_blockwise_fp16_nf4(*args)
|
| 345 |
+
elif A.dtype == torch.float32:
|
| 346 |
+
if quant_type == "fp4":
|
| 347 |
+
lib.cquantize_blockwise_fp32_fp4(*args)
|
| 348 |
+
else:
|
| 349 |
+
lib.cquantize_blockwise_fp32_nf4(*args)
|
| 350 |
+
|
| 351 |
+
return out, absmax
|
| 352 |
+
|
| 353 |
+
|
| 354 |
+
@register_kernel("bitsandbytes::dequantize_4bit", "cuda")
|
| 355 |
+
def _(
|
| 356 |
+
A: torch.Tensor,
|
| 357 |
+
absmax: torch.Tensor,
|
| 358 |
+
blocksize: int,
|
| 359 |
+
quant_type: str,
|
| 360 |
+
shape: Sequence[int],
|
| 361 |
+
dtype: torch.dtype,
|
| 362 |
+
) -> torch.Tensor:
|
| 363 |
+
out = torch.empty(shape, dtype=dtype, device=A.device)
|
| 364 |
+
_dequantize_4bit_impl(A, absmax, blocksize, quant_type, dtype, out=out)
|
| 365 |
+
return out
|
| 366 |
+
|
| 367 |
+
|
| 368 |
+
@register_kernel("bitsandbytes::dequantize_4bit.out", "cuda")
|
| 369 |
+
def _(
|
| 370 |
+
A: torch.Tensor,
|
| 371 |
+
absmax: torch.Tensor,
|
| 372 |
+
blocksize: int,
|
| 373 |
+
quant_type: str,
|
| 374 |
+
shape: Sequence[int],
|
| 375 |
+
dtype: torch.dtype,
|
| 376 |
+
out: torch.Tensor,
|
| 377 |
+
) -> None:
|
| 378 |
+
torch._check(out.shape == shape, lambda: f"Expected out.shape == {shape}, got {out.shape}")
|
| 379 |
+
torch._check(out.dtype == dtype, lambda: f"Expected out.dtype == {dtype}, got {out.dtype}")
|
| 380 |
+
_dequantize_4bit_impl(A, absmax, blocksize, quant_type, dtype, out=out)
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
def _dequantize_4bit_impl(
|
| 384 |
+
A: torch.Tensor,
|
| 385 |
+
absmax: torch.Tensor,
|
| 386 |
+
blocksize: int,
|
| 387 |
+
quant_type: str,
|
| 388 |
+
dtype: torch.dtype,
|
| 389 |
+
out: torch.Tensor,
|
| 390 |
+
) -> None:
|
| 391 |
+
A = A.contiguous()
|
| 392 |
+
if ROCM_WARP_SIZE_64:
|
| 393 |
+
torch._check(blocksize in [4096, 2048, 1024, 512, 256, 128, 64])
|
| 394 |
+
else:
|
| 395 |
+
torch._check(blocksize in [4096, 2048, 1024, 512, 256, 128, 64, 32])
|
| 396 |
+
|
| 397 |
+
torch._check(quant_type in ["fp4", "nf4"])
|
| 398 |
+
torch._check(
|
| 399 |
+
dtype in [torch.bfloat16, torch.float16, torch.float32],
|
| 400 |
+
lambda: f"Blockwise 4bit dequantization only supports 16/32-bit floats, but got {dtype}",
|
| 401 |
+
)
|
| 402 |
+
|
| 403 |
+
with _cuda_device_of(A):
|
| 404 |
+
args = (
|
| 405 |
+
None,
|
| 406 |
+
get_ptr(A),
|
| 407 |
+
get_ptr(absmax),
|
| 408 |
+
get_ptr(out),
|
| 409 |
+
ct.c_int(blocksize),
|
| 410 |
+
ct.c_int32(out.numel()),
|
| 411 |
+
_get_tensor_stream(A),
|
| 412 |
+
)
|
| 413 |
+
|
| 414 |
+
if out.dtype == torch.bfloat16:
|
| 415 |
+
if quant_type == "fp4":
|
| 416 |
+
lib.cdequantize_blockwise_bf16_fp4(*args)
|
| 417 |
+
else:
|
| 418 |
+
lib.cdequantize_blockwise_bf16_nf4(*args)
|
| 419 |
+
elif out.dtype == torch.float16:
|
| 420 |
+
if quant_type == "fp4":
|
| 421 |
+
lib.cdequantize_blockwise_fp16_fp4(*args)
|
| 422 |
+
else:
|
| 423 |
+
lib.cdequantize_blockwise_fp16_nf4(*args)
|
| 424 |
+
elif out.dtype == torch.float32:
|
| 425 |
+
if quant_type == "fp4":
|
| 426 |
+
lib.cdequantize_blockwise_fp32_fp4(*args)
|
| 427 |
+
else:
|
| 428 |
+
lib.cdequantize_blockwise_fp32_nf4(*args)
|
| 429 |
+
|
| 430 |
+
|
| 431 |
+
@register_kernel("bitsandbytes::gemv_4bit", "cuda")
|
| 432 |
+
def _(
|
| 433 |
+
A: torch.Tensor, B: torch.Tensor, shapeB: Sequence[int], absmax: torch.Tensor, code: torch.Tensor, blocksize: int
|
| 434 |
+
) -> torch.Tensor:
|
| 435 |
+
shape = (*A.shape[:-1], shapeB[0])
|
| 436 |
+
out = torch.empty(shape, device=A.device, dtype=A.dtype)
|
| 437 |
+
_gemv_4bit_impl(A, B, shapeB, absmax, code, blocksize, out=out)
|
| 438 |
+
return out
|
| 439 |
+
|
| 440 |
+
|
| 441 |
+
@register_kernel("bitsandbytes::gemv_4bit.out", "cuda")
|
| 442 |
+
def _(
|
| 443 |
+
A: torch.Tensor,
|
| 444 |
+
B: torch.Tensor,
|
| 445 |
+
shapeB: Sequence[int],
|
| 446 |
+
absmax: torch.Tensor,
|
| 447 |
+
code: torch.Tensor,
|
| 448 |
+
blocksize: int,
|
| 449 |
+
out: torch.Tensor,
|
| 450 |
+
) -> None:
|
| 451 |
+
torch._check(
|
| 452 |
+
out.shape == (*A.shape[:-1], shapeB[0]),
|
| 453 |
+
lambda: f"Expected out.shape == {(*A.shape[:-1], shapeB[0])}, got {out.shape}",
|
| 454 |
+
)
|
| 455 |
+
torch._check(out.dtype == A.dtype, lambda: f"Expected out.dtype == {A.dtype}, got {out.dtype}")
|
| 456 |
+
_gemv_4bit_impl(A, B, shapeB, absmax, code, blocksize, out=out)
|
| 457 |
+
|
| 458 |
+
|
| 459 |
+
def _gemv_4bit_impl(
|
| 460 |
+
A: torch.Tensor,
|
| 461 |
+
B: torch.Tensor,
|
| 462 |
+
shapeB: Sequence[int],
|
| 463 |
+
absmax: torch.Tensor,
|
| 464 |
+
code: torch.Tensor,
|
| 465 |
+
blocksize: int,
|
| 466 |
+
out: torch.Tensor,
|
| 467 |
+
) -> None:
|
| 468 |
+
torch._check_is_size(blocksize)
|
| 469 |
+
|
| 470 |
+
# Note: these checks are not strictly necessary, and cost more than they are worth, so they are commented out for now.
|
| 471 |
+
# torch._check(
|
| 472 |
+
# A.numel() == A.size(-1),
|
| 473 |
+
# lambda: f"A must be a vector with leading dimensions of 1, got {A.shape}",
|
| 474 |
+
# )
|
| 475 |
+
# torch._check(
|
| 476 |
+
# A.dtype in [torch.float16, torch.bfloat16, torch.float32],
|
| 477 |
+
# lambda: f"A must be float16, bfloat16, or float32, got {A.dtype}",
|
| 478 |
+
# )
|
| 479 |
+
# torch._check(
|
| 480 |
+
# B.dtype in [torch.uint8, torch.bfloat16, torch.float16, torch.float32],
|
| 481 |
+
# lambda: f"B must be backed by storage of type uint8, bfloat16, float16, or float32, got {B.dtype}",
|
| 482 |
+
# )
|
| 483 |
+
# torch._check(absmax.dtype == torch.float32, lambda: f"absmax must be float32, got {absmax.dtype}")
|
| 484 |
+
# torch._check(code.dtype == torch.float32, lambda: f"code must be float32, got {code.dtype}")
|
| 485 |
+
|
| 486 |
+
m = ct.c_int32(shapeB[0])
|
| 487 |
+
n = ct.c_int32(1)
|
| 488 |
+
k = ct.c_int32(shapeB[1])
|
| 489 |
+
|
| 490 |
+
lda = m
|
| 491 |
+
ldb = ct.c_int32((A.shape[-1] + 1) // 2)
|
| 492 |
+
ldc = m
|
| 493 |
+
|
| 494 |
+
stream = _get_tensor_stream(A)
|
| 495 |
+
|
| 496 |
+
with _cuda_device_of(A):
|
| 497 |
+
if A.dtype == torch.float16:
|
| 498 |
+
lib.cgemm_4bit_inference_naive_fp16(
|
| 499 |
+
m,
|
| 500 |
+
n,
|
| 501 |
+
k,
|
| 502 |
+
get_ptr(A),
|
| 503 |
+
get_ptr(B),
|
| 504 |
+
get_ptr(absmax),
|
| 505 |
+
get_ptr(code),
|
| 506 |
+
get_ptr(out),
|
| 507 |
+
lda,
|
| 508 |
+
ldb,
|
| 509 |
+
ldc,
|
| 510 |
+
ct.c_int32(blocksize),
|
| 511 |
+
stream,
|
| 512 |
+
)
|
| 513 |
+
elif A.dtype == torch.bfloat16:
|
| 514 |
+
lib.cgemm_4bit_inference_naive_bf16(
|
| 515 |
+
m,
|
| 516 |
+
n,
|
| 517 |
+
k,
|
| 518 |
+
get_ptr(A),
|
| 519 |
+
get_ptr(B),
|
| 520 |
+
get_ptr(absmax),
|
| 521 |
+
get_ptr(code),
|
| 522 |
+
get_ptr(out),
|
| 523 |
+
lda,
|
| 524 |
+
ldb,
|
| 525 |
+
ldc,
|
| 526 |
+
ct.c_int32(blocksize),
|
| 527 |
+
stream,
|
| 528 |
+
)
|
| 529 |
+
elif A.dtype == torch.float32:
|
| 530 |
+
lib.cgemm_4bit_inference_naive_fp32(
|
| 531 |
+
m,
|
| 532 |
+
n,
|
| 533 |
+
k,
|
| 534 |
+
get_ptr(A),
|
| 535 |
+
get_ptr(B),
|
| 536 |
+
get_ptr(absmax),
|
| 537 |
+
get_ptr(code),
|
| 538 |
+
get_ptr(out),
|
| 539 |
+
lda,
|
| 540 |
+
ldb,
|
| 541 |
+
ldc,
|
| 542 |
+
ct.c_int32(blocksize),
|
| 543 |
+
stream,
|
| 544 |
+
)
|
| 545 |
+
|
| 546 |
+
|
| 547 |
+
"""C FUNCTIONS FOR OPTIMIZERS"""
|
| 548 |
+
str2optimizer32bit = {
|
| 549 |
+
"adam": (
|
| 550 |
+
lib.cadam32bit_grad_fp32,
|
| 551 |
+
lib.cadam32bit_grad_fp16,
|
| 552 |
+
lib.cadam32bit_grad_bf16,
|
| 553 |
+
),
|
| 554 |
+
"momentum": (
|
| 555 |
+
lib.cmomentum32bit_grad_32,
|
| 556 |
+
lib.cmomentum32bit_grad_16,
|
| 557 |
+
),
|
| 558 |
+
"rmsprop": (
|
| 559 |
+
lib.crmsprop32bit_grad_32,
|
| 560 |
+
lib.crmsprop32bit_grad_16,
|
| 561 |
+
),
|
| 562 |
+
"lion": (
|
| 563 |
+
lib.clion32bit_grad_fp32,
|
| 564 |
+
lib.clion32bit_grad_fp16,
|
| 565 |
+
lib.clion32bit_grad_bf16,
|
| 566 |
+
),
|
| 567 |
+
"adagrad": (
|
| 568 |
+
lib.cadagrad32bit_grad_32,
|
| 569 |
+
lib.cadagrad32bit_grad_16,
|
| 570 |
+
),
|
| 571 |
+
"lamb": (
|
| 572 |
+
lib.cadam32bit_grad_fp32,
|
| 573 |
+
lib.cadam32bit_grad_fp16,
|
| 574 |
+
lib.cadam32bit_grad_bf16,
|
| 575 |
+
),
|
| 576 |
+
"ademamix": (
|
| 577 |
+
lib.cademamix32bit_grad_fp32,
|
| 578 |
+
lib.cademamix32bit_grad_fp16,
|
| 579 |
+
lib.cademamix32bit_grad_bf16,
|
| 580 |
+
),
|
| 581 |
+
}
|
| 582 |
+
|
| 583 |
+
str2optimizer8bit_blockwise = {
|
| 584 |
+
"adam": (
|
| 585 |
+
lib.cadam_8bit_blockwise_grad_fp32,
|
| 586 |
+
lib.cadam_8bit_blockwise_grad_fp16,
|
| 587 |
+
lib.cadam_8bit_blockwise_grad_bf16,
|
| 588 |
+
),
|
| 589 |
+
"momentum": (
|
| 590 |
+
lib.cmomentum_8bit_blockwise_grad_fp32,
|
| 591 |
+
lib.cmomentum_8bit_blockwise_grad_fp16,
|
| 592 |
+
lib.cmomentum_8bit_blockwise_grad_bf16,
|
| 593 |
+
),
|
| 594 |
+
"rmsprop": (
|
| 595 |
+
lib.crmsprop_8bit_blockwise_grad_fp32,
|
| 596 |
+
lib.crmsprop_8bit_blockwise_grad_fp16,
|
| 597 |
+
lib.crmsprop_8bit_blockwise_grad_bf16,
|
| 598 |
+
),
|
| 599 |
+
"lion": (
|
| 600 |
+
lib.clion_8bit_blockwise_grad_fp32,
|
| 601 |
+
lib.clion_8bit_blockwise_grad_fp16,
|
| 602 |
+
lib.clion_8bit_blockwise_grad_bf16,
|
| 603 |
+
),
|
| 604 |
+
"adagrad": (
|
| 605 |
+
lib.cadagrad_8bit_blockwise_grad_fp32,
|
| 606 |
+
lib.cadagrad_8bit_blockwise_grad_fp16,
|
| 607 |
+
lib.cadagrad_8bit_blockwise_grad_bf16,
|
| 608 |
+
),
|
| 609 |
+
"ademamix": (
|
| 610 |
+
lib.cademamix_8bit_blockwise_grad_fp32,
|
| 611 |
+
lib.cademamix_8bit_blockwise_grad_fp16,
|
| 612 |
+
lib.cademamix_8bit_blockwise_grad_bf16,
|
| 613 |
+
),
|
| 614 |
+
}
|
| 615 |
+
|
| 616 |
+
|
| 617 |
+
def _optimizer_update_32bit_impl(
|
| 618 |
+
optimizer_name: str,
|
| 619 |
+
g: torch.Tensor,
|
| 620 |
+
p: torch.Tensor,
|
| 621 |
+
state1: torch.Tensor,
|
| 622 |
+
state2: Optional[torch.Tensor],
|
| 623 |
+
unorm_vec: Optional[torch.Tensor],
|
| 624 |
+
max_unorm: float,
|
| 625 |
+
param_norm: float,
|
| 626 |
+
beta1: float,
|
| 627 |
+
beta2: float,
|
| 628 |
+
beta3: float,
|
| 629 |
+
alpha: float,
|
| 630 |
+
eps: float,
|
| 631 |
+
weight_decay: float,
|
| 632 |
+
step: int,
|
| 633 |
+
lr: float,
|
| 634 |
+
gnorm_scale: float,
|
| 635 |
+
skip_zeros=False,
|
| 636 |
+
) -> None:
|
| 637 |
+
optim_fns = str2optimizer32bit.get(optimizer_name, None)
|
| 638 |
+
if optim_fns is None:
|
| 639 |
+
raise ValueError(
|
| 640 |
+
f"Unsupported optimizer name: {optimizer_name}. Supported optimizers: {list(str2optimizer8bit_blockwise.keys())}"
|
| 641 |
+
)
|
| 642 |
+
if g.dtype == torch.float32:
|
| 643 |
+
optim_func = optim_fns[0]
|
| 644 |
+
elif g.dtype == torch.float16:
|
| 645 |
+
optim_func = optim_fns[1]
|
| 646 |
+
elif g.dtype == torch.bfloat16 and len(optim_fns) == 3:
|
| 647 |
+
optim_func = optim_fns[2]
|
| 648 |
+
else:
|
| 649 |
+
raise ValueError(
|
| 650 |
+
f"Gradient+optimizer bit data type combination not supported: grad {g.dtype}, optimizer {state1.dtype}",
|
| 651 |
+
)
|
| 652 |
+
|
| 653 |
+
with _cuda_device_of(g):
|
| 654 |
+
optim_func(
|
| 655 |
+
get_ptr(g),
|
| 656 |
+
get_ptr(p),
|
| 657 |
+
get_ptr(state1),
|
| 658 |
+
get_ptr(state2),
|
| 659 |
+
get_ptr(unorm_vec),
|
| 660 |
+
ct.c_float(max_unorm),
|
| 661 |
+
ct.c_float(param_norm),
|
| 662 |
+
ct.c_float(beta1),
|
| 663 |
+
ct.c_float(beta2),
|
| 664 |
+
ct.c_float(beta3),
|
| 665 |
+
ct.c_float(alpha),
|
| 666 |
+
ct.c_float(eps),
|
| 667 |
+
ct.c_float(weight_decay),
|
| 668 |
+
ct.c_int32(step),
|
| 669 |
+
ct.c_float(lr),
|
| 670 |
+
ct.c_float(gnorm_scale),
|
| 671 |
+
ct.c_bool(skip_zeros),
|
| 672 |
+
ct.c_int32(g.numel()),
|
| 673 |
+
)
|
| 674 |
+
|
| 675 |
+
|
| 676 |
+
def _optimizer_update_8bit_blockwise_impl(
|
| 677 |
+
optimizer_name: str,
|
| 678 |
+
g: torch.Tensor,
|
| 679 |
+
p: torch.Tensor,
|
| 680 |
+
state1: torch.Tensor,
|
| 681 |
+
state2: Optional[torch.Tensor],
|
| 682 |
+
beta1: float,
|
| 683 |
+
beta2: float,
|
| 684 |
+
beta3: float,
|
| 685 |
+
alpha: float,
|
| 686 |
+
eps: float,
|
| 687 |
+
step: int,
|
| 688 |
+
lr: float,
|
| 689 |
+
qmap1: torch.Tensor,
|
| 690 |
+
qmap2: Optional[torch.Tensor],
|
| 691 |
+
absmax1: torch.Tensor,
|
| 692 |
+
absmax2: Optional[torch.Tensor],
|
| 693 |
+
weight_decay: float,
|
| 694 |
+
gnorm_scale: float,
|
| 695 |
+
skip_zeros=False,
|
| 696 |
+
) -> None:
|
| 697 |
+
# torch._check(
|
| 698 |
+
# g.numel() == p.numel(),
|
| 699 |
+
# lambda: f"g and p must have the same number of elements, got {g.numel()} and {p.numel()}",
|
| 700 |
+
# )
|
| 701 |
+
# compute_dtypes = [torch.float16, torch.bfloat16, torch.float32]
|
| 702 |
+
|
| 703 |
+
# torch._check(
|
| 704 |
+
# g.dtype in compute_dtypes,
|
| 705 |
+
# lambda: f"g must be bfloat16, float16, or float32, got {g.dtype}",
|
| 706 |
+
# )
|
| 707 |
+
# torch._check(
|
| 708 |
+
# g.dtype == p.dtype,
|
| 709 |
+
# lambda: f"Expected all tensors to have the same dtype, got g.dtype={g.dtype}, p.dtype={p.dtype}",
|
| 710 |
+
# )
|
| 711 |
+
# torch._check(
|
| 712 |
+
# state1.dtype == torch.uint8,
|
| 713 |
+
# lambda: f"state1 must be uint8, got {state1.dtype}",
|
| 714 |
+
# )
|
| 715 |
+
# torch._check(
|
| 716 |
+
# qmap1.dtype == absmax1.dtype == torch.float32,
|
| 717 |
+
# lambda: f"Expected qmap1 and absmax1 to be float32, got qmap1.dtype={qmap1.dtype}, absmax1.dtype={absmax1.dtype}",
|
| 718 |
+
# )
|
| 719 |
+
# if state2 is not None:
|
| 720 |
+
# torch._check(
|
| 721 |
+
# state2.dtype == torch.uint8,
|
| 722 |
+
# lambda: f"state2 must be uint8, got {state2.dtype}",
|
| 723 |
+
# )
|
| 724 |
+
# torch._check(
|
| 725 |
+
# qmap2.dtype == absmax2.dtype == torch.float32,
|
| 726 |
+
# lambda: f"Expected qmap2 and absmax2 to be float32, got qmap2.dtype={qmap2.dtype}, absmax2.dtype={absmax2.dtype}",
|
| 727 |
+
# )
|
| 728 |
+
optimizer_fns = str2optimizer8bit_blockwise.get(optimizer_name)
|
| 729 |
+
if optimizer_fns is None:
|
| 730 |
+
raise ValueError(
|
| 731 |
+
f"Unsupported optimizer name: {optimizer_name}. Supported optimizers: {list(str2optimizer8bit_blockwise.keys())}"
|
| 732 |
+
)
|
| 733 |
+
|
| 734 |
+
if g.dtype == torch.float32:
|
| 735 |
+
optimizer_fn = optimizer_fns[0]
|
| 736 |
+
elif g.dtype == torch.float16:
|
| 737 |
+
optimizer_fn = optimizer_fns[1]
|
| 738 |
+
elif g.dtype == torch.bfloat16:
|
| 739 |
+
optimizer_fn = optimizer_fns[2]
|
| 740 |
+
else:
|
| 741 |
+
raise ValueError(
|
| 742 |
+
f"Unsupported gradient dtype: {g.dtype}. Supported dtypes: torch.float32, torch.float16, torch.bfloat16"
|
| 743 |
+
)
|
| 744 |
+
|
| 745 |
+
with _cuda_device_of(g):
|
| 746 |
+
optimizer_fn(
|
| 747 |
+
get_ptr(p),
|
| 748 |
+
get_ptr(g),
|
| 749 |
+
get_ptr(state1),
|
| 750 |
+
get_ptr(state2),
|
| 751 |
+
ct.c_float(beta1),
|
| 752 |
+
ct.c_float(beta2),
|
| 753 |
+
ct.c_float(beta3),
|
| 754 |
+
ct.c_float(alpha),
|
| 755 |
+
ct.c_float(eps),
|
| 756 |
+
ct.c_int32(step),
|
| 757 |
+
ct.c_float(lr),
|
| 758 |
+
get_ptr(qmap1),
|
| 759 |
+
get_ptr(qmap2),
|
| 760 |
+
get_ptr(absmax1),
|
| 761 |
+
get_ptr(absmax2),
|
| 762 |
+
ct.c_float(weight_decay),
|
| 763 |
+
ct.c_float(gnorm_scale),
|
| 764 |
+
ct.c_bool(skip_zeros),
|
| 765 |
+
ct.c_int32(g.numel()),
|
| 766 |
+
)
|
| 767 |
+
|
| 768 |
+
|
| 769 |
+
register_kernel("bitsandbytes::optimizer_update_8bit_blockwise", "cuda")(_optimizer_update_8bit_blockwise_impl)
|
| 770 |
+
register_kernel("bitsandbytes::optimizer_update_32bit", "cuda")(_optimizer_update_32bit_impl)
|
bitsandbytes/backends/default/__init__.py
ADDED
|
File without changes
|
bitsandbytes/backends/default/ops.py
ADDED
|
@@ -0,0 +1,616 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import Sequence
|
| 2 |
+
from functools import wraps
|
| 3 |
+
from math import prod, sqrt
|
| 4 |
+
from typing import Optional
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
from ..._ops import register_kernel
|
| 9 |
+
from ..utils import CODE
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def _try_torch_compile(func=None, **compile_kwargs):
|
| 13 |
+
"""
|
| 14 |
+
Wrapper around torch.compile that falls back to the original function if compilation fails.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
def decorator(fn):
|
| 18 |
+
try:
|
| 19 |
+
compiled_fn = torch.compile(fn, **compile_kwargs)
|
| 20 |
+
|
| 21 |
+
@wraps(fn)
|
| 22 |
+
def wrapper(*args, **kwargs):
|
| 23 |
+
try:
|
| 24 |
+
return compiled_fn(*args, **kwargs)
|
| 25 |
+
except Exception:
|
| 26 |
+
return fn(*args, **kwargs)
|
| 27 |
+
|
| 28 |
+
return wrapper
|
| 29 |
+
except Exception:
|
| 30 |
+
return fn
|
| 31 |
+
|
| 32 |
+
if func is None:
|
| 33 |
+
return decorator
|
| 34 |
+
else:
|
| 35 |
+
return decorator(func)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@register_kernel("bitsandbytes::int8_mm_dequant", "default")
|
| 39 |
+
def _(
|
| 40 |
+
A: torch.Tensor,
|
| 41 |
+
row_stats: torch.Tensor,
|
| 42 |
+
col_stats: torch.Tensor,
|
| 43 |
+
dtype: Optional[torch.dtype] = None,
|
| 44 |
+
bias: Optional[torch.Tensor] = None,
|
| 45 |
+
) -> torch.Tensor:
|
| 46 |
+
torch._check(A.dtype == torch.int32, lambda: f"A must be int32, got {A.dtype}")
|
| 47 |
+
torch._check(row_stats.dtype == torch.float32, lambda: f"row_stats must be float32, got {row_stats.dtype}")
|
| 48 |
+
torch._check(col_stats.dtype == torch.float32, lambda: f"col_stats must be float32, got {col_stats.dtype}")
|
| 49 |
+
|
| 50 |
+
A_calc = A.view(-1, A.shape[-1])
|
| 51 |
+
row_stats = row_stats.reshape(-1).unsqueeze(-1)
|
| 52 |
+
col_stats = col_stats.reshape(-1).unsqueeze(0)
|
| 53 |
+
|
| 54 |
+
out = A_calc * (row_stats * col_stats) * 6.200124e-05
|
| 55 |
+
if bias is not None:
|
| 56 |
+
out += bias
|
| 57 |
+
|
| 58 |
+
return out.to(dtype or torch.float16)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
@register_kernel("bitsandbytes::int8_mixed_scaled_mm", "default")
|
| 62 |
+
def _(
|
| 63 |
+
A: torch.Tensor,
|
| 64 |
+
CA: torch.Tensor,
|
| 65 |
+
CB: torch.Tensor,
|
| 66 |
+
SCA: torch.Tensor,
|
| 67 |
+
SCB: torch.Tensor,
|
| 68 |
+
outlier_cols: Optional[torch.Tensor] = None,
|
| 69 |
+
bias: Optional[torch.Tensor] = None,
|
| 70 |
+
) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
|
| 71 |
+
subB = None
|
| 72 |
+
|
| 73 |
+
if outlier_cols is not None and outlier_cols.numel():
|
| 74 |
+
# Extract the inputs with outliers in original precision
|
| 75 |
+
subA = A[:, outlier_cols].contiguous()
|
| 76 |
+
|
| 77 |
+
# Dequantize the corresponding weight columns
|
| 78 |
+
subB = (
|
| 79 |
+
torch.ops.bitsandbytes.int8_vectorwise_dequant.default(CB[:, outlier_cols].contiguous(), SCB)
|
| 80 |
+
.to(A.dtype)
|
| 81 |
+
.t()
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
# TODO: if state.has_fp16_weights: subB = B[:, outlier_cols].t()
|
| 85 |
+
|
| 86 |
+
else:
|
| 87 |
+
# Needed for torch.compile when there are no outliers.
|
| 88 |
+
subA = torch.empty(0, device=A.device, dtype=A.dtype)
|
| 89 |
+
|
| 90 |
+
# Int8 Matmul + Dequant + Bias
|
| 91 |
+
output = torch.ops.bitsandbytes.int8_scaled_mm.default(CA, CB, SCA, SCB, bias=bias, dtype=A.dtype)
|
| 92 |
+
|
| 93 |
+
if subB is not None:
|
| 94 |
+
# Add the outlier columns back to the output
|
| 95 |
+
output = output.addmm(subA, subB)
|
| 96 |
+
|
| 97 |
+
return output, subA
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
@register_kernel("bitsandbytes::int8_scaled_mm", "default")
|
| 101 |
+
def _(
|
| 102 |
+
A: torch.Tensor,
|
| 103 |
+
B: torch.Tensor,
|
| 104 |
+
row_stats: torch.Tensor,
|
| 105 |
+
col_stats: torch.Tensor,
|
| 106 |
+
bias: Optional[torch.Tensor] = None,
|
| 107 |
+
dtype: Optional[torch.dtype] = None,
|
| 108 |
+
) -> torch.Tensor:
|
| 109 |
+
out_i32 = torch.ops.bitsandbytes.int8_linear_matmul.default(A, B)
|
| 110 |
+
return torch.ops.bitsandbytes.int8_mm_dequant.default(
|
| 111 |
+
out_i32,
|
| 112 |
+
row_stats,
|
| 113 |
+
col_stats,
|
| 114 |
+
dtype=dtype or torch.float16,
|
| 115 |
+
bias=bias,
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
@register_kernel("bitsandbytes::int8_linear_matmul", "default")
|
| 120 |
+
def _(A: torch.Tensor, B: torch.Tensor):
|
| 121 |
+
return _int8_linear_matmul_impl(A, B)
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
@register_kernel("bitsandbytes::int8_linear_matmul.out", "default")
|
| 125 |
+
def _(A: torch.Tensor, B: torch.Tensor, out: torch.Tensor):
|
| 126 |
+
torch._check(out.dtype == torch.int32)
|
| 127 |
+
_int8_linear_matmul_impl(A, B, out)
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def _int8_linear_matmul_impl(A: torch.Tensor, B: torch.Tensor, out: Optional[torch.Tensor] = None):
|
| 131 |
+
# Naive implementation: perform matmul in fp32
|
| 132 |
+
result = torch.matmul(A.float(), B.float().t()).to(torch.int32)
|
| 133 |
+
if out is not None:
|
| 134 |
+
result = out.copy_(result)
|
| 135 |
+
return result
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
@register_kernel("bitsandbytes::int8_vectorwise_quant", "default")
|
| 139 |
+
def _(A: torch.Tensor, threshold=0.0):
|
| 140 |
+
rows = prod(A.shape[:-1])
|
| 141 |
+
outlier_cols = None
|
| 142 |
+
|
| 143 |
+
outlier_restore = None
|
| 144 |
+
|
| 145 |
+
if threshold > 0.0:
|
| 146 |
+
outliers = A.abs() >= threshold
|
| 147 |
+
|
| 148 |
+
if outliers.any():
|
| 149 |
+
# Determine which columns contain outliers, and zero out the
|
| 150 |
+
# outliers ahead of quantization. We need to keep a backup of these
|
| 151 |
+
# outliers to restore them after quantization.
|
| 152 |
+
outlier_cols = torch.argwhere(outliers.any(dim=0)).view(-1)
|
| 153 |
+
outlier_restore = A[outliers].clone()
|
| 154 |
+
A[outliers] = 0
|
| 155 |
+
else:
|
| 156 |
+
# Needed for torch.compile support.
|
| 157 |
+
outlier_cols = torch.empty(0, device=A.device, dtype=torch.int64)
|
| 158 |
+
|
| 159 |
+
# Get absmax for each row.
|
| 160 |
+
row_stats = torch.max(A.abs(), dim=1).values.float()
|
| 161 |
+
|
| 162 |
+
# Quantize row-wise to int8.
|
| 163 |
+
out_row = torch.round(A * (127.0 / row_stats.unsqueeze(-1))).to(torch.int8)
|
| 164 |
+
|
| 165 |
+
# Zero out values from outlier columns across all rows.
|
| 166 |
+
if rows > 1 and outlier_cols is not None:
|
| 167 |
+
out_row[:, outlier_cols] = 0
|
| 168 |
+
|
| 169 |
+
# Restore outliers.
|
| 170 |
+
if outlier_restore is not None:
|
| 171 |
+
A[outliers] = outlier_restore
|
| 172 |
+
|
| 173 |
+
return out_row, row_stats, outlier_cols
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
@register_kernel("bitsandbytes::quantize_blockwise", "default")
|
| 177 |
+
def _(A: torch.Tensor, code: torch.Tensor, blocksize: int) -> tuple[torch.Tensor, torch.Tensor]:
|
| 178 |
+
torch._check_is_size(blocksize)
|
| 179 |
+
|
| 180 |
+
n = A.numel()
|
| 181 |
+
rem = n % blocksize
|
| 182 |
+
has_rem = rem > 0
|
| 183 |
+
blocks = n // blocksize + has_rem
|
| 184 |
+
absmax = torch.zeros((blocks,), device=A.device, dtype=torch.float32)
|
| 185 |
+
A_reshaped = A.reshape(n)
|
| 186 |
+
A_com = A_reshaped[: n - rem]
|
| 187 |
+
A_com_reshaped = A_com.reshape(n // blocksize, blocksize)
|
| 188 |
+
absmax[: blocks - has_rem] = torch.abs(A_com_reshaped).max(dim=-1)[0]
|
| 189 |
+
scaled_A = torch.clamp(A_com_reshaped * (1 / absmax[: blocks - has_rem].view(-1, 1)), -1, 1)
|
| 190 |
+
scaled_A = scaled_A.reshape(-1)
|
| 191 |
+
if has_rem:
|
| 192 |
+
absmax[-1] = torch.abs(A_reshaped[n - rem :]).max()
|
| 193 |
+
scaled_A_rem = torch.clamp(A_reshaped[n - rem :] * (1 / absmax[-1]), -1, 1)
|
| 194 |
+
scaled_A = torch.cat([scaled_A, scaled_A_rem], dim=0)
|
| 195 |
+
|
| 196 |
+
diff = torch.abs(scaled_A.unsqueeze(-1) - code.to(scaled_A.device))
|
| 197 |
+
out = torch.argmin(diff, dim=-1).to(torch.uint8).to(scaled_A.device).reshape(A.shape)
|
| 198 |
+
|
| 199 |
+
return out, absmax
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
@register_kernel("bitsandbytes::dequantize_blockwise", "default")
|
| 203 |
+
def _(A: torch.Tensor, absmax: torch.Tensor, code: torch.Tensor, blocksize: int, dtype: torch.dtype) -> torch.Tensor:
|
| 204 |
+
torch._check_is_size(blocksize)
|
| 205 |
+
torch._check(A.dtype == torch.uint8, lambda: f"A must be uint8, got {A.dtype}")
|
| 206 |
+
|
| 207 |
+
out = code[A.reshape(-1).int()]
|
| 208 |
+
blocks = out.shape[-1] // blocksize
|
| 209 |
+
res = out.shape[-1] % blocksize
|
| 210 |
+
if res != 0:
|
| 211 |
+
out = torch.nn.functional.pad(out, (0, blocksize - res), mode="constant", value=0)
|
| 212 |
+
out = (out.view(-1, blocksize) * absmax.view(-1, 1)).to(dtype).reshape(-1)
|
| 213 |
+
out = out[: blocks * blocksize + res]
|
| 214 |
+
out = out.reshape(A.shape)
|
| 215 |
+
|
| 216 |
+
return out
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
@register_kernel("bitsandbytes::quantize_4bit", "default")
|
| 220 |
+
def _(
|
| 221 |
+
A: torch.Tensor, blocksize: int, quant_type: str, quant_storage: torch.dtype
|
| 222 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 223 |
+
torch._check_is_size(blocksize)
|
| 224 |
+
torch._check(quant_type in ("nf4", "fp4"), lambda: f"quant_type must be nf4 or fp4, got {quant_type}")
|
| 225 |
+
torch._check(
|
| 226 |
+
A.dtype in [torch.bfloat16, torch.float16, torch.float32],
|
| 227 |
+
lambda: f"Blockwise 4bit quantization only supports 16/32-bit floats, but got {A.dtype}",
|
| 228 |
+
)
|
| 229 |
+
|
| 230 |
+
n = A.numel()
|
| 231 |
+
full_blocks = n // blocksize
|
| 232 |
+
rem = n % blocksize
|
| 233 |
+
blocks = full_blocks + 1 if rem else full_blocks
|
| 234 |
+
absmax = torch.zeros((blocks,), device=A.device, dtype=torch.float32)
|
| 235 |
+
A_flattened = A.reshape(n)
|
| 236 |
+
|
| 237 |
+
# Scale full blocks of the tensor to [-1, 1]
|
| 238 |
+
A_full_blocks = A_flattened[: n - rem].reshape(n // blocksize, blocksize)
|
| 239 |
+
absmax[:full_blocks] = torch.abs(A_full_blocks).max(dim=-1)[0]
|
| 240 |
+
scaled = torch.clamp(A_full_blocks * (1 / absmax[:full_blocks].view(-1, 1)), -1, 1).reshape(-1)
|
| 241 |
+
|
| 242 |
+
# Scale any partial block
|
| 243 |
+
if rem:
|
| 244 |
+
A_rem = A_flattened[-rem:]
|
| 245 |
+
absmax[-1] = torch.abs(A_rem).max()
|
| 246 |
+
scaled_rem = torch.clamp(A_rem * (1 / absmax[-1]), -1, 1)
|
| 247 |
+
scaled = torch.cat([scaled, scaled_rem], dim=0)
|
| 248 |
+
|
| 249 |
+
# Quantize with the lookup table
|
| 250 |
+
code = CODE[quant_type].to(scaled.device).to(scaled.dtype)
|
| 251 |
+
quantized = torch.argmin(torch.abs(scaled.view(-1, 1) - code), dim=-1, keepdim=True).to(torch.uint8)
|
| 252 |
+
|
| 253 |
+
# Pack two quantized values per byte
|
| 254 |
+
packed = quantized[::2] << 4 | quantized[1::2]
|
| 255 |
+
|
| 256 |
+
if quant_storage != torch.uint8:
|
| 257 |
+
packed = packed.squeeze().view(quant_storage).unsqueeze(1)
|
| 258 |
+
|
| 259 |
+
return packed, absmax.float()
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
def _dequantize_4bit_impl(
|
| 263 |
+
A: torch.Tensor,
|
| 264 |
+
absmax: torch.Tensor,
|
| 265 |
+
blocksize: int,
|
| 266 |
+
quant_type: str,
|
| 267 |
+
shape: Sequence[int],
|
| 268 |
+
dtype: torch.dtype,
|
| 269 |
+
) -> torch.Tensor:
|
| 270 |
+
# Enable non uint8 dtype
|
| 271 |
+
if A.dtype != torch.uint8:
|
| 272 |
+
A = A.view(torch.uint8)
|
| 273 |
+
|
| 274 |
+
A = A.reshape(-1)
|
| 275 |
+
# Map nf4 to [-1, 1]
|
| 276 |
+
out_dq = torch.empty(A.size(0) * 2, dtype=torch.int32, device=A.device)
|
| 277 |
+
n = out_dq.numel()
|
| 278 |
+
out_dq[1::2] = A & 0xF
|
| 279 |
+
out_dq[::2] = A >> 4
|
| 280 |
+
# code is fp32, cast to dtype to avoid the mismatch issue
|
| 281 |
+
code = CODE[quant_type].to(dtype).to(A.device)
|
| 282 |
+
out_dq = code[out_dq]
|
| 283 |
+
|
| 284 |
+
# Apply scales
|
| 285 |
+
if out_dq.numel() != n:
|
| 286 |
+
assert out_dq.numel() == n + 1
|
| 287 |
+
out_dq = torch.narrow(out_dq, 0, 0, n)
|
| 288 |
+
blocks = n // blocksize
|
| 289 |
+
blocks += 1 if n % blocksize > 0 else 0
|
| 290 |
+
rem = n % blocksize
|
| 291 |
+
has_rem = rem > 0
|
| 292 |
+
|
| 293 |
+
out = torch.empty(shape, dtype=dtype, device=A.device).reshape(-1)
|
| 294 |
+
if has_rem:
|
| 295 |
+
out[: n - rem] = (out_dq[: n - rem].view(-1, blocksize) * absmax[: blocks - has_rem].view(-1, 1)).reshape(-1)
|
| 296 |
+
out[n - rem :] = out_dq[n - rem :] * absmax[-1]
|
| 297 |
+
else:
|
| 298 |
+
out = out_dq.view(-1, blocksize) * absmax.view(-1, 1)
|
| 299 |
+
|
| 300 |
+
out = out.reshape(-1, *shape[1:]).to(dtype)
|
| 301 |
+
|
| 302 |
+
return out
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
@register_kernel("bitsandbytes::dequantize_4bit", "default")
|
| 306 |
+
def _(
|
| 307 |
+
A: torch.Tensor,
|
| 308 |
+
absmax: torch.Tensor,
|
| 309 |
+
blocksize: int,
|
| 310 |
+
quant_type: str,
|
| 311 |
+
shape: Sequence[int],
|
| 312 |
+
dtype: torch.dtype,
|
| 313 |
+
) -> torch.Tensor:
|
| 314 |
+
torch._check_is_size(blocksize)
|
| 315 |
+
torch._check(quant_type in ("nf4", "fp4"), lambda: f"quant_type must be nf4 or fp4, got {quant_type}")
|
| 316 |
+
torch._check(
|
| 317 |
+
dtype in [torch.bfloat16, torch.float16, torch.float32],
|
| 318 |
+
lambda: f"Blockwise 4bit dequantization only supports 16/32-bit floats, but got {dtype}",
|
| 319 |
+
)
|
| 320 |
+
|
| 321 |
+
return _dequantize_4bit_impl(A, absmax, blocksize, quant_type, shape, dtype)
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
@register_kernel("bitsandbytes::gemv_4bit", "default")
|
| 325 |
+
def _(
|
| 326 |
+
A: torch.Tensor,
|
| 327 |
+
B: torch.Tensor,
|
| 328 |
+
shapeB: Sequence[int],
|
| 329 |
+
absmax: torch.Tensor,
|
| 330 |
+
code: torch.Tensor,
|
| 331 |
+
blocksize: int,
|
| 332 |
+
) -> torch.Tensor:
|
| 333 |
+
# Applied from dequantize_4bit
|
| 334 |
+
quant_type = "fp4" if code[1] > 0 else "nf4"
|
| 335 |
+
B_dq = torch.ops.bitsandbytes.dequantize_4bit.default(B, absmax, blocksize, quant_type, shapeB, A.dtype)
|
| 336 |
+
|
| 337 |
+
return torch.nn.functional.linear(
|
| 338 |
+
A,
|
| 339 |
+
B_dq,
|
| 340 |
+
bias=None,
|
| 341 |
+
)
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
MOMENTUM = 0
|
| 345 |
+
RMSPROP = 1
|
| 346 |
+
ADAGRAD = 2
|
| 347 |
+
ADAM = 3
|
| 348 |
+
# LION should be larger than MOMENTUM, RMSPROP, ADAGRAD due to comparison in kernels
|
| 349 |
+
LION = 4
|
| 350 |
+
ADEMAMIX = 5
|
| 351 |
+
|
| 352 |
+
name2optimizer_id = {
|
| 353 |
+
"momentum": MOMENTUM,
|
| 354 |
+
"rmsprop": RMSPROP,
|
| 355 |
+
"adagrad": ADAGRAD,
|
| 356 |
+
"adam": ADAM,
|
| 357 |
+
"lion": LION,
|
| 358 |
+
"ademamix": ADEMAMIX,
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
@_try_torch_compile
|
| 363 |
+
def _optimizer_precondition_32bit(
|
| 364 |
+
g: torch.Tensor,
|
| 365 |
+
p: torch.Tensor,
|
| 366 |
+
state1: torch.Tensor,
|
| 367 |
+
state2: Optional[torch.Tensor],
|
| 368 |
+
unorm_vec: torch.Tensor,
|
| 369 |
+
beta1: float,
|
| 370 |
+
beta2: float,
|
| 371 |
+
eps: float,
|
| 372 |
+
weight_decay: float,
|
| 373 |
+
step: int,
|
| 374 |
+
lr: float,
|
| 375 |
+
gnorm_scale: float,
|
| 376 |
+
optimizer_id: int,
|
| 377 |
+
):
|
| 378 |
+
"""Preprocessing optimizer, computing update norm"""
|
| 379 |
+
|
| 380 |
+
g_vals = gnorm_scale * g
|
| 381 |
+
|
| 382 |
+
if optimizer_id == 3: # ADAM
|
| 383 |
+
correction1 = 1.0 / (1.0 - beta1**step)
|
| 384 |
+
correction2 = 1.0 / (1.0 - beta2**step)
|
| 385 |
+
|
| 386 |
+
s1_vals = state1 * beta1 + (1.0 - beta1) * g_vals
|
| 387 |
+
s2_vals = state2 * beta2 + (1.0 - beta2) * g_vals * g_vals
|
| 388 |
+
|
| 389 |
+
s1_vals = s1_vals * correction1
|
| 390 |
+
s2_vals = s2_vals * correction2
|
| 391 |
+
|
| 392 |
+
update_vals = s1_vals / (torch.sqrt(s2_vals) + eps)
|
| 393 |
+
update_norm = update_vals * update_vals
|
| 394 |
+
|
| 395 |
+
elif optimizer_id == 5: # ADEMAMIX
|
| 396 |
+
update_norm = state1
|
| 397 |
+
|
| 398 |
+
elif optimizer_id == 0: # MOMENTUM
|
| 399 |
+
if step == 1:
|
| 400 |
+
s1_vals = g_vals
|
| 401 |
+
else:
|
| 402 |
+
s1_vals = state1 * beta1 + g_vals
|
| 403 |
+
update_norm = s1_vals * s1_vals
|
| 404 |
+
|
| 405 |
+
elif optimizer_id == 4: # LION
|
| 406 |
+
s1_vals = state1 * beta2 + (1.0 - beta2) * g_vals
|
| 407 |
+
update_norm = s1_vals
|
| 408 |
+
|
| 409 |
+
elif optimizer_id == 1: # RMSPROP
|
| 410 |
+
s1_vals = state1 * beta1 + (1.0 - beta1) * g_vals * g_vals
|
| 411 |
+
update_vals = g_vals / (torch.sqrt(s1_vals) + eps)
|
| 412 |
+
update_norm = update_vals * update_vals
|
| 413 |
+
|
| 414 |
+
elif optimizer_id == 2: # ADAGRAD
|
| 415 |
+
s1_vals = state1 + g_vals * g_vals
|
| 416 |
+
update_vals = g_vals / (torch.sqrt(s1_vals) + eps)
|
| 417 |
+
update_norm = update_vals * update_vals
|
| 418 |
+
|
| 419 |
+
total_norm = torch.sum(update_norm)
|
| 420 |
+
unorm_vec.add_(total_norm)
|
| 421 |
+
|
| 422 |
+
|
| 423 |
+
@_try_torch_compile
|
| 424 |
+
def _optimizer_update_32bit(
|
| 425 |
+
g: torch.Tensor,
|
| 426 |
+
p: torch.Tensor,
|
| 427 |
+
state1: torch.Tensor,
|
| 428 |
+
state2: Optional[torch.Tensor],
|
| 429 |
+
unorm_vec: Optional[torch.Tensor],
|
| 430 |
+
max_unorm: float,
|
| 431 |
+
param_norm: float,
|
| 432 |
+
beta1: float,
|
| 433 |
+
beta2: float,
|
| 434 |
+
beta3: float,
|
| 435 |
+
alpha: float,
|
| 436 |
+
eps: float,
|
| 437 |
+
weight_decay: float,
|
| 438 |
+
step: int,
|
| 439 |
+
lr: float,
|
| 440 |
+
gnorm_scale: float,
|
| 441 |
+
optimizer_id: int,
|
| 442 |
+
):
|
| 443 |
+
"""Unified optimizer update kernel"""
|
| 444 |
+
|
| 445 |
+
p_vals = p.float()
|
| 446 |
+
g_vals = (gnorm_scale * g).float()
|
| 447 |
+
if optimizer_id in [0, 1, 2, 4] and weight_decay > 0.0:
|
| 448 |
+
g_vals = g_vals + p_vals * weight_decay
|
| 449 |
+
|
| 450 |
+
update_scale = 1.0
|
| 451 |
+
if max_unorm > 0.0:
|
| 452 |
+
current_unorm = torch.sqrt(unorm_vec)
|
| 453 |
+
if optimizer_id in [0, 1, 2, 4]: # 1-state optimizers
|
| 454 |
+
if current_unorm > max_unorm * param_norm + eps:
|
| 455 |
+
update_scale = (max_unorm * param_norm + eps) / current_unorm
|
| 456 |
+
else: # 2-state optimizers
|
| 457 |
+
if current_unorm > max_unorm * param_norm:
|
| 458 |
+
update_scale = (max_unorm * param_norm) / current_unorm
|
| 459 |
+
|
| 460 |
+
if optimizer_id == 3: # ADAM
|
| 461 |
+
s1_vals = state1 * beta1 + (1.0 - beta1) * g_vals
|
| 462 |
+
s2_vals = state2 * beta2 + (1.0 - beta2) * g_vals * g_vals
|
| 463 |
+
|
| 464 |
+
correction1 = 1.0 - beta1**step
|
| 465 |
+
correction2 = sqrt(1.0 - beta2**step)
|
| 466 |
+
step_size = -lr * correction2 / correction1
|
| 467 |
+
|
| 468 |
+
if weight_decay > 0.0:
|
| 469 |
+
p_vals = p_vals * (1.0 - lr * weight_decay)
|
| 470 |
+
|
| 471 |
+
update_val = update_scale * step_size * (s1_vals / (torch.sqrt(s2_vals) + eps * correction2))
|
| 472 |
+
p_vals = p_vals + update_val
|
| 473 |
+
|
| 474 |
+
state1.copy_(s1_vals)
|
| 475 |
+
state2.copy_(s2_vals)
|
| 476 |
+
|
| 477 |
+
elif optimizer_id == 5: # ADEMAMIX
|
| 478 |
+
s1_vals = state1[0]
|
| 479 |
+
s3_vals = state1[1]
|
| 480 |
+
s2_vals = state2
|
| 481 |
+
|
| 482 |
+
m1 = s1_vals * beta1 + (1.0 - beta1) * g_vals
|
| 483 |
+
m2 = s3_vals * beta3 + (1.0 - beta3) * g_vals
|
| 484 |
+
nu = s2_vals * beta2 + (1.0 - beta2) * g_vals * g_vals
|
| 485 |
+
|
| 486 |
+
correction1 = 1.0 - beta1**step
|
| 487 |
+
correction2 = sqrt(1.0 - beta2**step)
|
| 488 |
+
|
| 489 |
+
if weight_decay > 0.0:
|
| 490 |
+
p_vals = p_vals * (1.0 - lr * weight_decay)
|
| 491 |
+
|
| 492 |
+
mixed_momentum = (m1 / correction1) + (alpha * m2)
|
| 493 |
+
adaptive_term = (torch.sqrt(nu) / correction2) + eps
|
| 494 |
+
p_vals = p_vals - lr * (mixed_momentum / adaptive_term)
|
| 495 |
+
|
| 496 |
+
state1[0].copy_(m1)
|
| 497 |
+
state1[1].copy_(m2)
|
| 498 |
+
state2.copy_(nu)
|
| 499 |
+
|
| 500 |
+
elif optimizer_id == 0: # MOMENTUM
|
| 501 |
+
if step == 1:
|
| 502 |
+
s1_vals = g_vals
|
| 503 |
+
else:
|
| 504 |
+
s1_vals = state1 * beta1 + g_vals
|
| 505 |
+
|
| 506 |
+
update_val = update_scale * (-lr * s1_vals)
|
| 507 |
+
p_vals = p_vals + update_val
|
| 508 |
+
|
| 509 |
+
state1.copy_(s1_vals)
|
| 510 |
+
|
| 511 |
+
elif optimizer_id == 4: # LION
|
| 512 |
+
momentum_update = state1 * beta1 + (1.0 - beta1) * g_vals
|
| 513 |
+
update_val = update_scale * lr * torch.sign(momentum_update)
|
| 514 |
+
p_vals = p_vals - update_val
|
| 515 |
+
|
| 516 |
+
s1_vals = state1 * beta2 + (1.0 - beta2) * g_vals
|
| 517 |
+
state1.copy_(s1_vals)
|
| 518 |
+
|
| 519 |
+
elif optimizer_id == 1: # RMSPROP
|
| 520 |
+
s1_vals = state1 * beta1 + (1.0 - beta1) * g_vals * g_vals
|
| 521 |
+
update_val = update_scale * lr * g_vals / (torch.sqrt(s1_vals) + eps)
|
| 522 |
+
p_vals = p_vals - update_val
|
| 523 |
+
|
| 524 |
+
state1.copy_(s1_vals)
|
| 525 |
+
|
| 526 |
+
elif optimizer_id == 2: # ADAGRAD
|
| 527 |
+
s1_vals = state1 + g_vals * g_vals
|
| 528 |
+
update_val = lr * g_vals / (torch.sqrt(s1_vals) + eps)
|
| 529 |
+
p_vals = p_vals - update_val
|
| 530 |
+
|
| 531 |
+
state1.copy_(s1_vals)
|
| 532 |
+
|
| 533 |
+
p.copy_(p_vals)
|
| 534 |
+
|
| 535 |
+
|
| 536 |
+
@register_kernel("bitsandbytes::optimizer_update_32bit", "default")
|
| 537 |
+
def _(
|
| 538 |
+
optimizer_name: str,
|
| 539 |
+
g: torch.Tensor,
|
| 540 |
+
p: torch.Tensor,
|
| 541 |
+
state1: torch.Tensor,
|
| 542 |
+
state2: Optional[torch.Tensor],
|
| 543 |
+
unorm_vec: Optional[torch.Tensor],
|
| 544 |
+
max_unorm: float,
|
| 545 |
+
param_norm: float,
|
| 546 |
+
beta1: float,
|
| 547 |
+
beta2: float,
|
| 548 |
+
beta3: float,
|
| 549 |
+
alpha: float,
|
| 550 |
+
eps: float,
|
| 551 |
+
weight_decay: float,
|
| 552 |
+
step: int,
|
| 553 |
+
lr: float,
|
| 554 |
+
gnorm_scale: float = 1.0,
|
| 555 |
+
skip_zeros=False,
|
| 556 |
+
) -> None:
|
| 557 |
+
"""
|
| 558 |
+
32-bit optimizer implemented by PyTorch with @torch.compile
|
| 559 |
+
"""
|
| 560 |
+
if skip_zeros:
|
| 561 |
+
raise NotImplementedError("skip_zeros is not supported yet")
|
| 562 |
+
|
| 563 |
+
optimizer_id = name2optimizer_id[optimizer_name]
|
| 564 |
+
|
| 565 |
+
if optimizer_name == "lion":
|
| 566 |
+
_optimizer_update_32bit(
|
| 567 |
+
g,
|
| 568 |
+
p,
|
| 569 |
+
state1,
|
| 570 |
+
state2,
|
| 571 |
+
unorm_vec,
|
| 572 |
+
max_unorm,
|
| 573 |
+
param_norm,
|
| 574 |
+
beta1,
|
| 575 |
+
beta2,
|
| 576 |
+
beta3,
|
| 577 |
+
alpha,
|
| 578 |
+
eps,
|
| 579 |
+
weight_decay,
|
| 580 |
+
step,
|
| 581 |
+
lr,
|
| 582 |
+
gnorm_scale,
|
| 583 |
+
optimizer_id,
|
| 584 |
+
)
|
| 585 |
+
|
| 586 |
+
if max_unorm > 0.0:
|
| 587 |
+
unorm_vec.zero_()
|
| 588 |
+
_optimizer_precondition_32bit(
|
| 589 |
+
g, p, state1, state2, unorm_vec, beta1, beta2, eps, weight_decay, step, lr, gnorm_scale, optimizer_id
|
| 590 |
+
)
|
| 591 |
+
else:
|
| 592 |
+
if max_unorm > 0.0:
|
| 593 |
+
unorm_vec.zero_()
|
| 594 |
+
_optimizer_precondition_32bit(
|
| 595 |
+
g, p, state1, state2, unorm_vec, beta1, beta2, eps, weight_decay, step, lr, gnorm_scale, optimizer_id
|
| 596 |
+
)
|
| 597 |
+
|
| 598 |
+
_optimizer_update_32bit(
|
| 599 |
+
g,
|
| 600 |
+
p,
|
| 601 |
+
state1,
|
| 602 |
+
state2,
|
| 603 |
+
unorm_vec,
|
| 604 |
+
max_unorm,
|
| 605 |
+
param_norm,
|
| 606 |
+
beta1,
|
| 607 |
+
beta2,
|
| 608 |
+
beta3,
|
| 609 |
+
alpha,
|
| 610 |
+
eps,
|
| 611 |
+
weight_decay,
|
| 612 |
+
step,
|
| 613 |
+
lr,
|
| 614 |
+
gnorm_scale,
|
| 615 |
+
optimizer_id,
|
| 616 |
+
)
|
bitsandbytes/backends/hpu/__init__.py
ADDED
|
File without changes
|
bitsandbytes/backends/hpu/ops.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import Sequence
|
| 2 |
+
import math
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
from ..._ops import register_kernel
|
| 7 |
+
from ..utils import GAUDI_SW_VER
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
# convert btw standard 4-bit compression format and ipex compression format
|
| 11 |
+
# needed for backward compatibility with older versions of gaudi sw
|
| 12 |
+
def _reverse_4bit_compress_format(weight: torch.Tensor):
|
| 13 |
+
out_1 = (weight & 0xF0) >> 4
|
| 14 |
+
out_2 = (weight & 0xF) << 4
|
| 15 |
+
out = out_1 | out_2
|
| 16 |
+
return out
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@register_kernel("bitsandbytes::dequantize_4bit", "hpu")
|
| 20 |
+
def _(
|
| 21 |
+
A: torch.Tensor,
|
| 22 |
+
absmax: torch.Tensor,
|
| 23 |
+
blocksize: int,
|
| 24 |
+
quant_type: str,
|
| 25 |
+
shape: Sequence[int],
|
| 26 |
+
dtype: torch.dtype,
|
| 27 |
+
) -> torch.Tensor:
|
| 28 |
+
torch._check_is_size(blocksize)
|
| 29 |
+
torch._check(quant_type == "nf4", lambda: f"quant_type must be nf4, got {quant_type}")
|
| 30 |
+
torch._check(
|
| 31 |
+
A.dtype in [torch.bfloat16, torch.uint8],
|
| 32 |
+
lambda: f"quant_storage supports uint8 or bfloat16, but got {A.dtype}",
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
# Enable non uint8 dtype
|
| 36 |
+
if A.dtype != torch.uint8:
|
| 37 |
+
A = A.view(torch.uint8)
|
| 38 |
+
|
| 39 |
+
A = A.reshape(-1)
|
| 40 |
+
|
| 41 |
+
if GAUDI_SW_VER and (GAUDI_SW_VER.major < 1 or GAUDI_SW_VER.minor < 22):
|
| 42 |
+
A = _reverse_4bit_compress_format(A)
|
| 43 |
+
|
| 44 |
+
# HPU dequantization function for NF4 quantized tensors.
|
| 45 |
+
out_dq = torch.ops.hpu.dequantize_nf4(
|
| 46 |
+
A,
|
| 47 |
+
absmax.to(dtype),
|
| 48 |
+
blocksize,
|
| 49 |
+
out_shape=(math.prod(shape),),
|
| 50 |
+
out_dtype=dtype,
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
output = out_dq.reshape(shape)
|
| 54 |
+
|
| 55 |
+
return output
|
bitsandbytes/backends/triton/__init__.py
ADDED
|
File without changes
|
bitsandbytes/backends/triton/kernels_4bit.py
ADDED
|
@@ -0,0 +1,577 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
|
| 3 |
+
import triton
|
| 4 |
+
import triton.language as tl
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
# Triton implementation of similar CUDA kernel to avoid loading code from csrc/kernels.cu::dQuantizeFP4
|
| 8 |
+
# @triton.autotune(
|
| 9 |
+
# configs=[
|
| 10 |
+
# triton.Config({"SPLIT_NUM_BLOCKS": 1, "grf_mode": "auto"}, num_stages=4, num_warps=32),
|
| 11 |
+
# triton.Config({"SPLIT_NUM_BLOCKS": 2, "grf_mode": "auto"}, num_stages=4, num_warps=32),
|
| 12 |
+
# triton.Config({"SPLIT_NUM_BLOCKS": 1}),
|
| 13 |
+
# triton.Config({"SPLIT_NUM_BLOCKS": 2}),
|
| 14 |
+
# triton.Config({"SPLIT_NUM_BLOCKS": 4}),
|
| 15 |
+
# triton.Config({"SPLIT_NUM_BLOCKS": 8}),
|
| 16 |
+
# ],
|
| 17 |
+
# key=["n_elements"],
|
| 18 |
+
# )
|
| 19 |
+
@triton.jit
|
| 20 |
+
def quantize_fp4_blockwise_kernel(
|
| 21 |
+
A_ptr,
|
| 22 |
+
absmax_ptr,
|
| 23 |
+
out_ptr,
|
| 24 |
+
n_elements,
|
| 25 |
+
BLOCK_SIZE: tl.constexpr,
|
| 26 |
+
SPLIT_NUM_BLOCKS: tl.constexpr,
|
| 27 |
+
):
|
| 28 |
+
PAIRED_SPLIT_NUM_BLOCKS: tl.constexpr = SPLIT_NUM_BLOCKS * 2
|
| 29 |
+
block_start_idx = tl.program_id(0) * PAIRED_SPLIT_NUM_BLOCKS
|
| 30 |
+
thread_idx = tl.arange(0, PAIRED_SPLIT_NUM_BLOCKS * BLOCK_SIZE)
|
| 31 |
+
|
| 32 |
+
offsets = block_start_idx * BLOCK_SIZE + thread_idx
|
| 33 |
+
mask = offsets < n_elements
|
| 34 |
+
|
| 35 |
+
A = tl.load(A_ptr + offsets, mask=mask, other=0.0)
|
| 36 |
+
|
| 37 |
+
# To be able process several blocks -> (PAIRED_SPLIT_NUM_BLOCKS, BLOCK_SIZE)
|
| 38 |
+
A_reshaped = tl.reshape(A, (PAIRED_SPLIT_NUM_BLOCKS, BLOCK_SIZE))
|
| 39 |
+
|
| 40 |
+
# Calculating absamax for each block
|
| 41 |
+
absmax = tl.max(tl.abs(A_reshaped), axis=1)
|
| 42 |
+
tl.store(absmax_ptr + block_start_idx + tl.arange(0, PAIRED_SPLIT_NUM_BLOCKS), absmax)
|
| 43 |
+
|
| 44 |
+
A_normalized = A_reshaped / absmax[:, None]
|
| 45 |
+
A_normalized = tl.clamp(A_normalized, -1.0, 1.0)
|
| 46 |
+
|
| 47 |
+
sign = tl.where(A_normalized < 0, 0b1000, 0b0000)
|
| 48 |
+
A_absf = tl.abs(A_normalized)
|
| 49 |
+
|
| 50 |
+
result = tl.where(
|
| 51 |
+
A_absf > 0.29166667,
|
| 52 |
+
tl.where(
|
| 53 |
+
A_absf > 0.583333, tl.where(A_absf > 0.8333333, 0b011, 0b010), tl.where(A_absf > 0.4166667, 0b101, 0b100)
|
| 54 |
+
),
|
| 55 |
+
tl.where(
|
| 56 |
+
A_absf > 0.0859375,
|
| 57 |
+
tl.where(A_absf > 0.20833333, 0b0111, 0b0110),
|
| 58 |
+
tl.where(A_absf > 0.00260417, 0b0001, 0b0000),
|
| 59 |
+
),
|
| 60 |
+
)
|
| 61 |
+
quantized = (result ^ sign).to(tl.uint8)
|
| 62 |
+
|
| 63 |
+
quantized = quantized.reshape((PAIRED_SPLIT_NUM_BLOCKS, BLOCK_SIZE // 2, 2))
|
| 64 |
+
left, right = quantized.split()
|
| 65 |
+
packed = left << 4 | (right & 0xF)
|
| 66 |
+
|
| 67 |
+
packed_flat = tl.reshape(packed, (BLOCK_SIZE * SPLIT_NUM_BLOCKS,))
|
| 68 |
+
out_offsets = block_start_idx * BLOCK_SIZE // 2 + tl.arange(0, SPLIT_NUM_BLOCKS * BLOCK_SIZE)
|
| 69 |
+
# Use n - n//2 instead of (n+1)//2 to avoid integer overflow for large n
|
| 70 |
+
out_mask = out_offsets < (n_elements - n_elements // 2)
|
| 71 |
+
tl.store(out_ptr + out_offsets, packed_flat, mask=out_mask)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
# Triton implementation of similar CUDA kernel to avoid loading code from csrc/kernels.cu::dQuantizeNF4
|
| 75 |
+
# @triton.autotune(
|
| 76 |
+
# configs=[
|
| 77 |
+
# triton.Config({"SPLIT_NUM_BLOCKS": 1, "grf_mode": "auto"}, num_stages=4, num_warps=32),
|
| 78 |
+
# triton.Config({"SPLIT_NUM_BLOCKS": 2, "grf_mode": "auto"}, num_stages=4, num_warps=32),
|
| 79 |
+
# triton.Config({"SPLIT_NUM_BLOCKS": 1}),
|
| 80 |
+
# triton.Config({"SPLIT_NUM_BLOCKS": 2}),
|
| 81 |
+
# triton.Config({"SPLIT_NUM_BLOCKS": 4}),
|
| 82 |
+
# triton.Config({"SPLIT_NUM_BLOCKS": 8}),
|
| 83 |
+
# ],
|
| 84 |
+
# key=["n_elements"],
|
| 85 |
+
# )
|
| 86 |
+
@triton.jit
|
| 87 |
+
def quantize_nf4_blockwise_kernel(
|
| 88 |
+
A_ptr,
|
| 89 |
+
absmax_ptr,
|
| 90 |
+
out_ptr,
|
| 91 |
+
n_elements,
|
| 92 |
+
BLOCK_SIZE: tl.constexpr,
|
| 93 |
+
SPLIT_NUM_BLOCKS: tl.constexpr,
|
| 94 |
+
):
|
| 95 |
+
PAIRED_SPLIT_NUM_BLOCKS: tl.constexpr = SPLIT_NUM_BLOCKS * 2
|
| 96 |
+
block_start_idx = tl.program_id(0) * PAIRED_SPLIT_NUM_BLOCKS
|
| 97 |
+
thread_idx = tl.arange(0, PAIRED_SPLIT_NUM_BLOCKS * BLOCK_SIZE)
|
| 98 |
+
|
| 99 |
+
offsets = block_start_idx * BLOCK_SIZE + thread_idx
|
| 100 |
+
mask = offsets < n_elements
|
| 101 |
+
|
| 102 |
+
A = tl.load(A_ptr + offsets, mask=mask, other=0.0)
|
| 103 |
+
|
| 104 |
+
# To be able process several blocks -> (PAIRED_SPLIT_NUM_BLOCKS, BLOCK_SIZE)
|
| 105 |
+
A_reshaped = tl.reshape(A, (PAIRED_SPLIT_NUM_BLOCKS, BLOCK_SIZE))
|
| 106 |
+
|
| 107 |
+
# Calculating absamax for each block
|
| 108 |
+
absmax = tl.max(tl.abs(A_reshaped), axis=1)
|
| 109 |
+
tl.store(absmax_ptr + block_start_idx + tl.arange(0, PAIRED_SPLIT_NUM_BLOCKS), absmax)
|
| 110 |
+
|
| 111 |
+
A_normalized = A_reshaped / absmax[:, None]
|
| 112 |
+
A_normalized = tl.clamp(A_normalized, -1.0, 1.0)
|
| 113 |
+
|
| 114 |
+
result = tl.where(
|
| 115 |
+
A_normalized > 0.03979014977812767,
|
| 116 |
+
tl.where(
|
| 117 |
+
A_normalized > 0.3893125355243683,
|
| 118 |
+
tl.where(
|
| 119 |
+
A_normalized > 0.6427869200706482,
|
| 120 |
+
tl.where(A_normalized > 0.8614784181118011, 0b1111, 0b1110),
|
| 121 |
+
tl.where(A_normalized > 0.5016634166240692, 0b1101, 0b1100),
|
| 122 |
+
),
|
| 123 |
+
tl.where(
|
| 124 |
+
A_normalized > 0.2035212516784668,
|
| 125 |
+
tl.where(A_normalized > 0.2920137718319893, 0b1011, 0b1010),
|
| 126 |
+
tl.where(A_normalized > 0.1202552504837513, 0b1001, 0b1000),
|
| 127 |
+
),
|
| 128 |
+
),
|
| 129 |
+
tl.where(
|
| 130 |
+
A_normalized > -0.33967943489551544,
|
| 131 |
+
tl.where(
|
| 132 |
+
A_normalized > -0.13791173323988914,
|
| 133 |
+
tl.where(A_normalized > -0.045525018125772476, 0b0111, 0b0110),
|
| 134 |
+
tl.where(A_normalized > -0.23460740596055984, 0b0101, 0b0100),
|
| 135 |
+
),
|
| 136 |
+
tl.where(
|
| 137 |
+
A_normalized > -0.6106329262256622,
|
| 138 |
+
tl.where(A_normalized > -0.4599952697753906, 0b0011, 0b0010),
|
| 139 |
+
tl.where(A_normalized > -0.8480964004993439, 0b0001, 0b0000),
|
| 140 |
+
),
|
| 141 |
+
),
|
| 142 |
+
)
|
| 143 |
+
quantized = result.to(tl.uint8)
|
| 144 |
+
|
| 145 |
+
quantized = quantized.reshape((PAIRED_SPLIT_NUM_BLOCKS, BLOCK_SIZE // 2, 2))
|
| 146 |
+
|
| 147 |
+
left, right = quantized.split()
|
| 148 |
+
packed = left << 4 | (right & 0xF)
|
| 149 |
+
|
| 150 |
+
packed_flat = tl.reshape(packed, (BLOCK_SIZE * SPLIT_NUM_BLOCKS,))
|
| 151 |
+
out_offsets = block_start_idx * BLOCK_SIZE // 2 + tl.arange(0, SPLIT_NUM_BLOCKS * BLOCK_SIZE)
|
| 152 |
+
# Use n - n//2 instead of (n+1)//2 to avoid integer overflow for large n
|
| 153 |
+
out_mask = out_offsets < (n_elements - n_elements // 2)
|
| 154 |
+
tl.store(out_ptr + out_offsets, packed_flat, mask=out_mask)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def quantize_4bit_blockwise_triton(A, blocksize, quant_type, blocks, absmax, num_elements, quantized_out):
|
| 158 |
+
# grid = lambda META: (triton.cdiv(blocks, META["SPLIT_NUM_BLOCKS"]),)
|
| 159 |
+
split_num_blocks = 4
|
| 160 |
+
grid = (triton.cdiv(blocks, split_num_blocks),)
|
| 161 |
+
if quant_type == "fp4":
|
| 162 |
+
quantize_fp4_blockwise_kernel[grid](
|
| 163 |
+
A_ptr=A,
|
| 164 |
+
absmax_ptr=absmax,
|
| 165 |
+
out_ptr=quantized_out,
|
| 166 |
+
n_elements=num_elements,
|
| 167 |
+
BLOCK_SIZE=blocksize,
|
| 168 |
+
SPLIT_NUM_BLOCKS=split_num_blocks,
|
| 169 |
+
)
|
| 170 |
+
else:
|
| 171 |
+
quantize_nf4_blockwise_kernel[grid](
|
| 172 |
+
A_ptr=A,
|
| 173 |
+
absmax_ptr=absmax,
|
| 174 |
+
out_ptr=quantized_out,
|
| 175 |
+
n_elements=num_elements,
|
| 176 |
+
BLOCK_SIZE=blocksize,
|
| 177 |
+
SPLIT_NUM_BLOCKS=split_num_blocks,
|
| 178 |
+
)
|
| 179 |
+
return quantized_out, absmax
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
@triton.jit
|
| 183 |
+
def dequant_4bit_body_util(a, offsets, quant_ptr, absmax_ptr, n_elems, QUANT_BLOCK: tl.constexpr):
|
| 184 |
+
PAIRED_QUANT_BLOCK: tl.constexpr = QUANT_BLOCK // 2
|
| 185 |
+
mask = offsets < n_elems
|
| 186 |
+
higher = a & 0xF
|
| 187 |
+
# lower 4bits
|
| 188 |
+
lower = a >> 4
|
| 189 |
+
|
| 190 |
+
abs_offsets = offsets // PAIRED_QUANT_BLOCK
|
| 191 |
+
absmax = tl.load(absmax_ptr + abs_offsets, mask=mask, other=1.0, eviction_policy="evict_last")
|
| 192 |
+
|
| 193 |
+
# apply conversion
|
| 194 |
+
lower_4 = tl.load(quant_ptr + lower, eviction_policy="evict_last")
|
| 195 |
+
higher_4 = tl.load(quant_ptr + higher, eviction_policy="evict_last")
|
| 196 |
+
|
| 197 |
+
mul_high = higher_4 * absmax
|
| 198 |
+
mul_low = lower_4 * absmax
|
| 199 |
+
out_dq = tl.interleave(mul_low, mul_high)
|
| 200 |
+
return out_dq
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
# Triton implementation of similar CUDA kernel to avoid loading code from csrc/kernels.cu::dDequantizeFP4Tree
|
| 204 |
+
@triton.jit
|
| 205 |
+
def dequantize_fp4_tree(val, absmax):
|
| 206 |
+
# val: tl.tensor (uint8)
|
| 207 |
+
# absmax: tl.tensor (float32/float16)
|
| 208 |
+
# 00001100 00001011 00001001 00001111
|
| 209 |
+
sign = tl.where((val & 0b1000) == 0b1000, -1.0, 1.0) # -1
|
| 210 |
+
third_bit = (val & 0b0100) == 0b0100 # True
|
| 211 |
+
second_bit = (val & 0b0010) == 0b0010 # False
|
| 212 |
+
first_bit = (val & 0b0001) == 0b0001 # False
|
| 213 |
+
|
| 214 |
+
branch1 = tl.where(
|
| 215 |
+
second_bit,
|
| 216 |
+
tl.where(first_bit, 0.25, 0.16666667), # 1111, 1110
|
| 217 |
+
tl.where(first_bit, 0.5, 0.33333333), # 1101, 1100
|
| 218 |
+
)
|
| 219 |
+
branch2 = tl.where(
|
| 220 |
+
second_bit,
|
| 221 |
+
tl.where(first_bit, 1.0, 0.66666667), # 1011, 1010
|
| 222 |
+
tl.where(first_bit, 0.00520833, 0.0), # 1001, 1000
|
| 223 |
+
)
|
| 224 |
+
out = tl.where(third_bit, branch1, branch2)
|
| 225 |
+
return out * sign * absmax
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
@triton.jit
|
| 229 |
+
def dequant_fp4_body_util(a, offsets, absmax_ptr, n_elems, QUANT_BLOCK: tl.constexpr):
|
| 230 |
+
PAIRED_QUANT_BLOCK: tl.constexpr = QUANT_BLOCK // 2
|
| 231 |
+
mask = offsets < n_elems
|
| 232 |
+
higher = a & 0xF
|
| 233 |
+
lower = a >> 4
|
| 234 |
+
|
| 235 |
+
abs_offsets = offsets // PAIRED_QUANT_BLOCK
|
| 236 |
+
absmax = tl.load(absmax_ptr + abs_offsets, mask=mask, other=1.0, eviction_policy="evict_last")
|
| 237 |
+
mul_high = dequantize_fp4_tree(higher, absmax)
|
| 238 |
+
mul_low = dequantize_fp4_tree(lower, absmax)
|
| 239 |
+
out_dq = tl.interleave(mul_low, mul_high)
|
| 240 |
+
return out_dq
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
# Triton implementation of similar CUDA kernel to avoid loading code from csrc/kernels.cu::dDequantizeNF4
|
| 244 |
+
@triton.jit
|
| 245 |
+
def dequantize_nf4_tree(val):
|
| 246 |
+
# val: tl.tensor (uint8)
|
| 247 |
+
cond0 = (val & 0b1000) == 0b1000
|
| 248 |
+
cond1 = (val & 0b0100) == 0b0100
|
| 249 |
+
cond2 = (val & 0b0010) == 0b0010
|
| 250 |
+
cond3 = (val & 0b0001) == 0b0001
|
| 251 |
+
|
| 252 |
+
# Positive branch (val & 0b1000) == 8
|
| 253 |
+
branch_pos = tl.where(
|
| 254 |
+
cond1,
|
| 255 |
+
tl.where(
|
| 256 |
+
cond2,
|
| 257 |
+
tl.where(cond3, 1.0, 0.7229568362236023), # 1111, 1110
|
| 258 |
+
tl.where(cond3, 0.5626170039176941, 0.44070982933044434), # 1101, 1100
|
| 259 |
+
),
|
| 260 |
+
tl.where(
|
| 261 |
+
cond2,
|
| 262 |
+
tl.where(cond3, 0.33791524171829224, 0.24611230194568634), # 1011, 1010
|
| 263 |
+
tl.where(cond3, 0.16093020141124725, 0.07958029955625534), # 1001, 1000
|
| 264 |
+
),
|
| 265 |
+
)
|
| 266 |
+
|
| 267 |
+
# Negative branch (val & 0b1000) == 0
|
| 268 |
+
branch_neg = tl.where(
|
| 269 |
+
cond1,
|
| 270 |
+
tl.where(
|
| 271 |
+
cond2,
|
| 272 |
+
tl.where(cond3, 0.0, -0.09105003625154495), # 0111, 0110
|
| 273 |
+
tl.where(cond3, -0.18477343022823334, -0.28444138169288635), # 0101, 0100
|
| 274 |
+
),
|
| 275 |
+
tl.where(
|
| 276 |
+
cond2,
|
| 277 |
+
tl.where(cond3, -0.39491748809814453, -0.5250730514526367), # 0011, 0010
|
| 278 |
+
tl.where(cond3, -0.6961928009986877, -1.0), # 0001, 0000
|
| 279 |
+
),
|
| 280 |
+
)
|
| 281 |
+
return tl.where(cond0, branch_pos, branch_neg)
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
@triton.jit
|
| 285 |
+
def dequant_nf4_body_util(a, offsets, absmax_ptr, n_elems, QUANT_BLOCK: tl.constexpr):
|
| 286 |
+
PAIRED_QUANT_BLOCK: tl.constexpr = QUANT_BLOCK // 2
|
| 287 |
+
mask = offsets < n_elems
|
| 288 |
+
higher = a & 0xF
|
| 289 |
+
# lower 4bits
|
| 290 |
+
lower = a >> 4
|
| 291 |
+
|
| 292 |
+
abs_offsets = offsets // PAIRED_QUANT_BLOCK
|
| 293 |
+
absmax = tl.load(absmax_ptr + abs_offsets, mask=mask, other=1.0, eviction_policy="evict_last")
|
| 294 |
+
mul_high = dequantize_nf4_tree(higher) * absmax
|
| 295 |
+
mul_low = dequantize_nf4_tree(lower) * absmax
|
| 296 |
+
out_dq = tl.interleave(mul_low, mul_high)
|
| 297 |
+
return out_dq
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
# All such kernels are similar, so maybe code can be generalised.
|
| 301 |
+
# @triton.autotune(
|
| 302 |
+
# configs=[
|
| 303 |
+
# # # triton.Config({'SPLIT_SIZE': 64}),
|
| 304 |
+
# # # # triton.Config({'SPLIT_SIZE': 64, 'grf_mode': 'large'}, num_stages=2, num_warps=32),
|
| 305 |
+
# # # # triton.Config({'SPLIT_SIZE': 64, 'grf_mode': 'auto'}, num_stages=2, num_warps=32),
|
| 306 |
+
# # # # triton.Config({'SPLIT_SIZE': 64, 'grf_mode': 'large'}, num_stages=4, num_warps=32),
|
| 307 |
+
# # # # triton.Config({'SPLIT_SIZE': 64, 'grf_mode': 'auto'}, num_stages=4, num_warps=32),
|
| 308 |
+
# triton.Config({'SPLIT_SIZE': 128}),
|
| 309 |
+
# triton.Config({'SPLIT_SIZE': 128}, num_warps = 32, num_stages = 2),
|
| 310 |
+
# # # triton.Config({'SPLIT_SIZE': 128}, num_warps = 4, num_stages = 4),
|
| 311 |
+
# # # # triton.Config({'SPLIT_SIZE': 128, 'grf_mode': 'large'}, num_stages=2, num_warps=32),
|
| 312 |
+
# # # # triton.Config({'SPLIT_SIZE': 128, 'grf_mode': 'auto'}, num_stages=2, num_warps=32),
|
| 313 |
+
# # # # triton.Config({'SPLIT_SIZE': 128, 'grf_mode': 'large'}, num_stages=4, num_warps=32),
|
| 314 |
+
# # # # triton.Config({'SPLIT_SIZE': 128, 'grf_mode': 'auto'}, num_stages=4, num_warps=32),
|
| 315 |
+
# triton.Config({'SPLIT_SIZE': 256}),
|
| 316 |
+
# triton.Config({'SPLIT_SIZE': 256}, num_warps = 32, num_stages = 2),
|
| 317 |
+
# # triton.Config({'SPLIT_SIZE': 256}, num_warps = 4, num_stages = 4),
|
| 318 |
+
# triton.Config({'SPLIT_SIZE': 512}),
|
| 319 |
+
# triton.Config({'SPLIT_SIZE': 512}, num_warps = 32, num_stages = 2),
|
| 320 |
+
# # triton.Config({'SPLIT_SIZE': 512}, num_warps = 4, num_stages = 4),
|
| 321 |
+
# # # # triton.Config({'SPLIT_SIZE': 512, 'grf_mode': 'large'}, num_stages=2, num_warps=32),
|
| 322 |
+
# # # # triton.Config({'SPLIT_SIZE': 512, 'grf_mode': 'auto'}, num_stages=2, num_warps=32),
|
| 323 |
+
# # # # triton.Config({'SPLIT_SIZE': 512, 'grf_mode': 'large'}, num_stages=4, num_warps=32),
|
| 324 |
+
# # # # triton.Config({'SPLIT_SIZE': 512, 'grf_mode': 'auto'}, num_stages=4, num_warps=32),
|
| 325 |
+
# # # triton.Config({'SPLIT_SIZE': 1024}),
|
| 326 |
+
# # # # triton.Config({'SPLIT_SIZE': 2048}),
|
| 327 |
+
# # # # triton.Config({'SPLIT_SIZE': 4096}),
|
| 328 |
+
# # # # triton.Config({'SPLIT_SIZE': 8192}),
|
| 329 |
+
# # # # triton.Config({'SPLIT_SIZE': 16384}),
|
| 330 |
+
# ],
|
| 331 |
+
# key=['num_paired_elements'],
|
| 332 |
+
# )
|
| 333 |
+
@triton.jit
|
| 334 |
+
def dequant_4bit_kernel(
|
| 335 |
+
a_ptr,
|
| 336 |
+
c_ptr,
|
| 337 |
+
quant_ptr,
|
| 338 |
+
absmax_ptr,
|
| 339 |
+
num_paired_elements,
|
| 340 |
+
num_output_elements,
|
| 341 |
+
QUANT_BLOCK: tl.constexpr,
|
| 342 |
+
SPLIT_SIZE: tl.constexpr,
|
| 343 |
+
):
|
| 344 |
+
pid = tl.program_id(axis=0) # We use a 1D launch grid so axis is 0.
|
| 345 |
+
block_start = pid * SPLIT_SIZE
|
| 346 |
+
offsets = block_start + tl.arange(0, SPLIT_SIZE)
|
| 347 |
+
mask = offsets < num_paired_elements
|
| 348 |
+
|
| 349 |
+
a = tl.load(a_ptr + offsets, mask, eviction_policy="evict_first")
|
| 350 |
+
|
| 351 |
+
out_dq = dequant_4bit_body_util(
|
| 352 |
+
a=a,
|
| 353 |
+
offsets=offsets,
|
| 354 |
+
quant_ptr=quant_ptr,
|
| 355 |
+
absmax_ptr=absmax_ptr,
|
| 356 |
+
n_elems=num_paired_elements,
|
| 357 |
+
QUANT_BLOCK=QUANT_BLOCK,
|
| 358 |
+
)
|
| 359 |
+
|
| 360 |
+
out_block_start = pid * SPLIT_SIZE * 2
|
| 361 |
+
offs = out_block_start + tl.arange(0, SPLIT_SIZE * 2)
|
| 362 |
+
mask = offs < num_output_elements
|
| 363 |
+
tl.store(c_ptr + offs, out_dq, mask)
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
# @triton.autotune(
|
| 367 |
+
# configs=[
|
| 368 |
+
# triton.Config({'SPLIT_SIZE': 128}, num_warps = 32, num_stages = 2),
|
| 369 |
+
# triton.Config({'SPLIT_SIZE': 256}),
|
| 370 |
+
# triton.Config({'SPLIT_SIZE': 256}, num_warps = 32, num_stages = 2),
|
| 371 |
+
# triton.Config({'SPLIT_SIZE': 512}),
|
| 372 |
+
# triton.Config({'SPLIT_SIZE': 512}, num_warps = 32, num_stages = 2),
|
| 373 |
+
# triton.Config({'SPLIT_SIZE': 1024}, num_warps = 32, num_stages = 2),
|
| 374 |
+
# ],
|
| 375 |
+
# key=['num_paired_elements'],
|
| 376 |
+
# )
|
| 377 |
+
@triton.jit
|
| 378 |
+
def dequant_fp4_kernel(
|
| 379 |
+
a_ptr,
|
| 380 |
+
c_ptr,
|
| 381 |
+
absmax_ptr,
|
| 382 |
+
num_paired_elements,
|
| 383 |
+
num_output_elements,
|
| 384 |
+
QUANT_BLOCK: tl.constexpr,
|
| 385 |
+
SPLIT_SIZE: tl.constexpr,
|
| 386 |
+
):
|
| 387 |
+
pid = tl.program_id(axis=0) # We use a 1D launch grid so axis is 0.
|
| 388 |
+
block_start = pid * SPLIT_SIZE
|
| 389 |
+
offsets = block_start + tl.arange(0, SPLIT_SIZE)
|
| 390 |
+
mask = offsets < num_paired_elements
|
| 391 |
+
|
| 392 |
+
a = tl.load(a_ptr + offsets, mask, eviction_policy="evict_first")
|
| 393 |
+
|
| 394 |
+
out_dq = dequant_fp4_body_util(
|
| 395 |
+
a=a,
|
| 396 |
+
offsets=offsets,
|
| 397 |
+
absmax_ptr=absmax_ptr,
|
| 398 |
+
n_elems=num_paired_elements,
|
| 399 |
+
QUANT_BLOCK=QUANT_BLOCK,
|
| 400 |
+
)
|
| 401 |
+
|
| 402 |
+
out_block_start = pid * SPLIT_SIZE * 2
|
| 403 |
+
offs = out_block_start + tl.arange(0, SPLIT_SIZE * 2)
|
| 404 |
+
mask = offs < num_output_elements
|
| 405 |
+
tl.store(c_ptr + offs, out_dq, mask)
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
# @triton.autotune(
|
| 409 |
+
# configs=[
|
| 410 |
+
# triton.Config({'SPLIT_SIZE': 128}, num_warps = 32, num_stages = 2),
|
| 411 |
+
# triton.Config({'SPLIT_SIZE': 256}),
|
| 412 |
+
# triton.Config({'SPLIT_SIZE': 256}, num_warps = 32, num_stages = 2),
|
| 413 |
+
# triton.Config({'SPLIT_SIZE': 512}),
|
| 414 |
+
# triton.Config({'SPLIT_SIZE': 512}, num_warps = 32, num_stages = 2),
|
| 415 |
+
# triton.Config({'SPLIT_SIZE': 1024}, num_warps = 32, num_stages = 2),
|
| 416 |
+
# ],
|
| 417 |
+
# key=['num_paired_elements'],
|
| 418 |
+
# )
|
| 419 |
+
@triton.jit
|
| 420 |
+
def dequant_nf4_kernel(
|
| 421 |
+
a_ptr,
|
| 422 |
+
c_ptr,
|
| 423 |
+
absmax_ptr,
|
| 424 |
+
num_paired_elements,
|
| 425 |
+
num_output_elements,
|
| 426 |
+
QUANT_BLOCK: tl.constexpr,
|
| 427 |
+
SPLIT_SIZE: tl.constexpr,
|
| 428 |
+
):
|
| 429 |
+
pid = tl.program_id(axis=0) # We use a 1D launch grid so axis is 0.
|
| 430 |
+
block_start = pid * SPLIT_SIZE
|
| 431 |
+
offsets = block_start + tl.arange(0, SPLIT_SIZE)
|
| 432 |
+
mask = offsets < num_paired_elements
|
| 433 |
+
|
| 434 |
+
a = tl.load(a_ptr + offsets, mask, eviction_policy="evict_first")
|
| 435 |
+
|
| 436 |
+
out_dq = dequant_nf4_body_util(
|
| 437 |
+
a=a,
|
| 438 |
+
offsets=offsets,
|
| 439 |
+
absmax_ptr=absmax_ptr,
|
| 440 |
+
n_elems=num_paired_elements,
|
| 441 |
+
QUANT_BLOCK=QUANT_BLOCK,
|
| 442 |
+
)
|
| 443 |
+
|
| 444 |
+
out_block_start = pid * SPLIT_SIZE * 2
|
| 445 |
+
offs = out_block_start + tl.arange(0, SPLIT_SIZE * 2)
|
| 446 |
+
mask = offs < num_output_elements
|
| 447 |
+
tl.store(c_ptr + offs, out_dq, mask)
|
| 448 |
+
|
| 449 |
+
|
| 450 |
+
def dequantize_4bit_impl(
|
| 451 |
+
A: torch.Tensor,
|
| 452 |
+
absmax: torch.Tensor,
|
| 453 |
+
blocksize: int,
|
| 454 |
+
quant_type: str,
|
| 455 |
+
dtype: torch.dtype,
|
| 456 |
+
out: torch.Tensor,
|
| 457 |
+
) -> None:
|
| 458 |
+
# It's will be processed as an array, so
|
| 459 |
+
# actual length is row * col
|
| 460 |
+
# Elements are in uint8 format, so interleaved
|
| 461 |
+
# so total amount of data is 2 * elem_count
|
| 462 |
+
number_of_paired_elements = A.numel()
|
| 463 |
+
num_output_elements = out.numel()
|
| 464 |
+
# we assume that split_size > quant_blocksize
|
| 465 |
+
|
| 466 |
+
SPLIT_SIZE = 256
|
| 467 |
+
# grid = lambda META: (triton.cdiv(number_of_paired_elements, META['SPLIT_SIZE']), )
|
| 468 |
+
grid = (triton.cdiv(number_of_paired_elements, SPLIT_SIZE),)
|
| 469 |
+
if quant_type == "fp4":
|
| 470 |
+
dequant_fp4_kernel[grid](A, out, absmax, number_of_paired_elements, num_output_elements, blocksize, SPLIT_SIZE)
|
| 471 |
+
else:
|
| 472 |
+
dequant_nf4_kernel[grid](A, out, absmax, number_of_paired_elements, num_output_elements, blocksize, SPLIT_SIZE)
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
def dequantize_4bit_impl_passing_code(
|
| 476 |
+
A: torch.Tensor,
|
| 477 |
+
absmax: torch.Tensor,
|
| 478 |
+
blocksize: int,
|
| 479 |
+
code: torch.Tensor,
|
| 480 |
+
dtype: torch.dtype,
|
| 481 |
+
out: torch.Tensor,
|
| 482 |
+
) -> None:
|
| 483 |
+
number_of_paired_elements = A.numel()
|
| 484 |
+
num_output_elements = out.numel()
|
| 485 |
+
# we assume that split_size > quant_blocksize
|
| 486 |
+
|
| 487 |
+
SPLIT_SIZE = 256
|
| 488 |
+
# grid = lambda META: (triton.cdiv(number_of_paired_elements, META['SPLIT_SIZE']), )
|
| 489 |
+
grid = (triton.cdiv(number_of_paired_elements, SPLIT_SIZE),)
|
| 490 |
+
dequant_4bit_kernel[grid](
|
| 491 |
+
A, out, code, absmax, number_of_paired_elements, num_output_elements, blocksize, SPLIT_SIZE
|
| 492 |
+
)
|
| 493 |
+
|
| 494 |
+
|
| 495 |
+
######################### Fallback dequantization functions #########################
|
| 496 |
+
## for debug ##
|
| 497 |
+
|
| 498 |
+
|
| 499 |
+
# @triton.autotune(
|
| 500 |
+
# configs=[
|
| 501 |
+
# # triton.Config({'SPLIT_NUM_BLOCKS': 1, 'grf_mode': 'large'}, num_stages=2, num_warps=32),
|
| 502 |
+
# # triton.Config({'SPLIT_NUM_BLOCKS': 1, 'grf_mode': 'auto'}, num_stages=2, num_warps=32),
|
| 503 |
+
# # triton.Config({'SPLIT_NUM_BLOCKS': 1, 'grf_mode': 'large'}, num_stages=4, num_warps=32),
|
| 504 |
+
# # #
|
| 505 |
+
# # triton.Config({"SPLIT_NUM_BLOCKS": 1, "grf_mode": "auto"}, num_stages=4, num_warps=32),
|
| 506 |
+
# #
|
| 507 |
+
# triton.Config({"SPLIT_NUM_BLOCKS": 2}),
|
| 508 |
+
# # triton.Config({"SPLIT_NUM_BLOCKS": 2, "grf_mode": "large"}, num_stages=2, num_warps=32),
|
| 509 |
+
# # # triton.Config({'SPLIT_NUM_BLOCKS': 2, 'grf_mode': 'large'}, num_stages=4, num_warps=32),
|
| 510 |
+
# # triton.Config({"SPLIT_NUM_BLOCKS": 2, "grf_mode": "auto"}, num_stages=2, num_warps=32),
|
| 511 |
+
# # triton.Config({"SPLIT_NUM_BLOCKS": 2, "grf_mode": "auto"}, num_stages=4, num_warps=32),
|
| 512 |
+
# # triton.Config({"SPLIT_NUM_BLOCKS": 4, "grf_mode": "large"}, num_stages=2, num_warps=32),
|
| 513 |
+
# # triton.Config({"SPLIT_NUM_BLOCKS": 4, "grf_mode": "large"}, num_stages=4, num_warps=32),
|
| 514 |
+
# # triton.Config({'SPLIT_NUM_BLOCKS': 8, 'grf_mode': 'large'}, num_stages=2, num_warps=32),
|
| 515 |
+
# ],
|
| 516 |
+
# key=["n_elements", "BLOCK_SIZE"],
|
| 517 |
+
# )
|
| 518 |
+
@triton.jit
|
| 519 |
+
def quantize_4bit_blockwise_kernel(
|
| 520 |
+
A_ptr,
|
| 521 |
+
code_ptr,
|
| 522 |
+
absmax_ptr,
|
| 523 |
+
out_ptr,
|
| 524 |
+
n_elements,
|
| 525 |
+
BLOCK_SIZE: tl.constexpr,
|
| 526 |
+
CODE_SIZE: tl.constexpr,
|
| 527 |
+
SPLIT_NUM_BLOCKS: tl.constexpr,
|
| 528 |
+
):
|
| 529 |
+
PAIRED_SPLIT_NUM_BLOCKS: tl.constexpr = SPLIT_NUM_BLOCKS * 2
|
| 530 |
+
block_start_idx = tl.program_id(0) * PAIRED_SPLIT_NUM_BLOCKS
|
| 531 |
+
thread_idx = tl.arange(0, PAIRED_SPLIT_NUM_BLOCKS * BLOCK_SIZE)
|
| 532 |
+
|
| 533 |
+
offsets = block_start_idx * BLOCK_SIZE + thread_idx
|
| 534 |
+
mask = offsets < n_elements
|
| 535 |
+
|
| 536 |
+
A = tl.load(A_ptr + offsets, mask=mask, other=0.0)
|
| 537 |
+
|
| 538 |
+
# To be able process several blocks -> (PAIRED_SPLIT_NUM_BLOCKS, BLOCK_SIZE)
|
| 539 |
+
A_reshaped = tl.reshape(A, (PAIRED_SPLIT_NUM_BLOCKS, BLOCK_SIZE))
|
| 540 |
+
|
| 541 |
+
# Calculating absamax for each block
|
| 542 |
+
absmax = tl.max(tl.abs(A_reshaped), axis=1)
|
| 543 |
+
tl.store(absmax_ptr + block_start_idx + tl.arange(0, PAIRED_SPLIT_NUM_BLOCKS), absmax)
|
| 544 |
+
|
| 545 |
+
A_normalized = A_reshaped / absmax[:, None]
|
| 546 |
+
A_normalized = tl.clamp(A_normalized, -1.0, 1.0)
|
| 547 |
+
|
| 548 |
+
lower_pivot = tl.zeros((PAIRED_SPLIT_NUM_BLOCKS, BLOCK_SIZE), dtype=tl.int32)
|
| 549 |
+
upper_pivot = tl.full((PAIRED_SPLIT_NUM_BLOCKS, BLOCK_SIZE), CODE_SIZE - 1, dtype=tl.int32)
|
| 550 |
+
|
| 551 |
+
for _ in range(4): # ceil(log2(code_size)) = 4, actually, in general case should be input parameter
|
| 552 |
+
pivot = (lower_pivot + upper_pivot) // 2
|
| 553 |
+
val = tl.load(code_ptr + pivot)
|
| 554 |
+
is_higher = A_normalized > val # code[pivot]
|
| 555 |
+
lower_pivot = tl.where(is_higher, pivot, lower_pivot)
|
| 556 |
+
upper_pivot = tl.where(is_higher, upper_pivot, pivot)
|
| 557 |
+
|
| 558 |
+
# Choose closest level
|
| 559 |
+
lower_val = tl.load(code_ptr + lower_pivot)
|
| 560 |
+
upper_val = tl.load(code_ptr + upper_pivot)
|
| 561 |
+
lower_dist = tl.abs(A_normalized - lower_val)
|
| 562 |
+
upper_dist = tl.abs(A_normalized - upper_val)
|
| 563 |
+
quantized = tl.where(lower_dist <= upper_dist, lower_pivot, upper_pivot).to(tl.uint8)
|
| 564 |
+
|
| 565 |
+
quantized = quantized.reshape((PAIRED_SPLIT_NUM_BLOCKS, BLOCK_SIZE // 2, 2))
|
| 566 |
+
quantized = quantized.to(tl.uint8, bitcast=True)
|
| 567 |
+
left, right = quantized.split()
|
| 568 |
+
packed = left << 4 | (right & 0xF)
|
| 569 |
+
|
| 570 |
+
# Reduce don't guarantee the order of the elements passed to unite_2_int4
|
| 571 |
+
# packed = tl.reduce(quantized, axis=2, combine_fn=unite_2_int4)
|
| 572 |
+
# packed = packed.to(tl.uint8, bitcast=True)
|
| 573 |
+
|
| 574 |
+
packed_flat = tl.reshape(packed, (BLOCK_SIZE * SPLIT_NUM_BLOCKS,))
|
| 575 |
+
out_offsets = block_start_idx * BLOCK_SIZE // 2 + tl.arange(0, SPLIT_NUM_BLOCKS * BLOCK_SIZE)
|
| 576 |
+
out_mask = out_offsets < n_elements // 2
|
| 577 |
+
tl.store(out_ptr + out_offsets, packed_flat, mask=out_mask)
|
bitsandbytes/backends/triton/kernels_8bit_quant.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
|
| 3 |
+
import triton
|
| 4 |
+
import triton.language as tl
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
# @triton.autotune(
|
| 8 |
+
# configs=[
|
| 9 |
+
# # triton.Config({'SPLIT_SIZE': 64}),
|
| 10 |
+
# # triton.Config({'SPLIT_SIZE': 64, 'grf_mode': 'large'}, num_stages=2, num_warps=32),
|
| 11 |
+
# # triton.Config({'SPLIT_SIZE': 64, 'grf_mode': 'auto'}, num_stages=2, num_warps=32),
|
| 12 |
+
# # triton.Config({'SPLIT_SIZE': 64, 'grf_mode': 'large'}, num_stages=4, num_warps=32),
|
| 13 |
+
# # triton.Config({'SPLIT_SIZE': 64, 'grf_mode': 'auto'}, num_stages=4, num_warps=32),
|
| 14 |
+
# # triton.Config({'SPLIT_SIZE': 128}),
|
| 15 |
+
# # triton.Config({'SPLIT_SIZE': 128, 'grf_mode': 'large'}, num_stages=2, num_warps=32),
|
| 16 |
+
# # triton.Config({'SPLIT_SIZE': 128, 'grf_mode': 'auto'}, num_stages=2, num_warps=32),
|
| 17 |
+
# # triton.Config({'SPLIT_SIZE': 128, 'grf_mode': 'large'}, num_stages=4, num_warps=32),
|
| 18 |
+
# # triton.Config({'SPLIT_SIZE': 128, 'grf_mode': 'auto'}, num_stages=4, num_warps=32),
|
| 19 |
+
# triton.Config({"SPLIT_SIZE": 256}),
|
| 20 |
+
# # triton.Config({'SPLIT_SIZE': 256, 'grf_mode': 'large'}, num_stages=2, num_warps=32),
|
| 21 |
+
# # triton.Config({'SPLIT_SIZE': 256, 'grf_mode': 'auto'}, num_stages=2, num_warps=32),
|
| 22 |
+
# triton.Config({"SPLIT_SIZE": 512}),
|
| 23 |
+
# # triton.Config({'SPLIT_SIZE': 1024}),
|
| 24 |
+
# ],
|
| 25 |
+
# key=["num_paired_elements", "QUANT_BLOCK"],
|
| 26 |
+
# )
|
| 27 |
+
@triton.jit
|
| 28 |
+
def dequant_8bit_kernel(
|
| 29 |
+
a_ptr,
|
| 30 |
+
out_ptr,
|
| 31 |
+
code_ptr,
|
| 32 |
+
absmax_ptr,
|
| 33 |
+
n,
|
| 34 |
+
QUANT_BLOCK: tl.constexpr,
|
| 35 |
+
SPLIT_SIZE: tl.constexpr,
|
| 36 |
+
):
|
| 37 |
+
pid = tl.program_id(axis=0)
|
| 38 |
+
block_start = pid * SPLIT_SIZE
|
| 39 |
+
offsets = block_start + tl.arange(0, SPLIT_SIZE)
|
| 40 |
+
mask = offsets < n
|
| 41 |
+
out_dq = dequant_8bit_blockwise_kernel_util(a_ptr, offsets, code_ptr, absmax_ptr, mask, QUANT_BLOCK)
|
| 42 |
+
tl.store(out_ptr + offsets, out_dq, mask)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def dequant_8bit_blockwise(
|
| 46 |
+
a: torch.Tensor,
|
| 47 |
+
absmax: torch.Tensor,
|
| 48 |
+
quant_state_code: torch.Tensor,
|
| 49 |
+
quant_blocksize: int = 64,
|
| 50 |
+
dtype: torch.dtype = None,
|
| 51 |
+
out: torch.Tensor = None,
|
| 52 |
+
):
|
| 53 |
+
n = a.numel()
|
| 54 |
+
if out is None:
|
| 55 |
+
if dtype is None:
|
| 56 |
+
raise ValueError("If out is None, dtype must be specified")
|
| 57 |
+
out = torch.empty_like(a, dtype=dtype, device=a.device)
|
| 58 |
+
|
| 59 |
+
SPLIT_SIZE = 256
|
| 60 |
+
# grid = lambda META: (triton.cdiv(number_of_paired_elements, META["SPLIT_SIZE"]),)
|
| 61 |
+
grid = (triton.cdiv(n, SPLIT_SIZE),)
|
| 62 |
+
dequant_8bit_kernel[grid](
|
| 63 |
+
a,
|
| 64 |
+
out,
|
| 65 |
+
quant_state_code,
|
| 66 |
+
absmax,
|
| 67 |
+
n,
|
| 68 |
+
quant_blocksize,
|
| 69 |
+
SPLIT_SIZE,
|
| 70 |
+
)
|
| 71 |
+
return out
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
# @triton.autotune(
|
| 75 |
+
# configs=[
|
| 76 |
+
# triton.Config({"SPLIT_NUM_BLOCKS": 1, "grf_mode": "auto"}, num_stages=4, num_warps=32),
|
| 77 |
+
# triton.Config({"SPLIT_NUM_BLOCKS": 2, "grf_mode": "auto"}, num_stages=4, num_warps=32),
|
| 78 |
+
# triton.Config({"SPLIT_NUM_BLOCKS": 1}),
|
| 79 |
+
# triton.Config({"SPLIT_NUM_BLOCKS": 2}),
|
| 80 |
+
# ],
|
| 81 |
+
# key=["n_elements"],
|
| 82 |
+
# )
|
| 83 |
+
@triton.jit
|
| 84 |
+
def quantize_8bit_blockwise_kernel(
|
| 85 |
+
A_ptr,
|
| 86 |
+
code_ptr,
|
| 87 |
+
absmax_ptr,
|
| 88 |
+
out_ptr,
|
| 89 |
+
n_elements,
|
| 90 |
+
BLOCK_SIZE: tl.constexpr,
|
| 91 |
+
CODE_SIZE: tl.constexpr,
|
| 92 |
+
SPLIT_NUM_BLOCKS: tl.constexpr,
|
| 93 |
+
):
|
| 94 |
+
block_start_idx = tl.program_id(0) * SPLIT_NUM_BLOCKS
|
| 95 |
+
thread_idx = tl.arange(0, SPLIT_NUM_BLOCKS * BLOCK_SIZE)
|
| 96 |
+
|
| 97 |
+
offsets = block_start_idx * BLOCK_SIZE + thread_idx
|
| 98 |
+
mask = offsets < n_elements
|
| 99 |
+
|
| 100 |
+
A = tl.load(A_ptr + offsets, mask=mask, other=0.0)
|
| 101 |
+
|
| 102 |
+
quantized, absmax = quantize_8bit_blockwise_kernel_util(A, code_ptr, CODE_SIZE, BLOCK_SIZE, SPLIT_NUM_BLOCKS)
|
| 103 |
+
tl.store(absmax_ptr + block_start_idx + tl.arange(0, SPLIT_NUM_BLOCKS), absmax)
|
| 104 |
+
tl.store(out_ptr + offsets, quantized, mask=mask)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def quantize_blockwise_triton(A, code, blocksize, absmax=None, out=None):
|
| 108 |
+
n = A.numel()
|
| 109 |
+
blocks = -(n // -blocksize)
|
| 110 |
+
|
| 111 |
+
if absmax is None:
|
| 112 |
+
absmax = torch.empty((blocks,), device=A.device, dtype=A.dtype)
|
| 113 |
+
if out is None:
|
| 114 |
+
out = torch.empty_like(A.flatten(), dtype=torch.uint8)
|
| 115 |
+
|
| 116 |
+
split_num_blocks = 1
|
| 117 |
+
grid = (triton.cdiv(blocks, split_num_blocks),)
|
| 118 |
+
# grid = lambda META: (triton.cdiv(blocks, META["SPLIT_NUM_BLOCKS"]),)
|
| 119 |
+
quantize_8bit_blockwise_kernel[grid](
|
| 120 |
+
A_ptr=A,
|
| 121 |
+
code_ptr=code,
|
| 122 |
+
absmax_ptr=absmax,
|
| 123 |
+
out_ptr=out,
|
| 124 |
+
n_elements=n,
|
| 125 |
+
BLOCK_SIZE=blocksize,
|
| 126 |
+
CODE_SIZE=code.numel(),
|
| 127 |
+
SPLIT_NUM_BLOCKS=split_num_blocks,
|
| 128 |
+
# num_warps=1,
|
| 129 |
+
# num_stages=2,
|
| 130 |
+
)
|
| 131 |
+
out = out.reshape(A.shape)
|
| 132 |
+
|
| 133 |
+
return out, absmax
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
@triton.jit
|
| 137 |
+
def quantize_8bit_blockwise_kernel_util(
|
| 138 |
+
a,
|
| 139 |
+
code_ptr,
|
| 140 |
+
CODE_SIZE: tl.constexpr,
|
| 141 |
+
BLOCK_SIZE: tl.constexpr,
|
| 142 |
+
N_PER_TH: tl.constexpr,
|
| 143 |
+
):
|
| 144 |
+
# To be able process several blocks -> (BLOCK_SIZE, SPLIT_NUM_BLOCKS)
|
| 145 |
+
a_reshaped = tl.reshape(a, (N_PER_TH, BLOCK_SIZE))
|
| 146 |
+
|
| 147 |
+
# Calculating absmax for each block
|
| 148 |
+
absmax = tl.max(tl.abs(a_reshaped), axis=1)
|
| 149 |
+
|
| 150 |
+
a_normalized = a_reshaped / absmax[:, None]
|
| 151 |
+
a_normalized = tl.clamp(a_normalized, -1.0, 1.0)
|
| 152 |
+
|
| 153 |
+
lower_pivot = tl.zeros((N_PER_TH, BLOCK_SIZE), dtype=tl.int32)
|
| 154 |
+
upper_pivot = tl.full((N_PER_TH, BLOCK_SIZE), CODE_SIZE - 1, dtype=tl.int32)
|
| 155 |
+
|
| 156 |
+
# ceil(log2(code_size)) = 8, actually, in general case should be input parameter
|
| 157 |
+
for _ in range(8):
|
| 158 |
+
pivot = (lower_pivot + upper_pivot) // 2
|
| 159 |
+
val = tl.load(code_ptr + pivot)
|
| 160 |
+
is_higher = a_normalized > val # code[pivot]
|
| 161 |
+
lower_pivot = tl.where(is_higher, pivot, lower_pivot)
|
| 162 |
+
upper_pivot = tl.where(is_higher, upper_pivot, pivot)
|
| 163 |
+
|
| 164 |
+
# Choose closest level
|
| 165 |
+
lower_val = tl.load(code_ptr + lower_pivot)
|
| 166 |
+
upper_val = tl.load(code_ptr + upper_pivot)
|
| 167 |
+
lower_dist = tl.abs(a_normalized - lower_val)
|
| 168 |
+
upper_dist = tl.abs(a_normalized - upper_val)
|
| 169 |
+
quantized = tl.where(lower_dist <= upper_dist, lower_pivot, upper_pivot).to(tl.uint8)
|
| 170 |
+
|
| 171 |
+
# too slow approach
|
| 172 |
+
# diff = tl.abs(A_normalized[:, :, None] - code[None, None, :])
|
| 173 |
+
# quantized = tl.argmin(diff, axis=2).to(tl.uint8)
|
| 174 |
+
|
| 175 |
+
quantized_flat = tl.reshape(quantized, (BLOCK_SIZE * N_PER_TH,))
|
| 176 |
+
return quantized_flat, absmax
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
@triton.jit
|
| 180 |
+
def dequant_8bit_blockwise_kernel_util(
|
| 181 |
+
a_ptr,
|
| 182 |
+
offsets,
|
| 183 |
+
code_ptr,
|
| 184 |
+
absmax_ptr,
|
| 185 |
+
mask,
|
| 186 |
+
BLOCK_SIZE: tl.constexpr,
|
| 187 |
+
):
|
| 188 |
+
a = tl.load(a_ptr + offsets, mask, other=0).to(tl.uint8)
|
| 189 |
+
scaled_int8 = tl.load(code_ptr + a, mask)
|
| 190 |
+
# Load scales
|
| 191 |
+
absmax_offsets = offsets // BLOCK_SIZE
|
| 192 |
+
absmax = tl.load(absmax_ptr + absmax_offsets, mask=mask, other=0.0, eviction_policy="evict_last")
|
| 193 |
+
# Apply scales
|
| 194 |
+
out_dq = scaled_int8 * absmax
|
| 195 |
+
return out_dq
|
bitsandbytes/backends/triton/kernels_optim.py
ADDED
|
@@ -0,0 +1,1154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
from typing import Optional
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
import triton
|
| 7 |
+
import triton.language as tl
|
| 8 |
+
|
| 9 |
+
# from triton.language.extra import libdevice
|
| 10 |
+
from .kernels_8bit_quant import (
|
| 11 |
+
dequant_8bit_blockwise,
|
| 12 |
+
dequant_8bit_blockwise_kernel_util,
|
| 13 |
+
quantize_8bit_blockwise_kernel_util,
|
| 14 |
+
quantize_blockwise_triton,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
MOMENTUM = 0
|
| 18 |
+
RMSPROP = 1
|
| 19 |
+
ADAGRAD = 2
|
| 20 |
+
ADAM = 3
|
| 21 |
+
# LION should be larger than MOMENTUM, RMSPROP, ADAGRAD due to comparison in kernels
|
| 22 |
+
LION = 4
|
| 23 |
+
ADEMAMIX = 5
|
| 24 |
+
|
| 25 |
+
name2optimizer_id = {
|
| 26 |
+
"momentum": MOMENTUM,
|
| 27 |
+
"rmsprop": RMSPROP,
|
| 28 |
+
"adagrad": ADAGRAD,
|
| 29 |
+
"adam": ADAM,
|
| 30 |
+
"lion": LION,
|
| 31 |
+
"ademamix": ADEMAMIX,
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
@triton.jit
|
| 36 |
+
def _optimizer_precondition_2state_32bit(
|
| 37 |
+
g_ptr,
|
| 38 |
+
p_ptr,
|
| 39 |
+
state1_ptr,
|
| 40 |
+
state2_ptr,
|
| 41 |
+
unorm_ptr,
|
| 42 |
+
beta1: tl.constexpr,
|
| 43 |
+
beta2: tl.constexpr,
|
| 44 |
+
eps: tl.constexpr,
|
| 45 |
+
weight_decay: tl.constexpr,
|
| 46 |
+
step,
|
| 47 |
+
beta1_step,
|
| 48 |
+
beta2_step,
|
| 49 |
+
lr,
|
| 50 |
+
gnorm_scale: tl.constexpr,
|
| 51 |
+
n_elements,
|
| 52 |
+
OPTIMIZER_ID: tl.constexpr,
|
| 53 |
+
BLOCK_SIZE: tl.constexpr,
|
| 54 |
+
N_PER_TH: tl.constexpr,
|
| 55 |
+
):
|
| 56 |
+
"""Preprocessing optimizer, computing update norm (2-state optimizer)"""
|
| 57 |
+
pid = tl.program_id(axis=0)
|
| 58 |
+
block_start_idx = pid * N_PER_TH
|
| 59 |
+
offsets = block_start_idx * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE * N_PER_TH)
|
| 60 |
+
mask = offsets < n_elements
|
| 61 |
+
|
| 62 |
+
g_vals = tl.load(g_ptr + offsets, mask=mask, other=0.0)
|
| 63 |
+
s1_vals = tl.load(state1_ptr + offsets, mask=mask, other=0.0)
|
| 64 |
+
s2_vals = tl.load(state2_ptr + offsets, mask=mask, other=0.0)
|
| 65 |
+
|
| 66 |
+
g_vals = gnorm_scale * g_vals
|
| 67 |
+
|
| 68 |
+
correction1 = 1.0 / (1.0 - beta1_step)
|
| 69 |
+
correction2 = 1.0 / (1.0 - beta2_step)
|
| 70 |
+
|
| 71 |
+
if OPTIMIZER_ID == 3: # ADAM
|
| 72 |
+
s1_vals = s1_vals * beta1 + (1.0 - beta1) * g_vals
|
| 73 |
+
s2_vals = s2_vals * beta2 + (1.0 - beta2) * g_vals * g_vals
|
| 74 |
+
|
| 75 |
+
s1_vals = s1_vals * correction1
|
| 76 |
+
s2_vals = s2_vals * correction2
|
| 77 |
+
|
| 78 |
+
update_vals = s1_vals / (tl.sqrt(s2_vals) + eps)
|
| 79 |
+
|
| 80 |
+
update_norm = update_vals * update_vals
|
| 81 |
+
|
| 82 |
+
elif OPTIMIZER_ID == 5: # ADEMAMIX
|
| 83 |
+
update_norm = s1_vals
|
| 84 |
+
|
| 85 |
+
total_norm = tl.sum(tl.where(mask, update_norm, 0.0))
|
| 86 |
+
|
| 87 |
+
tl.atomic_add(unorm_ptr, total_norm)
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
@triton.jit
|
| 91 |
+
def _optimizer_precondition_1state_32bit(
|
| 92 |
+
g_ptr,
|
| 93 |
+
p_ptr,
|
| 94 |
+
state1_ptr,
|
| 95 |
+
state2_ptr,
|
| 96 |
+
unorm_ptr,
|
| 97 |
+
beta1: tl.constexpr,
|
| 98 |
+
beta2: tl.constexpr,
|
| 99 |
+
eps: tl.constexpr,
|
| 100 |
+
weight_decay,
|
| 101 |
+
step,
|
| 102 |
+
beta1_step,
|
| 103 |
+
beta2_step,
|
| 104 |
+
lr,
|
| 105 |
+
gnorm_scale: tl.constexpr,
|
| 106 |
+
n_elements,
|
| 107 |
+
OPTIMIZER_ID: tl.constexpr,
|
| 108 |
+
BLOCK_SIZE: tl.constexpr,
|
| 109 |
+
N_PER_TH: tl.constexpr,
|
| 110 |
+
):
|
| 111 |
+
"""Preprocessing optimizer, computing update norm (1-state optimizer)"""
|
| 112 |
+
pid = tl.program_id(axis=0)
|
| 113 |
+
block_start_idx = pid * N_PER_TH
|
| 114 |
+
offsets = block_start_idx * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE * N_PER_TH)
|
| 115 |
+
mask = offsets < n_elements
|
| 116 |
+
|
| 117 |
+
g_vals = tl.load(g_ptr + offsets, mask=mask, other=0.0)
|
| 118 |
+
s1_vals = tl.load(state1_ptr + offsets, mask=mask, other=0.0)
|
| 119 |
+
|
| 120 |
+
g_vals = gnorm_scale * g_vals
|
| 121 |
+
|
| 122 |
+
if OPTIMIZER_ID == 0: # MOMENTUM
|
| 123 |
+
if step == 1:
|
| 124 |
+
s1_vals = g_vals
|
| 125 |
+
else:
|
| 126 |
+
s1_vals = s1_vals * beta1 + g_vals
|
| 127 |
+
update_norm = s1_vals * s1_vals
|
| 128 |
+
|
| 129 |
+
elif OPTIMIZER_ID == 4: # LION
|
| 130 |
+
s1_vals = s1_vals * beta2 + (1.0 - beta2) * g_vals
|
| 131 |
+
update_norm = s1_vals
|
| 132 |
+
|
| 133 |
+
elif OPTIMIZER_ID == 1: # RMSPROP
|
| 134 |
+
s1_vals = s1_vals * beta1 + (1.0 - beta1) * g_vals * g_vals
|
| 135 |
+
update_vals = g_vals / (tl.sqrt(s1_vals) + eps)
|
| 136 |
+
update_norm = update_vals * update_vals
|
| 137 |
+
|
| 138 |
+
elif OPTIMIZER_ID == 2: # ADAGRAD
|
| 139 |
+
s1_vals = s1_vals + g_vals * g_vals
|
| 140 |
+
update_vals = g_vals / (tl.sqrt(s1_vals) + eps)
|
| 141 |
+
update_norm = update_vals * update_vals
|
| 142 |
+
|
| 143 |
+
total_norm = tl.sum(tl.where(mask, update_norm, 0.0))
|
| 144 |
+
|
| 145 |
+
tl.atomic_add(unorm_ptr, total_norm)
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
@triton.jit
|
| 149 |
+
def _optimizer_update_2state_32bit_triton_kernel(
|
| 150 |
+
g_ptr,
|
| 151 |
+
p_ptr,
|
| 152 |
+
state1_ptr,
|
| 153 |
+
state2_ptr,
|
| 154 |
+
unorm_ptr,
|
| 155 |
+
max_unorm: tl.constexpr,
|
| 156 |
+
param_norm,
|
| 157 |
+
beta1: tl.constexpr,
|
| 158 |
+
beta2: tl.constexpr,
|
| 159 |
+
beta3,
|
| 160 |
+
alpha,
|
| 161 |
+
eps: tl.constexpr,
|
| 162 |
+
weight_decay: tl.constexpr,
|
| 163 |
+
step,
|
| 164 |
+
beta1_step,
|
| 165 |
+
beta2_step,
|
| 166 |
+
lr,
|
| 167 |
+
gnorm_scale: tl.constexpr,
|
| 168 |
+
skip_zeros,
|
| 169 |
+
n_elements,
|
| 170 |
+
OPTIMIZER_ID: tl.constexpr,
|
| 171 |
+
BLOCK_SIZE: tl.constexpr,
|
| 172 |
+
N_PER_TH: tl.constexpr,
|
| 173 |
+
):
|
| 174 |
+
"""2-state optimizer kernel"""
|
| 175 |
+
pid = tl.program_id(axis=0)
|
| 176 |
+
block_start_idx = pid * N_PER_TH
|
| 177 |
+
offsets = block_start_idx * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE * N_PER_TH)
|
| 178 |
+
mask = offsets < n_elements
|
| 179 |
+
|
| 180 |
+
g_vals = tl.load(g_ptr + offsets, mask=mask, other=0.0).to(tl.float32)
|
| 181 |
+
p_vals = tl.load(p_ptr + offsets, mask=mask, other=0.0).to(tl.float32)
|
| 182 |
+
s1_vals = tl.load(state1_ptr + offsets, mask=mask, other=0.0)
|
| 183 |
+
s2_vals = tl.load(state2_ptr + offsets, mask=mask, other=0.0)
|
| 184 |
+
|
| 185 |
+
if OPTIMIZER_ID == 5: # ADEMAMIX
|
| 186 |
+
s3_vals = tl.load(state1_ptr + n_elements + offsets, mask=mask, other=0.0)
|
| 187 |
+
|
| 188 |
+
g_vals = gnorm_scale * g_vals
|
| 189 |
+
|
| 190 |
+
update_scale = 1.0
|
| 191 |
+
if max_unorm > 0.0:
|
| 192 |
+
current_unorm = tl.sqrt(tl.load(unorm_ptr))
|
| 193 |
+
if current_unorm > max_unorm * param_norm:
|
| 194 |
+
update_scale = (max_unorm * param_norm) / current_unorm
|
| 195 |
+
|
| 196 |
+
if OPTIMIZER_ID == 3: # ADAM
|
| 197 |
+
s1_vals = s1_vals * beta1 + (1.0 - beta1) * g_vals
|
| 198 |
+
s2_vals = s2_vals * beta2 + (1.0 - beta2) * g_vals * g_vals
|
| 199 |
+
|
| 200 |
+
correction1 = 1.0 - beta1_step
|
| 201 |
+
correction2 = tl.sqrt(1.0 - beta2_step)
|
| 202 |
+
step_size = -lr * correction2 / correction1
|
| 203 |
+
|
| 204 |
+
if weight_decay > 0.0:
|
| 205 |
+
p_vals = p_vals * (1.0 - lr * weight_decay)
|
| 206 |
+
|
| 207 |
+
update_val = update_scale * step_size * (s1_vals / (tl.sqrt(s2_vals) + eps * correction2))
|
| 208 |
+
p_vals = p_vals + update_val
|
| 209 |
+
|
| 210 |
+
elif OPTIMIZER_ID == 5: # ADEMAMIX
|
| 211 |
+
s1_vals = s1_vals * beta1 + (1.0 - beta1) * g_vals # m1
|
| 212 |
+
s3_vals = s3_vals * beta3 + (1.0 - beta3) * g_vals # m2
|
| 213 |
+
s2_vals = s2_vals * beta2 + (1.0 - beta2) * g_vals * g_vals # nu
|
| 214 |
+
|
| 215 |
+
correction1 = 1.0 - beta1_step
|
| 216 |
+
correction2 = tl.sqrt(1.0 - beta2_step)
|
| 217 |
+
|
| 218 |
+
if weight_decay > 0.0:
|
| 219 |
+
p_vals = p_vals * (1.0 - lr * weight_decay)
|
| 220 |
+
|
| 221 |
+
mixed_momentum = (s1_vals / correction1) + (alpha * s3_vals)
|
| 222 |
+
adaptive_term = (tl.sqrt(s2_vals) / correction2) + eps
|
| 223 |
+
p_vals = p_vals - lr * (mixed_momentum / adaptive_term)
|
| 224 |
+
|
| 225 |
+
tl.store(p_ptr + offsets, p_vals, mask=mask)
|
| 226 |
+
tl.store(state1_ptr + offsets, s1_vals, mask=mask)
|
| 227 |
+
tl.store(state2_ptr + offsets, s2_vals, mask=mask)
|
| 228 |
+
|
| 229 |
+
if OPTIMIZER_ID == 5: # ADEMAMIX
|
| 230 |
+
tl.store(state1_ptr + n_elements + offsets, s3_vals, mask=mask)
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
@triton.jit
|
| 234 |
+
def _optimizer_update_1state_32bit_triton_kernel(
|
| 235 |
+
g_ptr,
|
| 236 |
+
p_ptr,
|
| 237 |
+
state1_ptr,
|
| 238 |
+
state2_ptr,
|
| 239 |
+
unorm_ptr,
|
| 240 |
+
max_unorm: tl.constexpr,
|
| 241 |
+
param_norm,
|
| 242 |
+
beta1: tl.constexpr,
|
| 243 |
+
beta2: tl.constexpr,
|
| 244 |
+
beta3,
|
| 245 |
+
alpha,
|
| 246 |
+
eps: tl.constexpr,
|
| 247 |
+
weight_decay: tl.constexpr,
|
| 248 |
+
step,
|
| 249 |
+
beta1_step,
|
| 250 |
+
beta2_step,
|
| 251 |
+
lr,
|
| 252 |
+
gnorm_scale: tl.constexpr,
|
| 253 |
+
skip_zeros,
|
| 254 |
+
n_elements,
|
| 255 |
+
OPTIMIZER_ID: tl.constexpr,
|
| 256 |
+
BLOCK_SIZE: tl.constexpr,
|
| 257 |
+
N_PER_TH: tl.constexpr,
|
| 258 |
+
):
|
| 259 |
+
"""1-state optimizer kernel"""
|
| 260 |
+
pid = tl.program_id(axis=0)
|
| 261 |
+
block_start_idx = pid * N_PER_TH
|
| 262 |
+
offsets = block_start_idx * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE * N_PER_TH)
|
| 263 |
+
mask = offsets < n_elements
|
| 264 |
+
|
| 265 |
+
g_vals = tl.load(g_ptr + offsets, mask=mask, other=0.0).to(tl.float32)
|
| 266 |
+
p_vals = tl.load(p_ptr + offsets, mask=mask, other=0.0).to(tl.float32)
|
| 267 |
+
s1_vals = tl.load(state1_ptr + offsets, mask=mask, other=0.0)
|
| 268 |
+
|
| 269 |
+
g_vals = gnorm_scale * g_vals
|
| 270 |
+
if weight_decay > 0.0:
|
| 271 |
+
g_vals = g_vals + p_vals * weight_decay
|
| 272 |
+
|
| 273 |
+
update_scale = 1.0
|
| 274 |
+
if max_unorm > 0.0:
|
| 275 |
+
current_unorm = tl.sqrt(tl.load(unorm_ptr))
|
| 276 |
+
if current_unorm > max_unorm * param_norm + eps:
|
| 277 |
+
update_scale = (max_unorm * param_norm + eps) / current_unorm
|
| 278 |
+
|
| 279 |
+
if OPTIMIZER_ID == 0: # MOMENTUM
|
| 280 |
+
if step == 1:
|
| 281 |
+
s1_vals = g_vals
|
| 282 |
+
else:
|
| 283 |
+
s1_vals = s1_vals * beta1 + g_vals
|
| 284 |
+
|
| 285 |
+
update_val = update_scale * (-lr * s1_vals)
|
| 286 |
+
p_vals = p_vals + update_val
|
| 287 |
+
|
| 288 |
+
elif OPTIMIZER_ID == 4: # LION
|
| 289 |
+
momentum_update = s1_vals * beta1 + (1.0 - beta1) * g_vals
|
| 290 |
+
update_val = update_scale * lr * tl.where(momentum_update > 0, 1.0, tl.where(momentum_update < 0, -1.0, 0.0))
|
| 291 |
+
p_vals = p_vals - update_val
|
| 292 |
+
|
| 293 |
+
s1_vals = s1_vals * beta2 + (1.0 - beta2) * g_vals
|
| 294 |
+
|
| 295 |
+
elif OPTIMIZER_ID == 1: # RMSPROP
|
| 296 |
+
s1_vals = s1_vals * beta1 + (1.0 - beta1) * g_vals * g_vals
|
| 297 |
+
|
| 298 |
+
update_val = update_scale * lr * g_vals / (tl.sqrt(s1_vals) + eps)
|
| 299 |
+
p_vals = p_vals - update_val
|
| 300 |
+
|
| 301 |
+
elif OPTIMIZER_ID == 2: # ADAGRAD
|
| 302 |
+
s1_vals = s1_vals + g_vals * g_vals
|
| 303 |
+
|
| 304 |
+
update_val = lr * g_vals / (tl.sqrt(s1_vals) + eps)
|
| 305 |
+
p_vals = p_vals - update_val
|
| 306 |
+
|
| 307 |
+
tl.store(p_ptr + offsets, p_vals, mask=mask)
|
| 308 |
+
tl.store(state1_ptr + offsets, s1_vals, mask=mask)
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
name2optimizer_32bit_fn = {
|
| 312 |
+
"adam": {
|
| 313 |
+
"preprocess": _optimizer_precondition_2state_32bit,
|
| 314 |
+
"update": _optimizer_update_2state_32bit_triton_kernel,
|
| 315 |
+
},
|
| 316 |
+
"ademamix": {
|
| 317 |
+
"preprocess": _optimizer_precondition_2state_32bit,
|
| 318 |
+
"update": _optimizer_update_2state_32bit_triton_kernel,
|
| 319 |
+
},
|
| 320 |
+
"momentum": {
|
| 321 |
+
"preprocess": _optimizer_precondition_1state_32bit,
|
| 322 |
+
"update": _optimizer_update_1state_32bit_triton_kernel,
|
| 323 |
+
},
|
| 324 |
+
"rmsprop": {
|
| 325 |
+
"preprocess": _optimizer_precondition_1state_32bit,
|
| 326 |
+
"update": _optimizer_update_1state_32bit_triton_kernel,
|
| 327 |
+
},
|
| 328 |
+
"adagrad": {
|
| 329 |
+
"preprocess": _optimizer_precondition_1state_32bit,
|
| 330 |
+
"update": _optimizer_update_1state_32bit_triton_kernel,
|
| 331 |
+
},
|
| 332 |
+
"lion": {
|
| 333 |
+
"preprocess": _optimizer_precondition_1state_32bit,
|
| 334 |
+
"update": _optimizer_update_1state_32bit_triton_kernel,
|
| 335 |
+
},
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
def optimizer_update_32bit_impl(
|
| 340 |
+
optimizer_name: str,
|
| 341 |
+
g: torch.Tensor,
|
| 342 |
+
p: torch.Tensor,
|
| 343 |
+
state1: torch.Tensor,
|
| 344 |
+
state2: Optional[torch.Tensor],
|
| 345 |
+
unorm_vec: Optional[torch.Tensor],
|
| 346 |
+
max_unorm: float,
|
| 347 |
+
param_norm: float,
|
| 348 |
+
beta1: float,
|
| 349 |
+
beta2: float,
|
| 350 |
+
beta3: float,
|
| 351 |
+
alpha: float,
|
| 352 |
+
eps: float,
|
| 353 |
+
weight_decay: float,
|
| 354 |
+
step: int,
|
| 355 |
+
lr: float,
|
| 356 |
+
gnorm_scale: float = 1.0,
|
| 357 |
+
skip_zeros=False,
|
| 358 |
+
) -> None:
|
| 359 |
+
"""
|
| 360 |
+
32-bit optimizer implemented by Triton
|
| 361 |
+
"""
|
| 362 |
+
if skip_zeros:
|
| 363 |
+
raise NotImplementedError("skip_zeros is not supported on XPU yet")
|
| 364 |
+
|
| 365 |
+
BLOCK_SIZE = 256
|
| 366 |
+
N_PER_TH = 1 # Number of blocks processed per thread.
|
| 367 |
+
grid = (triton.cdiv(p.numel(), BLOCK_SIZE * N_PER_TH),)
|
| 368 |
+
optimizer_id = name2optimizer_id[optimizer_name]
|
| 369 |
+
fn_preprocess = name2optimizer_32bit_fn[optimizer_name]["preprocess"]
|
| 370 |
+
fn_update = name2optimizer_32bit_fn[optimizer_name]["update"]
|
| 371 |
+
|
| 372 |
+
# In torch=2.7 on XPU there is an issue with libdevice.pow, leading to an error.
|
| 373 |
+
# For backwards compatibility we precompute the bias correction factors.
|
| 374 |
+
beta1_step = beta1**step
|
| 375 |
+
beta2_step = beta2**step
|
| 376 |
+
|
| 377 |
+
if optimizer_name == "lion":
|
| 378 |
+
fn_update[grid](
|
| 379 |
+
g,
|
| 380 |
+
p,
|
| 381 |
+
state1,
|
| 382 |
+
state2,
|
| 383 |
+
unorm_vec,
|
| 384 |
+
max_unorm,
|
| 385 |
+
param_norm,
|
| 386 |
+
beta1,
|
| 387 |
+
beta2,
|
| 388 |
+
beta3,
|
| 389 |
+
alpha,
|
| 390 |
+
eps,
|
| 391 |
+
weight_decay,
|
| 392 |
+
step,
|
| 393 |
+
beta1_step,
|
| 394 |
+
beta2_step,
|
| 395 |
+
lr,
|
| 396 |
+
gnorm_scale,
|
| 397 |
+
skip_zeros,
|
| 398 |
+
p.numel(),
|
| 399 |
+
optimizer_id,
|
| 400 |
+
BLOCK_SIZE,
|
| 401 |
+
N_PER_TH,
|
| 402 |
+
num_warps=2,
|
| 403 |
+
)
|
| 404 |
+
|
| 405 |
+
if max_unorm > 0.0:
|
| 406 |
+
unorm_vec.zero_()
|
| 407 |
+
fn_preprocess[grid](
|
| 408 |
+
g,
|
| 409 |
+
p,
|
| 410 |
+
state1,
|
| 411 |
+
state2,
|
| 412 |
+
unorm_vec,
|
| 413 |
+
beta1,
|
| 414 |
+
beta2,
|
| 415 |
+
eps,
|
| 416 |
+
weight_decay,
|
| 417 |
+
step,
|
| 418 |
+
beta1_step,
|
| 419 |
+
beta2_step,
|
| 420 |
+
lr,
|
| 421 |
+
gnorm_scale,
|
| 422 |
+
p.numel(),
|
| 423 |
+
optimizer_id,
|
| 424 |
+
BLOCK_SIZE,
|
| 425 |
+
N_PER_TH,
|
| 426 |
+
num_warps=2,
|
| 427 |
+
)
|
| 428 |
+
|
| 429 |
+
else:
|
| 430 |
+
if max_unorm > 0.0:
|
| 431 |
+
unorm_vec.zero_()
|
| 432 |
+
fn_preprocess[grid](
|
| 433 |
+
g,
|
| 434 |
+
p,
|
| 435 |
+
state1,
|
| 436 |
+
state2,
|
| 437 |
+
unorm_vec,
|
| 438 |
+
beta1,
|
| 439 |
+
beta2,
|
| 440 |
+
eps,
|
| 441 |
+
weight_decay,
|
| 442 |
+
step,
|
| 443 |
+
beta1_step,
|
| 444 |
+
beta2_step,
|
| 445 |
+
lr,
|
| 446 |
+
gnorm_scale,
|
| 447 |
+
p.numel(),
|
| 448 |
+
optimizer_id,
|
| 449 |
+
BLOCK_SIZE,
|
| 450 |
+
N_PER_TH,
|
| 451 |
+
num_warps=2,
|
| 452 |
+
)
|
| 453 |
+
|
| 454 |
+
fn_update[grid](
|
| 455 |
+
g,
|
| 456 |
+
p,
|
| 457 |
+
state1,
|
| 458 |
+
state2,
|
| 459 |
+
unorm_vec,
|
| 460 |
+
max_unorm,
|
| 461 |
+
param_norm,
|
| 462 |
+
beta1,
|
| 463 |
+
beta2,
|
| 464 |
+
beta3,
|
| 465 |
+
alpha,
|
| 466 |
+
eps,
|
| 467 |
+
weight_decay,
|
| 468 |
+
step,
|
| 469 |
+
beta1_step,
|
| 470 |
+
beta2_step,
|
| 471 |
+
lr,
|
| 472 |
+
gnorm_scale,
|
| 473 |
+
skip_zeros,
|
| 474 |
+
p.numel(),
|
| 475 |
+
optimizer_id,
|
| 476 |
+
BLOCK_SIZE,
|
| 477 |
+
N_PER_TH,
|
| 478 |
+
num_warps=2,
|
| 479 |
+
)
|
| 480 |
+
|
| 481 |
+
|
| 482 |
+
###########################################
|
| 483 |
+
# Pure torch implementation for reference #
|
| 484 |
+
###########################################
|
| 485 |
+
|
| 486 |
+
|
| 487 |
+
@torch.compile
|
| 488 |
+
def _dequantize_blockwise_pytorch(
|
| 489 |
+
A: torch.Tensor,
|
| 490 |
+
absmax: torch.Tensor,
|
| 491 |
+
code: torch.Tensor,
|
| 492 |
+
blocksize: int,
|
| 493 |
+
dtype: torch.dtype,
|
| 494 |
+
) -> torch.Tensor:
|
| 495 |
+
"""
|
| 496 |
+
Pure PyTorch reference implementation for block-wise dequantization.
|
| 497 |
+
"""
|
| 498 |
+
if A.numel() == 0:
|
| 499 |
+
return torch.empty_like(A, dtype=dtype)
|
| 500 |
+
|
| 501 |
+
A_flat = A.flatten()
|
| 502 |
+
num_elements = A_flat.numel()
|
| 503 |
+
|
| 504 |
+
dequantized_flat = code.to(A.device)[A_flat.long()].to(dtype)
|
| 505 |
+
|
| 506 |
+
num_blocks = math.ceil(num_elements / blocksize)
|
| 507 |
+
pad_len = num_blocks * blocksize - num_elements
|
| 508 |
+
if pad_len > 0:
|
| 509 |
+
dequantized_flat = torch.nn.functional.pad(dequantized_flat, (0, pad_len))
|
| 510 |
+
|
| 511 |
+
dequantized_blocks = dequantized_flat.reshape(num_blocks, blocksize)
|
| 512 |
+
|
| 513 |
+
rescaled_blocks = dequantized_blocks * absmax.unsqueeze(1).to(dtype)
|
| 514 |
+
|
| 515 |
+
rescaled_flat = rescaled_blocks.flatten()
|
| 516 |
+
if pad_len > 0:
|
| 517 |
+
rescaled_flat = rescaled_flat[:-pad_len]
|
| 518 |
+
|
| 519 |
+
return rescaled_flat.reshape(A.shape)
|
| 520 |
+
|
| 521 |
+
|
| 522 |
+
@torch.compile
|
| 523 |
+
def _quantize_blockwise_pytorch(
|
| 524 |
+
A: torch.Tensor,
|
| 525 |
+
code: torch.Tensor,
|
| 526 |
+
blocksize: int,
|
| 527 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 528 |
+
"""
|
| 529 |
+
Pure PyTorch reference implementation for block-wise quantization.
|
| 530 |
+
"""
|
| 531 |
+
if A.numel() == 0:
|
| 532 |
+
return torch.empty_like(A, dtype=torch.uint8), torch.empty(0, dtype=torch.float32, device=A.device)
|
| 533 |
+
|
| 534 |
+
A_flat = A.flatten()
|
| 535 |
+
num_elements = A_flat.numel()
|
| 536 |
+
|
| 537 |
+
num_blocks = math.ceil(num_elements / blocksize)
|
| 538 |
+
|
| 539 |
+
pad_len = num_blocks * blocksize - num_elements
|
| 540 |
+
if pad_len > 0:
|
| 541 |
+
A_flat = torch.nn.functional.pad(A_flat, (0, pad_len))
|
| 542 |
+
|
| 543 |
+
A_blocks = A_flat.reshape(num_blocks, blocksize)
|
| 544 |
+
|
| 545 |
+
absmax = torch.max(torch.abs(A_blocks), dim=1, keepdim=True)[0]
|
| 546 |
+
absmax[absmax == 0] = 1.0
|
| 547 |
+
|
| 548 |
+
scaled_blocks = A_blocks / absmax
|
| 549 |
+
|
| 550 |
+
# Inefficient but straightforward quantization, takes a lot of memory
|
| 551 |
+
diff = torch.abs(scaled_blocks.unsqueeze(2) - code.to(A.device))
|
| 552 |
+
quantized_indices = torch.argmin(diff, dim=2).to(torch.uint8)
|
| 553 |
+
|
| 554 |
+
quantized_flat = quantized_indices.flatten()
|
| 555 |
+
if pad_len > 0:
|
| 556 |
+
quantized_flat = quantized_flat[:-pad_len]
|
| 557 |
+
|
| 558 |
+
return quantized_flat.reshape(A.shape), absmax.flatten()
|
| 559 |
+
|
| 560 |
+
|
| 561 |
+
# Main updated function
|
| 562 |
+
def optimizer_update_8bit_blockwise_pytorch(
|
| 563 |
+
p: torch.Tensor,
|
| 564 |
+
g: torch.Tensor,
|
| 565 |
+
state1: torch.Tensor,
|
| 566 |
+
state2: Optional[torch.Tensor],
|
| 567 |
+
beta1: float,
|
| 568 |
+
beta2: float,
|
| 569 |
+
beta3: float, # ADEMIX
|
| 570 |
+
alpha: float, # ADEMIX
|
| 571 |
+
eps: float,
|
| 572 |
+
step: int,
|
| 573 |
+
lr: float,
|
| 574 |
+
qmap1: torch.Tensor,
|
| 575 |
+
qmap2: Optional[torch.Tensor],
|
| 576 |
+
absmax1: torch.Tensor,
|
| 577 |
+
absmax2: Optional[torch.Tensor],
|
| 578 |
+
weight_decay: float,
|
| 579 |
+
gnorm_scale: float,
|
| 580 |
+
skip_zeros: bool,
|
| 581 |
+
# ADEMIX
|
| 582 |
+
*,
|
| 583 |
+
optimizer_name: str,
|
| 584 |
+
) -> None:
|
| 585 |
+
"""
|
| 586 |
+
Pure PyTorch implementation of the 8-bit block-wise optimizer update step.
|
| 587 |
+
This version ensures high-precision updates for float16 parameters.
|
| 588 |
+
"""
|
| 589 |
+
if skip_zeros:
|
| 590 |
+
raise ValueError("skip_zeros is not supported on XPU yet.")
|
| 591 |
+
|
| 592 |
+
blocksize = 256
|
| 593 |
+
|
| 594 |
+
with torch.no_grad():
|
| 595 |
+
# Dequantize states to perform updates in 32-bit precision
|
| 596 |
+
if optimizer_name == "ademamix" and absmax1.ndim == 2:
|
| 597 |
+
# For AdEMAMix, state1 holds two EMAs, so absmax1 is stacked.
|
| 598 |
+
s1_1_fp32 = _dequantize_blockwise_pytorch(state1[0], absmax1[0], qmap1, blocksize, torch.float32)
|
| 599 |
+
s1_2_fp32 = _dequantize_blockwise_pytorch(state1[1], absmax1[1], qmap1, blocksize, torch.float32)
|
| 600 |
+
state1_fp32 = torch.stack([s1_1_fp32, s1_2_fp32])
|
| 601 |
+
else:
|
| 602 |
+
state1_fp32 = _dequantize_blockwise_pytorch(state1, absmax1, qmap1, blocksize, torch.float32)
|
| 603 |
+
|
| 604 |
+
state2_fp32 = None
|
| 605 |
+
if state2 is not None:
|
| 606 |
+
state2_fp32 = _dequantize_blockwise_pytorch(state2, absmax2, qmap2, blocksize, torch.float32)
|
| 607 |
+
|
| 608 |
+
grad = g.float() * gnorm_scale
|
| 609 |
+
|
| 610 |
+
# Create a 32-bit copy of the parameter for high-precision updates
|
| 611 |
+
p_fp32 = p.data.float()
|
| 612 |
+
|
| 613 |
+
if optimizer_name == "adam":
|
| 614 |
+
state1_fp32.mul_(beta1).add_(grad, alpha=1.0 - beta1)
|
| 615 |
+
state2_fp32.mul_(beta2).addcmul_(grad, grad, value=1.0 - beta2)
|
| 616 |
+
|
| 617 |
+
bias_correction1 = 1.0 - beta1**step
|
| 618 |
+
bias_correction2 = 1.0 - beta2**step
|
| 619 |
+
|
| 620 |
+
denom = (state2_fp32.sqrt() / math.sqrt(bias_correction2)).add_(eps)
|
| 621 |
+
|
| 622 |
+
if weight_decay > 0.0:
|
| 623 |
+
p_fp32.mul_(1.0 - lr * weight_decay)
|
| 624 |
+
p_fp32.addcdiv_(state1_fp32, denom, value=-lr / bias_correction1)
|
| 625 |
+
|
| 626 |
+
elif optimizer_name == "ademamix":
|
| 627 |
+
m1_fp32, m2_fp32 = state1_fp32[0], state1_fp32[1]
|
| 628 |
+
nu_fp32 = state2_fp32
|
| 629 |
+
|
| 630 |
+
m1_fp32.mul_(beta1).add_(grad, alpha=1.0 - beta1)
|
| 631 |
+
m2_fp32.mul_(beta3).add_(grad, alpha=1.0 - beta3)
|
| 632 |
+
nu_fp32.mul_(beta2).addcmul_(grad, grad, value=1.0 - beta2)
|
| 633 |
+
|
| 634 |
+
bias_correction1 = 1.0 - beta1**step
|
| 635 |
+
bias_correction2 = math.sqrt(1.0 - beta2**step)
|
| 636 |
+
|
| 637 |
+
update = (m1_fp32 / bias_correction1 + alpha * m2_fp32) / (nu_fp32.sqrt() / bias_correction2 + eps)
|
| 638 |
+
|
| 639 |
+
if weight_decay > 0.0:
|
| 640 |
+
p_fp32.mul_(1.0 - lr * weight_decay)
|
| 641 |
+
|
| 642 |
+
p_fp32.add_(update, alpha=-lr)
|
| 643 |
+
state1_fp32 = torch.stack([m1_fp32, m2_fp32])
|
| 644 |
+
|
| 645 |
+
elif optimizer_name == "momentum":
|
| 646 |
+
grad.add_(p_fp32, alpha=weight_decay)
|
| 647 |
+
if step == 1:
|
| 648 |
+
state1_fp32.copy_(grad)
|
| 649 |
+
else:
|
| 650 |
+
state1_fp32.mul_(beta1).add_(grad)
|
| 651 |
+
p_fp32.add_(state1_fp32, alpha=-lr)
|
| 652 |
+
|
| 653 |
+
elif optimizer_name == "rmsprop":
|
| 654 |
+
grad.add_(p_fp32, alpha=weight_decay)
|
| 655 |
+
state1_fp32.mul_(beta1).addcmul_(grad, grad, value=1.0 - beta1)
|
| 656 |
+
p_fp32.addcdiv_(grad, state1_fp32.sqrt().add_(eps), value=-lr)
|
| 657 |
+
|
| 658 |
+
elif optimizer_name == "lion":
|
| 659 |
+
if weight_decay > 0.0:
|
| 660 |
+
p_fp32.mul_(1.0 - lr * weight_decay)
|
| 661 |
+
|
| 662 |
+
update_dir = torch.sign(state1_fp32.mul(beta1) + grad.mul(1.0 - beta1))
|
| 663 |
+
p_fp32.add_(update_dir, alpha=-lr)
|
| 664 |
+
|
| 665 |
+
state1_fp32.mul_(beta2).add_(grad, alpha=1.0 - beta2)
|
| 666 |
+
|
| 667 |
+
elif optimizer_name == "adagrad":
|
| 668 |
+
grad.add_(p_fp32, alpha=weight_decay)
|
| 669 |
+
state1_fp32.addcmul_(grad, grad, value=1.0)
|
| 670 |
+
p_fp32.addcdiv_(grad, state1_fp32.sqrt().add_(eps), value=-lr)
|
| 671 |
+
|
| 672 |
+
else:
|
| 673 |
+
raise NotImplementedError(
|
| 674 |
+
f"Pure PyTorch implementation for optimizer '{optimizer_name}' is not available."
|
| 675 |
+
)
|
| 676 |
+
|
| 677 |
+
# Copy the updated 32-bit parameter back to the original tensor
|
| 678 |
+
p.data.copy_(p_fp32)
|
| 679 |
+
|
| 680 |
+
# Re-quantize states and update state tensors in-place
|
| 681 |
+
if optimizer_name == "ademamix":
|
| 682 |
+
new_m1_8bit, new_absmax_m1 = _quantize_blockwise_pytorch(state1_fp32[0], qmap1, blocksize)
|
| 683 |
+
new_m2_8bit, new_absmax_m2 = _quantize_blockwise_pytorch(state1_fp32[1], qmap1, blocksize)
|
| 684 |
+
state1[0].copy_(new_m1_8bit)
|
| 685 |
+
state1[1].copy_(new_m2_8bit)
|
| 686 |
+
absmax1[0].copy_(new_absmax_m1)
|
| 687 |
+
absmax1[1].copy_(new_absmax_m2)
|
| 688 |
+
|
| 689 |
+
new_state2_8bit, new_absmax2 = _quantize_blockwise_pytorch(state2_fp32, qmap2, blocksize)
|
| 690 |
+
state2.copy_(new_state2_8bit)
|
| 691 |
+
absmax2.copy_(new_absmax2)
|
| 692 |
+
else:
|
| 693 |
+
new_state1_8bit, new_absmax1 = _quantize_blockwise_pytorch(state1_fp32, qmap1, blocksize)
|
| 694 |
+
state1.copy_(new_state1_8bit)
|
| 695 |
+
absmax1.copy_(new_absmax1)
|
| 696 |
+
|
| 697 |
+
if state2_fp32 is not None:
|
| 698 |
+
new_state2_8bit, new_absmax2 = _quantize_blockwise_pytorch(state2_fp32, qmap2, blocksize)
|
| 699 |
+
state2.copy_(new_state2_8bit)
|
| 700 |
+
absmax2.copy_(new_absmax2)
|
| 701 |
+
|
| 702 |
+
|
| 703 |
+
#######################################
|
| 704 |
+
# Mixed torch + triton implementation #
|
| 705 |
+
#######################################
|
| 706 |
+
|
| 707 |
+
|
| 708 |
+
# Much more memory efficient due to using triton for quantization/dequantization
|
| 709 |
+
def optimizer_update_8bit_blockwise_triton_quant(
|
| 710 |
+
p: torch.Tensor,
|
| 711 |
+
g: torch.Tensor,
|
| 712 |
+
state1: torch.Tensor,
|
| 713 |
+
state2: Optional[torch.Tensor],
|
| 714 |
+
beta1: float,
|
| 715 |
+
beta2: float,
|
| 716 |
+
beta3: float, # ADEMIX
|
| 717 |
+
alpha: float, # ADEMIX
|
| 718 |
+
eps: float,
|
| 719 |
+
step: int,
|
| 720 |
+
lr: float,
|
| 721 |
+
qmap1: torch.Tensor,
|
| 722 |
+
qmap2: Optional[torch.Tensor],
|
| 723 |
+
absmax1: torch.Tensor,
|
| 724 |
+
absmax2: Optional[torch.Tensor],
|
| 725 |
+
weight_decay: float,
|
| 726 |
+
gnorm_scale: float,
|
| 727 |
+
skip_zeros: bool,
|
| 728 |
+
# ADEMIX
|
| 729 |
+
*,
|
| 730 |
+
optimizer_name: str,
|
| 731 |
+
) -> None:
|
| 732 |
+
"""
|
| 733 |
+
Pure PyTorch implementation of the 8-bit block-wise optimizer update step.
|
| 734 |
+
This version ensures high-precision updates for float16 parameters.
|
| 735 |
+
"""
|
| 736 |
+
if skip_zeros and not torch.any(g):
|
| 737 |
+
return
|
| 738 |
+
|
| 739 |
+
blocksize = 256
|
| 740 |
+
grad = g.float() * gnorm_scale
|
| 741 |
+
|
| 742 |
+
with torch.no_grad():
|
| 743 |
+
# Create a 32-bit copy of the parameter for high-precision updates
|
| 744 |
+
p_fp32 = p.data.float()
|
| 745 |
+
|
| 746 |
+
# Dequantize states to perform updates in 32-bit precision
|
| 747 |
+
if optimizer_name == "ademamix" and absmax1.ndim == 2:
|
| 748 |
+
# For AdEMAMix, state1 holds two EMAs, so absmax1 is stacked.
|
| 749 |
+
s1_1_fp32 = dequant_8bit_blockwise(state1[0], absmax1[0], qmap1, blocksize, dtype=torch.float32)
|
| 750 |
+
s1_2_fp32 = dequant_8bit_blockwise(state1[1], absmax1[1], qmap1, blocksize, dtype=torch.float32)
|
| 751 |
+
state1_fp32 = torch.stack([s1_1_fp32, s1_2_fp32])
|
| 752 |
+
else:
|
| 753 |
+
state1_fp32 = dequant_8bit_blockwise(state1, absmax1, qmap1, blocksize, dtype=torch.float32)
|
| 754 |
+
|
| 755 |
+
state2_fp32 = None
|
| 756 |
+
if state2 is not None:
|
| 757 |
+
state2_fp32 = dequant_8bit_blockwise(state2, absmax2, qmap2, blocksize, dtype=torch.float32)
|
| 758 |
+
|
| 759 |
+
# Apply optimizer-specific update logic
|
| 760 |
+
if optimizer_name == "adam":
|
| 761 |
+
if weight_decay > 0.0:
|
| 762 |
+
p_fp32.mul_(1.0 - lr * weight_decay)
|
| 763 |
+
|
| 764 |
+
state1_fp32.mul_(beta1).add_(grad, alpha=1.0 - beta1)
|
| 765 |
+
state2_fp32.mul_(beta2).addcmul_(grad, grad, value=1.0 - beta2)
|
| 766 |
+
|
| 767 |
+
bias_correction1 = 1.0 - beta1**step
|
| 768 |
+
bias_correction2 = 1.0 - beta2**step
|
| 769 |
+
|
| 770 |
+
denom = (state2_fp32.sqrt() / math.sqrt(bias_correction2)).add_(eps)
|
| 771 |
+
p_fp32.addcdiv_(state1_fp32, denom, value=-lr / bias_correction1)
|
| 772 |
+
|
| 773 |
+
elif optimizer_name == "ademamix":
|
| 774 |
+
m1_fp32, m2_fp32 = state1_fp32[0], state1_fp32[1]
|
| 775 |
+
nu_fp32 = state2_fp32
|
| 776 |
+
|
| 777 |
+
m1_fp32.mul_(beta1).add_(grad, alpha=1.0 - beta1)
|
| 778 |
+
m2_fp32.mul_(beta3).add_(grad, alpha=1.0 - beta3)
|
| 779 |
+
nu_fp32.mul_(beta2).addcmul_(grad, grad, value=1.0 - beta2)
|
| 780 |
+
|
| 781 |
+
bias_correction1 = 1.0 - beta1**step
|
| 782 |
+
bias_correction2 = math.sqrt(1.0 - beta2**step)
|
| 783 |
+
|
| 784 |
+
update = (m1_fp32 / bias_correction1 + alpha * m2_fp32) / (nu_fp32.sqrt() / bias_correction2 + eps)
|
| 785 |
+
|
| 786 |
+
if weight_decay > 0.0:
|
| 787 |
+
p_fp32.mul_(1.0 - lr * weight_decay)
|
| 788 |
+
|
| 789 |
+
p_fp32.add_(update, alpha=-lr)
|
| 790 |
+
state1_fp32 = torch.stack([m1_fp32, m2_fp32])
|
| 791 |
+
|
| 792 |
+
elif optimizer_name == "momentum":
|
| 793 |
+
grad.add_(p_fp32, alpha=weight_decay)
|
| 794 |
+
if step == 1:
|
| 795 |
+
state1_fp32.copy_(grad)
|
| 796 |
+
else:
|
| 797 |
+
state1_fp32.mul_(beta1).add_(grad)
|
| 798 |
+
p_fp32.add_(state1_fp32, alpha=-lr)
|
| 799 |
+
|
| 800 |
+
elif optimizer_name == "rmsprop":
|
| 801 |
+
grad.add_(p_fp32, alpha=weight_decay)
|
| 802 |
+
state1_fp32.mul_(beta1).addcmul_(grad, grad, value=1.0 - beta1)
|
| 803 |
+
p_fp32.addcdiv_(grad, state1_fp32.sqrt().add_(eps), value=-lr)
|
| 804 |
+
|
| 805 |
+
elif optimizer_name == "lion":
|
| 806 |
+
if weight_decay > 0.0:
|
| 807 |
+
p_fp32.mul_(1.0 - lr * weight_decay)
|
| 808 |
+
|
| 809 |
+
update_dir = torch.sign(state1_fp32.mul(beta1) + grad.mul(1.0 - beta1))
|
| 810 |
+
p_fp32.add_(update_dir, alpha=-lr)
|
| 811 |
+
|
| 812 |
+
state1_fp32.mul_(beta2).add_(grad, alpha=1.0 - beta2)
|
| 813 |
+
|
| 814 |
+
elif optimizer_name == "adagrad":
|
| 815 |
+
grad.add_(p_fp32, alpha=weight_decay)
|
| 816 |
+
state1_fp32.addcmul_(grad, grad, value=1.0)
|
| 817 |
+
p_fp32.addcdiv_(grad, state1_fp32.sqrt().add_(eps), value=-lr)
|
| 818 |
+
|
| 819 |
+
else:
|
| 820 |
+
raise NotImplementedError(
|
| 821 |
+
f"Pure PyTorch implementation for optimizer '{optimizer_name}' is not available."
|
| 822 |
+
)
|
| 823 |
+
|
| 824 |
+
# Copy the updated 32-bit parameter back to the original tensor
|
| 825 |
+
p.data.copy_(p_fp32)
|
| 826 |
+
|
| 827 |
+
# Re-quantize states and update state tensors in-place
|
| 828 |
+
if optimizer_name == "ademamix":
|
| 829 |
+
new_m1_8bit, new_absmax_m1 = quantize_blockwise_triton(state1_fp32[0], qmap1, blocksize)
|
| 830 |
+
new_m2_8bit, new_absmax_m2 = quantize_blockwise_triton(state1_fp32[1], qmap1, blocksize)
|
| 831 |
+
state1[0].copy_(new_m1_8bit)
|
| 832 |
+
state1[1].copy_(new_m2_8bit)
|
| 833 |
+
absmax1[0].copy_(new_absmax_m1)
|
| 834 |
+
absmax1[1].copy_(new_absmax_m2)
|
| 835 |
+
|
| 836 |
+
new_state2_8bit, new_absmax2 = quantize_blockwise_triton(state2_fp32, qmap2, blocksize)
|
| 837 |
+
state2.copy_(new_state2_8bit)
|
| 838 |
+
absmax2.copy_(new_absmax2)
|
| 839 |
+
else:
|
| 840 |
+
new_state1_8bit, new_absmax1 = quantize_blockwise_triton(state1_fp32, qmap1, blocksize)
|
| 841 |
+
state1.copy_(new_state1_8bit)
|
| 842 |
+
absmax1.copy_(new_absmax1)
|
| 843 |
+
|
| 844 |
+
if state2_fp32 is not None:
|
| 845 |
+
new_state2_8bit, new_absmax2 = quantize_blockwise_triton(state2_fp32, qmap2, blocksize)
|
| 846 |
+
state2.copy_(new_state2_8bit)
|
| 847 |
+
absmax2.copy_(new_absmax2)
|
| 848 |
+
|
| 849 |
+
|
| 850 |
+
#########################
|
| 851 |
+
# Triton implementation #
|
| 852 |
+
#########################
|
| 853 |
+
|
| 854 |
+
|
| 855 |
+
@triton.jit
|
| 856 |
+
def _optimizer_update_1state_8bit_blockwise_triton_kernel(
|
| 857 |
+
# Tensors
|
| 858 |
+
p_ptr,
|
| 859 |
+
g_ptr,
|
| 860 |
+
state1_ptr,
|
| 861 |
+
state2_ptr,
|
| 862 |
+
beta1: tl.constexpr,
|
| 863 |
+
beta2: tl.constexpr,
|
| 864 |
+
beta3,
|
| 865 |
+
alpha,
|
| 866 |
+
eps: tl.constexpr,
|
| 867 |
+
step,
|
| 868 |
+
beta1_step,
|
| 869 |
+
beta2_step,
|
| 870 |
+
lr,
|
| 871 |
+
qmap1_ptr,
|
| 872 |
+
qmap2_ptr,
|
| 873 |
+
absmax1_ptr,
|
| 874 |
+
absmax2_ptr,
|
| 875 |
+
weight_decay,
|
| 876 |
+
gnorm_scale,
|
| 877 |
+
# Meta-parameters
|
| 878 |
+
n_elements,
|
| 879 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 880 |
+
N_PER_TH: tl.constexpr,
|
| 881 |
+
OPTIMIZER_ID: tl.constexpr,
|
| 882 |
+
):
|
| 883 |
+
"""
|
| 884 |
+
Triton kernel for 8-bit optimizers that use one momentum state.
|
| 885 |
+
Supports: Momentum, RMSprop, Adagrad, Lion.
|
| 886 |
+
"""
|
| 887 |
+
# 1. Boilerplate: pid, offsets, mask
|
| 888 |
+
pid = tl.program_id(axis=0)
|
| 889 |
+
block_start_idx = pid * N_PER_TH
|
| 890 |
+
offsets = block_start_idx * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N * N_PER_TH)
|
| 891 |
+
mask = offsets < n_elements
|
| 892 |
+
|
| 893 |
+
# 2. Load and dequantize tensors
|
| 894 |
+
g = tl.load(g_ptr + offsets, mask=mask, other=0.0).to(tl.float32) * gnorm_scale
|
| 895 |
+
p = tl.load(p_ptr + offsets, mask=mask, other=0.0).to(tl.float32)
|
| 896 |
+
s1 = dequant_8bit_blockwise_kernel_util(state1_ptr, offsets, qmap1_ptr, absmax1_ptr, mask, BLOCK_SIZE_N)
|
| 897 |
+
|
| 898 |
+
# 3. Optimizer-specific updates
|
| 899 |
+
# LION
|
| 900 |
+
if weight_decay > 0.0 and OPTIMIZER_ID == 2:
|
| 901 |
+
p *= 1.0 - lr * weight_decay
|
| 902 |
+
# Apply weight decay for momentum, rmsprop, adagrad
|
| 903 |
+
elif weight_decay > 0.0:
|
| 904 |
+
g += p * weight_decay
|
| 905 |
+
|
| 906 |
+
# Momentum update
|
| 907 |
+
if OPTIMIZER_ID == 0: # MOMENTUM
|
| 908 |
+
if step == 1:
|
| 909 |
+
s1 = g
|
| 910 |
+
else:
|
| 911 |
+
s1 = s1 * beta1 + g
|
| 912 |
+
p -= lr * s1
|
| 913 |
+
|
| 914 |
+
# RMSprop update
|
| 915 |
+
elif OPTIMIZER_ID == 1: # RMSPROP
|
| 916 |
+
s1 = s1 * beta1 + (1.0 - beta1) * g * g
|
| 917 |
+
p -= lr * (g / (tl.sqrt(s1) + eps))
|
| 918 |
+
|
| 919 |
+
# Adagrad update
|
| 920 |
+
elif OPTIMIZER_ID == 2: # ADAGRAD
|
| 921 |
+
s1 += g * g
|
| 922 |
+
p -= lr * (g / (tl.sqrt(s1) + eps))
|
| 923 |
+
|
| 924 |
+
# Lion update
|
| 925 |
+
elif OPTIMIZER_ID == 4: # LION
|
| 926 |
+
val = s1 * beta1 + (1.0 - beta1) * g
|
| 927 |
+
update = tl.where(val > 0.0, 1.0, tl.where(val < 0.0, -1.0, 0.0))
|
| 928 |
+
p -= lr * update
|
| 929 |
+
s1 = s1 * beta2 + (1.0 - beta2) * g
|
| 930 |
+
|
| 931 |
+
# 4. Store updated parameter and requantized state
|
| 932 |
+
tl.store(p_ptr + offsets, p.to(p_ptr.dtype.element_ty), mask=mask)
|
| 933 |
+
s1_codes, new_absmax1 = quantize_8bit_blockwise_kernel_util(s1, qmap1_ptr, 256, BLOCK_SIZE_N, N_PER_TH)
|
| 934 |
+
tl.store(state1_ptr + offsets, s1_codes, mask=mask)
|
| 935 |
+
tl.store(absmax1_ptr + block_start_idx + tl.arange(0, N_PER_TH), new_absmax1)
|
| 936 |
+
|
| 937 |
+
|
| 938 |
+
@triton.jit
|
| 939 |
+
def _optimizer_update_2state_8bit_blockwise_triton_kernel(
|
| 940 |
+
# Tensors
|
| 941 |
+
p_ptr,
|
| 942 |
+
g_ptr,
|
| 943 |
+
state1_ptr,
|
| 944 |
+
state2_ptr,
|
| 945 |
+
beta1: tl.constexpr,
|
| 946 |
+
beta2: tl.constexpr,
|
| 947 |
+
# ademamix changes alpha and beta3
|
| 948 |
+
beta3,
|
| 949 |
+
# ademamix changes alpha and beta3
|
| 950 |
+
alpha,
|
| 951 |
+
eps: tl.constexpr,
|
| 952 |
+
step,
|
| 953 |
+
beta1_step,
|
| 954 |
+
beta2_step,
|
| 955 |
+
lr,
|
| 956 |
+
qmap1_ptr,
|
| 957 |
+
qmap2_ptr,
|
| 958 |
+
absmax1_ptr,
|
| 959 |
+
absmax2_ptr,
|
| 960 |
+
weight_decay: tl.constexpr,
|
| 961 |
+
gnorm_scale: tl.constexpr,
|
| 962 |
+
# Meta-parameters
|
| 963 |
+
n_elements,
|
| 964 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 965 |
+
N_PER_TH: tl.constexpr,
|
| 966 |
+
OPTIMIZER_ID: tl.constexpr,
|
| 967 |
+
):
|
| 968 |
+
"""
|
| 969 |
+
Triton kernel for 8-bit optimizers that use two momentum states.
|
| 970 |
+
Supports: Adam, AdEMAMix.
|
| 971 |
+
"""
|
| 972 |
+
# 1. Boilerplate: pid, offsets, mask
|
| 973 |
+
pid = tl.program_id(axis=0)
|
| 974 |
+
block_start_idx = pid * N_PER_TH
|
| 975 |
+
offsets = block_start_idx * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N * N_PER_TH)
|
| 976 |
+
mask = offsets < n_elements
|
| 977 |
+
|
| 978 |
+
# 2. Load and dequantize tensors
|
| 979 |
+
g = tl.load(g_ptr + offsets, mask=mask, other=0.0).to(tl.float32) * gnorm_scale
|
| 980 |
+
p = tl.load(p_ptr + offsets, mask=mask, other=0.0).to(tl.float32)
|
| 981 |
+
|
| 982 |
+
# 3. Optimizer-specific updates
|
| 983 |
+
if OPTIMIZER_ID == 3: # ADAM
|
| 984 |
+
s1 = dequant_8bit_blockwise_kernel_util(state1_ptr, offsets, qmap1_ptr, absmax1_ptr, mask, BLOCK_SIZE_N)
|
| 985 |
+
s2 = dequant_8bit_blockwise_kernel_util(state2_ptr, offsets, qmap2_ptr, absmax2_ptr, mask, BLOCK_SIZE_N)
|
| 986 |
+
|
| 987 |
+
s1 = s1 * beta1 + (1.0 - beta1) * g
|
| 988 |
+
s2 = s2 * beta2 + (1.0 - beta2) * g * g
|
| 989 |
+
|
| 990 |
+
# In torch=2.7 on XPU there is an issue with libdevice.pow, leading to an error.
|
| 991 |
+
# For backwards compatibility we precompute the bias correction factors.
|
| 992 |
+
# bias_correction1 = 1.0 - libdevice.pow(beta1, step)
|
| 993 |
+
# bias_correction2 = 1.0 - libdevice.pow(beta2, step)
|
| 994 |
+
bias_correction1 = 1.0 - beta1_step
|
| 995 |
+
bias_correction2 = 1.0 - beta2_step
|
| 996 |
+
|
| 997 |
+
if weight_decay > 0.0:
|
| 998 |
+
p *= 1.0 - lr * weight_decay
|
| 999 |
+
|
| 1000 |
+
denom = tl.sqrt(s2) / tl.sqrt(bias_correction2) + eps
|
| 1001 |
+
p -= (lr / bias_correction1) * (s1 / denom)
|
| 1002 |
+
|
| 1003 |
+
# Store updated parameter
|
| 1004 |
+
tl.store(p_ptr + offsets, p.to(p_ptr.dtype.element_ty), mask=mask)
|
| 1005 |
+
|
| 1006 |
+
# Requantize and store states
|
| 1007 |
+
s1_codes, new_absmax1 = quantize_8bit_blockwise_kernel_util(s1, qmap1_ptr, 256, BLOCK_SIZE_N, N_PER_TH)
|
| 1008 |
+
tl.store(state1_ptr + offsets, s1_codes, mask=mask)
|
| 1009 |
+
tl.store(absmax1_ptr + block_start_idx + tl.arange(0, N_PER_TH), new_absmax1)
|
| 1010 |
+
|
| 1011 |
+
s2_codes, new_absmax2 = quantize_8bit_blockwise_kernel_util(s2, qmap2_ptr, 256, BLOCK_SIZE_N, N_PER_TH)
|
| 1012 |
+
tl.store(state2_ptr + offsets, s2_codes, mask=mask)
|
| 1013 |
+
tl.store(absmax2_ptr + block_start_idx + tl.arange(0, N_PER_TH), new_absmax2)
|
| 1014 |
+
|
| 1015 |
+
elif OPTIMIZER_ID == 5: # ADEMAMIX
|
| 1016 |
+
# AdEMAMix has a stacked state1 (m1, m2) and state2 (nu)
|
| 1017 |
+
m1 = dequant_8bit_blockwise_kernel_util(state1_ptr, offsets, qmap1_ptr, absmax1_ptr, mask, BLOCK_SIZE_N)
|
| 1018 |
+
m2 = dequant_8bit_blockwise_kernel_util(
|
| 1019 |
+
state1_ptr + n_elements,
|
| 1020 |
+
offsets,
|
| 1021 |
+
qmap1_ptr,
|
| 1022 |
+
absmax1_ptr + n_elements // BLOCK_SIZE_N,
|
| 1023 |
+
mask,
|
| 1024 |
+
BLOCK_SIZE_N,
|
| 1025 |
+
)
|
| 1026 |
+
nu = dequant_8bit_blockwise_kernel_util(state2_ptr, offsets, qmap2_ptr, absmax2_ptr, mask, BLOCK_SIZE_N)
|
| 1027 |
+
|
| 1028 |
+
m1 = m1 * beta1 + (1.0 - beta1) * g
|
| 1029 |
+
m2 = m2 * beta3 + (1.0 - beta3) * g
|
| 1030 |
+
nu = nu * beta2 + (1.0 - beta2) * g * g
|
| 1031 |
+
|
| 1032 |
+
# In torch=2.7 on XPU there is an issue with libdevice.pow, leading to an error.
|
| 1033 |
+
# For backwards compatibility we precompute the bias correction factors.
|
| 1034 |
+
# bias_correction1 = 1.0 - libdevice.pow(beta1, step)
|
| 1035 |
+
# bias_correction2 = tl.sqrt(1.0 - libdevice.pow(beta2, step))
|
| 1036 |
+
bias_correction1 = 1.0 - beta1_step
|
| 1037 |
+
bias_correction2 = tl.sqrt(1.0 - beta2_step)
|
| 1038 |
+
|
| 1039 |
+
update = (m1 / bias_correction1 + alpha * m2) / (tl.sqrt(nu) / bias_correction2 + eps)
|
| 1040 |
+
|
| 1041 |
+
if weight_decay > 0.0:
|
| 1042 |
+
p *= 1.0 - lr * weight_decay
|
| 1043 |
+
|
| 1044 |
+
p -= lr * update
|
| 1045 |
+
|
| 1046 |
+
# Store updated parameter
|
| 1047 |
+
tl.store(p_ptr + offsets, p.to(p_ptr.dtype.element_ty), mask=mask)
|
| 1048 |
+
|
| 1049 |
+
# Requantize and store all three states
|
| 1050 |
+
m1_codes, new_absmax_m1 = quantize_8bit_blockwise_kernel_util(m1, qmap1_ptr, 256, BLOCK_SIZE_N, N_PER_TH)
|
| 1051 |
+
tl.store(state1_ptr + offsets, m1_codes, mask=mask)
|
| 1052 |
+
tl.store(absmax1_ptr + block_start_idx + tl.arange(0, N_PER_TH), new_absmax_m1)
|
| 1053 |
+
|
| 1054 |
+
m2_codes, new_absmax_m2 = quantize_8bit_blockwise_kernel_util(m2, qmap1_ptr, 256, BLOCK_SIZE_N, N_PER_TH)
|
| 1055 |
+
tl.store(state1_ptr + n_elements + offsets, m2_codes, mask=mask)
|
| 1056 |
+
tl.store(
|
| 1057 |
+
absmax1_ptr + block_start_idx + tl.arange(0, N_PER_TH) + n_elements // BLOCK_SIZE_N,
|
| 1058 |
+
new_absmax_m2,
|
| 1059 |
+
)
|
| 1060 |
+
|
| 1061 |
+
nu_codes, new_absmax_nu = quantize_8bit_blockwise_kernel_util(nu, qmap2_ptr, 256, BLOCK_SIZE_N, N_PER_TH)
|
| 1062 |
+
tl.store(state2_ptr + offsets, nu_codes, mask=mask)
|
| 1063 |
+
tl.store(absmax2_ptr + block_start_idx + tl.arange(0, N_PER_TH), new_absmax_nu)
|
| 1064 |
+
|
| 1065 |
+
|
| 1066 |
+
name2optimizer_fn = {
|
| 1067 |
+
"momentum": _optimizer_update_1state_8bit_blockwise_triton_kernel,
|
| 1068 |
+
"rmsprop": _optimizer_update_1state_8bit_blockwise_triton_kernel,
|
| 1069 |
+
"adagrad": _optimizer_update_1state_8bit_blockwise_triton_kernel,
|
| 1070 |
+
"adam": _optimizer_update_2state_8bit_blockwise_triton_kernel,
|
| 1071 |
+
"lion": _optimizer_update_1state_8bit_blockwise_triton_kernel,
|
| 1072 |
+
"ademamix": _optimizer_update_2state_8bit_blockwise_triton_kernel,
|
| 1073 |
+
}
|
| 1074 |
+
|
| 1075 |
+
|
| 1076 |
+
def optimizer_update_8bit_blockwise_impl(
|
| 1077 |
+
optimizer_name: str,
|
| 1078 |
+
g: torch.Tensor,
|
| 1079 |
+
p: torch.Tensor,
|
| 1080 |
+
state1: torch.Tensor,
|
| 1081 |
+
state2: Optional[torch.Tensor],
|
| 1082 |
+
beta1: float,
|
| 1083 |
+
beta2: float,
|
| 1084 |
+
beta3: float,
|
| 1085 |
+
alpha: float,
|
| 1086 |
+
eps: float,
|
| 1087 |
+
step: int,
|
| 1088 |
+
lr: float,
|
| 1089 |
+
qmap1: torch.Tensor,
|
| 1090 |
+
qmap2: Optional[torch.Tensor],
|
| 1091 |
+
absmax1: torch.Tensor,
|
| 1092 |
+
absmax2: Optional[torch.Tensor],
|
| 1093 |
+
weight_decay: float = 0.0,
|
| 1094 |
+
gnorm_scale: float = 1.0,
|
| 1095 |
+
skip_zeros=False,
|
| 1096 |
+
) -> None:
|
| 1097 |
+
if skip_zeros:
|
| 1098 |
+
raise NotImplementedError("skip_zeros is not supported on XPU yet")
|
| 1099 |
+
|
| 1100 |
+
if optimizer_name == "ademamix":
|
| 1101 |
+
# Handle AdEMAMIX's stacked state tensors
|
| 1102 |
+
if state1.dim() < 2 or state1.shape[0] != 2:
|
| 1103 |
+
raise ValueError(
|
| 1104 |
+
f"For ademamix, state1 must be a stacked tensor of shape (2, ...), but got {state1.shape}"
|
| 1105 |
+
)
|
| 1106 |
+
if absmax1.dim() < 2 or absmax1.shape[0] != 2:
|
| 1107 |
+
raise ValueError(
|
| 1108 |
+
f"For ademamix, absmax1 must be a stacked tensor of shape (2, ...), but got {absmax1.shape}"
|
| 1109 |
+
)
|
| 1110 |
+
|
| 1111 |
+
BLOCK_SIZE = 256
|
| 1112 |
+
N_PER_TH = 1 # Number of blocks processed per thread.
|
| 1113 |
+
grid = (triton.cdiv(p.numel(), BLOCK_SIZE * N_PER_TH),)
|
| 1114 |
+
fn = name2optimizer_fn[optimizer_name]
|
| 1115 |
+
optimizer_id = name2optimizer_id[optimizer_name]
|
| 1116 |
+
|
| 1117 |
+
# In torch=2.7 on XPU there is an issue with libdevice.pow, leading to an error.
|
| 1118 |
+
# For backwards compatibility we precompute the bias correction factors.
|
| 1119 |
+
beta1_step = beta1**step
|
| 1120 |
+
beta2_step = beta2**step
|
| 1121 |
+
|
| 1122 |
+
fn[grid](
|
| 1123 |
+
p,
|
| 1124 |
+
g,
|
| 1125 |
+
state1,
|
| 1126 |
+
state2,
|
| 1127 |
+
beta1,
|
| 1128 |
+
beta2,
|
| 1129 |
+
beta3,
|
| 1130 |
+
alpha,
|
| 1131 |
+
eps,
|
| 1132 |
+
step,
|
| 1133 |
+
beta1_step,
|
| 1134 |
+
beta2_step,
|
| 1135 |
+
lr,
|
| 1136 |
+
qmap1,
|
| 1137 |
+
qmap2,
|
| 1138 |
+
absmax1,
|
| 1139 |
+
absmax2,
|
| 1140 |
+
weight_decay,
|
| 1141 |
+
gnorm_scale,
|
| 1142 |
+
p.numel(),
|
| 1143 |
+
BLOCK_SIZE_N=BLOCK_SIZE,
|
| 1144 |
+
N_PER_TH=N_PER_TH,
|
| 1145 |
+
OPTIMIZER_ID=optimizer_id,
|
| 1146 |
+
num_warps=2,
|
| 1147 |
+
)
|
| 1148 |
+
|
| 1149 |
+
|
| 1150 |
+
# optimizer_update_8bit_blockwise_impl = optimizer_update_8bit_blockwise_pytorch
|
| 1151 |
+
# optimizer_update_8bit_blockwise_impl = torch.compile(optimizer_update_8bit_blockwise_pytorch_impl)
|
| 1152 |
+
# optimizer_update_8bit_blockwise_impl = optimizer_update_8bit_blockwise_triton_quant
|
| 1153 |
+
# optimizer_update_8bit_blockwise_impl = torch.compile(optimizer_update_8bit_blockwise_triton_quant)
|
| 1154 |
+
optimizer_update_8bit_blockwise_impl = optimizer_update_8bit_blockwise_impl
|
bitsandbytes/backends/triton/ops.py
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import Sequence
|
| 2 |
+
from typing import Optional
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
from . import kernels_4bit, kernels_8bit_quant, kernels_optim
|
| 7 |
+
|
| 8 |
+
# currently codes unused, kept for reference
|
| 9 |
+
# Should be the same for quant/dequant
|
| 10 |
+
# from bitsandbytes.functional import get_4bit_type
|
| 11 |
+
# _FP4_QUANT_TABLE = get_4bit_type("fp4", device="xpu")
|
| 12 |
+
# _NF4_QUANT_TABLE = get_4bit_type("nf4", device="xpu")
|
| 13 |
+
device_type = torch.accelerator.current_accelerator().type if hasattr(torch, "accelerator") else "cuda"
|
| 14 |
+
torch_accelerator_module = getattr(torch, device_type, torch.cuda)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def quantize_blockwise(A: torch.Tensor, code: torch.Tensor, blocksize: int) -> tuple[torch.Tensor, torch.Tensor]:
|
| 18 |
+
torch._check_is_size(blocksize)
|
| 19 |
+
# torch._check(A.dtype == torch.float32, lambda: f"A must be float32 on xpu, got {A.dtype}")
|
| 20 |
+
with torch_accelerator_module.device(A.device):
|
| 21 |
+
out, absmax = kernels_8bit_quant.quantize_blockwise_triton(A, code, blocksize)
|
| 22 |
+
return out, absmax.float()
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def dequantize_blockwise(
|
| 26 |
+
A: torch.Tensor, absmax: torch.Tensor, code: torch.Tensor, blocksize: int, dtype: torch.dtype
|
| 27 |
+
) -> torch.Tensor:
|
| 28 |
+
torch._check_is_size(blocksize)
|
| 29 |
+
torch._check(A.dtype == torch.uint8, lambda: f"A must be uint8, got {A.dtype}")
|
| 30 |
+
# torch._check(dtype == torch.float32, lambda: f"dtype must be float32 on xpu, got {dtype}")
|
| 31 |
+
with torch_accelerator_module.device(A.device):
|
| 32 |
+
out = kernels_8bit_quant.dequant_8bit_blockwise(
|
| 33 |
+
A,
|
| 34 |
+
absmax,
|
| 35 |
+
code,
|
| 36 |
+
blocksize,
|
| 37 |
+
dtype=dtype,
|
| 38 |
+
)
|
| 39 |
+
return out
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def dequantize_blockwise_inplace(
|
| 43 |
+
A: torch.Tensor,
|
| 44 |
+
absmax: torch.Tensor,
|
| 45 |
+
code: torch.Tensor,
|
| 46 |
+
blocksize: int,
|
| 47 |
+
dtype: torch.dtype,
|
| 48 |
+
out: torch.Tensor,
|
| 49 |
+
) -> None:
|
| 50 |
+
torch._check_is_size(blocksize)
|
| 51 |
+
torch._check(A.dtype == torch.uint8, lambda: f"A must be uint8, got {A.dtype}")
|
| 52 |
+
torch._check(out.shape == A.shape, lambda: f"Expected out.shape == {A.shape}, got {out.shape}")
|
| 53 |
+
torch._check(out.device == A.device, lambda: f"Expected out.device == {A.device}, got {out.device}")
|
| 54 |
+
torch._check(out.dtype == dtype, lambda: f"Expected out.dtype == {dtype}, got {out.dtype}")
|
| 55 |
+
|
| 56 |
+
with torch_accelerator_module.device(A.device):
|
| 57 |
+
kernels_8bit_quant.dequant_8bit_blockwise(
|
| 58 |
+
A,
|
| 59 |
+
absmax,
|
| 60 |
+
code,
|
| 61 |
+
blocksize,
|
| 62 |
+
dtype=dtype,
|
| 63 |
+
out=out,
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def quantize_4bit(
|
| 68 |
+
A: torch.Tensor, blocksize: int, quant_type: str, quant_storage: torch.dtype
|
| 69 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 70 |
+
torch._check_is_size(blocksize)
|
| 71 |
+
# torch._check(quant_type == "nf4", lambda: f"quant_type must be nf4 on CPU, got {quant_type}")
|
| 72 |
+
torch._check(
|
| 73 |
+
A.dtype in [torch.bfloat16, torch.float16, torch.float32],
|
| 74 |
+
lambda: f"Blockwise 4bit quantization only supports 16/32-bit floats, but got {A.dtype}",
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
n = A.numel()
|
| 78 |
+
|
| 79 |
+
# TODO: Support when weight matrix is not divisible by blocksize
|
| 80 |
+
# torch._check(n % blocksize == 0, lambda: f"n must be divisible by blocksize, got {n} and {blocksize}")
|
| 81 |
+
|
| 82 |
+
blocks = -(n // -(blocksize * 2))
|
| 83 |
+
|
| 84 |
+
absmax = torch.empty((blocks * 2,), device=A.device, dtype=A.dtype)
|
| 85 |
+
# Use n - n//2 instead of (n+1)//2 to avoid integer overflow for large n
|
| 86 |
+
out = torch.empty((n - n // 2, 1), device=A.device, dtype=torch.uint8)
|
| 87 |
+
|
| 88 |
+
with torch_accelerator_module.device(A.device):
|
| 89 |
+
kernels_4bit.quantize_4bit_blockwise_triton(
|
| 90 |
+
A, blocksize, quant_type, blocks, absmax, num_elements=n, quantized_out=out
|
| 91 |
+
)
|
| 92 |
+
packed = out
|
| 93 |
+
|
| 94 |
+
if quant_storage != torch.uint8:
|
| 95 |
+
packed = out.squeeze().view(quant_storage).unsqueeze(1)
|
| 96 |
+
|
| 97 |
+
return packed, absmax.float()
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def dequantize_4bit(
|
| 101 |
+
A: torch.Tensor,
|
| 102 |
+
absmax: torch.Tensor,
|
| 103 |
+
blocksize: int,
|
| 104 |
+
quant_type: str,
|
| 105 |
+
shape: Sequence[int],
|
| 106 |
+
dtype: torch.dtype,
|
| 107 |
+
) -> torch.Tensor:
|
| 108 |
+
torch._check_is_size(blocksize)
|
| 109 |
+
# torch._check(quant_type == "nf4", lambda: f"quant_type must be nf4 on XPU, got {quant_type}")
|
| 110 |
+
torch._check(
|
| 111 |
+
dtype in [torch.bfloat16, torch.float16, torch.float32],
|
| 112 |
+
lambda: f"Blockwise 4bit dequantization only supports 16/32-bit floats, but got {dtype}",
|
| 113 |
+
)
|
| 114 |
+
# torch._check(
|
| 115 |
+
# A.dtype == torch.uint8,
|
| 116 |
+
# lambda: f"Blockwise 4bit dequantization on XPU only supports uint8 storage, got {A.dtype}",
|
| 117 |
+
# )
|
| 118 |
+
# Check if this is fine and fast
|
| 119 |
+
if A.dtype != torch.uint8:
|
| 120 |
+
A = A.squeeze().view(torch.uint8).unsqueeze(1)
|
| 121 |
+
|
| 122 |
+
out = torch.empty(shape, dtype=dtype, device=A.device)
|
| 123 |
+
with torch_accelerator_module.device(A.device):
|
| 124 |
+
kernels_4bit.dequantize_4bit_impl(A, absmax, blocksize, quant_type, dtype, out=out)
|
| 125 |
+
|
| 126 |
+
return out
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def dequantize_4bit_inplace(
|
| 130 |
+
A: torch.Tensor,
|
| 131 |
+
absmax: torch.Tensor,
|
| 132 |
+
blocksize: int,
|
| 133 |
+
quant_type: str,
|
| 134 |
+
shape: Sequence[int],
|
| 135 |
+
dtype: torch.dtype,
|
| 136 |
+
out: torch.Tensor,
|
| 137 |
+
) -> None:
|
| 138 |
+
torch._check(out.shape == shape, lambda: f"Expected out.shape == {shape}, got {out.shape}")
|
| 139 |
+
torch._check(out.dtype == dtype, lambda: f"Expected out.dtype == {dtype}, got {out.dtype}")
|
| 140 |
+
with torch_accelerator_module.device(A.device):
|
| 141 |
+
kernels_4bit.dequantize_4bit_impl(A, absmax, blocksize, quant_type, dtype, out=out)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def gemv_4bit(
|
| 145 |
+
A: torch.Tensor,
|
| 146 |
+
B: torch.Tensor,
|
| 147 |
+
shapeB: Sequence[int],
|
| 148 |
+
absmax: torch.Tensor,
|
| 149 |
+
code: torch.Tensor,
|
| 150 |
+
blocksize: int,
|
| 151 |
+
) -> torch.Tensor:
|
| 152 |
+
if B.dtype != torch.uint8:
|
| 153 |
+
B = B.squeeze().view(torch.uint8).unsqueeze(1)
|
| 154 |
+
|
| 155 |
+
B_dq_triton = torch.empty(shapeB, dtype=A.dtype, device=A.device)
|
| 156 |
+
|
| 157 |
+
with torch_accelerator_module.device(A.device):
|
| 158 |
+
kernels_4bit.dequantize_4bit_impl_passing_code(
|
| 159 |
+
B,
|
| 160 |
+
absmax,
|
| 161 |
+
blocksize,
|
| 162 |
+
code,
|
| 163 |
+
dtype=A.dtype,
|
| 164 |
+
out=B_dq_triton,
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
return torch.nn.functional.linear(
|
| 168 |
+
A,
|
| 169 |
+
B_dq_triton,
|
| 170 |
+
bias=None,
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
# optimizer_update_8bit_blockwise_impl = kernels_optim.optimizer_update_8bit_blockwise_pytorch
|
| 175 |
+
# optimizer_update_8bit_blockwise_impl = torch.compile(kernels_optim.optimizer_update_8bit_blockwise_pytorch) # 60ms
|
| 176 |
+
# optimizer_update_8bit_blockwise_impl = kernels_optim.optimizer_update_8bit_blockwise_triton_quant #2.8ms
|
| 177 |
+
# optimizer_update_8bit_blockwise_impl = torch.compile(kernels_optim.optimizer_update_8bit_blockwise_triton_quant) # 2.3ms
|
| 178 |
+
optimizer_update_8bit_blockwise_impl = kernels_optim.optimizer_update_8bit_blockwise_impl # ~0.95ms for adam
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def optimizer_update_8bit_blockwise(
|
| 182 |
+
optimizer_name: str,
|
| 183 |
+
g: torch.Tensor,
|
| 184 |
+
p: torch.Tensor,
|
| 185 |
+
state1: torch.Tensor,
|
| 186 |
+
state2: Optional[torch.Tensor],
|
| 187 |
+
beta1: float,
|
| 188 |
+
beta2: float,
|
| 189 |
+
beta3: float,
|
| 190 |
+
alpha: float,
|
| 191 |
+
eps: float,
|
| 192 |
+
step: int,
|
| 193 |
+
lr: float,
|
| 194 |
+
qmap1: torch.Tensor,
|
| 195 |
+
qmap2: Optional[torch.Tensor],
|
| 196 |
+
absmax1: torch.Tensor,
|
| 197 |
+
absmax2: Optional[torch.Tensor],
|
| 198 |
+
weight_decay: float = 0.0,
|
| 199 |
+
gnorm_scale: float = 1.0,
|
| 200 |
+
skip_zeros=False,
|
| 201 |
+
) -> None:
|
| 202 |
+
# torch._check(
|
| 203 |
+
# g.numel() == p.numel(),
|
| 204 |
+
# lambda: f"g and p must have the same number of elements, got {g.numel()} and {p.numel()}",
|
| 205 |
+
# )
|
| 206 |
+
# compute_dtypes = [torch.float16, torch.bfloat16, torch.float32]
|
| 207 |
+
|
| 208 |
+
# torch._check(
|
| 209 |
+
# g.dtype in compute_dtypes,
|
| 210 |
+
# lambda: f"g must be bfloat16, float16, or float32, got {g.dtype}",
|
| 211 |
+
# )
|
| 212 |
+
# torch._check(
|
| 213 |
+
# g.dtype == p.dtype,
|
| 214 |
+
# lambda: f"Expected all tensors to have the same dtype, got g.dtype={g.dtype}, p.dtype={p.dtype}",
|
| 215 |
+
# )
|
| 216 |
+
# torch._check(
|
| 217 |
+
# state1.dtype == torch.uint8,
|
| 218 |
+
# lambda: f"state1 must be uint8, got {state1.dtype}",
|
| 219 |
+
# )
|
| 220 |
+
# torch._check(
|
| 221 |
+
# qmap1.dtype == absmax1.dtype == torch.float32,
|
| 222 |
+
# lambda: f"Expected qmap1 and absmax1 to be float32, got qmap1.dtype={qmap1.dtype}, absmax1.dtype={absmax1.dtype}",
|
| 223 |
+
# )
|
| 224 |
+
# if state2 is not None:
|
| 225 |
+
# torch._check(
|
| 226 |
+
# state2.dtype == torch.uint8,
|
| 227 |
+
# lambda: f"state2 must be uint8, got {state2.dtype}",
|
| 228 |
+
# )
|
| 229 |
+
# torch._check(
|
| 230 |
+
# qmap2.dtype == absmax2.dtype == torch.float32,
|
| 231 |
+
# lambda: f"Expected qmap2 and absmax2 to be float32, got qmap2.dtype={qmap2.dtype}, absmax2.dtype={absmax2.dtype}",
|
| 232 |
+
# )
|
| 233 |
+
|
| 234 |
+
with torch_accelerator_module.device(state1.device):
|
| 235 |
+
optimizer_update_8bit_blockwise_impl(
|
| 236 |
+
optimizer_name=optimizer_name,
|
| 237 |
+
g=g,
|
| 238 |
+
p=p,
|
| 239 |
+
state1=state1,
|
| 240 |
+
state2=state2,
|
| 241 |
+
beta1=beta1,
|
| 242 |
+
beta2=beta2,
|
| 243 |
+
beta3=beta3,
|
| 244 |
+
alpha=alpha,
|
| 245 |
+
eps=eps,
|
| 246 |
+
step=step,
|
| 247 |
+
lr=lr,
|
| 248 |
+
qmap1=qmap1,
|
| 249 |
+
qmap2=qmap2,
|
| 250 |
+
absmax1=absmax1,
|
| 251 |
+
absmax2=absmax2,
|
| 252 |
+
weight_decay=weight_decay,
|
| 253 |
+
gnorm_scale=gnorm_scale,
|
| 254 |
+
skip_zeros=skip_zeros,
|
| 255 |
+
)
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
def optimizer_update_32bit(
|
| 259 |
+
optimizer_name: str,
|
| 260 |
+
g: torch.Tensor,
|
| 261 |
+
p: torch.Tensor,
|
| 262 |
+
state1: torch.Tensor,
|
| 263 |
+
state2: Optional[torch.Tensor],
|
| 264 |
+
unorm_vec: Optional[torch.Tensor],
|
| 265 |
+
max_unorm: float,
|
| 266 |
+
param_norm: float,
|
| 267 |
+
beta1: float,
|
| 268 |
+
beta2: float,
|
| 269 |
+
beta3: float,
|
| 270 |
+
alpha: float,
|
| 271 |
+
eps: float,
|
| 272 |
+
weight_decay: float,
|
| 273 |
+
step: int,
|
| 274 |
+
lr: float,
|
| 275 |
+
gnorm_scale: float,
|
| 276 |
+
skip_zeros=False,
|
| 277 |
+
) -> None:
|
| 278 |
+
with torch_accelerator_module.device(state1.device):
|
| 279 |
+
kernels_optim.optimizer_update_32bit_impl(
|
| 280 |
+
optimizer_name=optimizer_name,
|
| 281 |
+
g=g,
|
| 282 |
+
p=p,
|
| 283 |
+
state1=state1,
|
| 284 |
+
state2=state2,
|
| 285 |
+
unorm_vec=unorm_vec,
|
| 286 |
+
max_unorm=max_unorm,
|
| 287 |
+
param_norm=param_norm,
|
| 288 |
+
beta1=beta1,
|
| 289 |
+
beta2=beta2,
|
| 290 |
+
beta3=beta3,
|
| 291 |
+
alpha=alpha,
|
| 292 |
+
eps=eps,
|
| 293 |
+
weight_decay=weight_decay,
|
| 294 |
+
step=step,
|
| 295 |
+
lr=lr,
|
| 296 |
+
gnorm_scale=gnorm_scale,
|
| 297 |
+
skip_zeros=skip_zeros,
|
| 298 |
+
)
|
bitsandbytes/backends/utils.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
|
| 3 |
+
from packaging import version
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
try:
|
| 7 |
+
import triton.language as tl # noqa: F401
|
| 8 |
+
|
| 9 |
+
import triton # noqa: F401
|
| 10 |
+
|
| 11 |
+
triton_available = True
|
| 12 |
+
except ImportError:
|
| 13 |
+
triton_available = False
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
_NF4_QUANT_TABLE = torch.tensor(
|
| 17 |
+
[
|
| 18 |
+
-1.0,
|
| 19 |
+
-0.6961928009986877,
|
| 20 |
+
-0.5250730514526367,
|
| 21 |
+
-0.39491748809814453,
|
| 22 |
+
-0.28444138169288635,
|
| 23 |
+
-0.18477343022823334,
|
| 24 |
+
-0.09105003625154495,
|
| 25 |
+
0.0,
|
| 26 |
+
0.07958029955625534,
|
| 27 |
+
0.16093020141124725,
|
| 28 |
+
0.24611230194568634,
|
| 29 |
+
0.33791524171829224,
|
| 30 |
+
0.44070982933044434,
|
| 31 |
+
0.5626170039176941,
|
| 32 |
+
0.7229568362236023,
|
| 33 |
+
1.0,
|
| 34 |
+
],
|
| 35 |
+
dtype=torch.float32,
|
| 36 |
+
device="xpu"
|
| 37 |
+
if hasattr(torch, "xpu") and torch.xpu.is_available()
|
| 38 |
+
else "cpu", # Only cpu/xpu use this table for now.
|
| 39 |
+
)
|
| 40 |
+
_FP4_QUANT_TABLE = torch.tensor(
|
| 41 |
+
[
|
| 42 |
+
0.0000,
|
| 43 |
+
0.0052,
|
| 44 |
+
0.6667,
|
| 45 |
+
1.0000,
|
| 46 |
+
0.3333,
|
| 47 |
+
0.5000,
|
| 48 |
+
0.1667,
|
| 49 |
+
0.2500,
|
| 50 |
+
0.0000,
|
| 51 |
+
-0.0052,
|
| 52 |
+
-0.6667,
|
| 53 |
+
-1.0000,
|
| 54 |
+
-0.3333,
|
| 55 |
+
-0.5000,
|
| 56 |
+
-0.1667,
|
| 57 |
+
-0.2500,
|
| 58 |
+
],
|
| 59 |
+
dtype=torch.float32,
|
| 60 |
+
device="xpu"
|
| 61 |
+
if hasattr(torch, "xpu") and torch.xpu.is_available()
|
| 62 |
+
else "cpu", # Only cpu/xpu use this table for now.
|
| 63 |
+
)
|
| 64 |
+
CODE = {"nf4": _NF4_QUANT_TABLE, "fp4": _FP4_QUANT_TABLE}
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def get_gaudi_sw_version():
|
| 68 |
+
"""
|
| 69 |
+
Returns the installed version of Gaudi SW.
|
| 70 |
+
"""
|
| 71 |
+
output = subprocess.run(
|
| 72 |
+
"pip list | grep habana-torch-plugin",
|
| 73 |
+
shell=True,
|
| 74 |
+
text=True,
|
| 75 |
+
capture_output=True,
|
| 76 |
+
)
|
| 77 |
+
# If grep return nothing
|
| 78 |
+
if not output.stdout.strip():
|
| 79 |
+
return None
|
| 80 |
+
|
| 81 |
+
return version.parse(output.stdout.split("\n")[0].split()[-1])
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
GAUDI_SW_VER = get_gaudi_sw_version()
|
bitsandbytes/backends/xpu/__init__.py
ADDED
|
File without changes
|
bitsandbytes/backends/xpu/ops.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import Sequence
|
| 2 |
+
import ctypes as ct
|
| 3 |
+
import logging
|
| 4 |
+
|
| 5 |
+
from packaging import version
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
from bitsandbytes.functional import _get_tensor_stream, get_ptr
|
| 9 |
+
|
| 10 |
+
from ..._ops import register_kernel
|
| 11 |
+
from ...cextension import ErrorHandlerMockBNBNativeLibrary, lib
|
| 12 |
+
from ..utils import triton_available
|
| 13 |
+
|
| 14 |
+
logger = logging.getLogger(__name__)
|
| 15 |
+
|
| 16 |
+
# _int_mm is available in torch starting from 2.9 version
|
| 17 |
+
if version.parse(torch.__version__).release >= version.parse("2.9").release:
|
| 18 |
+
|
| 19 |
+
@register_kernel("bitsandbytes::int8_linear_matmul", "xpu")
|
| 20 |
+
def _(A: torch.Tensor, B: torch.Tensor):
|
| 21 |
+
return torch._int_mm(
|
| 22 |
+
A.reshape(-1, A.shape[-1]),
|
| 23 |
+
B.t(),
|
| 24 |
+
).reshape(*A.shape[:-1], B.shape[0])
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def _dequantize_4bit_impl(
|
| 28 |
+
A: torch.Tensor,
|
| 29 |
+
absmax: torch.Tensor,
|
| 30 |
+
blocksize: int,
|
| 31 |
+
quant_type: str,
|
| 32 |
+
dtype: torch.dtype,
|
| 33 |
+
out: torch.Tensor,
|
| 34 |
+
) -> None:
|
| 35 |
+
args = (
|
| 36 |
+
None,
|
| 37 |
+
get_ptr(A),
|
| 38 |
+
get_ptr(absmax),
|
| 39 |
+
get_ptr(out),
|
| 40 |
+
ct.c_int(blocksize),
|
| 41 |
+
ct.c_int(out.numel()),
|
| 42 |
+
_get_tensor_stream(A),
|
| 43 |
+
)
|
| 44 |
+
if dtype == torch.bfloat16:
|
| 45 |
+
if quant_type == "fp4":
|
| 46 |
+
lib.cdequantize_blockwise_bf16_fp4(*args)
|
| 47 |
+
else:
|
| 48 |
+
lib.cdequantize_blockwise_bf16_nf4(*args)
|
| 49 |
+
elif dtype == torch.float16:
|
| 50 |
+
if quant_type == "fp4":
|
| 51 |
+
lib.cdequantize_blockwise_fp16_fp4(*args)
|
| 52 |
+
else:
|
| 53 |
+
lib.cdequantize_blockwise_fp16_nf4(*args)
|
| 54 |
+
elif dtype == torch.float32:
|
| 55 |
+
if quant_type == "fp4":
|
| 56 |
+
lib.cdequantize_blockwise_fp32_fp4(*args)
|
| 57 |
+
else:
|
| 58 |
+
lib.cdequantize_blockwise_fp32_nf4(*args)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def _dequantize_blockwise_impl(
|
| 62 |
+
A: torch.Tensor, absmax: torch.Tensor, code: torch.Tensor, blocksize: int, dtype: torch.dtype, out: torch.Tensor
|
| 63 |
+
) -> None:
|
| 64 |
+
args = (
|
| 65 |
+
get_ptr(code),
|
| 66 |
+
get_ptr(A),
|
| 67 |
+
get_ptr(absmax),
|
| 68 |
+
get_ptr(out),
|
| 69 |
+
ct.c_int(blocksize),
|
| 70 |
+
ct.c_int(A.numel()),
|
| 71 |
+
_get_tensor_stream(A),
|
| 72 |
+
)
|
| 73 |
+
if dtype == torch.float16:
|
| 74 |
+
lib.cdequantize_blockwise_fp16(*args)
|
| 75 |
+
elif dtype == torch.bfloat16:
|
| 76 |
+
lib.cdequantize_blockwise_bf16(*args)
|
| 77 |
+
elif dtype == torch.float32:
|
| 78 |
+
lib.cdequantize_blockwise_fp32(*args)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def _gemv_4bit_impl(
|
| 82 |
+
A: torch.Tensor,
|
| 83 |
+
B: torch.Tensor,
|
| 84 |
+
shapeB: Sequence[int],
|
| 85 |
+
absmax: torch.Tensor,
|
| 86 |
+
code: torch.Tensor,
|
| 87 |
+
blocksize: int,
|
| 88 |
+
out: torch.Tensor,
|
| 89 |
+
) -> None:
|
| 90 |
+
m = ct.c_int32(1)
|
| 91 |
+
n = ct.c_int32(shapeB[0])
|
| 92 |
+
k = ct.c_int32(shapeB[1])
|
| 93 |
+
|
| 94 |
+
lda = m
|
| 95 |
+
ldb = ct.c_int32((A.shape[-1] + 1) // 2)
|
| 96 |
+
ldc = m
|
| 97 |
+
|
| 98 |
+
stream = _get_tensor_stream(A)
|
| 99 |
+
if A.dtype == torch.float16:
|
| 100 |
+
lib.cgemv_4bit_inference_fp16(
|
| 101 |
+
m,
|
| 102 |
+
n,
|
| 103 |
+
k,
|
| 104 |
+
get_ptr(A),
|
| 105 |
+
get_ptr(B),
|
| 106 |
+
get_ptr(absmax),
|
| 107 |
+
get_ptr(code),
|
| 108 |
+
get_ptr(out),
|
| 109 |
+
lda,
|
| 110 |
+
ldb,
|
| 111 |
+
ldc,
|
| 112 |
+
ct.c_int32(blocksize),
|
| 113 |
+
stream,
|
| 114 |
+
)
|
| 115 |
+
elif A.dtype == torch.bfloat16:
|
| 116 |
+
lib.cgemv_4bit_inference_bf16(
|
| 117 |
+
m,
|
| 118 |
+
n,
|
| 119 |
+
k,
|
| 120 |
+
get_ptr(A),
|
| 121 |
+
get_ptr(B),
|
| 122 |
+
get_ptr(absmax),
|
| 123 |
+
get_ptr(code),
|
| 124 |
+
get_ptr(out),
|
| 125 |
+
lda,
|
| 126 |
+
ldb,
|
| 127 |
+
ldc,
|
| 128 |
+
ct.c_int32(blocksize),
|
| 129 |
+
stream,
|
| 130 |
+
)
|
| 131 |
+
elif A.dtype == torch.float32:
|
| 132 |
+
lib.cgemv_4bit_inference_fp32(
|
| 133 |
+
m,
|
| 134 |
+
n,
|
| 135 |
+
k,
|
| 136 |
+
get_ptr(A),
|
| 137 |
+
get_ptr(B),
|
| 138 |
+
get_ptr(absmax),
|
| 139 |
+
get_ptr(code),
|
| 140 |
+
get_ptr(out),
|
| 141 |
+
lda,
|
| 142 |
+
ldb,
|
| 143 |
+
ldc,
|
| 144 |
+
ct.c_int32(blocksize),
|
| 145 |
+
stream,
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
# SYCL should be faster for xpu, so at first checking if it is available.
|
| 150 |
+
if not isinstance(lib, ErrorHandlerMockBNBNativeLibrary):
|
| 151 |
+
logger.info("Register sycl bitsandbytes kernels for XPU")
|
| 152 |
+
|
| 153 |
+
# TODO: Remove the triton register when quantization sycl kernel is ready.
|
| 154 |
+
if triton_available:
|
| 155 |
+
from ..triton import ops as triton_ops
|
| 156 |
+
|
| 157 |
+
register_kernel("bitsandbytes::quantize_blockwise", "xpu")(triton_ops.quantize_blockwise)
|
| 158 |
+
register_kernel("bitsandbytes::quantize_4bit", "xpu")(triton_ops.quantize_4bit)
|
| 159 |
+
register_kernel("bitsandbytes::optimizer_update_8bit_blockwise", "xpu")(
|
| 160 |
+
triton_ops.optimizer_update_8bit_blockwise
|
| 161 |
+
)
|
| 162 |
+
register_kernel("bitsandbytes::optimizer_update_32bit", "xpu")(triton_ops.optimizer_update_32bit)
|
| 163 |
+
|
| 164 |
+
@register_kernel("bitsandbytes::dequantize_4bit", "xpu")
|
| 165 |
+
def _(
|
| 166 |
+
A: torch.Tensor,
|
| 167 |
+
absmax: torch.Tensor,
|
| 168 |
+
blocksize: int,
|
| 169 |
+
quant_type: str,
|
| 170 |
+
shape: Sequence[int],
|
| 171 |
+
dtype: torch.dtype,
|
| 172 |
+
) -> torch.Tensor:
|
| 173 |
+
out = torch.empty(shape, dtype=dtype, device=A.device)
|
| 174 |
+
_dequantize_4bit_impl(A, absmax, blocksize, quant_type, dtype, out=out)
|
| 175 |
+
return out
|
| 176 |
+
|
| 177 |
+
@register_kernel("bitsandbytes::dequantize_blockwise", "xpu")
|
| 178 |
+
def _(
|
| 179 |
+
A: torch.Tensor, absmax: torch.Tensor, code: torch.Tensor, blocksize: int, dtype: torch.dtype
|
| 180 |
+
) -> torch.Tensor:
|
| 181 |
+
out = torch.empty_like(A, dtype=dtype)
|
| 182 |
+
_dequantize_blockwise_impl(A, absmax, code, blocksize, dtype, out=out)
|
| 183 |
+
return out
|
| 184 |
+
|
| 185 |
+
@register_kernel("bitsandbytes::dequantize_blockwise.out", "xpu")
|
| 186 |
+
def _(
|
| 187 |
+
A: torch.Tensor,
|
| 188 |
+
absmax: torch.Tensor,
|
| 189 |
+
code: torch.Tensor,
|
| 190 |
+
blocksize: int,
|
| 191 |
+
dtype: torch.dtype,
|
| 192 |
+
out: torch.Tensor,
|
| 193 |
+
) -> None:
|
| 194 |
+
torch._check(out.dtype == dtype, lambda: f"Expected out.dtype == {dtype}, got {out.dtype}")
|
| 195 |
+
torch._check(out.shape == A.shape, lambda: f"Expected out.shape == {A.shape}, got {out.shape}")
|
| 196 |
+
_dequantize_blockwise_impl(A, absmax, code, blocksize, dtype, out=out)
|
| 197 |
+
|
| 198 |
+
@register_kernel("bitsandbytes::gemv_4bit", "xpu")
|
| 199 |
+
def _(
|
| 200 |
+
A: torch.Tensor,
|
| 201 |
+
B: torch.Tensor,
|
| 202 |
+
shapeB: Sequence[int],
|
| 203 |
+
absmax: torch.Tensor,
|
| 204 |
+
code: torch.Tensor,
|
| 205 |
+
blocksize: int,
|
| 206 |
+
) -> torch.Tensor:
|
| 207 |
+
shape = (*A.shape[:-1], shapeB[0])
|
| 208 |
+
out = torch.empty(shape, device=A.device, dtype=A.dtype)
|
| 209 |
+
_gemv_4bit_impl(A, B, shapeB, absmax, code, blocksize, out=out)
|
| 210 |
+
return out
|
| 211 |
+
|
| 212 |
+
@register_kernel("bitsandbytes::gemv_4bit.out", "xpu")
|
| 213 |
+
def _(
|
| 214 |
+
A: torch.Tensor,
|
| 215 |
+
B: torch.Tensor,
|
| 216 |
+
shapeB: Sequence[int],
|
| 217 |
+
absmax: torch.Tensor,
|
| 218 |
+
code: torch.Tensor,
|
| 219 |
+
blocksize: int,
|
| 220 |
+
out: torch.Tensor,
|
| 221 |
+
) -> None:
|
| 222 |
+
torch._check(
|
| 223 |
+
out.shape == (*A.shape[:-1], shapeB[0]),
|
| 224 |
+
lambda: f"Expected out.shape == {(*A.shape[:-1], shapeB[0])}, got {out.shape}",
|
| 225 |
+
)
|
| 226 |
+
torch._check(out.dtype == A.dtype, lambda: f"Expected out.dtype == {A.dtype}, got {out.dtype}")
|
| 227 |
+
_gemv_4bit_impl(A, B, shapeB, absmax, code, blocksize, out=out)
|
| 228 |
+
elif triton_available:
|
| 229 |
+
logger.info("Register triton bitsandbytes kernels for XPU")
|
| 230 |
+
from ..triton import ops as triton_ops
|
| 231 |
+
|
| 232 |
+
register_kernel("bitsandbytes::quantize_blockwise", "xpu")(triton_ops.quantize_blockwise)
|
| 233 |
+
register_kernel("bitsandbytes::dequantize_blockwise.out", "xpu")(triton_ops.dequantize_blockwise_inplace)
|
| 234 |
+
register_kernel("bitsandbytes::dequantize_blockwise", "xpu")(triton_ops.dequantize_blockwise)
|
| 235 |
+
register_kernel("bitsandbytes::quantize_4bit", "xpu")(triton_ops.quantize_4bit)
|
| 236 |
+
register_kernel("bitsandbytes::dequantize_4bit.out", "xpu")(triton_ops.dequantize_4bit_inplace)
|
| 237 |
+
register_kernel("bitsandbytes::dequantize_4bit", "xpu")(triton_ops.dequantize_4bit)
|
| 238 |
+
register_kernel("bitsandbytes::gemv_4bit", "xpu")(triton_ops.gemv_4bit)
|
| 239 |
+
register_kernel("bitsandbytes::optimizer_update_8bit_blockwise", "xpu")(triton_ops.optimizer_update_8bit_blockwise)
|
| 240 |
+
register_kernel("bitsandbytes::optimizer_update_32bit", "xpu")(triton_ops.optimizer_update_32bit)
|
| 241 |
+
else:
|
| 242 |
+
logger.warning("Register pytorch bitsandbytes kernels for XPU because no native library or triton packages found.")
|