|
24 | 24 | # computing biases = -scales * 2^(bits-1) during the init chain. |
25 | 25 | QUANTIZED_SERIALIZE_BIASES = False |
26 | 26 |
|
| 27 | +# Row-chunk size (in elements) for the int64 bit-packer in ``to_mlx_qparams``. |
| 28 | +# Packing widths that don't divide 32 (5/6-bit) needs int64 scratch; chunking the |
| 29 | +# rows bounds that scratch to ~this many elements instead of the whole weight |
| 30 | +# (a full lm_head would otherwise be tens of GB of int64 -> OOM). |
| 31 | +_PACK_CHUNK_ELEMS = 1 << 24 # ~16M int64 elements (~128 MB per scratch tensor) |
| 32 | + |
27 | 33 |
|
28 | 34 | def get_aten_target(target): |
29 | 35 | """ |
@@ -555,27 +561,43 @@ def to_mlx_qparams( |
555 | 561 | Q = q.contiguous().view(torch.uint32).reshape(rows, -1) |
556 | 562 | else: |
557 | 563 | # 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). |
| 564 | + # (e.g. 5/6-bit), matching MLX's affine pack_and_quantize (ops.cpp). |
| 565 | + # |
| 566 | + # We scatter each column's value directly into its uint32 word(s) rather |
| 567 | + # than expanding to a per-bit stream. Column j occupies global bits |
| 568 | + # [j*bits, (j+1)*bits): word j*bits//32 at shift j*bits%32, plus a carry |
| 569 | + # into the next word when it straddles the boundary (at most one carry |
| 570 | + # since bits <= 32). Column bit-ranges within a word are disjoint, so |
| 571 | + # index_add_ (sum) is equivalent to OR. |
559 | 572 | # |
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. |
| 573 | + # The scatter needs int64 (a value shifted by up to 31 overflows int32), |
| 574 | + # so we pack the rows in chunks to bound the peak int64 working set -- |
| 575 | + # packing a full lm_head in one shot would otherwise materialize a |
| 576 | + # multi-GB int64 tensor. |
567 | 577 | n_words = cols * bits // 32 |
568 | | - q = qdata.to(torch.int64) + offset # 0 .. 2**bits-1 |
569 | 578 | pos = torch.arange(cols, dtype=torch.int64) * bits |
570 | 579 | word = pos // 32 |
571 | 580 | 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 | 581 | 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) |
| 582 | + has_straddle = bool(straddle.any()) |
| 583 | + word_carry = (word + 1).clamp(max=n_words - 1) if has_straddle else None |
| 584 | + |
| 585 | + rows_per_chunk = max(1, _PACK_CHUNK_ELEMS // cols) |
| 586 | + chunks = [] |
| 587 | + for r0 in range(0, rows, rows_per_chunk): |
| 588 | + q = qdata[r0 : r0 + rows_per_chunk].to(torch.int64) + offset |
| 589 | + packed = torch.zeros(q.shape[0], n_words, dtype=torch.int64) |
| 590 | + packed.index_add_(1, word, q << shift) # low bits (+ overflow) |
| 591 | + if has_straddle: |
| 592 | + carry = torch.where(straddle, q >> (32 - shift), torch.zeros_like(q)) |
| 593 | + packed.index_add_(1, word_carry, carry) |
| 594 | + del carry |
| 595 | + del q |
| 596 | + chunks.append((packed & 0xFFFFFFFF).to(torch.int32)) |
| 597 | + del packed |
| 598 | + packed_i32 = chunks[0] if len(chunks) == 1 else torch.cat(chunks, dim=0) |
| 599 | + del chunks |
| 600 | + Q = packed_i32.contiguous().view(torch.uint32) |
579 | 601 |
|
580 | 602 | if compute_biases: |
581 | 603 | B = -scale * (zero_point.to(scale.dtype) + offset) |
|
0 commit comments