Quantized weight packs · .safetensors

AWQ GEMM packed checkpoint (safetensors / torch)

AWQ GEMM checkpoints store each linear as qweight, qzeros, and scales inside safetensors or a torch state dict. This spec is the pack in awq/modules/linear/gemm.py WQLinear_GEMM.from_linear at 88e4c76b20755. The quantizer selects that class when version == "gemm". GEMV, GEMV-fast, Marlin, and ExLlama classes in the same repo are different layouts and are not specified here. Group size and bit width come from quantization_config, not from the tensor file.

.safetensors .pt .bin

What it is
weight pack
Byte order
little
Dimension order
row major
Compression
none
Metadata
json
Loading
Loading it into a library can execute code, depending on the loader and the file.

How to read it

Where the values live

name
module prefix
dtype
int32 carrier; logical values are uint4
shape
logical weight [out_features, in_features]
data_offset
container offset of qweight
nbytes
qweight.nbytes + qzeros.nbytes + scales.nbytes
alignment
1. no extra alignment beyond the container

AwqGemmLayer

qweight
int32. shape [in_features, out_features // 8]
qzeros
int32. shape [in_features // group_size, out_features // 8]
scales
float16. shape [in_features // group_size, out_features] after the gemm transpose in quantizer.py
bias
float16. optional, shape [out_features]

Reader

The procedure that finds each tensor's bytes.

read

pseudo-python
ORDER = [0, 2, 4, 6, 1, 3, 5, 7]
def unpack_awq_gemm(qweight, qzeros, scales, group_size):
    # qweight int32 [in_features, packed_out]
    in_f, packed_out = qweight.shape
    out_f = packed_out * 8
    w = empty((in_f, out_f), int32)
    for col in range(packed_out):
        for i in range(8):
            w[:, col * 8 + ORDER[i]] = (qweight[:, col] >> (i * 4)) & 0xF
    z = empty((qzeros.shape[0], out_f), int32)
    for col in range(qzeros.shape[1]):
        for i in range(8):
            z[:, col * 8 + ORDER[i]] = (qzeros[:, col] >> (i * 4)) & 0xF
    # dequant along in_features: (code - zero) * scale, scale/zero row = k // group_size
    out = empty((out_f, in_f), float32)
    for k in range(in_f):
        g = k // group_size
        out[:, k] = (w[k].float() - z[g].float()) * scales[g].float()
    return out                               # [out_features, in_features]

Reject the file when

Listed in the spec's order. Only rules marked MUST that reject the file are shown. The other IDs are warnings or implementation limits.

  • AWQ-001. w_bit == 4
  • AWQ-002. in_features % group_size == 0 and out_features % 8 == 0
  • AWQ-003. qweight.shape == [in_features, out_features // 8]
  • AWQ-004. Unpack with order_map [0, 2, 4, 6, 1, 3, 5, 7], not sequential nibbles
  • AWQ-005. Do not add 1 to zeros. AWQ stores the zero that dequant subtracts.

Example

Example file. AutoAWQ WQLinear_GEMM.from_linear wrote the packed tensors on CPU. That writer stores scales as float16; this file stores them as float32. Names, dtypes, shapes, and stored bytes match the recorded hashes. This page does not unpack the weights. The header below is spaced for reading; the file pads that JSON with spaces out to a multiple of 8.

small-awq.safetensors1,392 bytes

NameDtypeShapeBytes
awq.scalesF32float321 × 32128
awq.qweightI32int3264 × 41,024
awq.qzerosI32int321 × 416

Header

{
  "awq.scales": {
    "dtype": "F32",
    "shape": [
      1,
      32
    ],
    "data_offsets": [
      0,
      128
    ]
  },
  "awq.qweight": {
    "dtype": "I32",
    "shape": [
      64,
      4
    ],
    "data_offsets": [
      128,
      1152
    ]
  },
  "awq.qzeros": {
    "dtype": "I32",
    "shape": [
      1,
      4
    ],
    "data_offsets": [
      1152,
      1168
    ]
  }
}

Sources

Nick Reynolds · @goldnode · rev 2.3