@@ -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