salmankhanpm commited on
Commit
dc9bb20
·
verified ·
1 Parent(s): fd0b02f

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. _distutils_hack/__init__.py +239 -0
  2. _distutils_hack/override.py +1 -0
  3. aiohappyeyeballs-2.6.1.dist-info/INSTALLER +1 -0
  4. aiohappyeyeballs-2.6.1.dist-info/LICENSE +279 -0
  5. aiohappyeyeballs-2.6.1.dist-info/METADATA +123 -0
  6. aiohappyeyeballs-2.6.1.dist-info/RECORD +12 -0
  7. aiohappyeyeballs-2.6.1.dist-info/REQUESTED +0 -0
  8. aiohappyeyeballs-2.6.1.dist-info/WHEEL +4 -0
  9. aiohappyeyeballs/__init__.py +14 -0
  10. aiohappyeyeballs/_staggered.py +207 -0
  11. aiohappyeyeballs/impl.py +259 -0
  12. aiohappyeyeballs/py.typed +0 -0
  13. aiohappyeyeballs/types.py +17 -0
  14. aiohappyeyeballs/utils.py +97 -0
  15. aiosignal/__init__.py +59 -0
  16. aiosignal/py.typed +0 -0
  17. annotated_doc-0.0.4.dist-info/INSTALLER +1 -0
  18. annotated_doc-0.0.4.dist-info/METADATA +145 -0
  19. annotated_doc-0.0.4.dist-info/RECORD +10 -0
  20. annotated_doc-0.0.4.dist-info/REQUESTED +0 -0
  21. annotated_doc-0.0.4.dist-info/WHEEL +4 -0
  22. annotated_doc-0.0.4.dist-info/entry_points.txt +4 -0
  23. annotated_doc-0.0.4.dist-info/licenses/LICENSE +21 -0
  24. attrs-26.1.0.dist-info/INSTALLER +1 -0
  25. attrs-26.1.0.dist-info/METADATA +199 -0
  26. attrs-26.1.0.dist-info/RECORD +37 -0
  27. attrs-26.1.0.dist-info/REQUESTED +0 -0
  28. attrs-26.1.0.dist-info/WHEEL +4 -0
  29. bitsandbytes/__init__.py +75 -0
  30. bitsandbytes/__main__.py +4 -0
  31. bitsandbytes/_ops.py +433 -0
  32. bitsandbytes/autograd/__init__.py +0 -0
  33. bitsandbytes/autograd/_functions.py +401 -0
  34. bitsandbytes/backends/__init__.py +0 -0
  35. bitsandbytes/backends/cpu/__init__.py +0 -0
  36. bitsandbytes/backends/cpu/ops.py +301 -0
  37. bitsandbytes/backends/cuda/__init__.py +0 -0
  38. bitsandbytes/backends/cuda/ops.py +770 -0
  39. bitsandbytes/backends/default/__init__.py +0 -0
  40. bitsandbytes/backends/default/ops.py +616 -0
  41. bitsandbytes/backends/hpu/__init__.py +0 -0
  42. bitsandbytes/backends/hpu/ops.py +55 -0
  43. bitsandbytes/backends/triton/__init__.py +0 -0
  44. bitsandbytes/backends/triton/kernels_4bit.py +577 -0
  45. bitsandbytes/backends/triton/kernels_8bit_quant.py +195 -0
  46. bitsandbytes/backends/triton/kernels_optim.py +1154 -0
  47. bitsandbytes/backends/triton/ops.py +298 -0
  48. bitsandbytes/backends/utils.py +84 -0
  49. bitsandbytes/backends/xpu/__init__.py +0 -0
  50. 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&amp;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.")