Skip to content

Commit f4b01a8

Browse files
authored
Mlx improve 6bit packing perf (pytorch#20806)
Improves memory and speed (2-3x) of 6-bit packing in MLX lowering.
1 parent 910c45c commit f4b01a8

1 file changed

Lines changed: 22 additions & 12 deletions

File tree

backends/mlx/builder/op_helpers.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -554,18 +554,28 @@ def to_mlx_qparams(
554554
q = qdata.view(torch.uint8) + offset
555555
Q = q.contiguous().view(torch.uint32).reshape(rows, -1)
556556
else:
557-
# Contiguous bit-packing for widths that don't divide 32 (e.g. 6-bit),
558-
# matching MLX's affine pack_and_quantize (ops.cpp): each value
559-
# contributes `bits` LSB-first bits to a continuous stream that is
560-
# chunked into uint32 words (also LSB-first), straddling word
561-
# boundaries -> (rows, cols*bits//32) uint32.
562-
q = qdata.to(torch.int32) + offset # 0 .. 2**bits-1
563-
bit_ids = torch.arange(bits, dtype=torch.int32)
564-
stream = (q.unsqueeze(-1) >> bit_ids) & 1 # (rows, cols, bits) LSB-first
565-
stream = stream.reshape(rows, -1, 32) # (rows, n_words, 32)
566-
word_shifts = torch.arange(32, dtype=torch.int64)
567-
packed = (stream.to(torch.int64) << word_shifts).sum(-1) # (rows, n_words)
568-
Q = packed.to(torch.int32).contiguous().view(torch.uint32)
557+
# Contiguous LSB-first bit-packing for widths that don't divide 32
558+
# (e.g. 6-bit), matching MLX's affine pack_and_quantize (ops.cpp).
559+
#
560+
# We scatter each column's value directly into its uint32 word(s)
561+
# rather than expanding to a per-bit stream, which would materialize an
562+
# int64 (rows, cols, bits) tensor (tens of GB for lm_head -> OOM).
563+
# Column j occupies global bits [j*bits, (j+1)*bits): word j*bits//32 at
564+
# shift j*bits%32, plus a carry into the next word when it straddles the
565+
# boundary (at most one carry since bits <= 32). Column bit-ranges within
566+
# a word are disjoint, so index_add_ (sum) is equivalent to OR.
567+
n_words = cols * bits // 32
568+
q = qdata.to(torch.int64) + offset # 0 .. 2**bits-1
569+
pos = torch.arange(cols, dtype=torch.int64) * bits
570+
word = pos // 32
571+
shift = pos % 32
572+
packed = torch.zeros(rows, n_words, dtype=torch.int64)
573+
packed.index_add_(1, word, q << shift) # low bits (+ overflow)
574+
straddle = (shift + bits) > 32
575+
if bool(straddle.any()):
576+
carry = torch.where(straddle, q >> (32 - shift), torch.zeros_like(q))
577+
packed.index_add_(1, (word + 1).clamp(max=n_words - 1), carry)
578+
Q = (packed & 0xFFFFFFFF).to(torch.int32).contiguous().view(torch.uint32)
569579

570580
if compute_biases:
571581
B = -scale * (zero_point.to(scale.dtype) + offset)

0 commit comments

Comments
 (0)