Create the NormalFloat (NF4) quantization map. Constructs a lookup table of 16 quantization values (stored in a 256-element tensor for indexing convenience) derived from quantiles of the standard normal distribution N(0, 1). Each bin has approximately equal probability mass under the no
(offset=0.9677083, use_extra_value=True)
| 167 | |
| 168 | |
| 169 | def create_normal_map(offset=0.9677083, use_extra_value=True): |
| 170 | """Create the NormalFloat (NF4) quantization map. |
| 171 | |
| 172 | Constructs a lookup table of 16 quantization values (stored in a 256-element tensor for |
| 173 | indexing convenience) derived from quantiles of the standard normal distribution N(0, 1). |
| 174 | Each bin has approximately equal probability mass under the normal distribution, which is |
| 175 | optimal for normally-distributed data like neural network weights. |
| 176 | |
| 177 | Unlike floating-point types (FP4, FP8), NF4 is NOT a float encoding — the 4-bit index is |
| 178 | simply a lookup into this table. There is no sign/exponent/mantissa decomposition. |
| 179 | |
| 180 | The values are generated by computing ``scipy.stats.norm.ppf()`` (inverse CDF) at evenly |
| 181 | spaced quantile points, then normalizing to [-1, 1]. |
| 182 | |
| 183 | For more details, see: QLoRA: Efficient Finetuning of Quantized LLMs |
| 184 | (https://arxiv.org/abs/2305.14314) |
| 185 | |
| 186 | Args: |
| 187 | offset: The outermost quantile boundary, controlling the range of the normal distribution |
| 188 | that is covered. ``norm.ppf(offset)`` gives the largest bin edge in standard deviations. |
| 189 | The default (0.9677083) covers up to ~1.845 standard deviations and was empirically |
| 190 | optimized to minimize quantization error for typical neural network weight distributions. |
| 191 | use_extra_value: If True, creates an asymmetric type with 8 negative and 9 positive values |
| 192 | (including zero), for 15 non-zero values total. If False, creates a symmetric type |
| 193 | with 7 negative and 7 positive values (14 non-zero values total). |
| 194 | |
| 195 | Returns: |
| 196 | A 256-element tensor where the first 16 values are the sorted NF4 quantization levels |
| 197 | normalized to [-1, 1], and the remaining values are zero (padding for 8-bit indexing). |
| 198 | """ |
| 199 | try: |
| 200 | from scipy.stats import norm |
| 201 | except ImportError as ie: |
| 202 | raise ImportError( |
| 203 | "Scipy is required for `create_normal_map`. Install `bitsandbytes` with the `[test]` extra.", |
| 204 | ) from ie |
| 205 | |
| 206 | if use_extra_value: |
| 207 | # one more positive value, this is an asymmetric type |
| 208 | v1 = norm.ppf(torch.linspace(offset, 0.5, 9)[:-1]).tolist() |
| 209 | v2 = [0] * (256 - 15) ## we have 15 non-zero values in this data type |
| 210 | v3 = (-norm.ppf(torch.linspace(offset, 0.5, 8)[:-1])).tolist() |
| 211 | else: |
| 212 | v1 = norm.ppf(torch.linspace(offset, 0.5, 8)[:-1]).tolist() |
| 213 | v2 = [0] * (256 - 14) ## we have 14 non-zero values in this data type |
| 214 | v3 = (-norm.ppf(torch.linspace(offset, 0.5, 8)[:-1])).tolist() |
| 215 | |
| 216 | v = v1 + v2 + v3 |
| 217 | |
| 218 | values = torch.Tensor(v) |
| 219 | values = values.sort().values |
| 220 | values /= values.max() |
| 221 | |
| 222 | assert values.numel() == 256 |
| 223 | |
| 224 | return values |
| 225 | |
| 226 |
nothing calls this directly
no outgoing calls
no test coverage detected