Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions python/mlx/nn/layers/embedding.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ def to_quantized(
group_size: Optional[int] = None,
bits: Optional[int] = None,
mode: str = "affine",
quantize_input: bool = False,
):
"""Return a :obj:`QuantizedEmbedding` layer that approximates this embedding layer."""
if quantize_input:
raise ValueError("Quantized input is not supported.")
return QuantizedEmbedding.from_embedding(self, group_size, bits, mode)
32 changes: 30 additions & 2 deletions python/mlx/nn/layers/linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import mlx.core as mx
from mlx.nn.layers.base import Module
from mlx.nn.layers.quantized import QuantizedLinear
from mlx.nn.layers.quantized import QQLinear, QuantizedLinear


class Identity(Module):
Expand Down Expand Up @@ -75,8 +75,36 @@ def to_quantized(
group_size: Optional[int] = None,
bits: Optional[int] = None,
mode: str = "affine",
quantize_input: bool = False,
):
"""Return a :obj:`QuantizedLinear` layer that approximates this layer."""
"""Return a quantized approximation of this layer.

If ``quantize_input`` is ``False``, returns a :obj:`QuantizedLinear`
(weights are quantized). If ``quantize_input`` is ``True``, returns
a :obj:`QQLinear` (weights and activations are quantized).

Args:
group_size (Optional[int]): The quantization group size (see
:func:`mlx.core.quantize`). Default: ``None``.
bits (Optional[int]): The number of bits per parameter (see
:func:`mlx.core.quantize`). Default: ``None``.
mode (str): The quantization method to use (see
:func:`mlx.core.quantize`). Default: ``"affine"``.
quantize_input (bool): Whether to quantize input. Default: ``False``.

Returns:
QuantizedLinear or QQLinear: A quantized version of this layer.

Notes:
Quantized input is only supported for ``"nvfp4"`` and ``"mxfp8"``
modes.
"""
if quantize_input:
if mode not in ["nvfp4", "mxfp8"]:
raise ValueError(
f"Quantized activations are only supported for 'nvfp4' and 'mxfp8' modes, got {mode}."
)
return QQLinear.from_linear(self, group_size, bits, mode)
return QuantizedLinear.from_linear(self, group_size, bits, mode)


Expand Down
41 changes: 32 additions & 9 deletions python/mlx/nn/layers/quantized.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,18 @@ def quantize(
bits: int = None,
*,
mode: str = "affine",
quantize_input: bool = False,
class_predicate: Optional[Callable[[str, Module], Union[bool, dict]]] = None,
):
"""Quantize the sub-modules of a module according to a predicate.

By default all layers that define a ``to_quantized(group_size, bits)``
method will be quantized. Both :obj:`Linear` and :obj:`Embedding` layers
will be quantized. Note also, the module is updated in-place.
By default all layers that define a ``to_quantized()`` method will be
quantized. Both :obj:`Linear` and :obj:`Embedding` layers will be
quantized. The module is updated in-place.

Note:
``quantize_input=True`` is only supported for ``"nvfp4"`` and ``"mxfp8"``
modes and :obj:`Linear` layers.

Args:
model (mlx.nn.Module): The model whose leaf modules may be quantized.
Expand All @@ -41,21 +46,39 @@ def quantize(
:func:`mlx.core.quantize`). Default: ``None``.
mode (str): The quantization method to use (see
:func:`mlx.core.quantize`). Default: ``"affine"``.
quantize_input (bool): Whether to quantize activations. Default: ``False``.
class_predicate (Optional[Callable]): A callable which receives the
:obj:`Module` path and :obj:`Module` itself and returns ``True`` or a
dict of params for `to_quantized` if it should be quantized and
``False`` otherwise. If ``None``, then all layers that define a
``to_quantized(group_size, bits)`` method are quantized.
Default: ``None``.
:obj:`Module` path and :obj:`Module` itself and returns ``True`` or a
dict of params for ``to_quantized`` if it should be quantized and
``False`` otherwise. If ``None``, then all layers that define a
``to_quantized()`` method are quantized. Default: ``None``.

Example:
Weight only quantization for all layers that define a ``to_quantized()`` method:

>>> import mlx.nn as nn
>>> nn.quantize(model, group_size=64, bits=4, mode="affine")

Weight and input quantization for all linear layers:

>>> predicate = lambda p, m: isinstance(m, nn.Linear)
>>> nn.quantize(model, mode="nvfp4", quantize_input=True, class_predicate=predicate)
"""
class_predicate = class_predicate or (lambda _, m: hasattr(m, "to_quantized"))

def _maybe_quantize(path, m):
if bool_or_params := class_predicate(path, m):
if hasattr(m, "to_quantized"):
if isinstance(bool_or_params, bool):
Comment thread
nastya236 marked this conversation as resolved.
return m.to_quantized(group_size=group_size, bits=bits, mode=mode)
kwargs = {"group_size": group_size, "bits": bits, "mode": mode}
if quantize_input:
kwargs["quantize_input"] = quantize_input
return m.to_quantized(**kwargs)
elif isinstance(bool_or_params, dict):
if ("quantize_input" in bool_or_params) and not bool_or_params[
"quantize_input"
]:
bool_or_params.pop("quantize_input")
return m.to_quantized(**bool_or_params)
else:
raise ValueError(
Expand Down
21 changes: 21 additions & 0 deletions python/tests/test_nn.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,27 @@ def test_quantize(self):
self.assertTrue(isinstance(m.layers[2], nn.QuantizedLinear))
self.assertTrue(isinstance(m.layers[2].scales, mx.array))

m = nn.Sequential(
nn.Embedding(5, 256), nn.ReLU(), nn.Linear(256, 256, bias=False)
)
nn.quantize(
m,
group_size=32,
mode="mxfp8",
quantize_input=True,
class_predicate=lambda path, module: isinstance(module, nn.Linear),
)
self.assertTrue(isinstance(m.layers[0], nn.Embedding))
self.assertTrue(isinstance(m.layers[1], nn.ReLU))
self.assertTrue(isinstance(m.layers[2], nn.QQLinear))

# Check that Embedding does not support quantize_input
m = nn.Sequential(
nn.Embedding(5, 256), nn.ReLU(), nn.Linear(256, 256, bias=False)
)
with self.assertRaises(ValueError) as context:
nn.quantize(m, group_size=32, mode="mxfp8", quantize_input=True)

def test_quantize_freeze(self):
lin = nn.Linear(512, 512)
qlin = lin.to_quantized()
Expand Down
Loading